mirror of
https://github.com/bitwarden/browser.git
synced 2024-08-27 23:31:41 +02:00
PM-2047 Migrated Change Avatar component (#8522)
* PM-2047 Migrated Change Avatar component * PM-2047 Addressed the review comments * PM-2047 Changed the file name * PM-2047 Removed form promise
This commit is contained in:
parent
d58103dfb1
commit
a1442194ae
|
@ -0,0 +1,61 @@
|
||||||
|
<bit-dialog dialogSize="large" [title]="'customizeAvatar' | i18n">
|
||||||
|
<ng-container bitDialogContent>
|
||||||
|
<div class="tw-text-center" *ngIf="loading">
|
||||||
|
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||||
|
{{ "loading" | i18n }}
|
||||||
|
</div>
|
||||||
|
<app-callout type="error" *ngIf="error">
|
||||||
|
{{ error }}
|
||||||
|
</app-callout>
|
||||||
|
<p class="tw-text-lg">{{ "pickAnAvatarColor" | i18n }}</p>
|
||||||
|
<div class="tw-flex tw-flex-wrap tw-justify-center tw-gap-8">
|
||||||
|
<ng-container *ngFor="let c of defaultColorPalette">
|
||||||
|
<selectable-avatar
|
||||||
|
appStopClick
|
||||||
|
(select)="setSelection(c.color)"
|
||||||
|
[selected]="c.selected"
|
||||||
|
[title]="c.name"
|
||||||
|
text="{{ profile | userName }}"
|
||||||
|
[color]="c.color"
|
||||||
|
[border]="true"
|
||||||
|
>
|
||||||
|
</selectable-avatar>
|
||||||
|
</ng-container>
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
[tabIndex]="0"
|
||||||
|
(keyup.enter)="showCustomPicker()"
|
||||||
|
(click)="showCustomPicker()"
|
||||||
|
title="{{ 'customColor' | i18n }}"
|
||||||
|
[ngClass]="{
|
||||||
|
'!tw-outline-[3px] tw-outline-primary-600 hover:tw-outline-[3px] hover:tw-outline-primary-600':
|
||||||
|
customColorSelected
|
||||||
|
}"
|
||||||
|
class="tw-relative tw-flex tw-h-24 tw-w-24 tw-cursor-pointer tw-place-content-center tw-content-center tw-justify-center tw-rounded-full tw-border tw-border-solid tw-border-secondary-600 tw-outline tw-outline-0 tw-outline-offset-1 hover:tw-outline-1 hover:tw-outline-primary-300 focus:tw-outline-2 focus:tw-outline-primary-600"
|
||||||
|
[style.background-color]="customColor$ | async"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
[style.color]="customTextColor$ | async"
|
||||||
|
class="bwi bwi-pencil tw-m-auto tw-text-3xl"
|
||||||
|
></i>
|
||||||
|
<input
|
||||||
|
tabindex="-1"
|
||||||
|
class="tw-absolute tw-bottom-0 tw-right-0 tw-h-px tw-w-px tw-border-none tw-bg-transparent tw-opacity-0"
|
||||||
|
#colorPicker
|
||||||
|
type="color"
|
||||||
|
[ngModel]="customColor$ | async"
|
||||||
|
(ngModelChange)="customColor$.next($event)"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container bitDialogFooter>
|
||||||
|
<button bitButton type="submit" buttonType="primary" [disabled]="loading" [bitAction]="submit">
|
||||||
|
{{ "save" | i18n }}
|
||||||
|
</button>
|
||||||
|
<button bitButton type="button" buttonType="secondary" bitDialogClose>
|
||||||
|
{{ "close" | i18n }}
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
</bit-dialog>
|
|
@ -1,11 +1,10 @@
|
||||||
|
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
EventEmitter,
|
Inject,
|
||||||
Input,
|
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
OnInit,
|
OnInit,
|
||||||
Output,
|
|
||||||
ViewChild,
|
ViewChild,
|
||||||
ViewEncapsulation,
|
ViewEncapsulation,
|
||||||
} from "@angular/core";
|
} from "@angular/core";
|
||||||
|
@ -14,20 +13,20 @@ import { BehaviorSubject, debounceTime, firstValueFrom, Subject, takeUntil } fro
|
||||||
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
|
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
|
||||||
import { ProfileResponse } from "@bitwarden/common/models/response/profile.response";
|
import { ProfileResponse } from "@bitwarden/common/models/response/profile.response";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
|
type ChangeAvatarDialogData = {
|
||||||
|
profile: ProfileResponse;
|
||||||
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-change-avatar",
|
templateUrl: "change-avatar-dialog.component.html",
|
||||||
templateUrl: "change-avatar.component.html",
|
|
||||||
encapsulation: ViewEncapsulation.None,
|
encapsulation: ViewEncapsulation.None,
|
||||||
})
|
})
|
||||||
export class ChangeAvatarComponent implements OnInit, OnDestroy {
|
export class ChangeAvatarDialogComponent implements OnInit, OnDestroy {
|
||||||
@Input() profile: ProfileResponse;
|
profile: ProfileResponse;
|
||||||
|
|
||||||
@Output() changeColor: EventEmitter<string | null> = new EventEmitter();
|
|
||||||
@Output() onSaved = new EventEmitter();
|
|
||||||
|
|
||||||
@ViewChild("colorPicker") colorPickerElement: ElementRef<HTMLElement>;
|
@ViewChild("colorPicker") colorPickerElement: ElementRef<HTMLElement>;
|
||||||
|
|
||||||
|
@ -52,11 +51,14 @@ export class ChangeAvatarComponent implements OnInit, OnDestroy {
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DIALOG_DATA) protected data: ChangeAvatarDialogData,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private logService: LogService,
|
|
||||||
private avatarService: AvatarService,
|
private avatarService: AvatarService,
|
||||||
) {}
|
private dialogRef: DialogRef,
|
||||||
|
) {
|
||||||
|
this.profile = data.profile;
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
//localize the default colors
|
//localize the default colors
|
||||||
|
@ -88,20 +90,15 @@ export class ChangeAvatarComponent implements OnInit, OnDestroy {
|
||||||
Utils.stringToColor(this.profile.name.toString());
|
Utils.stringToColor(this.profile.name.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
submit = async () => {
|
||||||
try {
|
if (Utils.validateHexColor(this.currentSelection) || this.currentSelection == null) {
|
||||||
if (Utils.validateHexColor(this.currentSelection) || this.currentSelection == null) {
|
await this.avatarService.setAvatarColor(this.currentSelection);
|
||||||
await this.avatarService.setAvatarColor(this.currentSelection);
|
this.dialogRef.close();
|
||||||
this.changeColor.emit(this.currentSelection);
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("avatarUpdated"));
|
||||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("avatarUpdated"));
|
} else {
|
||||||
} else {
|
|
||||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.logService.error(e);
|
|
||||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
|
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
async ngOnDestroy() {
|
async ngOnDestroy() {
|
||||||
this.destroy$.next();
|
this.destroy$.next();
|
||||||
|
@ -131,6 +128,10 @@ export class ChangeAvatarComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static open(dialogService: DialogService, config: DialogConfig<ChangeAvatarDialogData>) {
|
||||||
|
return dialogService.open(ChangeAvatarDialogComponent, config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NamedAvatarColor {
|
export class NamedAvatarColor {
|
|
@ -1,84 +0,0 @@
|
||||||
<!-- Please remove this disable statement when editing this file! -->
|
|
||||||
<!-- eslint-disable tailwindcss/no-custom-classname -->
|
|
||||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="customizeTitle">
|
|
||||||
<div class="modal-dialog modal-dialog-scrollable tw-w-[600px] tw-max-w-none" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2 class="modal-title" id="customizeTitle">{{ "customizeAvatar" | i18n }}</h2>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="close"
|
|
||||||
data-dismiss="modal"
|
|
||||||
appA11yTitle="{{ 'close' | i18n }}"
|
|
||||||
>
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="card-body text-center" *ngIf="loading">
|
|
||||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
|
||||||
{{ "loading" | i18n }}
|
|
||||||
</div>
|
|
||||||
<app-callout type="error" *ngIf="error">
|
|
||||||
{{ error }}
|
|
||||||
</app-callout>
|
|
||||||
<p class="tw-text-lg">{{ "pickAnAvatarColor" | i18n }}</p>
|
|
||||||
<div class="tw-flex tw-flex-wrap tw-justify-center tw-gap-8">
|
|
||||||
<ng-container *ngFor="let c of defaultColorPalette">
|
|
||||||
<selectable-avatar
|
|
||||||
appStopClick
|
|
||||||
(select)="setSelection(c.color)"
|
|
||||||
[selected]="c.selected"
|
|
||||||
[title]="c.name"
|
|
||||||
text="{{ profile | userName }}"
|
|
||||||
[color]="c.color"
|
|
||||||
[border]="true"
|
|
||||||
>
|
|
||||||
</selectable-avatar>
|
|
||||||
</ng-container>
|
|
||||||
<span>
|
|
||||||
<span
|
|
||||||
[tabIndex]="0"
|
|
||||||
(keyup.enter)="showCustomPicker()"
|
|
||||||
(click)="showCustomPicker()"
|
|
||||||
title="{{ 'customColor' | i18n }}"
|
|
||||||
[ngClass]="{
|
|
||||||
'!tw-outline-[3px] tw-outline-primary-600 hover:tw-outline-[3px] hover:tw-outline-primary-600':
|
|
||||||
customColorSelected
|
|
||||||
}"
|
|
||||||
class="tw-outline-solid tw-bg-white tw-relative tw-flex tw-h-24 tw-w-24 tw-cursor-pointer tw-place-content-center tw-content-center tw-justify-center tw-rounded-full tw-border tw-border-solid tw-border-secondary-600 tw-outline tw-outline-0 tw-outline-offset-1 hover:tw-outline-1 hover:tw-outline-primary-300 focus:tw-outline-2 focus:tw-outline-primary-600"
|
|
||||||
[style.background-color]="customColor$ | async"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
[style.color]="customTextColor$ | async"
|
|
||||||
class="bwi bwi-pencil tw-m-auto tw-text-3xl"
|
|
||||||
></i>
|
|
||||||
<input
|
|
||||||
tabindex="-1"
|
|
||||||
class="tw-absolute tw-bottom-0 tw-right-0 tw-h-px tw-w-px tw-border-none tw-bg-transparent tw-opacity-0"
|
|
||||||
#colorPicker
|
|
||||||
type="color"
|
|
||||||
[ngModel]="customColor$ | async"
|
|
||||||
(ngModelChange)="customColor$.next($event)"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="btn btn-primary btn-submit"
|
|
||||||
[disabled]="loading"
|
|
||||||
(click)="submit()"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
|
||||||
<span>{{ "save" | i18n }}</span>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
|
||||||
{{ "close" | i18n }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -45,4 +45,3 @@
|
||||||
</div>
|
</div>
|
||||||
<button bitButton bitFormButton type="submit" buttonType="primary">{{ "save" | i18n }}</button>
|
<button bitButton bitFormButton type="submit" buttonType="primary">{{ "save" | i18n }}</button>
|
||||||
</form>
|
</form>
|
||||||
<ng-template #avatarModalTemplate></ng-template>
|
|
||||||
|
|
|
@ -1,28 +1,25 @@
|
||||||
import { ViewChild, ViewContainerRef, Component, OnDestroy, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
import { FormControl, FormGroup } from "@angular/forms";
|
import { FormControl, FormGroup } from "@angular/forms";
|
||||||
import { Subject, takeUntil } from "rxjs";
|
import { Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { UpdateProfileRequest } from "@bitwarden/common/auth/models/request/update-profile.request";
|
import { UpdateProfileRequest } from "@bitwarden/common/auth/models/request/update-profile.request";
|
||||||
import { ProfileResponse } from "@bitwarden/common/models/response/profile.response";
|
import { ProfileResponse } from "@bitwarden/common/models/response/profile.response";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { ChangeAvatarComponent } from "./change-avatar.component";
|
import { ChangeAvatarDialogComponent } from "./change-avatar-dialog.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-profile",
|
selector: "app-profile",
|
||||||
templateUrl: "profile.component.html",
|
templateUrl: "profile.component.html",
|
||||||
})
|
})
|
||||||
export class ProfileComponent implements OnInit, OnDestroy {
|
export class ProfileComponent implements OnInit {
|
||||||
loading = true;
|
loading = true;
|
||||||
profile: ProfileResponse;
|
profile: ProfileResponse;
|
||||||
fingerprintMaterial: string;
|
fingerprintMaterial: string;
|
||||||
|
|
||||||
@ViewChild("avatarModalTemplate", { read: ViewContainerRef, static: true })
|
|
||||||
avatarModalRef: ViewContainerRef;
|
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
protected formGroup = new FormGroup({
|
protected formGroup = new FormGroup({
|
||||||
|
@ -35,7 +32,7 @@ export class ProfileComponent implements OnInit, OnDestroy {
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private modalService: ModalService,
|
private dialogService: DialogService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
@ -53,24 +50,17 @@ export class ProfileComponent implements OnInit, OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openChangeAvatar = async () => {
|
||||||
|
ChangeAvatarDialogComponent.open(this.dialogService, {
|
||||||
|
data: { profile: this.profile },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
async ngOnDestroy() {
|
async ngOnDestroy() {
|
||||||
this.destroy$.next();
|
this.destroy$.next();
|
||||||
this.destroy$.complete();
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
openChangeAvatar = async () => {
|
|
||||||
const modalOpened = await this.modalService.openViewRef(
|
|
||||||
ChangeAvatarComponent,
|
|
||||||
this.avatarModalRef,
|
|
||||||
(modal) => {
|
|
||||||
modal.profile = this.profile;
|
|
||||||
modal.changeColor.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
||||||
modalOpened[0].close();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
submit = async () => {
|
submit = async () => {
|
||||||
const request = new UpdateProfileRequest(
|
const request = new UpdateProfileRequest(
|
||||||
this.formGroup.get("name").value,
|
this.formGroup.get("name").value,
|
||||||
|
|
|
@ -28,7 +28,7 @@ import { RegisterFormModule } from "../auth/register-form/register-form.module";
|
||||||
import { RemovePasswordComponent } from "../auth/remove-password.component";
|
import { RemovePasswordComponent } from "../auth/remove-password.component";
|
||||||
import { SetPasswordComponent } from "../auth/set-password.component";
|
import { SetPasswordComponent } from "../auth/set-password.component";
|
||||||
import { AccountComponent } from "../auth/settings/account/account.component";
|
import { AccountComponent } from "../auth/settings/account/account.component";
|
||||||
import { ChangeAvatarComponent } from "../auth/settings/account/change-avatar.component";
|
import { ChangeAvatarDialogComponent } from "../auth/settings/account/change-avatar-dialog.component";
|
||||||
import { ChangeEmailComponent } from "../auth/settings/account/change-email.component";
|
import { ChangeEmailComponent } from "../auth/settings/account/change-email.component";
|
||||||
import { DangerZoneComponent } from "../auth/settings/account/danger-zone.component";
|
import { DangerZoneComponent } from "../auth/settings/account/danger-zone.component";
|
||||||
import { DeauthorizeSessionsComponent } from "../auth/settings/account/deauthorize-sessions.component";
|
import { DeauthorizeSessionsComponent } from "../auth/settings/account/deauthorize-sessions.component";
|
||||||
|
@ -156,7 +156,7 @@ import { SharedModule } from "./shared.module";
|
||||||
PreferencesComponent,
|
PreferencesComponent,
|
||||||
PremiumBadgeComponent,
|
PremiumBadgeComponent,
|
||||||
ProfileComponent,
|
ProfileComponent,
|
||||||
ChangeAvatarComponent,
|
ChangeAvatarDialogComponent,
|
||||||
ProvidersComponent,
|
ProvidersComponent,
|
||||||
PurgeVaultComponent,
|
PurgeVaultComponent,
|
||||||
RecoverDeleteComponent,
|
RecoverDeleteComponent,
|
||||||
|
@ -230,7 +230,7 @@ import { SharedModule } from "./shared.module";
|
||||||
PreferencesComponent,
|
PreferencesComponent,
|
||||||
PremiumBadgeComponent,
|
PremiumBadgeComponent,
|
||||||
ProfileComponent,
|
ProfileComponent,
|
||||||
ChangeAvatarComponent,
|
ChangeAvatarDialogComponent,
|
||||||
ProvidersComponent,
|
ProvidersComponent,
|
||||||
PurgeVaultComponent,
|
PurgeVaultComponent,
|
||||||
RecoverDeleteComponent,
|
RecoverDeleteComponent,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user