mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 23:33:31 +00:00
[PM-18766] Changes to use the new Assign Collections Component in Desktop (#14180)
* Initial changes to use the new Assign Collections Component in Desktop * Renaming component properly and adding the missing messages.json entries * Adding an option in right click menu to assign to collections * lint fix * prettier * updates so that the feature flag being on will show the new assign collections dialog * lint fix * set collections property after updating cipher collections * update revision date from server response in shareManyWithServer * Removing changes from non-feature flagged files, fixing the refresh issue * return CipherResponse instead of Record * adding in the master password reprompt check if they try and share --------- Co-authored-by: bnagawiecki <107435978+bnagawiecki@users.noreply.github.com> Co-authored-by: jaasen-livefront <jaasen@livefront.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe";
|
||||
import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe";
|
||||
import { CalloutModule, DialogModule } from "@bitwarden/components";
|
||||
import { AssignCollectionsComponent } from "@bitwarden/vault";
|
||||
|
||||
import { DeleteAccountComponent } from "../auth/delete-account.component";
|
||||
import { LoginModule } from "../auth/login/login.module";
|
||||
@@ -55,6 +56,7 @@ import { SharedModule } from "./shared/shared.module";
|
||||
DeleteAccountComponent,
|
||||
UserVerificationComponent,
|
||||
NavComponent,
|
||||
AssignCollectionsComponent,
|
||||
VaultV2Component,
|
||||
],
|
||||
declarations: [
|
||||
|
||||
@@ -3812,5 +3812,139 @@
|
||||
"message": "Learn more about SSH agent",
|
||||
"description": "Two part message",
|
||||
"example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent"
|
||||
},
|
||||
"assignToCollections": {
|
||||
"message": "Assign to collections"
|
||||
},
|
||||
"assignToTheseCollections": {
|
||||
"message": "Assign to these collections"
|
||||
},
|
||||
"bulkCollectionAssignmentDialogDescriptionSingular": {
|
||||
"message": "Only organization members with access to these collections will be able to see the item."
|
||||
},
|
||||
"bulkCollectionAssignmentDialogDescriptionPlural": {
|
||||
"message": "Only organization members with access to these collections will be able to see the items."
|
||||
},
|
||||
"noCollectionsAssigned": {
|
||||
"message": "No collections have been assigned"
|
||||
},
|
||||
"assign": {
|
||||
"message": "Assign"
|
||||
},
|
||||
"bulkCollectionAssignmentDialogDescription": {
|
||||
"message": "Only organization members with access to these collections will be able to see the items."
|
||||
},
|
||||
"bulkCollectionAssignmentWarning": {
|
||||
"message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.",
|
||||
"placeholders": {
|
||||
"total_count": {
|
||||
"content": "$1",
|
||||
"example": "10"
|
||||
},
|
||||
"readonly_count": {
|
||||
"content": "$2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectCollectionsToAssign": {
|
||||
"message": "Select collections to assign"
|
||||
},
|
||||
"personalItemsTransferWarning": {
|
||||
"message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.",
|
||||
"placeholders": {
|
||||
"personal_items_count": {
|
||||
"content": "$1",
|
||||
"example": "2 items"
|
||||
}
|
||||
}
|
||||
},
|
||||
"personalItemsWithOrgTransferWarning": {
|
||||
"message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.",
|
||||
"placeholders": {
|
||||
"personal_items_count": {
|
||||
"content": "$1",
|
||||
"example": "2 items"
|
||||
},
|
||||
"org": {
|
||||
"content": "$2",
|
||||
"example": "Organization name"
|
||||
}
|
||||
}
|
||||
},
|
||||
"personalItemTransferWarningSingular": {
|
||||
"message": "1 item will be permanently transferred to the selected organization. You will no longer own this item."
|
||||
},
|
||||
"personalItemWithOrgTransferWarningSingular": {
|
||||
"message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.",
|
||||
"placeholders": {
|
||||
"org": {
|
||||
"content": "$1",
|
||||
"example": "Organization name"
|
||||
}
|
||||
}
|
||||
},
|
||||
"successfullyAssignedCollections": {
|
||||
"message": "Successfully assigned collections"
|
||||
},
|
||||
"nothingSelected": {
|
||||
"message": "You have not selected anything."
|
||||
},
|
||||
"itemsMovedToOrg": {
|
||||
"message": "Items moved to $ORGNAME$",
|
||||
"placeholders": {
|
||||
"orgname": {
|
||||
"content": "$1",
|
||||
"example": "Company Name"
|
||||
}
|
||||
}
|
||||
},
|
||||
"itemMovedToOrg": {
|
||||
"message": "Item moved to $ORGNAME$",
|
||||
"placeholders": {
|
||||
"orgname": {
|
||||
"content": "$1",
|
||||
"example": "Company Name"
|
||||
}
|
||||
}
|
||||
},
|
||||
"movedItemsToOrg": {
|
||||
"message": "Selected items moved to $ORGNAME$",
|
||||
"placeholders": {
|
||||
"orgname": {
|
||||
"content": "$1",
|
||||
"example": "Company Name"
|
||||
}
|
||||
},
|
||||
"personalItemsTransferWarningPlural": {
|
||||
"message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.",
|
||||
"placeholders": {
|
||||
"personal_items_count": {
|
||||
"content": "$1",
|
||||
"example": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"personalItemWithOrgTransferWarningSingular": {
|
||||
"message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.",
|
||||
"placeholders": {
|
||||
"org": {
|
||||
"content": "$1",
|
||||
"example": "Organization name"
|
||||
}
|
||||
}
|
||||
},
|
||||
"personalItemsWithOrgTransferWarningPlural": {
|
||||
"message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.",
|
||||
"placeholders": {
|
||||
"personal_items_count": {
|
||||
"content": "$1",
|
||||
"example": "2"
|
||||
},
|
||||
"org": {
|
||||
"content": "$2",
|
||||
"example": "Organization name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<bit-dialog dialogSize="large">
|
||||
<span bitDialogTitle>
|
||||
{{ "assignToCollections" | i18n }}
|
||||
<span class="tw-text-sm tw-normal-case tw-text-muted">
|
||||
{{ editableItemCount | pluralize: ("item" | i18n) : ("items" | i18n) }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<div bitDialogContent>
|
||||
<assign-collections
|
||||
[params]="params"
|
||||
[submitBtn]="assignSubmitButton"
|
||||
(onCollectionAssign)="onCollectionAssign($event)"
|
||||
(editableItemCountChange)="editableItemCount = $event"
|
||||
></assign-collections>
|
||||
</div>
|
||||
|
||||
<ng-container bitDialogFooter>
|
||||
<button
|
||||
#assignSubmitButton
|
||||
form="assign_collections_form"
|
||||
type="submit"
|
||||
bitButton
|
||||
bitFormButton
|
||||
buttonType="primary"
|
||||
>
|
||||
{{ "assign" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="secondary" bitDialogClose>
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
@@ -0,0 +1,36 @@
|
||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { PluralizePipe } from "@bitwarden/angular/pipes/pluralize.pipe";
|
||||
import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components";
|
||||
import {
|
||||
AssignCollectionsComponent,
|
||||
CollectionAssignmentParams,
|
||||
CollectionAssignmentResult,
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
templateUrl: "./assign-collections-desktop.component.html",
|
||||
imports: [AssignCollectionsComponent, PluralizePipe, DialogModule, ButtonModule, JslibModule],
|
||||
})
|
||||
export class AssignCollectionsDesktopComponent {
|
||||
protected editableItemCount: number;
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) public params: CollectionAssignmentParams,
|
||||
private dialogRef: DialogRef<CollectionAssignmentResult>,
|
||||
) {}
|
||||
|
||||
protected async onCollectionAssign(result: CollectionAssignmentResult) {
|
||||
this.dialogRef.close(result);
|
||||
}
|
||||
|
||||
static open(dialogService: DialogService, config: DialogConfig<CollectionAssignmentParams>) {
|
||||
return dialogService.open<CollectionAssignmentResult, CollectionAssignmentParams>(
|
||||
AssignCollectionsDesktopComponent,
|
||||
config,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./assign-collections-desktop.component";
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
ViewContainerRef,
|
||||
} from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { firstValueFrom, Subject, takeUntil, switchMap, lastValueFrom } from "rxjs";
|
||||
import { firstValueFrom, Subject, takeUntil, switchMap, lastValueFrom, Observable } from "rxjs";
|
||||
import { filter, map, take } from "rxjs/operators";
|
||||
|
||||
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
|
||||
@@ -19,6 +19,8 @@ import { VaultViewPasswordHistoryService } from "@bitwarden/angular/services/vie
|
||||
import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
@@ -57,6 +59,7 @@ import {
|
||||
CipherFormMode,
|
||||
CipherFormModule,
|
||||
CipherViewComponent,
|
||||
CollectionAssignmentResult,
|
||||
DecryptionFailureDialogComponent,
|
||||
DefaultChangeLoginPasswordService,
|
||||
DefaultCipherFormConfigService,
|
||||
@@ -69,6 +72,7 @@ import { DesktopCredentialGenerationService } from "../../../services/desktop-ci
|
||||
import { DesktopPremiumUpgradePromptService } from "../../../services/desktop-premium-upgrade-prompt.service";
|
||||
import { invokeMenu, RendererMenuItem } from "../../../utils";
|
||||
|
||||
import { AssignCollectionsDesktopComponent } from "./assign-collections";
|
||||
import { ItemFooterComponent } from "./item-footer.component";
|
||||
import { VaultFilterComponent } from "./vault-filter/vault-filter.component";
|
||||
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
|
||||
@@ -142,6 +146,11 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
||||
config: CipherFormConfig | null = null;
|
||||
isSubmitting = false;
|
||||
|
||||
private organizations$: Observable<Organization[]> = this.accountService.activeAccount$.pipe(
|
||||
map((a) => a?.id),
|
||||
switchMap((id) => this.organizationService.organizations$(id)),
|
||||
);
|
||||
|
||||
protected canAccessAttachments$ = this.accountService.activeAccount$.pipe(
|
||||
filter((account): account is Account => !!account),
|
||||
switchMap((account) =>
|
||||
@@ -151,6 +160,8 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
||||
|
||||
private modal: ModalRef | null = null;
|
||||
private componentIsDestroyed$ = new Subject<boolean>();
|
||||
private allOrganizations: Organization[] = [];
|
||||
private allCollections: CollectionView[] = [];
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
@@ -176,6 +187,7 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
||||
private formConfigService: CipherFormConfigService,
|
||||
private premiumUpgradePromptService: PremiumUpgradePromptService,
|
||||
private collectionService: CollectionService,
|
||||
private organizationService: OrganizationService,
|
||||
private folderService: FolderService,
|
||||
) {}
|
||||
|
||||
@@ -312,6 +324,16 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.organizations$.pipe(takeUntil(this.componentIsDestroyed$)).subscribe((orgs) => {
|
||||
this.allOrganizations = orgs;
|
||||
});
|
||||
|
||||
this.collectionService.decryptedCollections$
|
||||
.pipe(takeUntil(this.componentIsDestroyed$))
|
||||
.subscribe((collections) => {
|
||||
this.allCollections = collections;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -420,6 +442,16 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (cipher.canAssignToCollections) {
|
||||
menu.push({
|
||||
label: this.i18nService.t("assignToCollections"),
|
||||
click: () =>
|
||||
this.functionWithChangeDetection(async () => {
|
||||
await this.shareCipher(cipher);
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
switch (cipher.type) {
|
||||
@@ -531,6 +563,36 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
||||
await this.go().catch(() => {});
|
||||
}
|
||||
|
||||
async shareCipher(cipher: CipherView) {
|
||||
if (!cipher) {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccurred"),
|
||||
message: this.i18nService.t("nothingSelected"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await this.passwordReprompt(cipher))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const availableCollections = this.getAvailableCollections(cipher);
|
||||
|
||||
const dialog = AssignCollectionsDesktopComponent.open(this.dialogService, {
|
||||
data: {
|
||||
ciphers: [cipher],
|
||||
organizationId: cipher.organizationId as OrganizationId,
|
||||
availableCollections,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(dialog.closed);
|
||||
if (result === CollectionAssignmentResult.Saved) {
|
||||
await this.savedCipher(cipher);
|
||||
}
|
||||
}
|
||||
|
||||
async addCipher(type: CipherType) {
|
||||
if (this.action === "add") {
|
||||
return;
|
||||
@@ -603,6 +665,16 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
||||
await this.go().catch(() => {});
|
||||
}
|
||||
|
||||
private getAvailableCollections(cipher: CipherView): CollectionView[] {
|
||||
const orgId = cipher.organizationId;
|
||||
if (!orgId || orgId === "MyVault") {
|
||||
return [];
|
||||
}
|
||||
|
||||
const organization = this.allOrganizations.find((o) => o.id === orgId);
|
||||
return this.allCollections.filter((c) => c.organizationId === organization?.id && !c.readOnly);
|
||||
}
|
||||
|
||||
private calculateSearchBarLocalizationString(vaultFilter: VaultFilter): string {
|
||||
if (vaultFilter.status === "favorites") {
|
||||
return "searchFavorites";
|
||||
|
||||
Reference in New Issue
Block a user