mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 13:23:34 +00:00
[PM-1400] Update IconComponent to use OnPush ChangeDetection (#5181)
* Add disableFavicon$ to stateService * Change IconComponent's ChangeDetectionStrategy and use disableFavicon$ observable * Only get first result from disableFavicon observable * Move disabledFavicon$ to SettingsService * Update usage of disableFavicon to use SettingsService * Remove getting and setting of disabledFavicon on login * Settings service observable adjustments * Fix for popup initially having a null value for the disableFavicon setting in settingsService * Move disabledFavicon$ subscription to ngOnInit * feat: experiment with observables * Remove SettingsService from browser app component * Fix storybook changes * Update apps/web/src/app/vault/components/vault-items/vault-items.stories.ts Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * Fix mock function signature --------- Co-authored-by: Andreas Coroiu <andreas.coroiu@gmail.com> Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com>
This commit is contained in:
@@ -260,8 +260,6 @@ export class LockComponent implements OnInit, OnDestroy {
|
||||
|
||||
private async doContinue(evaluatePasswordAfterUnlock: boolean) {
|
||||
await this.stateService.setEverBeenUnlocked(true);
|
||||
const disableFavicon = await this.stateService.getDisableFavicon();
|
||||
await this.stateService.setDisableFavicon(!!disableFavicon);
|
||||
this.messagingService.send("unlocked");
|
||||
|
||||
if (evaluatePasswordAfterUnlock) {
|
||||
|
||||
@@ -152,8 +152,6 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
|
||||
this.router.navigate([this.forcePasswordResetRoute]);
|
||||
}
|
||||
} else {
|
||||
const disableFavicon = await this.stateService.getDisableFavicon();
|
||||
await this.stateService.setDisableFavicon(!!disableFavicon);
|
||||
if (this.onSuccessfulLogin != null) {
|
||||
this.onSuccessfulLogin();
|
||||
}
|
||||
|
||||
@@ -212,8 +212,6 @@ export class SsoComponent {
|
||||
this.router.navigate([this.forcePasswordResetRoute]);
|
||||
}
|
||||
} else {
|
||||
const disableFavicon = await this.stateService.getDisableFavicon();
|
||||
await this.stateService.setDisableFavicon(!!disableFavicon);
|
||||
if (this.onSuccessfulLogin != null) {
|
||||
await this.onSuccessfulLogin();
|
||||
}
|
||||
|
||||
@@ -197,8 +197,6 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
||||
this.captchaToken
|
||||
);
|
||||
const response: AuthResult = await this.formPromise;
|
||||
const disableFavicon = await this.stateService.getDisableFavicon();
|
||||
await this.stateService.setDisableFavicon(!!disableFavicon);
|
||||
if (this.handleCaptchaRequired(response)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
<div class="icon" aria-hidden="true">
|
||||
<img
|
||||
[src]="image"
|
||||
appFallbackSrc="{{ fallbackImage }}"
|
||||
*ngIf="imageEnabled && image"
|
||||
alt=""
|
||||
decoding="async"
|
||||
loading="lazy"
|
||||
/>
|
||||
<i class="tw-text-muted bwi bwi-lg {{ icon }}" *ngIf="!imageEnabled || !image"></i>
|
||||
<ng-container *ngIf="data$ | async as data">
|
||||
<img
|
||||
[src]="data.image"
|
||||
[appFallbackSrc]="data.fallbackImage"
|
||||
*ngIf="data.imageEnabled && data.image"
|
||||
alt=""
|
||||
decoding="async"
|
||||
loading="lazy"
|
||||
/>
|
||||
<i
|
||||
class="tw-text-muted bwi bwi-lg {{ data.icon }}"
|
||||
*ngIf="!data.imageEnabled || !data.image"
|
||||
></i>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { Component, Input, OnChanges } from "@angular/core";
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from "@angular/core";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
map,
|
||||
Observable,
|
||||
} from "rxjs";
|
||||
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
@@ -25,89 +33,101 @@ const cardIcons: Record<string, string> = {
|
||||
@Component({
|
||||
selector: "app-vault-icon",
|
||||
templateUrl: "icon.component.html",
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class IconComponent implements OnChanges {
|
||||
@Input() cipher: CipherView;
|
||||
icon: string;
|
||||
image: string;
|
||||
fallbackImage: string;
|
||||
imageEnabled: boolean;
|
||||
|
||||
private iconsUrl: string;
|
||||
|
||||
constructor(environmentService: EnvironmentService, private stateService: StateService) {
|
||||
this.iconsUrl = environmentService.getIconsUrl();
|
||||
export class IconComponent implements OnInit {
|
||||
@Input()
|
||||
set cipher(value: CipherView) {
|
||||
this.cipher$.next(value);
|
||||
}
|
||||
|
||||
async ngOnChanges() {
|
||||
// Components may be re-used when using cdk-virtual-scroll. Which puts the component in a weird state,
|
||||
// to avoid this we reset all state variables.
|
||||
this.image = null;
|
||||
this.fallbackImage = null;
|
||||
this.imageEnabled = !(await this.stateService.getDisableFavicon());
|
||||
this.load();
|
||||
}
|
||||
protected data$: Observable<{
|
||||
imageEnabled: boolean;
|
||||
image?: string;
|
||||
fallbackImage: string;
|
||||
icon?: string;
|
||||
}>;
|
||||
|
||||
protected load() {
|
||||
switch (this.cipher.type) {
|
||||
case CipherType.Login:
|
||||
this.icon = "bwi-globe";
|
||||
this.setLoginIcon();
|
||||
break;
|
||||
case CipherType.SecureNote:
|
||||
this.icon = "bwi-sticky-note";
|
||||
break;
|
||||
case CipherType.Card:
|
||||
this.icon = "bwi-credit-card";
|
||||
this.setCardIcon();
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
this.icon = "bwi-id-card";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
private cipher$ = new BehaviorSubject<CipherView>(undefined);
|
||||
|
||||
private setLoginIcon() {
|
||||
if (this.cipher.login.uri) {
|
||||
let hostnameUri = this.cipher.login.uri;
|
||||
let isWebsite = false;
|
||||
constructor(
|
||||
private environmentService: EnvironmentService,
|
||||
private settingsService: SettingsService
|
||||
) {}
|
||||
|
||||
if (hostnameUri.indexOf("androidapp://") === 0) {
|
||||
this.icon = "bwi-android";
|
||||
this.image = null;
|
||||
} else if (hostnameUri.indexOf("iosapp://") === 0) {
|
||||
this.icon = "bwi-apple";
|
||||
this.image = null;
|
||||
} else if (
|
||||
this.imageEnabled &&
|
||||
hostnameUri.indexOf("://") === -1 &&
|
||||
hostnameUri.indexOf(".") > -1
|
||||
) {
|
||||
hostnameUri = "http://" + hostnameUri;
|
||||
isWebsite = true;
|
||||
} else if (this.imageEnabled) {
|
||||
isWebsite = hostnameUri.indexOf("http") === 0 && hostnameUri.indexOf(".") > -1;
|
||||
}
|
||||
async ngOnInit() {
|
||||
const iconsUrl = this.environmentService.getIconsUrl();
|
||||
|
||||
if (this.imageEnabled && isWebsite) {
|
||||
try {
|
||||
this.image = this.iconsUrl + "/" + Utils.getHostname(hostnameUri) + "/icon.png";
|
||||
this.fallbackImage = "images/bwi-globe.png";
|
||||
} catch (e) {
|
||||
// Ignore error since the fallback icon will be shown if image is null.
|
||||
this.data$ = combineLatest([
|
||||
this.settingsService.disableFavicon$.pipe(distinctUntilChanged()),
|
||||
this.cipher$.pipe(filter((c) => c !== undefined)),
|
||||
]).pipe(
|
||||
map(([disableFavicon, cipher]) => {
|
||||
const imageEnabled = !disableFavicon;
|
||||
let image = undefined;
|
||||
let fallbackImage = "";
|
||||
let icon = undefined;
|
||||
|
||||
switch (cipher.type) {
|
||||
case CipherType.Login:
|
||||
icon = "bwi-globe";
|
||||
|
||||
if (cipher.login.uri) {
|
||||
let hostnameUri = cipher.login.uri;
|
||||
let isWebsite = false;
|
||||
|
||||
if (hostnameUri.indexOf("androidapp://") === 0) {
|
||||
icon = "bwi-android";
|
||||
image = null;
|
||||
} else if (hostnameUri.indexOf("iosapp://") === 0) {
|
||||
icon = "bwi-apple";
|
||||
image = null;
|
||||
} else if (
|
||||
imageEnabled &&
|
||||
hostnameUri.indexOf("://") === -1 &&
|
||||
hostnameUri.indexOf(".") > -1
|
||||
) {
|
||||
hostnameUri = "http://" + hostnameUri;
|
||||
isWebsite = true;
|
||||
} else if (imageEnabled) {
|
||||
isWebsite = hostnameUri.indexOf("http") === 0 && hostnameUri.indexOf(".") > -1;
|
||||
}
|
||||
|
||||
if (imageEnabled && isWebsite) {
|
||||
try {
|
||||
image = iconsUrl + "/" + Utils.getHostname(hostnameUri) + "/icon.png";
|
||||
fallbackImage = "images/bwi-globe.png";
|
||||
} catch (e) {
|
||||
// Ignore error since the fallback icon will be shown if image is null.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
image = null;
|
||||
}
|
||||
break;
|
||||
case CipherType.SecureNote:
|
||||
icon = "bwi-sticky-note";
|
||||
break;
|
||||
case CipherType.Card:
|
||||
icon = "bwi-credit-card";
|
||||
if (imageEnabled && cipher.card.brand in cardIcons) {
|
||||
icon = "credit-card-icon " + cardIcons[cipher.card.brand];
|
||||
}
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
icon = "bwi-id-card";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.image = null;
|
||||
}
|
||||
}
|
||||
|
||||
private setCardIcon() {
|
||||
const brand = this.cipher.card.brand;
|
||||
if (this.imageEnabled && brand in cardIcons) {
|
||||
this.icon = "credit-card-icon " + cardIcons[brand];
|
||||
}
|
||||
return {
|
||||
imageEnabled,
|
||||
image,
|
||||
fallbackImage,
|
||||
icon,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user