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:
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user