Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SG-58] Avatar color selector #3691

Merged
merged 52 commits into from
Jan 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
d50b9e1
changes
BrandonM-Bitwarden Sep 6, 2022
741f6a5
Merge branch 'master' into change/SG-58
BrandonM-Bitwarden Sep 10, 2022
7d06496
merge
BrandonM-Bitwarden Sep 10, 2022
9217788
undo
BrandonM-Bitwarden Sep 10, 2022
208aa25
work
BrandonM-Bitwarden Sep 15, 2022
ca7535f
stuffs
BrandonM-Bitwarden Sep 23, 2022
4898ccb
chore: added custom color picker
BrandonM-Bitwarden Sep 29, 2022
85d0820
oops
BrandonM-Bitwarden Sep 29, 2022
4fad306
chore: everything but the broken sink
BrandonM-Bitwarden Sep 30, 2022
23daae6
picker v2
BrandonM-Bitwarden Oct 4, 2022
30b8356
Merge branch 'master' into change/SG-58
BrandonM-Bitwarden Oct 5, 2022
4260e09
Merge branch 'change/SG-58' of https://github.com/bitwarden/clients i…
BrandonM-Bitwarden Oct 5, 2022
4821f3c
fix: cleanup
BrandonM-Bitwarden Oct 5, 2022
d42b681
fix: linty
BrandonM-Bitwarden Oct 12, 2022
9a53a6d
Merge branch 'master' into change/SG-58
BrandonM-Bitwarden Oct 12, 2022
ea06342
fix: use tailwind
BrandonM-Bitwarden Oct 13, 2022
6d042c3
fix: use tailwind
BrandonM-Bitwarden Oct 13, 2022
e092824
Merge branch 'change/SG-58' of https://github.com/bitwarden/clients i…
BrandonM-Bitwarden Oct 20, 2022
a123118
Merge branch 'master' into change/SG-58
BrandonM-Bitwarden Oct 20, 2022
94ccbc8
undo: merge error
BrandonM-Bitwarden Oct 20, 2022
eb71f36
remove: old color picker
BrandonM-Bitwarden Oct 20, 2022
b9cf278
fix: merge issue
BrandonM-Bitwarden Oct 25, 2022
aac3371
chore: use input vs component
BrandonM-Bitwarden Oct 25, 2022
17ccd47
fix: move logic out!
BrandonM-Bitwarden Nov 2, 2022
19dc7b6
fix: revert changes to bit-avatar
BrandonM-Bitwarden Nov 2, 2022
5585d71
fix: cleanup undos
BrandonM-Bitwarden Nov 2, 2022
6c323ff
feat: color lookup for "me" badge in vault
BrandonM-Bitwarden Nov 2, 2022
046f66e
fix: naming stuff
BrandonM-Bitwarden Nov 2, 2022
37a8cb6
Merge branch 'master' into change/SG-58
BrandonM-Bitwarden Nov 3, 2022
fa30cb9
fix: event emitter
BrandonM-Bitwarden Nov 3, 2022
8cecc46
fix: linty
BrandonM-Bitwarden Nov 3, 2022
838dc0f
fix: protect
BrandonM-Bitwarden Nov 3, 2022
63129fc
Merge branch 'master' into change/SG-58
BrandonM-Bitwarden Nov 4, 2022
ed2f4e7
fix: remove v1 states
BrandonM-Bitwarden Nov 7, 2022
394063f
fix: big
BrandonM-Bitwarden Nov 7, 2022
556ec62
fix: messages merge issue
BrandonM-Bitwarden Nov 8, 2022
c92735e
Merge branch 'master' into change/SG-58
BrandonM-Bitwarden Nov 8, 2022
9892162
bug: differing bg colors for generated components
BrandonM-Bitwarden Nov 15, 2022
62b0386
feat: added sync stuff
BrandonM-Bitwarden Nov 15, 2022
edc6dc6
Merge branch 'master' into change/SG-58
BrandonM-Bitwarden Nov 16, 2022
627a8ba
fix: cli
BrandonM-Bitwarden Nov 16, 2022
50f523c
fix: remove service refs, use state
BrandonM-Bitwarden Nov 16, 2022
2372db3
Merge branch 'change/SG-58' of https://github.com/bitwarden/clients i…
BrandonM-Bitwarden Nov 16, 2022
4f14316
fix: moved from EventEmitter to Subjects
BrandonM-Bitwarden Nov 21, 2022
d23c8fb
Merge branch 'master' into change/SG-58
BrandonM-Bitwarden Nov 29, 2022
2904f47
fix: srs
BrandonM-Bitwarden Nov 29, 2022
0c212a6
fix: strict stuff is nice tbh
BrandonM-Bitwarden Dec 1, 2022
7c36280
SG-920 + SG-921 (#4342)
BrandonM-Bitwarden Dec 29, 2022
fc45270
[SG-926] [SG-58] [Defect] - Selected Avatar color does not persist in…
BrandonM-Bitwarden Dec 30, 2022
569fe07
work: done with static values (#4272)
BrandonM-Bitwarden Dec 30, 2022
f2553df
[SG-35] (#4361)
BrandonM-Bitwarden Dec 30, 2022
a0238b0
Merge branch 'master' into change/SG-58
trmartin4 Jan 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/browser/src/background/main.background.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AvatarUpdateService as AvatarUpdateServiceAbstraction } from "@bitwarden/common/abstractions/account/avatar-update.service";
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/abstractions/appId.service";
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
Expand Down Expand Up @@ -45,6 +46,7 @@ import { CipherType } from "@bitwarden/common/enums/cipherType";
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
import { GlobalState } from "@bitwarden/common/models/domain/global-state";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service";
import { ApiService } from "@bitwarden/common/services/api.service";
import { AppIdService } from "@bitwarden/common/services/appId.service";
import { AuditService } from "@bitwarden/common/services/audit.service";
Expand Down Expand Up @@ -168,6 +170,7 @@ export default class MainBackground {
policyApiService: PolicyApiServiceAbstraction;
userVerificationApiService: UserVerificationApiServiceAbstraction;
syncNotifierService: SyncNotifierServiceAbstraction;
avatarUpdateService: AvatarUpdateServiceAbstraction;

// Passed to the popup for Safari to workaround issues with theming, downloading, etc.
backgroundWindow = window;
Expand Down Expand Up @@ -565,6 +568,8 @@ export default class MainBackground {
this.stateService,
this.apiService
);

this.avatarUpdateService = new AvatarUpdateService(this.apiService, this.stateService);
}

async bootstrap() {
Expand Down
1 change: 1 addition & 0 deletions apps/browser/src/background/runtime.background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export default class RuntimeBackground {
await this.main.refreshBadge();
await this.main.refreshMenu();
}, 2000);
this.main.avatarUpdateService.loadColorFromState();
}
break;
case "openPopup":
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/app/layout/account-switcher.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<app-avatar
[text]="activeAccount.name"
[id]="activeAccount.id"
[color]="activeAccount.avatarColor"
[size]="25"
[circle]="true"
[fontSize]="14"
Expand Down Expand Up @@ -65,6 +66,7 @@
[circle]="true"
[fontSize]="14"
[dynamic]="true"
[color]="a.value.avatarColor"
*ngIf="a.value.profile.email != null"
aria-hidden="true"
></app-avatar>
Expand Down
7 changes: 7 additions & 0 deletions apps/desktop/src/app/layout/account-switcher.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type ActiveAccount = {
id: string;
name: string;
email: string;
avatarColor: string;
};

export class SwitcherAccount extends Account {
Expand All @@ -27,6 +28,8 @@ export class SwitcherAccount extends Account {
);
}

avatarColor: string;

private removeWebProtocolFromString(urlString: string) {
const regex = /http(s)?(:)?(\/\/)?|(\/\/)?(www\.)?/g;
return urlString.replace(regex, "");
Expand Down Expand Up @@ -112,6 +115,7 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
id: await this.tokenService.getUserId(),
name: (await this.tokenService.getName()) ?? (await this.tokenService.getEmail()),
email: await this.tokenService.getEmail(),
avatarColor: await this.stateService.getAvatarColor(),
};
} catch {
this.activeAccount = undefined;
Expand Down Expand Up @@ -162,6 +166,9 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
userId: userId,
});
switcherAccounts[userId] = new SwitcherAccount(baseAccounts[userId]);
switcherAccounts[userId].avatarColor = await this.stateService.getAvatarColor({
userId: userId,
});
}
return switcherAccounts;
}
Expand Down
41 changes: 41 additions & 0 deletions apps/web/src/app/components/dynamic-avatar.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Component, Input, OnDestroy } from "@angular/core";
import { Observable, Subject } from "rxjs";

import { AvatarUpdateService } from "@bitwarden/common/abstractions/account/avatar-update.service";
type SizeTypes = "xlarge" | "large" | "default" | "small" | "xsmall";
@Component({
selector: "dynamic-avatar",
template: `<span [title]="title">
<bit-avatar
appStopClick
[text]="text"
[size]="size"
[color]="color$ | async"
[border]="border"
[id]="id"
[title]="title"
>
</bit-avatar>
</span>`,
})
export class DynamicAvatarComponent implements OnDestroy {
@Input() border = false;
@Input() id: string;
@Input() text: string;
@Input() title: string;
@Input() size: SizeTypes = "default";
color$: Observable<string | null>;
private destroy$ = new Subject<void>();

constructor(private accountUpdateService: AvatarUpdateService) {
if (this.text) {
this.text = this.text.toUpperCase();
}
this.color$ = this.accountUpdateService.avatarUpdate$;
}

async ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
54 changes: 54 additions & 0 deletions apps/web/src/app/components/selectable-avatar.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Component, EventEmitter, Input, Output } from "@angular/core";

@Component({
selector: "selectable-avatar",
template: `<span
[title]="title"
(click)="onFire()"
(keyup.enter)="onFire()"
tabindex="0"
[ngClass]="classList"
>
<bit-avatar
appStopClick
[text]="text"
size="xlarge"
[text]="text"
[color]="color"
[border]="false"
[id]="id"
[border]="border"
[title]="title"
>
</bit-avatar>
</span>`,
})
export class SelectableAvatarComponent {
@Input() id: string;
@Input() text: string;
@Input() title: string;
@Input() color: string;
@Input() border = false;
@Input() selected = false;
@Output() select = new EventEmitter<string>();

onFire() {
this.select.emit(this.color);
}

get classList() {
return ["tw-rounded-full tw-inline-block"]
.concat(["tw-cursor-pointer", "tw-outline", "tw-outline-solid", "tw-outline-offset-1"])
.concat(
this.selected
? ["tw-outline-[3px]", "tw-outline-primary-500"]
: [
"tw-outline-0",
"hover:tw-outline-1",
"hover:tw-outline-primary-300",
"focus:tw-outline-2",
"focus:tw-outline-primary-500",
]
);
}
}
4 changes: 2 additions & 2 deletions apps/web/src/app/layouts/navbar.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
[bitMenuTriggerFor]="accountMenu"
class="tw-border-0 tw-bg-transparent tw-text-alt2 tw-opacity-70 hover:tw-opacity-90"
>
<i class="bwi bwi-user-circle bwi-lg" aria-hidden="true"></i>
<dynamic-avatar [text]="name" size="xsmall" aria-hidden="true"></dynamic-avatar>
<i class="bwi bwi-caret-down bwi-sm" aria-hidden="true"></i>
</button>
<bit-menu class="dropdown-menu" #accountMenu>
Expand All @@ -55,7 +55,7 @@
*ngIf="name"
appStopProp
>
<bit-avatar [text]="name" [id]="userId" size="small"></bit-avatar>
<dynamic-avatar [text]="name" size="small"></dynamic-avatar>
<div class="tw-ml-2 tw-block tw-overflow-hidden tw-whitespace-nowrap">
<span>{{ "loggedInAs" | i18n }}</span>
<small class="tw-block tw-overflow-hidden tw-whitespace-nowrap tw-text-muted">{{
Expand Down
82 changes: 82 additions & 0 deletions apps/web/src/app/settings/change-avatar.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<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">&times;</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 tw-gap-y-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-500 hover:tw-outline-[3px] hover:tw-outline-primary-500':
customColorSelected
}"
class="tw-outline-solid tw-bg-white tw-relative tw-inline-block 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-500 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-500"
[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-right-0 tw-bottom-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>
Loading