mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
[PM-18946] Improve Vault loading experience (#13714)
* [PM-18946] Refactor loading$ in vault-v2. Update icon-component, and build-cipher-icon
This commit is contained in:
@@ -2,16 +2,18 @@
|
||||
<ng-container *ngIf="data$ | async as data">
|
||||
<img
|
||||
[src]="data.image"
|
||||
[appFallbackSrc]="data.fallbackImage"
|
||||
*ngIf="data.imageEnabled && data.image"
|
||||
class="tw-size-6 tw-rounded-md"
|
||||
alt=""
|
||||
decoding="async"
|
||||
loading="lazy"
|
||||
[ngClass]="{ 'tw-invisible tw-absolute': !imageLoaded() }"
|
||||
(load)="imageLoaded.set(true)"
|
||||
(error)="imageLoaded.set(false)"
|
||||
/>
|
||||
<i
|
||||
class="tw-w-6 tw-text-muted bwi bwi-lg {{ data.icon }}"
|
||||
*ngIf="!data.imageEnabled || !data.image"
|
||||
*ngIf="!data.imageEnabled || !data.image || !imageLoaded()"
|
||||
></i>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from "@angular/core";
|
||||
import { ChangeDetectionStrategy, Component, input, signal } from "@angular/core";
|
||||
import { toObservable } from "@angular/core/rxjs-interop";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
map,
|
||||
tap,
|
||||
Observable,
|
||||
startWith,
|
||||
pairwise,
|
||||
} from "rxjs";
|
||||
|
||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { buildCipherIcon } from "@bitwarden/common/vault/icon/build-cipher-icon";
|
||||
import { buildCipherIcon, CipherIconDetails } from "@bitwarden/common/vault/icon/build-cipher-icon";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
@Component({
|
||||
@@ -20,33 +20,40 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
templateUrl: "icon.component.html",
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class IconComponent implements OnInit {
|
||||
@Input()
|
||||
set cipher(value: CipherView) {
|
||||
this.cipher$.next(value);
|
||||
}
|
||||
export class IconComponent {
|
||||
/**
|
||||
* The cipher to display the icon for.
|
||||
*/
|
||||
cipher = input.required<CipherView>();
|
||||
|
||||
protected data$: Observable<{
|
||||
imageEnabled: boolean;
|
||||
image?: string;
|
||||
fallbackImage: string;
|
||||
icon?: string;
|
||||
}>;
|
||||
imageLoaded = signal(false);
|
||||
|
||||
private cipher$ = new BehaviorSubject<CipherView>(undefined);
|
||||
protected data$: Observable<CipherIconDetails>;
|
||||
|
||||
constructor(
|
||||
private environmentService: EnvironmentService,
|
||||
private domainSettingsService: DomainSettingsService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.data$ = combineLatest([
|
||||
) {
|
||||
const iconSettings$ = combineLatest([
|
||||
this.environmentService.environment$.pipe(map((e) => e.getIconsUrl())),
|
||||
this.domainSettingsService.showFavicons$.pipe(distinctUntilChanged()),
|
||||
this.cipher$.pipe(filter((c) => c !== undefined)),
|
||||
]).pipe(
|
||||
map(([iconsUrl, showFavicon, cipher]) => buildCipherIcon(iconsUrl, cipher, showFavicon)),
|
||||
map(([iconsUrl, showFavicon]) => ({ iconsUrl, showFavicon })),
|
||||
startWith({ iconsUrl: null, showFavicon: false }), // Start with a safe default to avoid flickering icons
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
this.data$ = combineLatest([iconSettings$, toObservable(this.cipher)]).pipe(
|
||||
map(([{ iconsUrl, showFavicon }, cipher]) => buildCipherIcon(iconsUrl, cipher, showFavicon)),
|
||||
startWith(null),
|
||||
pairwise(),
|
||||
tap(([prev, next]) => {
|
||||
if (prev?.image !== next?.image) {
|
||||
// The image changed, reset the loaded state to not show an empty icon
|
||||
this.imageLoaded.set(false);
|
||||
}
|
||||
}),
|
||||
map(([_, next]) => next!),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user