mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-22 16:29:09 +01:00
[PM-2170] Update collections component (#6794)
* PM-2170 Updated Collections to use Component Library * PM-2170 Removed some extra space * PM-2170 Fix typo * PM-2170 Refresh vault when saving * PM-2170 Fix PR comments * PM-2170 Refactor to use CollectionsDialogResult to fix lint error * PM-2170 Refactor subtitle * PM-4788 Fix dismiss of modal * PM-2170 Fix PR comments
This commit is contained in:
parent
0765240886
commit
62ed7e5abc
@ -1,29 +1,20 @@
|
|||||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="collectionsTitle">
|
<form (ngSubmit)="submit()">
|
||||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
<bit-dialog>
|
||||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
<span bitDialogTitle>
|
||||||
<div class="modal-header">
|
|
||||||
<h1 class="modal-title" id="collectionsTitle">
|
|
||||||
{{ "collections" | i18n }}
|
{{ "collections" | i18n }}
|
||||||
<small *ngIf="cipher">{{ cipher.name }}</small>
|
<small *ngIf="cipher">{{ cipher.name }}</small>
|
||||||
</h1>
|
</span>
|
||||||
<button
|
<ng-container bitDialogContent>
|
||||||
type="button"
|
|
||||||
class="close"
|
|
||||||
data-dismiss="modal"
|
|
||||||
appA11yTitle="{{ 'close' | i18n }}"
|
|
||||||
>
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>{{ "collectionsDesc" | i18n }}</p>
|
<p>{{ "collectionsDesc" | i18n }}</p>
|
||||||
<div class="d-flex">
|
<div class="tw-flex">
|
||||||
<h3>{{ "collections" | i18n }}</h3>
|
<label class="tw-mb-1 tw-block tw-font-semibold tw-text-main">{{
|
||||||
<div class="ml-auto d-flex" *ngIf="collections && collections.length">
|
"collections" | i18n
|
||||||
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
|
}}</label>
|
||||||
|
<div class="tw-ml-auto tw-flex" *ngIf="collections && collections.length">
|
||||||
|
<button bitLink type="button" (click)="selectAll(true)" class="tw-px-2">
|
||||||
{{ "selectAll" | i18n }}
|
{{ "selectAll" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
|
<button bitLink type="button" (click)="selectAll(false)" class="tw-px-2">
|
||||||
{{ "unselectAll" | i18n }}
|
{{ "unselectAll" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -31,34 +22,31 @@
|
|||||||
<div *ngIf="!collections || !collections.length">
|
<div *ngIf="!collections || !collections.length">
|
||||||
{{ "noCollectionsInList" | i18n }}
|
{{ "noCollectionsInList" | i18n }}
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover table-list mb-0" *ngIf="collections && collections.length">
|
<bit-table *ngIf="collections && collections.length">
|
||||||
<tbody>
|
<ng-template body>
|
||||||
<tr *ngFor="let c of collections; let i = index" (click)="check(c)">
|
<tr bitRow *ngFor="let c of collections; let i = index" (click)="check(c)">
|
||||||
<td class="table-list-checkbox">
|
<td bitCell>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
bitCheckbox
|
||||||
[(ngModel)]="$any(c).checked"
|
[(ngModel)]="$any(c).checked"
|
||||||
name="Collection[{{ i }}].Checked"
|
name="Collection[{{ i }}].Checked"
|
||||||
appStopProp
|
appStopProp
|
||||||
[disabled]="!c.canEditItems(this.organization, this.flexibleCollectionsV1Enabled)"
|
[disabled]="!c.canEditItems(this.organization, this.flexibleCollectionsV1Enabled)"
|
||||||
/>
|
/>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ c.name }}
|
{{ c.name }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</ng-template>
|
||||||
</table>
|
</bit-table>
|
||||||
</div>
|
</ng-container>
|
||||||
<div class="modal-footer">
|
<ng-container bitDialogFooter>
|
||||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
<button bitButton buttonType="primary" type="submit">
|
||||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
{{ "save" | i18n }}
|
||||||
<span>{{ "save" | i18n }}</span>
|
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
<button bitButton bitDialogClose buttonType="secondary" type="button">
|
||||||
{{ "cancel" | i18n }}
|
{{ "cancel" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</ng-container>
|
||||||
</form>
|
</bit-dialog>
|
||||||
</div>
|
</form>
|
||||||
</div>
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Component, OnDestroy } from "@angular/core";
|
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||||
|
import { Component, OnDestroy, Inject } from "@angular/core";
|
||||||
|
|
||||||
import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component";
|
import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
@ -8,6 +9,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
|||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||||
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-vault-collections",
|
selector: "app-vault-collections",
|
||||||
@ -21,6 +23,8 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
|
|||||||
cipherService: CipherService,
|
cipherService: CipherService,
|
||||||
organizationSerivce: OrganizationService,
|
organizationSerivce: OrganizationService,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
|
protected dialogRef: DialogRef,
|
||||||
|
@Inject(DIALOG_DATA) params: CollectionsDialogParams,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
collectionService,
|
collectionService,
|
||||||
@ -30,10 +34,16 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
|
|||||||
organizationSerivce,
|
organizationSerivce,
|
||||||
logService,
|
logService,
|
||||||
);
|
);
|
||||||
|
this.cipherId = params?.cipherId;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
override async submit(): Promise<boolean> {
|
||||||
this.selectAll(false);
|
const success = await super.submit();
|
||||||
|
if (success) {
|
||||||
|
this.dialogRef.close(CollectionsDialogResult.Saved);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
check(c: CollectionView, select?: boolean) {
|
check(c: CollectionView, select?: boolean) {
|
||||||
@ -46,4 +56,31 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
|
|||||||
selectAll(select: boolean) {
|
selectAll(select: boolean) {
|
||||||
this.collections.forEach((c) => this.check(c, select));
|
this.collections.forEach((c) => this.check(c, select));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.selectAll(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CollectionsDialogParams {
|
||||||
|
cipherId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CollectionsDialogResult {
|
||||||
|
Saved = "saved",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strongly typed helper to open a Collections dialog
|
||||||
|
* @param dialogService Instance of the dialog service that will be used to open the dialog
|
||||||
|
* @param config Optional configuration for the dialog
|
||||||
|
*/
|
||||||
|
export function openIndividualVaultCollectionsDialog(
|
||||||
|
dialogService: DialogService,
|
||||||
|
config?: DialogConfig<CollectionsDialogParams>,
|
||||||
|
) {
|
||||||
|
return dialogService.open<CollectionsDialogResult, CollectionsDialogParams>(
|
||||||
|
CollectionsComponent,
|
||||||
|
config,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ import {
|
|||||||
BulkShareDialogResult,
|
BulkShareDialogResult,
|
||||||
openBulkShareDialog,
|
openBulkShareDialog,
|
||||||
} from "./bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component";
|
} from "./bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component";
|
||||||
import { CollectionsComponent } from "./collections.component";
|
import { openIndividualVaultCollectionsDialog } from "./collections.component";
|
||||||
import { FolderAddEditDialogResult, openFolderAddEditDialog } from "./folder-add-edit.component";
|
import { FolderAddEditDialogResult, openFolderAddEditDialog } from "./folder-add-edit.component";
|
||||||
import { ShareComponent } from "./share.component";
|
import { ShareComponent } from "./share.component";
|
||||||
import { VaultFilterComponent } from "./vault-filter/components/vault-filter.component";
|
import { VaultFilterComponent } from "./vault-filter/components/vault-filter.component";
|
||||||
@ -568,17 +568,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async editCipherCollections(cipher: CipherView) {
|
async editCipherCollections(cipher: CipherView) {
|
||||||
const [modal] = await this.modalService.openViewRef(
|
openIndividualVaultCollectionsDialog(this.dialogService, { data: { cipherId: cipher.id } });
|
||||||
CollectionsComponent,
|
|
||||||
this.collectionsModalRef,
|
|
||||||
(comp) => {
|
|
||||||
comp.cipherId = cipher.id;
|
|
||||||
comp.onSavedCollections.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
||||||
modal.close();
|
|
||||||
this.refresh();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async addCipher() {
|
async addCipher() {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Component } from "@angular/core";
|
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||||
|
import { Component, Inject } from "@angular/core";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
@ -11,8 +12,13 @@ import { CollectionService } from "@bitwarden/common/vault/abstractions/collecti
|
|||||||
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
|
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
|
||||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||||
import { CipherCollectionsRequest } from "@bitwarden/common/vault/models/request/cipher-collections.request";
|
import { CipherCollectionsRequest } from "@bitwarden/common/vault/models/request/cipher-collections.request";
|
||||||
|
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||||
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { CollectionsComponent as BaseCollectionsComponent } from "../individual-vault/collections.component";
|
import {
|
||||||
|
CollectionsComponent as BaseCollectionsComponent,
|
||||||
|
CollectionsDialogResult,
|
||||||
|
} from "../individual-vault/collections.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-org-vault-collections",
|
selector: "app-org-vault-collections",
|
||||||
@ -29,6 +35,8 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
|||||||
organizationService: OrganizationService,
|
organizationService: OrganizationService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
|
protected dialogRef: DialogRef,
|
||||||
|
@Inject(DIALOG_DATA) params: OrgVaultCollectionsDialogParams,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
collectionService,
|
collectionService,
|
||||||
@ -37,8 +45,14 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
|||||||
cipherService,
|
cipherService,
|
||||||
organizationService,
|
organizationService,
|
||||||
logService,
|
logService,
|
||||||
|
dialogRef,
|
||||||
|
params,
|
||||||
);
|
);
|
||||||
this.allowSelectNone = true;
|
this.allowSelectNone = true;
|
||||||
|
this.collectionIds = params?.collectionIds;
|
||||||
|
this.collections = params?.collections;
|
||||||
|
this.organization = params?.organization;
|
||||||
|
this.cipherId = params?.cipherId;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async loadCipher() {
|
protected async loadCipher() {
|
||||||
@ -79,3 +93,25 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OrgVaultCollectionsDialogParams {
|
||||||
|
collectionIds: string[];
|
||||||
|
collections: CollectionView[];
|
||||||
|
organization: Organization;
|
||||||
|
cipherId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strongly typed helper to open a Collections dialog
|
||||||
|
* @param dialogService Instance of the dialog service that will be used to open the dialog
|
||||||
|
* @param config Optional configuration for the dialog
|
||||||
|
*/
|
||||||
|
export function openOrgVaultCollectionsDialog(
|
||||||
|
dialogService: DialogService,
|
||||||
|
config?: DialogConfig<OrgVaultCollectionsDialogParams>,
|
||||||
|
) {
|
||||||
|
return dialogService.open<CollectionsDialogResult, OrgVaultCollectionsDialogParams>(
|
||||||
|
CollectionsComponent,
|
||||||
|
config,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -75,6 +75,7 @@ import {
|
|||||||
BulkDeleteDialogResult,
|
BulkDeleteDialogResult,
|
||||||
openBulkDeleteDialog,
|
openBulkDeleteDialog,
|
||||||
} from "../individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component";
|
} from "../individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component";
|
||||||
|
import { CollectionsDialogResult } from "../individual-vault/collections.component";
|
||||||
import { RoutedVaultFilterBridgeService } from "../individual-vault/vault-filter/services/routed-vault-filter-bridge.service";
|
import { RoutedVaultFilterBridgeService } from "../individual-vault/vault-filter/services/routed-vault-filter-bridge.service";
|
||||||
import { RoutedVaultFilterService } from "../individual-vault/vault-filter/services/routed-vault-filter.service";
|
import { RoutedVaultFilterService } from "../individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||||
import { createFilterFunction } from "../individual-vault/vault-filter/shared/models/filter-function";
|
import { createFilterFunction } from "../individual-vault/vault-filter/shared/models/filter-function";
|
||||||
@ -95,7 +96,7 @@ import {
|
|||||||
BulkCollectionsDialogComponent,
|
BulkCollectionsDialogComponent,
|
||||||
BulkCollectionsDialogResult,
|
BulkCollectionsDialogResult,
|
||||||
} from "./bulk-collections-dialog";
|
} from "./bulk-collections-dialog";
|
||||||
import { CollectionsComponent } from "./collections.component";
|
import { openOrgVaultCollectionsDialog } from "./collections.component";
|
||||||
import { VaultFilterComponent } from "./vault-filter/vault-filter.component";
|
import { VaultFilterComponent } from "./vault-filter/vault-filter.component";
|
||||||
|
|
||||||
const BroadcasterSubscriptionId = "OrgVaultComponent";
|
const BroadcasterSubscriptionId = "OrgVaultComponent";
|
||||||
@ -711,6 +712,16 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
} else {
|
} else {
|
||||||
collections = await firstValueFrom(this.allCollectionsWithoutUnassigned$);
|
collections = await firstValueFrom(this.allCollectionsWithoutUnassigned$);
|
||||||
}
|
}
|
||||||
|
const dialog = openOrgVaultCollectionsDialog(this.dialogService, {
|
||||||
|
data: {
|
||||||
|
collectionIds: cipher.collectionIds,
|
||||||
|
collections: collections.filter((c) => !c.readOnly && c.id != Unassigned),
|
||||||
|
organization: this.organization,
|
||||||
|
cipherId: cipher.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
|
||||||
const [modal] = await this.modalService.openViewRef(
|
const [modal] = await this.modalService.openViewRef(
|
||||||
CollectionsComponent,
|
CollectionsComponent,
|
||||||
this.collectionsModalRef,
|
this.collectionsModalRef,
|
||||||
@ -726,6 +737,12 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ((await lastValueFrom(dialog.closed)) == CollectionsDialogResult.Saved) {
|
||||||
|
await this.refresh();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async addCipher() {
|
async addCipher() {
|
||||||
|
@ -59,7 +59,7 @@ export class CollectionsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
async submit(): Promise<boolean> {
|
||||||
const selectedCollectionIds = this.collections
|
const selectedCollectionIds = this.collections
|
||||||
.filter((c) => {
|
.filter((c) => {
|
||||||
if (this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
if (this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
||||||
@ -75,7 +75,7 @@ export class CollectionsComponent implements OnInit {
|
|||||||
this.i18nService.t("errorOccurred"),
|
this.i18nService.t("errorOccurred"),
|
||||||
this.i18nService.t("selectOneCollection"),
|
this.i18nService.t("selectOneCollection"),
|
||||||
);
|
);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
this.cipherDomain.collectionIds = selectedCollectionIds;
|
this.cipherDomain.collectionIds = selectedCollectionIds;
|
||||||
try {
|
try {
|
||||||
@ -83,8 +83,10 @@ export class CollectionsComponent implements OnInit {
|
|||||||
await this.formPromise;
|
await this.formPromise;
|
||||||
this.onSavedCollections.emit();
|
this.onSavedCollections.emit();
|
||||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("editedItem"));
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("editedItem"));
|
||||||
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logService.error(e);
|
this.logService.error(e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user