From 6aba4550a4b1e643528392d6305df00977046232 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 25 Oct 2018 09:38:37 -0400 Subject: [PATCH] Nested folders --- src/abstractions/folder.service.ts | 2 + src/angular/components/groupings.component.ts | 7 +++ src/models/domain/treeNode.ts | 8 ++++ src/services/folder.service.ts | 47 +++++++++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 src/models/domain/treeNode.ts diff --git a/src/abstractions/folder.service.ts b/src/abstractions/folder.service.ts index 1fec203798..fa1c6809de 100644 --- a/src/abstractions/folder.service.ts +++ b/src/abstractions/folder.service.ts @@ -2,6 +2,7 @@ import { FolderData } from '../models/data/folderData'; import { Folder } from '../models/domain/folder'; import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; +import { TreeNode } from '../models/domain/treeNode'; import { FolderView } from '../models/view/folderView'; @@ -13,6 +14,7 @@ export abstract class FolderService { get: (id: string) => Promise; getAll: () => Promise; getAllDecrypted: () => Promise; + getAllNested: () => Promise>>; saveWithServer: (folder: Folder) => Promise; upsert: (folder: FolderData | FolderData[]) => Promise; replace: (folders: { [id: string]: FolderData; }) => Promise; diff --git a/src/angular/components/groupings.component.ts b/src/angular/components/groupings.component.ts index 88ea88d88e..4643f3490b 100644 --- a/src/angular/components/groupings.component.ts +++ b/src/angular/components/groupings.component.ts @@ -9,11 +9,14 @@ import { CipherType } from '../../enums/cipherType'; import { CollectionView } from '../../models/view/collectionView'; import { FolderView } from '../../models/view/folderView'; +import { TreeNode } from '../../models/domain/treeNode'; + import { CollectionService } from '../../abstractions/collection.service'; import { FolderService } from '../../abstractions/folder.service'; export class GroupingsComponent { @Input() showFolders = true; + @Input() loadNestedFolder = false; @Input() showCollections = true; @Input() showFavorites = true; @@ -26,6 +29,7 @@ export class GroupingsComponent { @Output() onCollectionClicked = new EventEmitter(); folders: FolderView[]; + nestedFolders: Array>; collections: CollectionView[]; loaded: boolean = false; cipherType = CipherType; @@ -64,6 +68,9 @@ export class GroupingsComponent { return; } this.folders = await this.folderService.getAllDecrypted(); + if (this.loadNestedFolder) { + this.nestedFolders = await this.folderService.getAllNested(); + } } selectAll() { diff --git a/src/models/domain/treeNode.ts b/src/models/domain/treeNode.ts new file mode 100644 index 0000000000..4463c50fb4 --- /dev/null +++ b/src/models/domain/treeNode.ts @@ -0,0 +1,8 @@ +export class TreeNode { + node: T; + children: Array> = []; + + constructor(node: T) { + this.node = node; + } +} diff --git a/src/services/folder.service.ts b/src/services/folder.service.ts index 34cecf775c..07dc79024a 100644 --- a/src/services/folder.service.ts +++ b/src/services/folder.service.ts @@ -19,11 +19,13 @@ import { UserService } from '../abstractions/user.service'; import { CipherData } from '../models/data/cipherData'; import { Utils } from '../misc/utils'; +import { TreeNode } from '../models/domain/treeNode'; const Keys = { foldersPrefix: 'folders_', ciphersPrefix: 'ciphers_', }; +const NestingDelimiter = '/'; export class FolderService implements FolderServiceAbstraction { decryptedFolderCache: FolderView[]; @@ -95,6 +97,18 @@ export class FolderService implements FolderServiceAbstraction { return this.decryptedFolderCache; } + async getAllNested(): Promise>> { + const folders = await this.getAllDecrypted(); + const nodes: Array> = []; + folders.forEach((f) => { + const folderCopy = new FolderView(); + folderCopy.id = f.id; + folderCopy.revisionDate = f.revisionDate; + this.nestedTraverse(nodes, 0, f.name.split(NestingDelimiter), folderCopy); + }); + return nodes; + } + async saveWithServer(folder: Folder): Promise { const request = new FolderRequest(folder); @@ -185,4 +199,37 @@ export class FolderService implements FolderServiceAbstraction { await this.apiService.deleteFolder(id); await this.delete(id); } + + private nestedTraverse(nodeTree: Array>, partIndex: number, + parts: string[], folder: FolderView) { + if (parts.length <= partIndex) { + return; + } + + const end = partIndex === parts.length - 1; + const partName = parts[partIndex]; + + for (let i = 0; i < nodeTree.length; i++) { + if (nodeTree[i].node.name === parts[partIndex]) { + if (end && nodeTree[i].node.id !== folder.id) { + // Another node with the same name. + folder.name = partName; + nodeTree.push(new TreeNode(folder)); + return; + } + this.nestedTraverse(nodeTree[i].children, partIndex + 1, parts, folder); + return; + } + } + + if (nodeTree.filter((n) => n.node.name === partName).length === 0) { + if (end) { + folder.name = partName; + nodeTree.push(new TreeNode(folder)); + return; + } + const newPartName = parts[partIndex] + NestingDelimiter + parts[partIndex + 1]; + this.nestedTraverse(nodeTree, 0, [newPartName, ...parts.slice(partIndex + 2)], folder); + } + } }