1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 00:03:56 +00:00

[SM-251] Migrate to new avatar component (#3600)

This commit is contained in:
Oscar Hinton
2022-10-27 14:38:34 +02:00
committed by GitHub
parent 7fa0231616
commit 5f6f4bad82
37 changed files with 230 additions and 360 deletions

View File

@@ -1,127 +0,0 @@
import { Component, Input, OnChanges, OnInit } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { Utils } from "@bitwarden/common/misc/utils";
@Component({
selector: "app-avatar",
template:
'<img *ngIf="src" [src]="sanitizer.bypassSecurityTrustResourceUrl(src)" title="{{data}}" ' +
"[ngClass]=\"{'rounded-circle': circle}\">",
})
export class AvatarComponent implements OnChanges, OnInit {
@Input() data: string;
@Input() email: string;
@Input() size = 45;
@Input() charCount = 2;
@Input() textColor = "#ffffff";
@Input() fontSize = 20;
@Input() fontWeight = 300;
@Input() dynamic = false;
@Input() circle = false;
src: string;
constructor(
public sanitizer: DomSanitizer,
private cryptoFunctionService: CryptoFunctionService,
private stateService: StateService
) {}
ngOnInit() {
if (!this.dynamic) {
this.generate();
}
}
ngOnChanges() {
if (this.dynamic) {
this.generate();
}
}
private async generate() {
const enableGravatars = await this.stateService.getEnableGravitars();
if (enableGravatars && this.email != null) {
const hashBytes = await this.cryptoFunctionService.hash(
this.email.toLowerCase().trim(),
"md5"
);
const hash = Utils.fromBufferToHex(hashBytes).toLowerCase();
this.src = "https://www.gravatar.com/avatar/" + hash + "?s=" + this.size + "&r=pg&d=retro";
} else {
let chars: string = null;
const upperData = this.data.toUpperCase();
if (this.charCount > 1) {
chars = this.getFirstLetters(upperData, this.charCount);
}
if (chars == null) {
chars = this.unicodeSafeSubstring(upperData, this.charCount);
}
// If the chars contain an emoji, only show it.
if (chars.match(Utils.regexpEmojiPresentation)) {
chars = chars.match(Utils.regexpEmojiPresentation)[0];
}
const charObj = this.getCharText(chars);
const color = Utils.stringToColor(upperData);
const svg = this.getSvg(this.size, color);
svg.appendChild(charObj);
const html = window.document.createElement("div").appendChild(svg).outerHTML;
const svgHtml = window.btoa(unescape(encodeURIComponent(html)));
this.src = "data:image/svg+xml;base64," + svgHtml;
}
}
private getFirstLetters(data: string, count: number): string {
const parts = data.split(" ");
if (parts.length > 1) {
let text = "";
for (let i = 0; i < count; i++) {
text += this.unicodeSafeSubstring(parts[i], 1);
}
return text;
}
return null;
}
private getSvg(size: number, color: string): HTMLElement {
const svgTag = window.document.createElement("svg");
svgTag.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svgTag.setAttribute("pointer-events", "none");
svgTag.setAttribute("width", size.toString());
svgTag.setAttribute("height", size.toString());
svgTag.style.backgroundColor = color;
svgTag.style.width = size + "px";
svgTag.style.height = size + "px";
return svgTag;
}
private getCharText(character: string): HTMLElement {
const textTag = window.document.createElement("text");
textTag.setAttribute("text-anchor", "middle");
textTag.setAttribute("y", "50%");
textTag.setAttribute("x", "50%");
textTag.setAttribute("dy", "0.35em");
textTag.setAttribute("pointer-events", "auto");
textTag.setAttribute("fill", this.textColor);
textTag.setAttribute(
"font-family",
'"Open Sans","Helvetica Neue",Helvetica,Arial,' +
'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"'
);
textTag.textContent = character;
textTag.style.fontWeight = this.fontWeight.toString();
textTag.style.fontSize = this.fontSize + "px";
return textTag;
}
private unicodeSafeSubstring(str: string, count: number) {
const characters = str.match(/./gu);
return characters != null ? characters.slice(0, count).join("") : "";
}
}

View File

@@ -2,7 +2,6 @@ import { CommonModule, DatePipe } from "@angular/common";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { AvatarComponent } from "./components/avatar.component";
import { CalloutComponent } from "./components/callout.component";
import { ExportScopeCalloutComponent } from "./components/export-scope-callout.component";
import { IconComponent } from "./components/icon.component";
@@ -46,7 +45,6 @@ import { PasswordStrengthComponent } from "./shared/components/password-strength
A11yTitleDirective,
ApiActionDirective,
AutofocusDirective,
AvatarComponent,
BoxRowDirective,
CalloutComponent,
ColorPasswordCountPipe,
@@ -74,7 +72,6 @@ import { PasswordStrengthComponent } from "./shared/components/password-strength
A11yTitleDirective,
ApiActionDirective,
AutofocusDirective,
AvatarComponent,
BitwardenToastModule,
BoxRowDirective,
CalloutComponent,

View File

@@ -37,9 +37,11 @@ const kdf = 0;
const kdfIterations = 10000;
const userId = Utils.newGuid();
const masterPasswordHash = "MASTER_PASSWORD_HASH";
const name = "NAME";
const decodedToken = {
sub: userId,
name: name,
email: email,
premium: false,
};
@@ -122,6 +124,7 @@ describe("LogInStrategy", () => {
...new AccountProfile(),
...{
userId: userId,
name: name,
email: email,
hasPremiumPersonally: false,
kdfIterations: kdfIterations,

View File

@@ -173,8 +173,6 @@ export abstract class StateService<T extends Account = Account> {
) => Promise<void>;
getEnableFullWidth: (options?: StorageOptions) => Promise<boolean>;
setEnableFullWidth: (value: boolean, options?: StorageOptions) => Promise<void>;
getEnableGravitars: (options?: StorageOptions) => Promise<boolean>;
setEnableGravitars: (value: boolean, options?: StorageOptions) => Promise<void>;
getEnableMinimizeToTray: (options?: StorageOptions) => Promise<boolean>;
setEnableMinimizeToTray: (value: boolean, options?: StorageOptions) => Promise<void>;
getEnableStartToTray: (options?: StorageOptions) => Promise<boolean>;

View File

@@ -105,6 +105,7 @@ export abstract class LogInStrategy {
...new AccountProfile(),
...{
userId: accountInformation.sub,
name: accountInformation.name,
email: accountInformation.email,
hasPremiumPersonally: accountInformation.premium,
kdfIterations: tokenResponse.kdfIterations,

View File

@@ -175,6 +175,7 @@ export class AccountProfile {
apiKeyClientId?: string;
authenticationStatus?: AuthenticationStatus;
convertAccountToKeyConnector?: boolean;
name?: string;
email?: string;
emailVerified?: boolean;
entityId?: string;
@@ -219,7 +220,6 @@ export class AccountSettings {
enableAutoFillOnPageLoad?: boolean;
enableBiometric?: boolean;
enableFullWidth?: boolean;
enableGravitars?: boolean;
environmentUrls: EnvironmentUrls = new EnvironmentUrls();
equivalentDomains?: any;
minimizeOnCopyToClipboard?: boolean;

View File

@@ -1228,27 +1228,6 @@ export class StateService<
);
}
async getEnableGravitars(options?: StorageOptions): Promise<boolean> {
return (
(
await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
)
)?.settings?.enableGravitars ?? false
);
}
async setEnableGravitars(value: boolean, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
);
account.settings.enableGravitars = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
);
}
async getEnableMinimizeToTray(options?: StorageOptions): Promise<boolean> {
return (
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))

View File

@@ -61,7 +61,6 @@ const v1Keys: { [key: string]: string } = {
enableBrowserIntegrationFingerprint: "enableBrowserIntegrationFingerprint",
enableCloseToTray: "enableCloseToTray",
enableFullWidth: "enableFullWidth",
enableGravatars: "enableGravatars",
enableMinimizeToTray: "enableMinimizeToTray",
enableStartToTray: "enableStartToTrayKey",
enableTray: "enableTray",
@@ -305,9 +304,6 @@ export class StateMigrationService<
enableFullWidth:
(await this.get<boolean>(v1Keys.enableFullWidth)) ??
defaultAccount.settings.enableFullWidth,
enableGravitars:
(await this.get<boolean>(v1Keys.enableGravatars)) ??
defaultAccount.settings.enableGravitars,
environmentUrls: globals.environmentUrls ?? defaultAccount.settings.environmentUrls,
equivalentDomains:
(await this.get<any>(v1Keys.equivalentDomains)) ??

View File

@@ -17,9 +17,9 @@ const SizeClasses: Record<SizeTypes, string[]> = {
})
export class AvatarComponent implements OnChanges {
@Input() border = false;
@Input() color: string;
@Input() id: number;
@Input() text: string;
@Input() color?: string;
@Input() id?: string;
@Input() text?: string;
@Input() size: SizeTypes = "default";
private svgCharCount = 2;
@@ -42,7 +42,7 @@ export class AvatarComponent implements OnChanges {
private generate() {
let chars: string = null;
const upperCaseText = this.text.toUpperCase();
const upperCaseText = this.text?.toUpperCase() ?? "";
chars = this.getFirstLetters(upperCaseText, this.svgCharCount);
@@ -58,9 +58,9 @@ export class AvatarComponent implements OnChanges {
let svg: HTMLElement;
let hexColor = this.color;
if (this.color != null) {
if (!Utils.isNullOrWhitespace(this.color)) {
svg = this.createSvgElement(this.svgSize, hexColor);
} else if (this.id != null) {
} else if (!Utils.isNullOrWhitespace(this.id)) {
hexColor = Utils.stringToColor(this.id.toString());
svg = this.createSvgElement(this.svgSize, hexColor);
} else {

View File

@@ -1,15 +1,16 @@
export * from "./async-actions";
export * from "./avatar";
export * from "./badge";
export * from "./banner";
export * from "./button";
export * from "./callout";
export * from "./dialog";
export * from "./form-field";
export * from "./icon";
export * from "./icon-button";
export * from "./icon";
export * from "./link";
export * from "./menu";
export * from "./multi-select";
export * from "./dialog";
export * from "./link";
export * from "./tabs";
export * from "./toggle-group";
export * from "./utils/i18n-mock.service";