mirror of
https://github.com/bitwarden/browser.git
synced 2024-09-27 04:03:00 +02:00
[SM-251] Migrate to new avatar component (#3600)
This commit is contained in:
parent
7fa0231616
commit
5f6f4bad82
128
apps/desktop/src/app/components/avatar.component.ts
Normal file
128
apps/desktop/src/app/components/avatar.component.ts
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import { Component, Input, OnChanges, OnInit } from "@angular/core";
|
||||||
|
import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser";
|
||||||
|
|
||||||
|
import { Utils } from "@bitwarden/common/misc/utils";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-avatar",
|
||||||
|
template: `<img
|
||||||
|
*ngIf="src"
|
||||||
|
[src]="src"
|
||||||
|
title="{{ data }}"
|
||||||
|
[ngClass]="{ 'rounded-circle': circle }"
|
||||||
|
/>`,
|
||||||
|
})
|
||||||
|
export class AvatarComponent implements OnChanges, OnInit {
|
||||||
|
@Input() size = 45;
|
||||||
|
@Input() charCount = 2;
|
||||||
|
@Input() fontSize = 20;
|
||||||
|
@Input() dynamic = false;
|
||||||
|
@Input() circle = false;
|
||||||
|
|
||||||
|
@Input() color?: string;
|
||||||
|
@Input() id?: number;
|
||||||
|
@Input() text?: string;
|
||||||
|
|
||||||
|
private svgCharCount = 2;
|
||||||
|
private svgFontWeight = 300;
|
||||||
|
src: SafeResourceUrl;
|
||||||
|
|
||||||
|
constructor(public sanitizer: DomSanitizer) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
if (!this.dynamic) {
|
||||||
|
this.generate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges() {
|
||||||
|
if (this.dynamic) {
|
||||||
|
this.generate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async generate() {
|
||||||
|
let chars: string = null;
|
||||||
|
const upperCaseText = this.text?.toUpperCase() ?? "";
|
||||||
|
|
||||||
|
chars = this.getFirstLetters(upperCaseText, this.svgCharCount);
|
||||||
|
|
||||||
|
if (chars == null) {
|
||||||
|
chars = this.unicodeSafeSubstring(upperCaseText, this.svgCharCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the chars contain an emoji, only show it.
|
||||||
|
if (chars.match(Utils.regexpEmojiPresentation)) {
|
||||||
|
chars = chars.match(Utils.regexpEmojiPresentation)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
let svg: HTMLElement;
|
||||||
|
let hexColor = this.color;
|
||||||
|
|
||||||
|
if (this.color != null) {
|
||||||
|
svg = this.createSvgElement(this.size, hexColor);
|
||||||
|
} else if (this.id != null) {
|
||||||
|
hexColor = Utils.stringToColor(this.id.toString());
|
||||||
|
svg = this.createSvgElement(this.size, hexColor);
|
||||||
|
} else {
|
||||||
|
hexColor = Utils.stringToColor(upperCaseText);
|
||||||
|
svg = this.createSvgElement(this.size, hexColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
const charObj = this.createTextElement(chars, hexColor);
|
||||||
|
svg.appendChild(charObj);
|
||||||
|
const html = window.document.createElement("div").appendChild(svg).outerHTML;
|
||||||
|
const svgHtml = window.btoa(unescape(encodeURIComponent(html)));
|
||||||
|
this.src = this.sanitizer.bypassSecurityTrustResourceUrl(
|
||||||
|
"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 createSvgElement(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 createTextElement(character: string, color: 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", Utils.pickTextColorBasedOnBgColor(color, 135, true));
|
||||||
|
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.svgFontWeight.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("") : "";
|
||||||
|
}
|
||||||
|
}
|
@ -8,17 +8,18 @@
|
|||||||
aria-controls="cdk-overlay-container"
|
aria-controls="cdk-overlay-container"
|
||||||
[attr.aria-expanded]="isOpen"
|
[attr.aria-expanded]="isOpen"
|
||||||
>
|
>
|
||||||
<ng-container *ngIf="activeAccountEmail != null; else noActiveAccount">
|
<ng-container *ngIf="activeAccount?.email != null; else noActiveAccount">
|
||||||
<app-avatar
|
<app-avatar
|
||||||
[data]="activeAccountEmail"
|
[text]="activeAccount.name"
|
||||||
|
[id]="activeAccount.id"
|
||||||
size="25"
|
size="25"
|
||||||
[circle]="true"
|
[circle]="true"
|
||||||
[fontSize]="14"
|
[fontSize]="14"
|
||||||
[dynamic]="true"
|
[dynamic]="true"
|
||||||
*ngIf="activeAccountEmail != null"
|
*ngIf="activeAccount.email != null"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></app-avatar>
|
></app-avatar>
|
||||||
<span>{{ activeAccountEmail }}</span>
|
<span>{{ activeAccount.email }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #noActiveAccount>
|
<ng-template #noActiveAccount>
|
||||||
<span>{{ "switchAccount" | i18n }}</span>
|
<span>{{ "switchAccount" | i18n }}</span>
|
||||||
@ -58,7 +59,8 @@
|
|||||||
attr.aria-label="{{ 'switchAccount' | i18n }}"
|
attr.aria-label="{{ 'switchAccount' | i18n }}"
|
||||||
>
|
>
|
||||||
<app-avatar
|
<app-avatar
|
||||||
[data]="a.value.profile.email"
|
[text]="a.value.profile.name ?? a.value.profile.email"
|
||||||
|
[id]="a.value.profile.userId"
|
||||||
size="25"
|
size="25"
|
||||||
[circle]="true"
|
[circle]="true"
|
||||||
[fontSize]="14"
|
[fontSize]="14"
|
||||||
@ -85,7 +87,7 @@
|
|||||||
></i>
|
></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="activeAccountEmail != null">
|
<ng-container *ngIf="activeAccount?.email != null">
|
||||||
<div class="border" *ngIf="numberOfAccounts > 0"></div>
|
<div class="border" *ngIf="numberOfAccounts > 0"></div>
|
||||||
<ng-container *ngIf="numberOfAccounts < 4">
|
<ng-container *ngIf="numberOfAccounts < 4">
|
||||||
<button type="button" class="add" routerLink="/login" (click)="addAccount()">
|
<button type="button" class="add" routerLink="/login" (click)="addAccount()">
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
import { animate, state, style, transition, trigger } from "@angular/animations";
|
import { animate, state, style, transition, trigger } from "@angular/animations";
|
||||||
import { ConnectedPosition } from "@angular/cdk/overlay";
|
import { ConnectedPosition } from "@angular/cdk/overlay";
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||||
|
import { concatMap, Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||||
|
import { TokenService } from "@bitwarden/common/abstractions/token.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus";
|
import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus";
|
||||||
import { Utils } from "@bitwarden/common/misc/utils";
|
import { Utils } from "@bitwarden/common/misc/utils";
|
||||||
import { Account } from "@bitwarden/common/models/domain/account";
|
import { Account } from "@bitwarden/common/models/domain/account";
|
||||||
|
|
||||||
|
type ActiveAccount = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
|
||||||
export class SwitcherAccount extends Account {
|
export class SwitcherAccount extends Account {
|
||||||
get serverUrl() {
|
get serverUrl() {
|
||||||
return this.removeWebProtocolFromString(
|
return this.removeWebProtocolFromString(
|
||||||
@ -48,11 +56,12 @@ export class SwitcherAccount extends Account {
|
|||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
export class AccountSwitcherComponent implements OnInit, OnDestroy {
|
||||||
export class AccountSwitcherComponent implements OnInit {
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
accounts: { [userId: string]: SwitcherAccount } = {};
|
accounts: { [userId: string]: SwitcherAccount } = {};
|
||||||
activeAccountEmail: string;
|
activeAccount?: ActiveAccount;
|
||||||
serverUrl: string;
|
serverUrl: string;
|
||||||
authStatus = AuthenticationStatus;
|
authStatus = AuthenticationStatus;
|
||||||
overlayPostition: ConnectedPosition[] = [
|
overlayPostition: ConnectedPosition[] = [
|
||||||
@ -65,7 +74,7 @@ export class AccountSwitcherComponent implements OnInit {
|
|||||||
];
|
];
|
||||||
|
|
||||||
get showSwitcher() {
|
get showSwitcher() {
|
||||||
const userIsInAVault = !Utils.isNullOrWhitespace(this.activeAccountEmail);
|
const userIsInAVault = !Utils.isNullOrWhitespace(this.activeAccount?.email);
|
||||||
const userIsAddingAnAdditionalAccount = Object.keys(this.accounts).length > 0;
|
const userIsAddingAnAdditionalAccount = Object.keys(this.accounts).length > 0;
|
||||||
return userIsInAVault || userIsAddingAnAdditionalAccount;
|
return userIsInAVault || userIsAddingAnAdditionalAccount;
|
||||||
}
|
}
|
||||||
@ -81,21 +90,39 @@ export class AccountSwitcherComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private messagingService: MessagingService
|
private messagingService: MessagingService,
|
||||||
|
private tokenService: TokenService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
this.stateService.accounts
|
||||||
this.stateService.accounts.subscribe(async (accounts: { [userId: string]: Account }) => {
|
.pipe(
|
||||||
for (const userId in accounts) {
|
concatMap(async (accounts: { [userId: string]: Account }) => {
|
||||||
accounts[userId].profile.authenticationStatus = await this.authService.getAuthStatus(
|
for (const userId in accounts) {
|
||||||
userId
|
accounts[userId].profile.authenticationStatus = await this.authService.getAuthStatus(
|
||||||
);
|
userId
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.accounts = await this.createSwitcherAccounts(accounts);
|
this.accounts = await this.createSwitcherAccounts(accounts);
|
||||||
this.activeAccountEmail = await this.stateService.getEmail();
|
try {
|
||||||
});
|
this.activeAccount = {
|
||||||
|
id: await this.tokenService.getUserId(),
|
||||||
|
name: (await this.tokenService.getName()) ?? (await this.tokenService.getEmail()),
|
||||||
|
email: await this.tokenService.getEmail(),
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
this.activeAccount = undefined;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
takeUntil(this.destroy$)
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle() {
|
toggle() {
|
||||||
|
@ -10,6 +10,7 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
|||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
|
|
||||||
|
import { AvatarComponent } from "../components/avatar.component";
|
||||||
import { ServicesModule } from "../services/services.module";
|
import { ServicesModule } from "../services/services.module";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -25,6 +26,7 @@ import { ServicesModule } from "../services/services.module";
|
|||||||
ScrollingModule,
|
ScrollingModule,
|
||||||
ServicesModule,
|
ServicesModule,
|
||||||
],
|
],
|
||||||
|
declarations: [AvatarComponent],
|
||||||
exports: [
|
exports: [
|
||||||
A11yModule,
|
A11yModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
@ -37,6 +39,7 @@ import { ServicesModule } from "../services/services.module";
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
ScrollingModule,
|
ScrollingModule,
|
||||||
ServicesModule,
|
ServicesModule,
|
||||||
|
AvatarComponent,
|
||||||
],
|
],
|
||||||
providers: [DatePipe],
|
providers: [DatePipe],
|
||||||
})
|
})
|
||||||
|
@ -6,12 +6,7 @@
|
|||||||
[appA11yTitle]="'organizationPicker' | i18n"
|
[appA11yTitle]="'organizationPicker' | i18n"
|
||||||
[bitMenuTriggerFor]="orgPickerMenu"
|
[bitMenuTriggerFor]="orgPickerMenu"
|
||||||
>
|
>
|
||||||
<app-avatar
|
<bit-avatar [text]="activeOrganization.name" [id]="activeOrganization.id"></bit-avatar>
|
||||||
[data]="activeOrganization.name"
|
|
||||||
size="45"
|
|
||||||
[circle]="true"
|
|
||||||
[dynamic]="true"
|
|
||||||
></app-avatar>
|
|
||||||
<div class="tw-flex">
|
<div class="tw-flex">
|
||||||
<div class="org-name tw-ml-3">
|
<div class="org-name tw-ml-3">
|
||||||
<span>{{ activeOrganization.name }}</span>
|
<span>{{ activeOrganization.name }}</span>
|
||||||
|
@ -54,13 +54,7 @@
|
|||||||
*ngIf="name"
|
*ngIf="name"
|
||||||
appStopProp
|
appStopProp
|
||||||
>
|
>
|
||||||
<app-avatar
|
<bit-avatar [text]="name" [id]="userId" size="small"></bit-avatar>
|
||||||
[data]="name"
|
|
||||||
[email]="email"
|
|
||||||
size="25"
|
|
||||||
fontSize="14"
|
|
||||||
[circle]="true"
|
|
||||||
></app-avatar>
|
|
||||||
<div class="tw-ml-2 tw-block tw-overflow-hidden tw-whitespace-nowrap">
|
<div class="tw-ml-2 tw-block tw-overflow-hidden tw-whitespace-nowrap">
|
||||||
<span>{{ "loggedInAs" | i18n }}</span>
|
<span>{{ "loggedInAs" | i18n }}</span>
|
||||||
<small class="tw-block tw-overflow-hidden tw-whitespace-nowrap tw-text-muted">{{
|
<small class="tw-block tw-overflow-hidden tw-whitespace-nowrap tw-text-muted">{{
|
||||||
|
@ -24,6 +24,7 @@ export class NavbarComponent implements OnInit {
|
|||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
providers: Provider[] = [];
|
providers: Provider[] = [];
|
||||||
|
userId: string;
|
||||||
organizations$: Observable<Organization[]>;
|
organizations$: Observable<Organization[]>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -41,6 +42,7 @@ export class NavbarComponent implements OnInit {
|
|||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.name = await this.tokenService.getName();
|
this.name = await this.tokenService.getName();
|
||||||
this.email = await this.tokenService.getEmail();
|
this.email = await this.tokenService.getEmail();
|
||||||
|
this.userId = await this.tokenService.getUserId();
|
||||||
if (this.name == null || this.name.trim() === "") {
|
if (this.name == null || this.name.trim() === "") {
|
||||||
this.name = this.email;
|
this.name = this.email;
|
||||||
}
|
}
|
||||||
|
@ -41,14 +41,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tr *ngFor="let user of filteredUsers">
|
<tr *ngFor="let user of filteredUsers">
|
||||||
<td width="30">
|
<td width="30">
|
||||||
<app-avatar
|
<bit-avatar [text]="user | userName" [id]="user.id" size="small"></bit-avatar>
|
||||||
[data]="user | userName"
|
|
||||||
[email]="user.email"
|
|
||||||
size="25"
|
|
||||||
[circle]="true"
|
|
||||||
[fontSize]="14"
|
|
||||||
>
|
|
||||||
</app-avatar>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ user.email }}
|
{{ user.email }}
|
||||||
@ -60,14 +53,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr *ngFor="let user of excludedUsers">
|
<tr *ngFor="let user of excludedUsers">
|
||||||
<td width="30">
|
<td width="30">
|
||||||
<app-avatar
|
<bit-avatar [text]="user | userName" [id]="user.id" size="small"></bit-avatar>
|
||||||
[data]="user | userName"
|
|
||||||
[email]="user.email"
|
|
||||||
size="25"
|
|
||||||
[circle]="true"
|
|
||||||
[fontSize]="14"
|
|
||||||
>
|
|
||||||
</app-avatar>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ user.email }}
|
{{ user.email }}
|
||||||
@ -89,14 +75,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tr *ngFor="let user of filteredUsers">
|
<tr *ngFor="let user of filteredUsers">
|
||||||
<td width="30">
|
<td width="30">
|
||||||
<app-avatar
|
<bit-avatar [text]="user | userName" [id]="user.id" size="small"></bit-avatar>
|
||||||
[data]="user | userName"
|
|
||||||
[email]="user.email"
|
|
||||||
size="25"
|
|
||||||
[circle]="true"
|
|
||||||
[fontSize]="14"
|
|
||||||
>
|
|
||||||
</app-avatar>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ user.email }}
|
{{ user.email }}
|
||||||
|
@ -33,14 +33,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tr *ngFor="let user of users">
|
<tr *ngFor="let user of users">
|
||||||
<td width="30">
|
<td width="30">
|
||||||
<app-avatar
|
<bit-avatar [text]="user | userName" [id]="user.id" size="small"></bit-avatar>
|
||||||
[data]="user | userName"
|
|
||||||
[email]="user.email"
|
|
||||||
size="25"
|
|
||||||
[circle]="true"
|
|
||||||
[fontSize]="14"
|
|
||||||
>
|
|
||||||
</app-avatar>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ user.email }}
|
{{ user.email }}
|
||||||
@ -59,14 +52,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tr *ngFor="let user of users">
|
<tr *ngFor="let user of users">
|
||||||
<td width="30">
|
<td width="30">
|
||||||
<app-avatar
|
<bit-avatar [text]="user | userName" [id]="user.id" size="small"></bit-avatar>
|
||||||
[data]="user | userName"
|
|
||||||
[email]="user.email"
|
|
||||||
size="25"
|
|
||||||
[circle]="true"
|
|
||||||
[fontSize]="14"
|
|
||||||
>
|
|
||||||
</app-avatar>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ user.email }}
|
{{ user.email }}
|
||||||
|
@ -33,14 +33,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tr *ngFor="let user of users">
|
<tr *ngFor="let user of users">
|
||||||
<td width="30">
|
<td width="30">
|
||||||
<app-avatar
|
<bit-avatar [text]="user | userName" [id]="user.id" size="small"></bit-avatar>
|
||||||
[data]="user | userName"
|
|
||||||
[email]="user.email"
|
|
||||||
size="25"
|
|
||||||
[circle]="true"
|
|
||||||
[fontSize]="14"
|
|
||||||
>
|
|
||||||
</app-avatar>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ user.email }}
|
{{ user.email }}
|
||||||
@ -59,14 +52,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tr *ngFor="let user of users">
|
<tr *ngFor="let user of users">
|
||||||
<td width="30">
|
<td width="30">
|
||||||
<app-avatar
|
<bit-avatar [text]="user | userName" [id]="user.id" size="small"></bit-avatar>
|
||||||
[data]="user | userName"
|
|
||||||
[email]="user.email"
|
|
||||||
size="25"
|
|
||||||
[circle]="true"
|
|
||||||
[fontSize]="14"
|
|
||||||
>
|
|
||||||
</app-avatar>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ user.email }}
|
{{ user.email }}
|
||||||
|
@ -28,13 +28,11 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tr *ngFor="let item of users">
|
<tr *ngFor="let item of users">
|
||||||
<td width="30">
|
<td width="30">
|
||||||
<app-avatar
|
<bit-avatar
|
||||||
[data]="item.user | userName"
|
[text]="item.user | userName"
|
||||||
[email]="item.user.email"
|
[id]="item.user.id"
|
||||||
size="25"
|
size="small"
|
||||||
[circle]="true"
|
></bit-avatar>
|
||||||
[fontSize]="14"
|
|
||||||
></app-avatar>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ item.user.email }}
|
{{ item.user.email }}
|
||||||
|
@ -101,14 +101,7 @@
|
|||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td width="30" (click)="check(u)">
|
<td width="30" (click)="check(u)">
|
||||||
<app-avatar
|
<bit-avatar [text]="u | userName" [id]="u.id" size="small"></bit-avatar>
|
||||||
[data]="u | userName"
|
|
||||||
[email]="u.email"
|
|
||||||
size="25"
|
|
||||||
[circle]="true"
|
|
||||||
[fontSize]="14"
|
|
||||||
>
|
|
||||||
</app-avatar>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ u.email }}
|
{{ u.email }}
|
||||||
|
@ -141,14 +141,7 @@
|
|||||||
<input type="checkbox" [(ngModel)]="u.checked" appStopProp />
|
<input type="checkbox" [(ngModel)]="u.checked" appStopProp />
|
||||||
</td>
|
</td>
|
||||||
<td width="30">
|
<td width="30">
|
||||||
<app-avatar
|
<bit-avatar [text]="u | userName" [id]="u.userId" size="small"></bit-avatar>
|
||||||
[data]="u | userName"
|
|
||||||
[email]="u.email"
|
|
||||||
size="25"
|
|
||||||
[circle]="true"
|
|
||||||
[fontSize]="14"
|
|
||||||
>
|
|
||||||
</app-avatar>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" appStopClick (click)="edit(u)">{{ u.email }}</a>
|
<a href="#" appStopClick (click)="edit(u)">{{ u.email }}</a>
|
||||||
|
@ -63,7 +63,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<app-avatar data="{{ org.name }}" dynamic="true" size="75" fontSize="35"></app-avatar>
|
<bit-avatar [text]="org.name" [id]="org.id" size="large"></bit-avatar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" buttonType="primary" bitButton [loading]="form.loading">
|
<button type="submit" buttonType="primary" bitButton [loading]="form.loading">
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let p of providers">
|
<tr *ngFor="let p of providers">
|
||||||
<td width="30">
|
<td width="30">
|
||||||
<app-avatar [data]="p.name" size="25" [circle]="true" [fontSize]="14"></app-avatar>
|
<bit-avatar [text]="p.name" [id]="p.id" size="small"></bit-avatar>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" [routerLink]="['/providers', p.id]">{{ p.name }}</a>
|
<a href="#" [routerLink]="['/providers', p.id]">{{ p.name }}</a>
|
||||||
|
@ -34,14 +34,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let c of trustedContacts; let i = index">
|
<tr *ngFor="let c of trustedContacts; let i = index">
|
||||||
<td width="30">
|
<td width="30">
|
||||||
<app-avatar
|
<bit-avatar [text]="c | userName" [id]="c.granteeId" size="small"></bit-avatar>
|
||||||
[data]="c | userName"
|
|
||||||
[email]="c.email"
|
|
||||||
size="25"
|
|
||||||
[circle]="true"
|
|
||||||
[fontSize]="14"
|
|
||||||
>
|
|
||||||
</app-avatar>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" appStopClick (click)="edit(c)">{{ c.email }}</a>
|
<a href="#" appStopClick (click)="edit(c)">{{ c.email }}</a>
|
||||||
@ -149,14 +142,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let c of grantedContacts; let i = index">
|
<tr *ngFor="let c of grantedContacts; let i = index">
|
||||||
<td width="30">
|
<td width="30">
|
||||||
<app-avatar
|
<bit-avatar [text]="c | userName" [id]="c.grantorId" size="small"></bit-avatar>
|
||||||
[data]="c | userName"
|
|
||||||
[email]="c.email"
|
|
||||||
size="25"
|
|
||||||
[circle]="true"
|
|
||||||
[fontSize]="14"
|
|
||||||
>
|
|
||||||
</app-avatar>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span>{{ c.email }}</span>
|
<span>{{ c.email }}</span>
|
||||||
|
@ -90,29 +90,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<small class="form-text text-muted">{{ "faviconDesc" | i18n }}</small>
|
<small class="form-text text-muted">{{ "faviconDesc" | i18n }}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input
|
|
||||||
class="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
id="enableGravatars"
|
|
||||||
name="enableGravatars"
|
|
||||||
[(ngModel)]="enableGravatars"
|
|
||||||
/>
|
|
||||||
<label class="form-check-label" for="enableGravatars">
|
|
||||||
{{ "enableGravatars" | i18n }}
|
|
||||||
</label>
|
|
||||||
<a
|
|
||||||
href="https://gravatar.com/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<small class="form-text text-muted">{{ "enableGravatarsDesc" | i18n }}</small>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input
|
<input
|
||||||
|
@ -17,7 +17,6 @@ import { Utils } from "@bitwarden/common/misc/utils";
|
|||||||
export class PreferencesComponent implements OnInit {
|
export class PreferencesComponent implements OnInit {
|
||||||
vaultTimeoutAction = "lock";
|
vaultTimeoutAction = "lock";
|
||||||
enableFavicons: boolean;
|
enableFavicons: boolean;
|
||||||
enableGravatars: boolean;
|
|
||||||
enableFullWidth: boolean;
|
enableFullWidth: boolean;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
locale: string;
|
locale: string;
|
||||||
@ -73,7 +72,6 @@ export class PreferencesComponent implements OnInit {
|
|||||||
this.vaultTimeout.setValue(await this.vaultTimeoutSettingsService.getVaultTimeout());
|
this.vaultTimeout.setValue(await this.vaultTimeoutSettingsService.getVaultTimeout());
|
||||||
this.vaultTimeoutAction = await this.stateService.getVaultTimeoutAction();
|
this.vaultTimeoutAction = await this.stateService.getVaultTimeoutAction();
|
||||||
this.enableFavicons = !(await this.stateService.getDisableFavicon());
|
this.enableFavicons = !(await this.stateService.getDisableFavicon());
|
||||||
this.enableGravatars = await this.stateService.getEnableGravitars();
|
|
||||||
this.enableFullWidth = await this.stateService.getEnableFullWidth();
|
this.enableFullWidth = await this.stateService.getEnableFullWidth();
|
||||||
|
|
||||||
this.locale = (await this.stateService.getLocale()) ?? null;
|
this.locale = (await this.stateService.getLocale()) ?? null;
|
||||||
@ -98,7 +96,6 @@ export class PreferencesComponent implements OnInit {
|
|||||||
this.vaultTimeoutAction
|
this.vaultTimeoutAction
|
||||||
);
|
);
|
||||||
await this.stateService.setDisableFavicon(!this.enableFavicons);
|
await this.stateService.setDisableFavicon(!this.enableFavicons);
|
||||||
await this.stateService.setEnableGravitars(this.enableGravatars);
|
|
||||||
await this.stateService.setEnableFullWidth(this.enableFullWidth);
|
await this.stateService.setEnableFullWidth(this.enableFullWidth);
|
||||||
this.messagingService.send("setFullWidth");
|
this.messagingService.send("setFullWidth");
|
||||||
if (this.theme !== this.startingTheme) {
|
if (this.theme !== this.startingTheme) {
|
||||||
|
@ -33,14 +33,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<app-avatar
|
<bit-avatar [text]="profile | userName" [id]="profile.id" size="large"></bit-avatar>
|
||||||
data="{{ profile | userName }}"
|
|
||||||
[email]="profile.email"
|
|
||||||
dynamic="true"
|
|
||||||
size="75"
|
|
||||||
fontSize="35"
|
|
||||||
>
|
|
||||||
</app-avatar>
|
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<p *ngIf="fingerprint">
|
<p *ngIf="fingerprint">
|
||||||
|
@ -8,13 +8,14 @@ import { ToastrModule } from "ngx-toastr";
|
|||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import {
|
import {
|
||||||
|
AsyncActionsModule,
|
||||||
|
AvatarModule,
|
||||||
BadgeModule,
|
BadgeModule,
|
||||||
ButtonModule,
|
ButtonModule,
|
||||||
CalloutModule,
|
CalloutModule,
|
||||||
FormFieldModule,
|
FormFieldModule,
|
||||||
MenuModule,
|
|
||||||
IconModule,
|
IconModule,
|
||||||
AsyncActionsModule,
|
MenuModule,
|
||||||
} from "@bitwarden/components";
|
} from "@bitwarden/components";
|
||||||
|
|
||||||
// Register the locales for the application
|
// Register the locales for the application
|
||||||
@ -45,6 +46,7 @@ import "./locales";
|
|||||||
MenuModule,
|
MenuModule,
|
||||||
FormFieldModule,
|
FormFieldModule,
|
||||||
IconModule,
|
IconModule,
|
||||||
|
AvatarModule,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@ -64,6 +66,7 @@ import "./locales";
|
|||||||
MenuModule,
|
MenuModule,
|
||||||
FormFieldModule,
|
FormFieldModule,
|
||||||
IconModule,
|
IconModule,
|
||||||
|
AvatarModule,
|
||||||
],
|
],
|
||||||
providers: [DatePipe],
|
providers: [DatePipe],
|
||||||
bootstrap: [],
|
bootstrap: [],
|
||||||
|
@ -1235,13 +1235,6 @@
|
|||||||
"faviconDesc": {
|
"faviconDesc": {
|
||||||
"message": "Show a recognizable image next to each login."
|
"message": "Show a recognizable image next to each login."
|
||||||
},
|
},
|
||||||
"enableGravatars": {
|
|
||||||
"message": "Show Gravatars",
|
|
||||||
"description": "Use avatar images loaded from gravatar.com."
|
|
||||||
},
|
|
||||||
"enableGravatarsDesc": {
|
|
||||||
"message": "Use avatar images loaded from gravatar.com."
|
|
||||||
},
|
|
||||||
"enableFullWidth": {
|
"enableFullWidth": {
|
||||||
"message": "Display full width layout",
|
"message": "Display full width layout",
|
||||||
"description": "Allows scaling the web vault UI's width"
|
"description": "Allows scaling the web vault UI's width"
|
||||||
|
@ -243,7 +243,6 @@ const devServer =
|
|||||||
https://www.paypalobjects.com
|
https://www.paypalobjects.com
|
||||||
https://q.stripe.com
|
https://q.stripe.com
|
||||||
https://haveibeenpwned.com
|
https://haveibeenpwned.com
|
||||||
https://www.gravatar.com
|
|
||||||
;child-src
|
;child-src
|
||||||
'self'
|
'self'
|
||||||
https://js.stripe.com
|
https://js.stripe.com
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
<table class="table table-hover table-list">
|
<table class="table table-hover table-list">
|
||||||
<tr *ngFor="let o of organizations">
|
<tr *ngFor="let o of organizations">
|
||||||
<td width="30">
|
<td width="30">
|
||||||
<app-avatar [data]="o.name" size="25" [circle]="true" [fontSize]="14"></app-avatar>
|
<bit-avatar [text]="o.name" [id]="o.id" size="small"></bit-avatar>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ o.name }}
|
{{ o.name }}
|
||||||
|
@ -59,12 +59,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let o of searchedClients">
|
<tr *ngFor="let o of searchedClients">
|
||||||
<td width="30">
|
<td width="30">
|
||||||
<app-avatar
|
<bit-avatar [text]="o.organizationName" [id]="o.id" size="small"></bit-avatar>
|
||||||
[data]="o.organizationName"
|
|
||||||
size="25"
|
|
||||||
[circle]="true"
|
|
||||||
[fontSize]="14"
|
|
||||||
></app-avatar>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a [routerLink]="['/organizations', o.organizationId]">{{ o.organizationName }}</a>
|
<a [routerLink]="['/organizations', o.organizationId]">{{ o.organizationName }}</a>
|
||||||
|
@ -124,14 +124,7 @@
|
|||||||
<input type="checkbox" [(ngModel)]="u.checked" appStopProp />
|
<input type="checkbox" [(ngModel)]="u.checked" appStopProp />
|
||||||
</td>
|
</td>
|
||||||
<td width="30">
|
<td width="30">
|
||||||
<app-avatar
|
<bit-avatar [text]="u | userName" [id]="u.userId" size="small"></bit-avatar>
|
||||||
[data]="u | userName"
|
|
||||||
[email]="u.email"
|
|
||||||
size="25"
|
|
||||||
[circle]="true"
|
|
||||||
[fontSize]="14"
|
|
||||||
>
|
|
||||||
</app-avatar>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" appStopClick (click)="edit(u)">{{ u.email }}</a>
|
<a href="#" appStopClick (click)="edit(u)">{{ u.email }}</a>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<div class="container d-flex">
|
<div class="container d-flex">
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<div class="my-auto d-flex align-items-center pl-1">
|
<div class="my-auto d-flex align-items-center pl-1">
|
||||||
<app-avatar [data]="provider.name" size="45" [circle]="true"></app-avatar>
|
<bit-avatar [text]="provider.name" [id]="provider.id"></bit-avatar>
|
||||||
<div class="org-name ml-3">
|
<div class="org-name ml-3">
|
||||||
<span>{{ provider.name }}</span>
|
<span>{{ provider.name }}</span>
|
||||||
<small class="text-muted">{{ "provider" | i18n }}</small>
|
<small class="text-muted">{{ "provider" | i18n }}</small>
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<app-avatar data="{{ provider.name }}" dynamic="true" size="75" fontSize="35"></app-avatar>
|
<bit-avatar [text]="provider.name" [id]="provider.id" size="large"></bit-avatar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||||
|
@ -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("") : "";
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,7 +2,6 @@ import { CommonModule, DatePipe } from "@angular/common";
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||||
|
|
||||||
import { AvatarComponent } from "./components/avatar.component";
|
|
||||||
import { CalloutComponent } from "./components/callout.component";
|
import { CalloutComponent } from "./components/callout.component";
|
||||||
import { ExportScopeCalloutComponent } from "./components/export-scope-callout.component";
|
import { ExportScopeCalloutComponent } from "./components/export-scope-callout.component";
|
||||||
import { IconComponent } from "./components/icon.component";
|
import { IconComponent } from "./components/icon.component";
|
||||||
@ -46,7 +45,6 @@ import { PasswordStrengthComponent } from "./shared/components/password-strength
|
|||||||
A11yTitleDirective,
|
A11yTitleDirective,
|
||||||
ApiActionDirective,
|
ApiActionDirective,
|
||||||
AutofocusDirective,
|
AutofocusDirective,
|
||||||
AvatarComponent,
|
|
||||||
BoxRowDirective,
|
BoxRowDirective,
|
||||||
CalloutComponent,
|
CalloutComponent,
|
||||||
ColorPasswordCountPipe,
|
ColorPasswordCountPipe,
|
||||||
@ -74,7 +72,6 @@ import { PasswordStrengthComponent } from "./shared/components/password-strength
|
|||||||
A11yTitleDirective,
|
A11yTitleDirective,
|
||||||
ApiActionDirective,
|
ApiActionDirective,
|
||||||
AutofocusDirective,
|
AutofocusDirective,
|
||||||
AvatarComponent,
|
|
||||||
BitwardenToastModule,
|
BitwardenToastModule,
|
||||||
BoxRowDirective,
|
BoxRowDirective,
|
||||||
CalloutComponent,
|
CalloutComponent,
|
||||||
|
@ -37,9 +37,11 @@ const kdf = 0;
|
|||||||
const kdfIterations = 10000;
|
const kdfIterations = 10000;
|
||||||
const userId = Utils.newGuid();
|
const userId = Utils.newGuid();
|
||||||
const masterPasswordHash = "MASTER_PASSWORD_HASH";
|
const masterPasswordHash = "MASTER_PASSWORD_HASH";
|
||||||
|
const name = "NAME";
|
||||||
|
|
||||||
const decodedToken = {
|
const decodedToken = {
|
||||||
sub: userId,
|
sub: userId,
|
||||||
|
name: name,
|
||||||
email: email,
|
email: email,
|
||||||
premium: false,
|
premium: false,
|
||||||
};
|
};
|
||||||
@ -122,6 +124,7 @@ describe("LogInStrategy", () => {
|
|||||||
...new AccountProfile(),
|
...new AccountProfile(),
|
||||||
...{
|
...{
|
||||||
userId: userId,
|
userId: userId,
|
||||||
|
name: name,
|
||||||
email: email,
|
email: email,
|
||||||
hasPremiumPersonally: false,
|
hasPremiumPersonally: false,
|
||||||
kdfIterations: kdfIterations,
|
kdfIterations: kdfIterations,
|
||||||
|
@ -173,8 +173,6 @@ export abstract class StateService<T extends Account = Account> {
|
|||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
getEnableFullWidth: (options?: StorageOptions) => Promise<boolean>;
|
getEnableFullWidth: (options?: StorageOptions) => Promise<boolean>;
|
||||||
setEnableFullWidth: (value: boolean, options?: StorageOptions) => Promise<void>;
|
setEnableFullWidth: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||||
getEnableGravitars: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setEnableGravitars: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getEnableMinimizeToTray: (options?: StorageOptions) => Promise<boolean>;
|
getEnableMinimizeToTray: (options?: StorageOptions) => Promise<boolean>;
|
||||||
setEnableMinimizeToTray: (value: boolean, options?: StorageOptions) => Promise<void>;
|
setEnableMinimizeToTray: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||||
getEnableStartToTray: (options?: StorageOptions) => Promise<boolean>;
|
getEnableStartToTray: (options?: StorageOptions) => Promise<boolean>;
|
||||||
|
@ -105,6 +105,7 @@ export abstract class LogInStrategy {
|
|||||||
...new AccountProfile(),
|
...new AccountProfile(),
|
||||||
...{
|
...{
|
||||||
userId: accountInformation.sub,
|
userId: accountInformation.sub,
|
||||||
|
name: accountInformation.name,
|
||||||
email: accountInformation.email,
|
email: accountInformation.email,
|
||||||
hasPremiumPersonally: accountInformation.premium,
|
hasPremiumPersonally: accountInformation.premium,
|
||||||
kdfIterations: tokenResponse.kdfIterations,
|
kdfIterations: tokenResponse.kdfIterations,
|
||||||
|
@ -175,6 +175,7 @@ export class AccountProfile {
|
|||||||
apiKeyClientId?: string;
|
apiKeyClientId?: string;
|
||||||
authenticationStatus?: AuthenticationStatus;
|
authenticationStatus?: AuthenticationStatus;
|
||||||
convertAccountToKeyConnector?: boolean;
|
convertAccountToKeyConnector?: boolean;
|
||||||
|
name?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
emailVerified?: boolean;
|
emailVerified?: boolean;
|
||||||
entityId?: string;
|
entityId?: string;
|
||||||
@ -219,7 +220,6 @@ export class AccountSettings {
|
|||||||
enableAutoFillOnPageLoad?: boolean;
|
enableAutoFillOnPageLoad?: boolean;
|
||||||
enableBiometric?: boolean;
|
enableBiometric?: boolean;
|
||||||
enableFullWidth?: boolean;
|
enableFullWidth?: boolean;
|
||||||
enableGravitars?: boolean;
|
|
||||||
environmentUrls: EnvironmentUrls = new EnvironmentUrls();
|
environmentUrls: EnvironmentUrls = new EnvironmentUrls();
|
||||||
equivalentDomains?: any;
|
equivalentDomains?: any;
|
||||||
minimizeOnCopyToClipboard?: boolean;
|
minimizeOnCopyToClipboard?: boolean;
|
||||||
|
@ -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> {
|
async getEnableMinimizeToTray(options?: StorageOptions): Promise<boolean> {
|
||||||
return (
|
return (
|
||||||
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
|
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
|
||||||
|
@ -61,7 +61,6 @@ const v1Keys: { [key: string]: string } = {
|
|||||||
enableBrowserIntegrationFingerprint: "enableBrowserIntegrationFingerprint",
|
enableBrowserIntegrationFingerprint: "enableBrowserIntegrationFingerprint",
|
||||||
enableCloseToTray: "enableCloseToTray",
|
enableCloseToTray: "enableCloseToTray",
|
||||||
enableFullWidth: "enableFullWidth",
|
enableFullWidth: "enableFullWidth",
|
||||||
enableGravatars: "enableGravatars",
|
|
||||||
enableMinimizeToTray: "enableMinimizeToTray",
|
enableMinimizeToTray: "enableMinimizeToTray",
|
||||||
enableStartToTray: "enableStartToTrayKey",
|
enableStartToTray: "enableStartToTrayKey",
|
||||||
enableTray: "enableTray",
|
enableTray: "enableTray",
|
||||||
@ -305,9 +304,6 @@ export class StateMigrationService<
|
|||||||
enableFullWidth:
|
enableFullWidth:
|
||||||
(await this.get<boolean>(v1Keys.enableFullWidth)) ??
|
(await this.get<boolean>(v1Keys.enableFullWidth)) ??
|
||||||
defaultAccount.settings.enableFullWidth,
|
defaultAccount.settings.enableFullWidth,
|
||||||
enableGravitars:
|
|
||||||
(await this.get<boolean>(v1Keys.enableGravatars)) ??
|
|
||||||
defaultAccount.settings.enableGravitars,
|
|
||||||
environmentUrls: globals.environmentUrls ?? defaultAccount.settings.environmentUrls,
|
environmentUrls: globals.environmentUrls ?? defaultAccount.settings.environmentUrls,
|
||||||
equivalentDomains:
|
equivalentDomains:
|
||||||
(await this.get<any>(v1Keys.equivalentDomains)) ??
|
(await this.get<any>(v1Keys.equivalentDomains)) ??
|
||||||
|
@ -17,9 +17,9 @@ const SizeClasses: Record<SizeTypes, string[]> = {
|
|||||||
})
|
})
|
||||||
export class AvatarComponent implements OnChanges {
|
export class AvatarComponent implements OnChanges {
|
||||||
@Input() border = false;
|
@Input() border = false;
|
||||||
@Input() color: string;
|
@Input() color?: string;
|
||||||
@Input() id: number;
|
@Input() id?: string;
|
||||||
@Input() text: string;
|
@Input() text?: string;
|
||||||
@Input() size: SizeTypes = "default";
|
@Input() size: SizeTypes = "default";
|
||||||
|
|
||||||
private svgCharCount = 2;
|
private svgCharCount = 2;
|
||||||
@ -42,7 +42,7 @@ export class AvatarComponent implements OnChanges {
|
|||||||
|
|
||||||
private generate() {
|
private generate() {
|
||||||
let chars: string = null;
|
let chars: string = null;
|
||||||
const upperCaseText = this.text.toUpperCase();
|
const upperCaseText = this.text?.toUpperCase() ?? "";
|
||||||
|
|
||||||
chars = this.getFirstLetters(upperCaseText, this.svgCharCount);
|
chars = this.getFirstLetters(upperCaseText, this.svgCharCount);
|
||||||
|
|
||||||
@ -58,9 +58,9 @@ export class AvatarComponent implements OnChanges {
|
|||||||
let svg: HTMLElement;
|
let svg: HTMLElement;
|
||||||
let hexColor = this.color;
|
let hexColor = this.color;
|
||||||
|
|
||||||
if (this.color != null) {
|
if (!Utils.isNullOrWhitespace(this.color)) {
|
||||||
svg = this.createSvgElement(this.svgSize, hexColor);
|
svg = this.createSvgElement(this.svgSize, hexColor);
|
||||||
} else if (this.id != null) {
|
} else if (!Utils.isNullOrWhitespace(this.id)) {
|
||||||
hexColor = Utils.stringToColor(this.id.toString());
|
hexColor = Utils.stringToColor(this.id.toString());
|
||||||
svg = this.createSvgElement(this.svgSize, hexColor);
|
svg = this.createSvgElement(this.svgSize, hexColor);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
export * from "./async-actions";
|
export * from "./async-actions";
|
||||||
|
export * from "./avatar";
|
||||||
export * from "./badge";
|
export * from "./badge";
|
||||||
export * from "./banner";
|
export * from "./banner";
|
||||||
export * from "./button";
|
export * from "./button";
|
||||||
export * from "./callout";
|
export * from "./callout";
|
||||||
|
export * from "./dialog";
|
||||||
export * from "./form-field";
|
export * from "./form-field";
|
||||||
export * from "./icon";
|
|
||||||
export * from "./icon-button";
|
export * from "./icon-button";
|
||||||
|
export * from "./icon";
|
||||||
|
export * from "./link";
|
||||||
export * from "./menu";
|
export * from "./menu";
|
||||||
export * from "./multi-select";
|
export * from "./multi-select";
|
||||||
export * from "./dialog";
|
|
||||||
export * from "./link";
|
|
||||||
export * from "./tabs";
|
export * from "./tabs";
|
||||||
export * from "./toggle-group";
|
export * from "./toggle-group";
|
||||||
export * from "./utils/i18n-mock.service";
|
export * from "./utils/i18n-mock.service";
|
||||||
|
Loading…
Reference in New Issue
Block a user