From d57050f1daa42538675d160ee2180793fca12e40 Mon Sep 17 00:00:00 2001 From: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> Date: Thu, 15 May 2025 16:50:49 -0400 Subject: [PATCH] [PM-18767] Using new dialog for adding/editing name of folder in Desktop (#14049) * Using new dialog for adding/editing name of folder in Desktop * removing unecessary changes * removing the template for AppFolderAddEdit * Fixing the issue where it doesn't know the nested folder info * lint fixes and removing uneeded param * removing uneeded messages.json entry * Updating the vault-v2 file to use the new folder dialog component * Fixing the merge commit --- apps/desktop/src/app/app.component.ts | 40 +++++-------- apps/desktop/src/locales/en/messages.json | 12 +++- apps/desktop/src/main/menu/menu.file.ts | 4 +- .../src/vault/app/vault/vault-v2.component.ts | 60 ++++++++----------- .../src/vault/app/vault/vault.component.ts | 55 ++++++++--------- 5 files changed, 76 insertions(+), 95 deletions(-) diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 38c5ca3a2a8..77ac783ac9f 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -11,7 +11,16 @@ import { } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { Router } from "@angular/router"; -import { filter, firstValueFrom, map, Subject, switchMap, takeUntil, timeout } from "rxjs"; +import { + filter, + firstValueFrom, + lastValueFrom, + map, + Subject, + switchMap, + takeUntil, + timeout, +} from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction"; @@ -56,11 +65,11 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { DialogRef, DialogService, ToastOptions, ToastService } from "@bitwarden/components"; import { CredentialGeneratorHistoryDialogComponent } from "@bitwarden/generator-components"; import { KeyService, BiometricStateService } from "@bitwarden/key-management"; +import { AddEditFolderDialogComponent, AddEditFolderDialogResult } from "@bitwarden/vault"; import { DeleteAccountComponent } from "../auth/delete-account.component"; import { PremiumComponent } from "../billing/app/accounts/premium.component"; import { MenuAccount, MenuUpdateRequest } from "../main/menu/menu.updater"; -import { FolderAddEditComponent } from "../vault/app/vault/folder-add-edit.component"; import { SettingsComponent } from "./accounts/settings.component"; import { ExportDesktopComponent } from "./tools/export/export-desktop.component"; @@ -78,7 +87,6 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours - @@ -102,8 +110,6 @@ export class AppComponent implements OnInit, OnDestroy { passwordHistoryRef: ViewContainerRef; @ViewChild("exportVault", { read: ViewContainerRef, static: true }) exportVaultModalRef: ViewContainerRef; - @ViewChild("appFolderAddEdit", { read: ViewContainerRef, static: true }) - folderAddEditModalRef: ViewContainerRef; @ViewChild("appGenerator", { read: ViewContainerRef, static: true }) generatorModalRef: ViewContainerRef; @ViewChild("loginApproval", { read: ViewContainerRef, static: true }) @@ -465,25 +471,11 @@ export class AppComponent implements OnInit, OnDestroy { async addFolder() { this.modalService.closeAll(); - const [modal, childComponent] = await this.modalService.openViewRef( - FolderAddEditComponent, - this.folderAddEditModalRef, - (comp) => (comp.folderId = null), - ); - this.modal = modal; - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - childComponent.onSavedFolder.subscribe(async () => { - this.modal.close(); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.syncService.fullSync(false); - }); - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - this.modal.onClosed.subscribe(() => { - this.modal = null; - }); + const dialogRef = AddEditFolderDialogComponent.open(this.dialogService); + const result = await lastValueFrom(dialogRef.closed); + if (result === AddEditFolderDialogResult.Created) { + await this.syncService.fullSync(false); + } } async openGenerator() { diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index ea198086f8d..1ae304287c7 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "New item" }, - "addNewFolder": { - "message": "New folder" - }, "view": { "message": "View" }, @@ -3709,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/main/menu/menu.file.ts b/apps/desktop/src/main/menu/menu.file.ts index 25db5b695e7..f132a464788 100644 --- a/apps/desktop/src/main/menu/menu.file.ts +++ b/apps/desktop/src/main/menu/menu.file.ts @@ -114,8 +114,8 @@ export class FileMenu extends FirstMenu implements IMenubarMenu { private get addNewFolder(): MenuItemConstructorOptions { return { - id: "addNewFolder", - label: this.localize("addNewFolder"), + id: "newFolder", + label: this.localize("newFolder"), click: () => this.sendMessage("newFolder"), enabled: !this._isLocked, }; diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-v2.component.ts index 6c60aaf0f02..b45d943dcdd 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -8,9 +8,8 @@ import { ViewChild, ViewContainerRef, } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, Subject, takeUntil, switchMap } from "rxjs"; +import { firstValueFrom, Subject, takeUntil, switchMap, lastValueFrom } from "rxjs"; import { filter, map, take } from "rxjs/operators"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; @@ -31,13 +30,13 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { BadgeModule, ButtonModule, @@ -47,6 +46,8 @@ import { } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; import { + AddEditFolderDialogComponent, + AddEditFolderDialogResult, AttachmentDialogResult, AttachmentsV2Component, ChangeLoginPasswordService, @@ -68,7 +69,6 @@ import { DesktopCredentialGenerationService } from "../../../services/desktop-ci import { DesktopPremiumUpgradePromptService } from "../../../services/desktop-premium-upgrade-prompt.service"; import { invokeMenu, RendererMenuItem } from "../../../utils"; -import { FolderAddEditComponent } from "./folder-add-edit.component"; import { ItemFooterComponent } from "./item-footer.component"; import { VaultFilterComponent } from "./vault-filter/vault-filter.component"; import { VaultFilterModule } from "./vault-filter/vault-filter.module"; @@ -177,6 +177,7 @@ export class VaultV2Component implements OnInit, OnDestroy { private formConfigService: CipherFormConfigService, private premiumUpgradePromptService: PremiumUpgradePromptService, private collectionService: CollectionService, + private folderService: FolderService, ) {} async ngOnInit() { @@ -634,38 +635,25 @@ export class VaultV2Component implements OnInit, OnDestroy { } async editFolder(folderId: string) { - if (this.modal != null) { - this.modal.close(); - } - if (this.folderAddEditModalRef == null) { - return; - } - const [modal, childComponent] = await this.modalService - .openViewRef( - FolderAddEditComponent, - this.folderAddEditModalRef, - (comp) => (comp.folderId = folderId), - ) - .catch(() => [null, null] as any); - this.modal = modal; - if (childComponent) { - childComponent.onSavedFolder.subscribe(async (folder: FolderView) => { - this.modal?.close(); - await this.vaultFilterComponent - ?.reloadCollectionsAndFolders(this.activeFilter) - .catch(() => {}); - }); - childComponent.onDeletedFolder.subscribe(async (folder: FolderView) => { - this.modal?.close(); - await this.vaultFilterComponent - ?.reloadCollectionsAndFolders(this.activeFilter) - .catch(() => {}); - }); - } - if (this.modal) { - this.modal.onClosed.pipe(takeUntilDestroyed()).subscribe(() => { - this.modal = null; - }); + const folderView = await firstValueFrom( + this.folderService.getDecrypted$(folderId, this.activeUserId), + ); + + const dialogRef = AddEditFolderDialogComponent.open(this.dialogService, { + editFolderConfig: { + folder: { + ...folderView, + }, + }, + }); + + const result = await lastValueFrom(dialogRef.closed); + + if ( + result === AddEditFolderDialogResult.Deleted || + result === AddEditFolderDialogResult.Created + ) { + await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); } } diff --git a/apps/desktop/src/vault/app/vault/vault.component.ts b/apps/desktop/src/vault/app/vault/vault.component.ts index 560855347b3..6c9a3217bfc 100644 --- a/apps/desktop/src/vault/app/vault/vault.component.ts +++ b/apps/desktop/src/vault/app/vault/vault.component.ts @@ -10,7 +10,7 @@ import { ViewContainerRef, } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, Subject, takeUntil, switchMap } from "rxjs"; +import { firstValueFrom, Subject, takeUntil, switchMap, lastValueFrom } from "rxjs"; import { filter, first, map, take } from "rxjs/operators"; import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; @@ -23,20 +23,24 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EventType } from "@bitwarden/common/enums"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { DialogService, ToastService } from "@bitwarden/components"; -import { DecryptionFailureDialogComponent, PasswordRepromptService } from "@bitwarden/vault"; +import { + AddEditFolderDialogComponent, + AddEditFolderDialogResult, + DecryptionFailureDialogComponent, + PasswordRepromptService, +} from "@bitwarden/vault"; import { SearchBarService } from "../../../app/layout/search/search-bar.service"; import { invokeMenu, RendererMenuItem } from "../../../utils"; @@ -45,7 +49,6 @@ import { AddEditComponent } from "./add-edit.component"; import { AttachmentsComponent } from "./attachments.component"; import { CollectionsComponent } from "./collections.component"; import { CredentialGeneratorDialogComponent } from "./credential-generator-dialog.component"; -import { FolderAddEditComponent } from "./folder-add-edit.component"; import { PasswordHistoryComponent } from "./password-history.component"; import { ShareComponent } from "./share.component"; import { VaultFilterComponent } from "./vault-filter/vault-filter.component"; @@ -73,8 +76,6 @@ export class VaultComponent implements OnInit, OnDestroy { @ViewChild("share", { read: ViewContainerRef, static: true }) shareModalRef: ViewContainerRef; @ViewChild("collections", { read: ViewContainerRef, static: true }) collectionsModalRef: ViewContainerRef; - @ViewChild("folderAddEdit", { read: ViewContainerRef, static: true }) - folderAddEditModalRef: ViewContainerRef; action: string; cipherId: string = null; @@ -116,9 +117,9 @@ export class VaultComponent implements OnInit, OnDestroy { private dialogService: DialogService, private billingAccountProfileStateService: BillingAccountProfileStateService, private toastService: ToastService, - private configService: ConfigService, private accountService: AccountService, private cipherService: CipherService, + private folderService: FolderService, ) {} async ngOnInit() { @@ -706,32 +707,26 @@ export class VaultComponent implements OnInit, OnDestroy { } async editFolder(folderId: string) { - if (this.modal != null) { - this.modal.close(); - } - - const [modal, childComponent] = await this.modalService.openViewRef( - FolderAddEditComponent, - this.folderAddEditModalRef, - (comp) => (comp.folderId = folderId), + const folderView = await firstValueFrom( + this.folderService.getDecrypted$(folderId, this.activeUserId), ); - this.modal = modal; - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - childComponent.onSavedFolder.subscribe(async (folder: FolderView) => { - this.modal.close(); - await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); - }); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - childComponent.onDeletedFolder.subscribe(async (folder: FolderView) => { - this.modal.close(); - await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); + const dialogRef = AddEditFolderDialogComponent.open(this.dialogService, { + editFolderConfig: { + folder: { + ...folderView, + }, + }, }); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - this.modal.onClosed.subscribe(() => { - this.modal = null; - }); + const result = await lastValueFrom(dialogRef.closed); + + if ( + result === AddEditFolderDialogResult.Deleted || + result === AddEditFolderDialogResult.Created + ) { + await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); + } } private dirtyInput(): boolean {