From 93b5a155bd4318a1e7c07eb8d1e956de088137ba Mon Sep 17 00:00:00 2001 From: addison Date: Fri, 18 Mar 2022 12:46:38 -0400 Subject: [PATCH] [feature] Add organization filters This is an MVP implementation of the changes needed for the vault refresh. This includes collapsable top level groupings, and organization based filters that dynamically adjust folders and collections. --- angular/src/components/groupings.component.ts | 79 ++++++++++++++++++- 1 file changed, 75 insertions(+), 4 deletions(-) diff --git a/angular/src/components/groupings.component.ts b/angular/src/components/groupings.component.ts index fd5c1aa1..670862e7 100644 --- a/angular/src/components/groupings.component.ts +++ b/angular/src/components/groupings.component.ts @@ -1,9 +1,12 @@ import { Directive, EventEmitter, Input, Output } from "@angular/core"; +import { CipherService } from "jslib-common/abstractions/cipher.service"; import { CollectionService } from "jslib-common/abstractions/collection.service"; import { FolderService } from "jslib-common/abstractions/folder.service"; +import { OrganizationService } from "jslib-common/abstractions/organization.service"; import { StateService } from "jslib-common/abstractions/state.service"; import { CipherType } from "jslib-common/enums/cipherType"; +import { Organization } from "jslib-common/models/domain/organization"; import { ITreeNodeObject, TreeNode } from "jslib-common/models/domain/treeNode"; import { CollectionView } from "jslib-common/models/view/collectionView"; import { FolderView } from "jslib-common/models/view/folderView"; @@ -19,6 +22,7 @@ export class GroupingsComponent { @Input() showCollections = true; @Input() showFavorites = true; @Input() showTrash = true; + @Input() showOrganizations = true; @Output() onAllClicked = new EventEmitter(); @Output() onFavoritesClicked = new EventEmitter(); @@ -28,6 +32,9 @@ export class GroupingsComponent { @Output() onAddFolder = new EventEmitter(); @Output() onEditFolder = new EventEmitter(); @Output() onCollectionClicked = new EventEmitter(); + @Output() onOrganizationClicked = new EventEmitter(); + @Output() onMyVaultClicked = new EventEmitter(); + @Output() onAllVaultsClicked = new EventEmitter(); folders: FolderView[]; nestedFolders: TreeNode[]; @@ -42,6 +49,9 @@ export class GroupingsComponent { selectedFolder = false; selectedFolderId: string = null; selectedCollectionId: string = null; + selectedOrganizationId: string = null; + organizations: Organization[]; + myVaultOnly = false; readonly vaultsGrouping: TopLevelGroupingView = { id: "vaults", @@ -68,7 +78,9 @@ export class GroupingsComponent { constructor( protected collectionService: CollectionService, protected folderService: FolderService, - protected stateService: StateService + protected stateService: StateService, + protected organizationService: OrganizationService, + protected cipherService: CipherService ) {} async load(setLoaded = true) { @@ -81,6 +93,7 @@ export class GroupingsComponent { await this.loadFolders(); await this.loadCollections(); + await this.loadOrganizations(); if (setLoaded) { this.loaded = true; @@ -100,12 +113,32 @@ export class GroupingsComponent { this.nestedCollections = await this.collectionService.getAllNested(this.collections); } - async loadFolders() { + async loadFolders(organizationId?: string) { if (!this.showFolders) { return; } - this.folders = await this.folderService.getAllDecrypted(); - this.nestedFolders = await this.folderService.getAllNested(); + const folders = await this.folderService.getAllDecrypted(); + if (organizationId != null) { + const ciphers = await this.cipherService.getAllDecrypted(); + const orgCiphers = ciphers.filter((c) => c.organizationId == organizationId); + this.folders = folders.filter( + (f) => + f.id != null && + (orgCiphers.filter((oc) => oc.folderId == f.id).length > 0 || + ciphers.filter((c) => c.folderId == f.id).length < 1) + ); + } else { + this.folders = folders; + } + this.nestedFolders = await this.folderService.getAllNested(this.folders); + } + + async loadOrganizations() { + this.showOrganizations = await this.organizationService.hasOrganizations(); + if (!this.showOrganizations) { + return; + } + this.organizations = await this.organizationService.getAll(); } selectAll() { @@ -153,6 +186,26 @@ export class GroupingsComponent { this.onCollectionClicked.emit(collection); } + async selectOrganization(organization: Organization) { + this.clearSelectedOrganization(); + this.selectedOrganizationId = organization.id; + await this.reloadCollectionsAndFolders(this.selectedOrganizationId); + this.onOrganizationClicked.emit(organization); + } + + async selectMyVault() { + this.clearSelectedOrganization(); + this.myVaultOnly = true; + await this.reloadCollectionsAndFolders(this.selectedOrganizationId); + this.onMyVaultClicked.emit(); + } + + async selectAllVaults() { + this.clearSelectedOrganization(); + await this.reloadCollectionsAndFolders(this.selectedOrganizationId); + this.onAllVaultsClicked.emit(); + } + clearSelections() { this.selectedAll = false; this.selectedFavorites = false; @@ -163,6 +216,19 @@ export class GroupingsComponent { this.selectedCollectionId = null; } + clearSelectedOrganization() { + this.selectedOrganizationId = null; + this.myVaultOnly = false; + const clearingFolderOrCollectionSelection = + this.selectedFolderId != null || this.selectedCollectionId != null; + if (clearingFolderOrCollectionSelection) { + this.selectedFolder = false; + this.selectedFolderId = null; + this.selectedCollectionId = null; + this.selectedAll = true; + } + } + async collapse(node: ITreeNodeObject, idPrefix = "") { if (node.id == null) { return; @@ -179,4 +245,9 @@ export class GroupingsComponent { isCollapsed(node: ITreeNodeObject, idPrefix = "") { return this.collapsedGroupings.has(idPrefix + node.id); } + + private async reloadCollectionsAndFolders(organizationId?: string) { + await this.loadCollections(organizationId); + await this.loadFolders(organizationId); + } }