1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 16:53:34 +00:00

[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
This commit is contained in:
cd-bitwarden
2025-05-15 16:50:49 -04:00
committed by GitHub
parent de03e3ef25
commit d57050f1da
5 changed files with 76 additions and 95 deletions

View File

@@ -11,7 +11,16 @@ import {
} from "@angular/core"; } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { Router } from "@angular/router"; 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 { CollectionService } from "@bitwarden/admin-console/common";
import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction"; 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 { DialogRef, DialogService, ToastOptions, ToastService } from "@bitwarden/components";
import { CredentialGeneratorHistoryDialogComponent } from "@bitwarden/generator-components"; import { CredentialGeneratorHistoryDialogComponent } from "@bitwarden/generator-components";
import { KeyService, BiometricStateService } from "@bitwarden/key-management"; import { KeyService, BiometricStateService } from "@bitwarden/key-management";
import { AddEditFolderDialogComponent, AddEditFolderDialogResult } from "@bitwarden/vault";
import { DeleteAccountComponent } from "../auth/delete-account.component"; import { DeleteAccountComponent } from "../auth/delete-account.component";
import { PremiumComponent } from "../billing/app/accounts/premium.component"; import { PremiumComponent } from "../billing/app/accounts/premium.component";
import { MenuAccount, MenuUpdateRequest } from "../main/menu/menu.updater"; 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 { SettingsComponent } from "./accounts/settings.component";
import { ExportDesktopComponent } from "./tools/export/export-desktop.component"; import { ExportDesktopComponent } from "./tools/export/export-desktop.component";
@@ -78,7 +87,6 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
<ng-template #settings></ng-template> <ng-template #settings></ng-template>
<ng-template #premium></ng-template> <ng-template #premium></ng-template>
<ng-template #passwordHistory></ng-template> <ng-template #passwordHistory></ng-template>
<ng-template #appFolderAddEdit></ng-template>
<ng-template #exportVault></ng-template> <ng-template #exportVault></ng-template>
<ng-template #appGenerator></ng-template> <ng-template #appGenerator></ng-template>
<ng-template #loginApproval></ng-template> <ng-template #loginApproval></ng-template>
@@ -102,8 +110,6 @@ export class AppComponent implements OnInit, OnDestroy {
passwordHistoryRef: ViewContainerRef; passwordHistoryRef: ViewContainerRef;
@ViewChild("exportVault", { read: ViewContainerRef, static: true }) @ViewChild("exportVault", { read: ViewContainerRef, static: true })
exportVaultModalRef: ViewContainerRef; exportVaultModalRef: ViewContainerRef;
@ViewChild("appFolderAddEdit", { read: ViewContainerRef, static: true })
folderAddEditModalRef: ViewContainerRef;
@ViewChild("appGenerator", { read: ViewContainerRef, static: true }) @ViewChild("appGenerator", { read: ViewContainerRef, static: true })
generatorModalRef: ViewContainerRef; generatorModalRef: ViewContainerRef;
@ViewChild("loginApproval", { read: ViewContainerRef, static: true }) @ViewChild("loginApproval", { read: ViewContainerRef, static: true })
@@ -465,25 +471,11 @@ export class AppComponent implements OnInit, OnDestroy {
async addFolder() { async addFolder() {
this.modalService.closeAll(); this.modalService.closeAll();
const [modal, childComponent] = await this.modalService.openViewRef( const dialogRef = AddEditFolderDialogComponent.open(this.dialogService);
FolderAddEditComponent, const result = await lastValueFrom(dialogRef.closed);
this.folderAddEditModalRef, if (result === AddEditFolderDialogResult.Created) {
(comp) => (comp.folderId = null), await this.syncService.fullSync(false);
); }
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;
});
} }
async openGenerator() { async openGenerator() {

View File

@@ -1103,9 +1103,6 @@
"addNewItem": { "addNewItem": {
"message": "New item" "message": "New item"
}, },
"addNewFolder": {
"message": "New folder"
},
"view": { "view": {
"message": "View" "message": "View"
}, },
@@ -3709,6 +3706,15 @@
"move": { "move": {
"message": "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": { "newLoginNudgeTitle": {
"message": "Save time with autofill" "message": "Save time with autofill"
}, },

View File

@@ -114,8 +114,8 @@ export class FileMenu extends FirstMenu implements IMenubarMenu {
private get addNewFolder(): MenuItemConstructorOptions { private get addNewFolder(): MenuItemConstructorOptions {
return { return {
id: "addNewFolder", id: "newFolder",
label: this.localize("addNewFolder"), label: this.localize("newFolder"),
click: () => this.sendMessage("newFolder"), click: () => this.sendMessage("newFolder"),
enabled: !this._isLocked, enabled: !this._isLocked,
}; };

View File

@@ -8,9 +8,8 @@ import {
ViewChild, ViewChild,
ViewContainerRef, ViewContainerRef,
} from "@angular/core"; } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute, Router } from "@angular/router"; 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 { filter, map, take } from "rxjs/operators";
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; 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 { SyncService } from "@bitwarden/common/platform/sync";
import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; 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 { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service";
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { import {
BadgeModule, BadgeModule,
ButtonModule, ButtonModule,
@@ -47,6 +46,8 @@ import {
} from "@bitwarden/components"; } from "@bitwarden/components";
import { I18nPipe } from "@bitwarden/ui-common"; import { I18nPipe } from "@bitwarden/ui-common";
import { import {
AddEditFolderDialogComponent,
AddEditFolderDialogResult,
AttachmentDialogResult, AttachmentDialogResult,
AttachmentsV2Component, AttachmentsV2Component,
ChangeLoginPasswordService, ChangeLoginPasswordService,
@@ -68,7 +69,6 @@ import { DesktopCredentialGenerationService } from "../../../services/desktop-ci
import { DesktopPremiumUpgradePromptService } from "../../../services/desktop-premium-upgrade-prompt.service"; import { DesktopPremiumUpgradePromptService } from "../../../services/desktop-premium-upgrade-prompt.service";
import { invokeMenu, RendererMenuItem } from "../../../utils"; import { invokeMenu, RendererMenuItem } from "../../../utils";
import { FolderAddEditComponent } from "./folder-add-edit.component";
import { ItemFooterComponent } from "./item-footer.component"; import { ItemFooterComponent } from "./item-footer.component";
import { VaultFilterComponent } from "./vault-filter/vault-filter.component"; import { VaultFilterComponent } from "./vault-filter/vault-filter.component";
import { VaultFilterModule } from "./vault-filter/vault-filter.module"; import { VaultFilterModule } from "./vault-filter/vault-filter.module";
@@ -177,6 +177,7 @@ export class VaultV2Component implements OnInit, OnDestroy {
private formConfigService: CipherFormConfigService, private formConfigService: CipherFormConfigService,
private premiumUpgradePromptService: PremiumUpgradePromptService, private premiumUpgradePromptService: PremiumUpgradePromptService,
private collectionService: CollectionService, private collectionService: CollectionService,
private folderService: FolderService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
@@ -634,38 +635,25 @@ export class VaultV2Component implements OnInit, OnDestroy {
} }
async editFolder(folderId: string) { async editFolder(folderId: string) {
if (this.modal != null) { const folderView = await firstValueFrom(
this.modal.close(); this.folderService.getDecrypted$(folderId, this.activeUserId),
} );
if (this.folderAddEditModalRef == null) {
return; const dialogRef = AddEditFolderDialogComponent.open(this.dialogService, {
} editFolderConfig: {
const [modal, childComponent] = await this.modalService folder: {
.openViewRef( ...folderView,
FolderAddEditComponent, },
this.folderAddEditModalRef, },
(comp) => (comp.folderId = folderId), });
)
.catch(() => [null, null] as any); const result = await lastValueFrom(dialogRef.closed);
this.modal = modal;
if (childComponent) { if (
childComponent.onSavedFolder.subscribe(async (folder: FolderView) => { result === AddEditFolderDialogResult.Deleted ||
this.modal?.close(); result === AddEditFolderDialogResult.Created
await this.vaultFilterComponent ) {
?.reloadCollectionsAndFolders(this.activeFilter) 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;
});
} }
} }

View File

@@ -10,7 +10,7 @@ import {
ViewContainerRef, ViewContainerRef,
} from "@angular/core"; } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; 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 { filter, first, map, take } from "rxjs/operators";
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; 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 { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EventType } from "@bitwarden/common/enums"; import { EventType } from "@bitwarden/common/enums";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SyncService } from "@bitwarden/common/platform/sync"; import { SyncService } from "@bitwarden/common/platform/sync";
import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherId, UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; 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 { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; 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 { 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 { SearchBarService } from "../../../app/layout/search/search-bar.service";
import { invokeMenu, RendererMenuItem } from "../../../utils"; import { invokeMenu, RendererMenuItem } from "../../../utils";
@@ -45,7 +49,6 @@ import { AddEditComponent } from "./add-edit.component";
import { AttachmentsComponent } from "./attachments.component"; import { AttachmentsComponent } from "./attachments.component";
import { CollectionsComponent } from "./collections.component"; import { CollectionsComponent } from "./collections.component";
import { CredentialGeneratorDialogComponent } from "./credential-generator-dialog.component"; import { CredentialGeneratorDialogComponent } from "./credential-generator-dialog.component";
import { FolderAddEditComponent } from "./folder-add-edit.component";
import { PasswordHistoryComponent } from "./password-history.component"; import { PasswordHistoryComponent } from "./password-history.component";
import { ShareComponent } from "./share.component"; import { ShareComponent } from "./share.component";
import { VaultFilterComponent } from "./vault-filter/vault-filter.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("share", { read: ViewContainerRef, static: true }) shareModalRef: ViewContainerRef;
@ViewChild("collections", { read: ViewContainerRef, static: true }) @ViewChild("collections", { read: ViewContainerRef, static: true })
collectionsModalRef: ViewContainerRef; collectionsModalRef: ViewContainerRef;
@ViewChild("folderAddEdit", { read: ViewContainerRef, static: true })
folderAddEditModalRef: ViewContainerRef;
action: string; action: string;
cipherId: string = null; cipherId: string = null;
@@ -116,9 +117,9 @@ export class VaultComponent implements OnInit, OnDestroy {
private dialogService: DialogService, private dialogService: DialogService,
private billingAccountProfileStateService: BillingAccountProfileStateService, private billingAccountProfileStateService: BillingAccountProfileStateService,
private toastService: ToastService, private toastService: ToastService,
private configService: ConfigService,
private accountService: AccountService, private accountService: AccountService,
private cipherService: CipherService, private cipherService: CipherService,
private folderService: FolderService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
@@ -706,32 +707,26 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
async editFolder(folderId: string) { async editFolder(folderId: string) {
if (this.modal != null) { const folderView = await firstValueFrom(
this.modal.close(); this.folderService.getDecrypted$(folderId, this.activeUserId),
}
const [modal, childComponent] = await this.modalService.openViewRef(
FolderAddEditComponent,
this.folderAddEditModalRef,
(comp) => (comp.folderId = folderId),
); );
this.modal = modal;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe const dialogRef = AddEditFolderDialogComponent.open(this.dialogService, {
childComponent.onSavedFolder.subscribe(async (folder: FolderView) => { editFolderConfig: {
this.modal.close(); folder: {
await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); ...folderView,
}); },
// 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);
}); });
// eslint-disable-next-line rxjs-angular/prefer-takeuntil const result = await lastValueFrom(dialogRef.closed);
this.modal.onClosed.subscribe(() => {
this.modal = null; if (
}); result === AddEditFolderDialogResult.Deleted ||
result === AddEditFolderDialogResult.Created
) {
await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter);
}
} }
private dirtyInput(): boolean { private dirtyInput(): boolean {