1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-06 02:23:44 +00:00

[PM-27675] Browser item transfer integration (#17918)

* [PM-27675] Integrate dialogs into VaultItemTransferService

* [PM-27675] Update tests for new dialogs

* [PM-27675] Center dialogs and prevent closing with escape or pointer events

* [PM-27675] Add transferInProgress$ observable to VaultItemsTransferService

* [PM-27675] Hook vault item transfer service into browser vault component

* [PM-27675] Move defaultUserCollection$ to collection service

* [PM-27675] Cleanup dialog styles

* [PM-27675] Introduce readySubject to popup vault component to keep prevent flashing content while item transfer is in progress

* [PM-27675] Fix vault-v2 tests
This commit is contained in:
Shane Melton
2025-12-16 15:03:48 -08:00
committed by GitHub
parent 3049cfad7d
commit 06d15e9681
13 changed files with 464 additions and 137 deletions

View File

@@ -108,7 +108,7 @@
</div>
<ng-template #vaultContentTemplate>
<ng-container *ngIf="vaultState === null">
<ng-container *ngIf="vaultState === null && !(loading$ | async)">
<app-autofill-vault-list-items></app-autofill-vault-list-items>
<app-vault-list-items-container
[title]="'favorites' | i18n"

View File

@@ -28,7 +28,11 @@ import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/res
import { TaskService } from "@bitwarden/common/vault/tasks";
import { DialogService } from "@bitwarden/components";
import { StateProvider } from "@bitwarden/state";
import { DecryptionFailureDialogComponent } from "@bitwarden/vault";
import {
DecryptionFailureDialogComponent,
VaultItemsTransferService,
DefaultVaultItemsTransferService,
} from "@bitwarden/vault";
import { BrowserApi } from "../../../../platform/browser/browser-api";
import BrowserPopupUtils from "../../../../platform/browser/browser-popup-utils";
@@ -193,6 +197,11 @@ describe("VaultV2Component", () => {
stop: jest.fn(),
} as Partial<VaultPopupScrollPositionService>;
const vaultItemsTransferSvc = {
transferInProgress$: new BehaviorSubject<boolean>(false),
enforceOrganizationDataOwnership: jest.fn().mockResolvedValue(undefined),
} as Partial<VaultItemsTransferService>;
function getObs<T = unknown>(cmp: any, key: string): Observable<T> {
return cmp[key] as Observable<T>;
}
@@ -283,6 +292,9 @@ describe("VaultV2Component", () => {
AutofillVaultListItemsComponent,
VaultListItemsContainerComponent,
],
providers: [
{ provide: VaultItemsTransferService, useValue: DefaultVaultItemsTransferService },
],
},
add: {
imports: [
@@ -296,6 +308,7 @@ describe("VaultV2Component", () => {
AutofillVaultListItemsStubComponent,
VaultListItemsContainerStubComponent,
],
providers: [{ provide: VaultItemsTransferService, useValue: vaultItemsTransferSvc }],
},
});
@@ -344,6 +357,7 @@ describe("VaultV2Component", () => {
it("loading$ is true when items loading or filters missing; false when both ready", () => {
const itemsLoading$ = itemsSvc.loading$ as unknown as BehaviorSubject<boolean>;
const allFilters$ = filtersSvc.allFilters$ as unknown as Subject<any>;
const readySubject$ = component["readySubject"] as unknown as BehaviorSubject<boolean>;
const values: boolean[] = [];
getObs<boolean>(component, "loading$").subscribe((v) => values.push(!!v));
@@ -354,6 +368,8 @@ describe("VaultV2Component", () => {
itemsLoading$.next(false);
readySubject$.next(true);
expect(values[values.length - 1]).toBe(false);
});

View File

@@ -16,6 +16,7 @@ import {
switchMap,
take,
tap,
BehaviorSubject,
} from "rxjs";
import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components";
@@ -42,7 +43,11 @@ import {
NoItemsModule,
TypographyModule,
} from "@bitwarden/components";
import { DecryptionFailureDialogComponent } from "@bitwarden/vault";
import {
DecryptionFailureDialogComponent,
VaultItemsTransferService,
DefaultVaultItemsTransferService,
} from "@bitwarden/vault";
import { CurrentAccountComponent } from "../../../../auth/popup/account-switching/current-account.component";
import { BrowserApi } from "../../../../platform/browser/browser-api";
@@ -105,6 +110,7 @@ type VaultState = UnionOfValues<typeof VaultState>;
VaultFadeInOutSkeletonComponent,
VaultFadeInOutComponent,
],
providers: [{ provide: VaultItemsTransferService, useClass: DefaultVaultItemsTransferService }],
})
export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
@@ -125,7 +131,22 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
activeUserId: UserId | null = null;
private loading$ = this.vaultPopupLoadingService.loading$.pipe(
/**
* Subject that indicates whether the vault is ready to render
* and that all initialization tasks have been completed (ngOnInit).
* @private
*/
private readySubject = new BehaviorSubject(false);
/**
* Indicates whether the vault is loading and not yet ready to be displayed.
* @protected
*/
protected loading$ = combineLatest([
this.vaultPopupLoadingService.loading$,
this.readySubject.asObservable(),
]).pipe(
map(([loading, ready]) => loading || !ready),
distinctUntilChanged(),
tap((loading) => {
const key = loading ? "loadingVault" : "vaultLoaded";
@@ -200,14 +221,15 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
protected showSkeletonsLoaders$ = combineLatest([
this.loading$,
this.searchService.isCipherSearching$,
this.vaultItemsTransferService.transferInProgress$,
this.skeletonFeatureFlag$,
]).pipe(
map(
([loading, cipherSearching, skeletonsEnabled]) =>
(loading || cipherSearching) && skeletonsEnabled,
),
map(([loading, cipherSearching, transferInProgress, skeletonsEnabled]) => {
return (loading || cipherSearching || transferInProgress) && skeletonsEnabled;
}),
distinctUntilChanged(),
skeletonLoadingDelay(),
shareReplay({ bufferSize: 1, refCount: true }),
);
protected newItemItemValues$: Observable<NewItemInitialValues> =
@@ -251,6 +273,7 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
private i18nService: I18nService,
private configService: ConfigService,
private searchService: SearchService,
private vaultItemsTransferService: VaultItemsTransferService,
) {
combineLatest([
this.vaultPopupItemsService.emptyVault$,
@@ -305,6 +328,10 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
cipherIds: ciphers.map((c) => c.id as CipherId),
});
});
await this.vaultItemsTransferService.enforceOrganizationDataOwnership(this.activeUserId);
this.readySubject.next(true);
}
ngOnDestroy() {