mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-21 16:18:28 +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,64 +1,52 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="collectionsTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title" id="collectionsTitle">
|
||||
{{ "collections" | i18n }}
|
||||
<small *ngIf="cipher">{{ cipher.name }}</small>
|
||||
</h1>
|
||||
<button
|
||||
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>
|
||||
<div class="d-flex">
|
||||
<h3>{{ "collections" | i18n }}</h3>
|
||||
<div class="ml-auto d-flex" *ngIf="collections && collections.length">
|
||||
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
|
||||
{{ "selectAll" | i18n }}
|
||||
</button>
|
||||
<button type="button" (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
|
||||
{{ "unselectAll" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<form (ngSubmit)="submit()">
|
||||
<bit-dialog>
|
||||
<span bitDialogTitle>
|
||||
{{ "collections" | i18n }}
|
||||
<small *ngIf="cipher">{{ cipher.name }}</small>
|
||||
</span>
|
||||
<ng-container bitDialogContent>
|
||||
<p>{{ "collectionsDesc" | i18n }}</p>
|
||||
<div class="tw-flex">
|
||||
<label class="tw-mb-1 tw-block tw-font-semibold tw-text-main">{{
|
||||
"collections" | i18n
|
||||
}}</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 }}
|
||||
</button>
|
||||
<button bitLink type="button" (click)="selectAll(false)" class="tw-px-2">
|
||||
{{ "unselectAll" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="!collections || !collections.length">
|
||||
{{ "noCollectionsInList" | i18n }}
|
||||
</div>
|
||||
<table class="table table-hover table-list mb-0" *ngIf="collections && collections.length">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of collections; let i = index" (click)="check(c)">
|
||||
<td class="table-list-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
[(ngModel)]="$any(c).checked"
|
||||
name="Collection[{{ i }}].Checked"
|
||||
appStopProp
|
||||
[disabled]="!c.canEditItems(this.organization, this.flexibleCollectionsV1Enabled)"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{{ c.name }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
<div *ngIf="!collections || !collections.length">
|
||||
{{ "noCollectionsInList" | i18n }}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<bit-table *ngIf="collections && collections.length">
|
||||
<ng-template body>
|
||||
<tr bitRow *ngFor="let c of collections; let i = index" (click)="check(c)">
|
||||
<td bitCell>
|
||||
<input
|
||||
type="checkbox"
|
||||
bitCheckbox
|
||||
[(ngModel)]="$any(c).checked"
|
||||
name="Collection[{{ i }}].Checked"
|
||||
appStopProp
|
||||
[disabled]="!c.canEditItems(this.organization, this.flexibleCollectionsV1Enabled)"
|
||||
/>
|
||||
{{ c.name }}
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
</ng-container>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary" type="submit">
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
<button bitButton bitDialogClose buttonType="secondary" type="button">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
</form>
|
||||
|
@ -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 { 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 { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
selector: "app-vault-collections",
|
||||
@ -21,6 +23,8 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
|
||||
cipherService: CipherService,
|
||||
organizationSerivce: OrganizationService,
|
||||
logService: LogService,
|
||||
protected dialogRef: DialogRef,
|
||||
@Inject(DIALOG_DATA) params: CollectionsDialogParams,
|
||||
) {
|
||||
super(
|
||||
collectionService,
|
||||
@ -30,10 +34,16 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
|
||||
organizationSerivce,
|
||||
logService,
|
||||
);
|
||||
this.cipherId = params?.cipherId;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.selectAll(false);
|
||||
override async submit(): Promise<boolean> {
|
||||
const success = await super.submit();
|
||||
if (success) {
|
||||
this.dialogRef.close(CollectionsDialogResult.Saved);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
check(c: CollectionView, select?: boolean) {
|
||||
@ -46,4 +56,31 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
|
||||
selectAll(select: boolean) {
|
||||
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,
|
||||
openBulkShareDialog,
|
||||
} 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 { ShareComponent } from "./share.component";
|
||||
import { VaultFilterComponent } from "./vault-filter/components/vault-filter.component";
|
||||
@ -568,17 +568,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async editCipherCollections(cipher: CipherView) {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
CollectionsComponent,
|
||||
this.collectionsModalRef,
|
||||
(comp) => {
|
||||
comp.cipherId = cipher.id;
|
||||
comp.onSavedCollections.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
||||
modal.close();
|
||||
this.refresh();
|
||||
});
|
||||
},
|
||||
);
|
||||
openIndividualVaultCollectionsDialog(this.dialogService, { data: { cipherId: cipher.id } });
|
||||
}
|
||||
|
||||
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 { 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 { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
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({
|
||||
selector: "app-org-vault-collections",
|
||||
@ -29,6 +35,8 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
organizationService: OrganizationService,
|
||||
private apiService: ApiService,
|
||||
logService: LogService,
|
||||
protected dialogRef: DialogRef,
|
||||
@Inject(DIALOG_DATA) params: OrgVaultCollectionsDialogParams,
|
||||
) {
|
||||
super(
|
||||
collectionService,
|
||||
@ -37,8 +45,14 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
cipherService,
|
||||
organizationService,
|
||||
logService,
|
||||
dialogRef,
|
||||
params,
|
||||
);
|
||||
this.allowSelectNone = true;
|
||||
this.collectionIds = params?.collectionIds;
|
||||
this.collections = params?.collections;
|
||||
this.organization = params?.organization;
|
||||
this.cipherId = params?.cipherId;
|
||||
}
|
||||
|
||||
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,
|
||||
openBulkDeleteDialog,
|
||||
} 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 { RoutedVaultFilterService } from "../individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { createFilterFunction } from "../individual-vault/vault-filter/shared/models/filter-function";
|
||||
@ -95,7 +96,7 @@ import {
|
||||
BulkCollectionsDialogComponent,
|
||||
BulkCollectionsDialogResult,
|
||||
} from "./bulk-collections-dialog";
|
||||
import { CollectionsComponent } from "./collections.component";
|
||||
import { openOrgVaultCollectionsDialog } from "./collections.component";
|
||||
import { VaultFilterComponent } from "./vault-filter/vault-filter.component";
|
||||
|
||||
const BroadcasterSubscriptionId = "OrgVaultComponent";
|
||||
@ -711,21 +712,37 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
} else {
|
||||
collections = await firstValueFrom(this.allCollectionsWithoutUnassigned$);
|
||||
}
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
CollectionsComponent,
|
||||
this.collectionsModalRef,
|
||||
(comp) => {
|
||||
comp.flexibleCollectionsV1Enabled = this.flexibleCollectionsV1Enabled;
|
||||
comp.collectionIds = cipher.collectionIds;
|
||||
comp.collections = collections;
|
||||
comp.organization = this.organization;
|
||||
comp.cipherId = cipher.id;
|
||||
comp.onSavedCollections.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
||||
modal.close();
|
||||
this.refresh();
|
||||
});
|
||||
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(
|
||||
CollectionsComponent,
|
||||
this.collectionsModalRef,
|
||||
(comp) => {
|
||||
comp.flexibleCollectionsV1Enabled = this.flexibleCollectionsV1Enabled;
|
||||
comp.collectionIds = cipher.collectionIds;
|
||||
comp.collections = collections;
|
||||
comp.organization = this.organization;
|
||||
comp.cipherId = cipher.id;
|
||||
comp.onSavedCollections.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
||||
modal.close();
|
||||
this.refresh();
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
*/
|
||||
|
||||
if ((await lastValueFrom(dialog.closed)) == CollectionsDialogResult.Saved) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async addCipher() {
|
||||
|
@ -59,7 +59,7 @@ export class CollectionsComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
async submit(): Promise<boolean> {
|
||||
const selectedCollectionIds = this.collections
|
||||
.filter((c) => {
|
||||
if (this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
||||
@ -75,7 +75,7 @@ export class CollectionsComponent implements OnInit {
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("selectOneCollection"),
|
||||
);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
this.cipherDomain.collectionIds = selectedCollectionIds;
|
||||
try {
|
||||
@ -83,8 +83,10 @@ export class CollectionsComponent implements OnInit {
|
||||
await this.formPromise;
|
||||
this.onSavedCollections.emit();
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("editedItem"));
|
||||
return true;
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user