diff --git a/apps/desktop/src/services/vault-state.service.ts b/apps/desktop/src/services/vault-state.service.ts new file mode 100644 index 00000000000..e262ba2dc0e --- /dev/null +++ b/apps/desktop/src/services/vault-state.service.ts @@ -0,0 +1,108 @@ +import { Injectable } from "@angular/core"; +import { Subject } from "rxjs"; + +import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + +import { SearchBarService } from "../app/layout/search/search-bar.service"; + +/** + * Service to coordinate vault state, including filter state and folder actions, + * between the navigation component and the vault component. + */ +@Injectable({ providedIn: "root" }) +export class VaultStateService { + private filterChangeSubject = new Subject(); + private addFolderSubject = new Subject(); + private editFolderSubject = new Subject(); + + /** + * The currently active vault filter. + */ + activeFilter: VaultFilter = new VaultFilter(); + + /** + * Observable stream of vault filter changes. + * Subscribe to this to react to filter changes from the navigation. + */ + readonly filterChange$ = this.filterChangeSubject.asObservable(); + + /** + * Observable stream of add folder requests. + * Subscribe to this to handle folder creation. + */ + readonly addFolder$ = this.addFolderSubject.asObservable(); + + /** + * Observable stream of edit folder requests. + * Subscribe to this to handle folder editing. + * Emits the folder ID to edit. + */ + readonly editFolder$ = this.editFolderSubject.asObservable(); + + constructor( + private i18nService: I18nService, + private searchBarService: SearchBarService, + ) {} + + /** + * Apply a new vault filter. + * This updates the search bar placeholder and notifies all subscribers. + */ + applyFilter(filter: VaultFilter): void { + // Store the active filter + this.activeFilter = filter; + + // Update search bar placeholder text based on the filter + this.searchBarService.setPlaceholderText( + this.i18nService.t(this.calculateSearchBarLocalizationString(filter)), + ); + + // Emit the filter change to subscribers + this.filterChangeSubject.next(filter); + } + + /** + * Request to add a new folder. + * This will notify subscribers to show the folder creation dialog. + */ + requestAddFolder(): void { + this.addFolderSubject.next(); + } + + /** + * Request to edit an existing folder. + * This will notify subscribers to show the folder edit dialog. + */ + requestEditFolder(folderId: string): void { + this.editFolderSubject.next(folderId); + } + + /** + * Calculate the appropriate search bar localization string based on the active filter. + */ + private calculateSearchBarLocalizationString(vaultFilter: VaultFilter): string { + if (vaultFilter.status === "favorites") { + return "searchFavorites"; + } + if (vaultFilter.status === "trash") { + return "searchTrash"; + } + if (vaultFilter.cipherType != null) { + return "searchType"; + } + if (vaultFilter.selectedFolderId != null && vaultFilter.selectedFolderId !== "none") { + return "searchFolder"; + } + if (vaultFilter.selectedCollectionId != null) { + return "searchCollection"; + } + if (vaultFilter.selectedOrganizationId != null) { + return "searchOrganization"; + } + if (vaultFilter.myVaultOnly) { + return "searchMyVault"; + } + return "searchVault"; + } +} diff --git a/apps/desktop/src/vault/app/vault-v3/nav/vault-nav.component.html b/apps/desktop/src/vault/app/vault-v3/nav/vault-nav.component.html index d1c0399c1b2..3c8e5eb16da 100644 --- a/apps/desktop/src/vault/app/vault-v3/nav/vault-nav.component.html +++ b/apps/desktop/src/vault/app/vault-v3/nav/vault-nav.component.html @@ -1 +1,9 @@ - + + + diff --git a/apps/desktop/src/vault/app/vault-v3/nav/vault-nav.component.ts b/apps/desktop/src/vault/app/vault-v3/nav/vault-nav.component.ts index 6169124e76b..d30be43ecb8 100644 --- a/apps/desktop/src/vault/app/vault-v3/nav/vault-nav.component.ts +++ b/apps/desktop/src/vault/app/vault-v3/nav/vault-nav.component.ts @@ -1,12 +1,15 @@ import { ChangeDetectionStrategy, Component } from "@angular/core"; - import { NavigationModule } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; +import { VaultStateService } from "../../../../services/vault-state.service"; +import { VaultFilterModule } from "../vault-filter/vault-filter.module"; @Component({ selector: "app-vault-nav", - imports: [I18nPipe, NavigationModule], + imports: [I18nPipe, NavigationModule, VaultFilterModule], templateUrl: "./vault-nav.component.html", changeDetection: ChangeDetectionStrategy.OnPush, }) -export class VaultNavComponent {} +export class VaultNavComponent { + constructor(protected vaultStateService: VaultStateService) {} +} diff --git a/apps/desktop/src/vault/app/vault-v3/vault.component.ts b/apps/desktop/src/vault/app/vault-v3/vault.component.ts index 21ba7547f8b..6cb5eb05379 100644 --- a/apps/desktop/src/vault/app/vault-v3/vault.component.ts +++ b/apps/desktop/src/vault/app/vault-v3/vault.component.ts @@ -83,6 +83,7 @@ import { import { SearchBarService } from "../../../app/layout/search/search-bar.service"; import { DesktopCredentialGenerationService } from "../../../services/desktop-cipher-form-generator.service"; import { DesktopPremiumUpgradePromptService } from "../../../services/desktop-premium-upgrade-prompt.service"; +import { VaultStateService } from "../../../services/vault-state.service"; import { invokeMenu, RendererMenuItem } from "../../../utils"; import { AssignCollectionsDesktopComponent } from "../vault/assign-collections"; import { ItemFooterComponent } from "../vault/item-footer.component"; @@ -225,6 +226,7 @@ export class VaultComponent private cipherArchiveService: CipherArchiveService, private policyService: PolicyService, private archiveCipherUtilitiesService: ArchiveCipherUtilitiesService, + private vaultStateService: VaultStateService, ) {} async ngOnInit() { @@ -240,6 +242,30 @@ export class VaultComponent this.userHasPremiumAccess = canAccessPremium; }); + // Subscribe to filter changes from VaultNavComponent + this.vaultStateService.filterChange$ + .pipe( + switchMap((vaultFilter: VaultFilter) => this.applyVaultFilter(vaultFilter)), + takeUntil(this.componentIsDestroyed$), + ) + .subscribe(); + + // Subscribe to add folder requests from VaultNavComponent + this.vaultStateService.addFolder$ + .pipe( + switchMap(() => this.addFolder()), + takeUntil(this.componentIsDestroyed$), + ) + .subscribe(); + + // Subscribe to edit folder requests from VaultNavComponent + this.vaultStateService.editFolder$ + .pipe( + switchMap((folderId: string) => this.editFolder(folderId)), + takeUntil(this.componentIsDestroyed$), + ) + .subscribe(); + this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { this.ngZone .run(async () => {