Skip to content

Commit

Permalink
[PM-16097] Separate copy buttons appearance setting (#12428)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: William Martin <[email protected]>
(cherry picked from commit a4db527)
  • Loading branch information
kspearrin authored and vgrassia committed Dec 16, 2024
1 parent 0540e38 commit fbf87e7
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 50 deletions.
3 changes: 3 additions & 0 deletions apps/browser/src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -4658,6 +4658,9 @@
"showNumberOfAutofillSuggestions": {
"message": "Show number of login autofill suggestions on extension icon"
},
"showQuickCopyActions": {
"message": "Show quick copy actions on Vault"
},
"systemDefault": {
"message": "System default"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,53 +1,117 @@
<bit-item-action *ngIf="cipher.type === CipherType.Login">
<button
type="button"
bitIconButton="bwi-clone"
size="small"
[appA11yTitle]="
hasLoginValues ? ('copyInfoTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n)
"
[disabled]="!hasLoginValues"
[bitMenuTriggerFor]="loginOptions"
></button>
<bit-menu #loginOptions>
<button type="button" bitMenuItem appCopyField="username" [cipher]="cipher">
{{ "copyUsername" | i18n }}
</button>
<button
*ngIf="cipher.viewPassword"
type="button"
bitMenuItem
appCopyField="password"
[cipher]="cipher"
>
{{ "copyPassword" | i18n }}
</button>
<button type="button" bitMenuItem appCopyField="totp" [cipher]="cipher">
{{ "copyVerificationCode" | i18n }}
</button>
</bit-menu>
</bit-item-action>
<ng-container *ngIf="cipher.type === CipherType.Login">
<ng-container *ngIf="showQuickCopyActions$ | async; else loginCopyMenu">
<bit-item-action>
<button
type="button"
bitIconButton="bwi-user"
size="small"
appCopyField="username"
[cipher]="cipher"
[appA11yTitle]="'copyUsername' | i18n"
></button>
</bit-item-action>
<bit-item-action>
<button
*ngIf="cipher.viewPassword"
type="button"
bitIconButton="bwi-key"
size="small"
appCopyField="password"
[cipher]="cipher"
[appA11yTitle]="'copyPassword' | i18n"
></button>
</bit-item-action>
<bit-item-action>
<button
type="button"
bitIconButton="bwi-clock"
size="small"
appCopyField="totp"
[cipher]="cipher"
[appA11yTitle]="'copyVerificationCode' | i18n"
></button>
</bit-item-action>
</ng-container>

<bit-item-action *ngIf="cipher.type === CipherType.Card">
<button
type="button"
bitIconButton="bwi-clone"
size="small"
[appA11yTitle]="
hasCardValues ? ('copyInfoTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n)
"
[disabled]="!hasCardValues"
[bitMenuTriggerFor]="cardOptions"
></button>
<bit-menu #cardOptions>
<button type="button" bitMenuItem appCopyField="cardNumber" [cipher]="cipher">
{{ "copyNumber" | i18n }}
</button>
<button type="button" bitMenuItem appCopyField="securityCode" [cipher]="cipher">
{{ "copySecurityCode" | i18n }}
</button>
</bit-menu>
</bit-item-action>
<ng-template #loginCopyMenu>
<bit-item-action>
<button
type="button"
bitIconButton="bwi-clone"
size="small"
[appA11yTitle]="
hasLoginValues ? ('copyInfoTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n)
"
[disabled]="!hasLoginValues"
[bitMenuTriggerFor]="loginOptions"
></button>
<bit-menu #loginOptions>
<button type="button" bitMenuItem appCopyField="username" [cipher]="cipher">
{{ "copyUsername" | i18n }}
</button>
<button
*ngIf="cipher.viewPassword"
type="button"
bitMenuItem
appCopyField="password"
[cipher]="cipher"
>
{{ "copyPassword" | i18n }}
</button>
<button type="button" bitMenuItem appCopyField="totp" [cipher]="cipher">
{{ "copyVerificationCode" | i18n }}
</button>
</bit-menu>
</bit-item-action>
</ng-template>
</ng-container>

<ng-container *ngIf="cipher.type === CipherType.Card">
<ng-container *ngIf="showQuickCopyActions$ | async; else cardCopyMenu">
<bit-item-action>
<button
type="button"
bitIconButton="bwi-hashtag"
size="small"
appCopyField="cardNumber"
[cipher]="cipher"
[appA11yTitle]="'copyNumber' | i18n"
></button>
</bit-item-action>
<bit-item-action>
<button
type="button"
bitIconButton="bwi-key"
size="small"
appCopyField="securityCode"
[cipher]="cipher"
[appA11yTitle]="'copySecurityCode' | i18n"
></button>
</bit-item-action>
</ng-container>
<ng-template #cardCopyMenu>
<bit-item-action>
<button
type="button"
bitIconButton="bwi-clone"
size="small"
[appA11yTitle]="
hasCardValues ? ('copyInfoTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n)
"
[disabled]="!hasCardValues"
[bitMenuTriggerFor]="cardOptions"
></button>
<bit-menu #cardOptions>
<button type="button" bitMenuItem appCopyField="cardNumber" [cipher]="cipher">
{{ "copyNumber" | i18n }}
</button>
<button type="button" bitMenuItem appCopyField="securityCode" [cipher]="cipher">
{{ "copySecurityCode" | i18n }}
</button>
</bit-menu>
</bit-item-action>
</ng-template>
</ng-container>

<bit-item-action *ngIf="cipher.type === CipherType.Identity">
<button
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { CommonModule } from "@angular/common";
import { Component, Input } from "@angular/core";
import { Component, Input, inject } from "@angular/core";

import { JslibModule } from "@bitwarden/angular/jslib.module";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { IconButtonModule, ItemModule, MenuModule } from "@bitwarden/components";
import { CopyCipherFieldDirective } from "@bitwarden/vault";

import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy-buttons.service";

@Component({
standalone: true,
selector: "app-item-copy-actions",
Expand All @@ -21,6 +23,8 @@ import { CopyCipherFieldDirective } from "@bitwarden/vault";
],
})
export class ItemCopyActionsComponent {
protected showQuickCopyActions$ = inject(VaultPopupCopyButtonsService).showQuickCopyActions$;

@Input() cipher: CipherView;

protected CipherType = CipherType;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { inject, Injectable } from "@angular/core";
import { map, Observable } from "rxjs";

import {
GlobalStateProvider,
KeyDefinition,
VAULT_APPEARANCE,
} from "@bitwarden/common/platform/state";

export type CopyButtonDisplayMode = "combined" | "quick";

const COPY_BUTTON = new KeyDefinition<CopyButtonDisplayMode>(VAULT_APPEARANCE, "copyButtons", {
deserializer: (s) => s,
});

/**
* Settings service for vault copy button settings
**/
@Injectable({ providedIn: "root" })
export class VaultPopupCopyButtonsService {
private readonly DEFAULT_DISPLAY_MODE = "combined";
private state = inject(GlobalStateProvider).get(COPY_BUTTON);

displayMode$: Observable<CopyButtonDisplayMode> = this.state.state$.pipe(
map((state) => state ?? this.DEFAULT_DISPLAY_MODE),
);

async setDisplayMode(displayMode: CopyButtonDisplayMode) {
await this.state.update(() => displayMode);
}

showQuickCopyActions$: Observable<boolean> = this.displayMode$.pipe(
map((displayMode) => displayMode === "quick"),
);

async setShowQuickCopyActions(value: boolean) {
await this.setDisplayMode(value ? "quick" : "combined");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
>
</bit-form-control>

<bit-form-control>
<input bitCheckbox formControlName="showQuickCopyActions" type="checkbox" />
<bit-label>{{ "showQuickCopyActions" | i18n }}</bit-label>
</bit-form-control>

<bit-form-control>
<input bitCheckbox formControlName="enableBadgeCounter" type="checkbox" />
<bit-label>{{ "showNumberOfAutofillSuggestions" | i18n }}</bit-label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { PopupCompactModeService } from "../../../platform/popup/layout/popup-co
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
import { PopupWidthService } from "../../../platform/popup/layout/popup-width.service";
import { VaultPopupCopyButtonsService } from "../services/vault-popup-copy-buttons.service";

import { AppearanceV2Component } from "./appearance-v2.component";

Expand Down Expand Up @@ -46,11 +47,13 @@ describe("AppearanceV2Component", () => {
const selectedTheme$ = new BehaviorSubject<ThemeType>(ThemeType.Nord);
const enableRoutingAnimation$ = new BehaviorSubject<boolean>(true);
const enableCompactMode$ = new BehaviorSubject<boolean>(false);
const showQuickCopyActions$ = new BehaviorSubject<boolean>(false);
const setSelectedTheme = jest.fn().mockResolvedValue(undefined);
const setShowFavicons = jest.fn().mockResolvedValue(undefined);
const setEnableBadgeCounter = jest.fn().mockResolvedValue(undefined);
const setEnableRoutingAnimation = jest.fn().mockResolvedValue(undefined);
const setEnableCompactMode = jest.fn().mockResolvedValue(undefined);
const setShowQuickCopyActions = jest.fn().mockResolvedValue(undefined);

const mockWidthService: Partial<PopupWidthService> = {
width$: new BehaviorSubject("default"),
Expand Down Expand Up @@ -84,6 +87,13 @@ describe("AppearanceV2Component", () => {
provide: PopupCompactModeService,
useValue: { enabled$: enableCompactMode$, setEnabled: setEnableCompactMode },
},
{
provide: VaultPopupCopyButtonsService,
useValue: {
showQuickCopyActions$,
setShowQuickCopyActions,
} as Partial<VaultPopupCopyButtonsService>,
},
{
provide: PopupWidthService,
useValue: mockWidthService,
Expand Down Expand Up @@ -112,6 +122,7 @@ describe("AppearanceV2Component", () => {
enableBadgeCounter: true,
theme: ThemeType.Nord,
enableCompactMode: false,
showQuickCopyActions: false,
width: "default",
});
});
Expand Down
17 changes: 17 additions & 0 deletions apps/browser/src/vault/popup/settings/appearance-v2.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
PopupWidthOption,
PopupWidthService,
} from "../../../platform/popup/layout/popup-width.service";
import { VaultPopupCopyButtonsService } from "../services/vault-popup-copy-buttons.service";

@Component({
standalone: true,
Expand All @@ -45,6 +46,7 @@ import {
})
export class AppearanceV2Component implements OnInit {
private compactModeService = inject(PopupCompactModeService);
private copyButtonsService = inject(VaultPopupCopyButtonsService);
private popupWidthService = inject(PopupWidthService);
private i18nService = inject(I18nService);

Expand All @@ -54,6 +56,7 @@ export class AppearanceV2Component implements OnInit {
theme: ThemeType.System,
enableAnimations: true,
enableCompactMode: false,
showQuickCopyActions: false,
width: "default" as PopupWidthOption,
});

Expand Down Expand Up @@ -95,6 +98,9 @@ export class AppearanceV2Component implements OnInit {
this.animationControlService.enableRoutingAnimation$,
);
const enableCompactMode = await firstValueFrom(this.compactModeService.enabled$);
const showQuickCopyActions = await firstValueFrom(
this.copyButtonsService.showQuickCopyActions$,
);
const width = await firstValueFrom(this.popupWidthService.width$);

// Set initial values for the form
Expand All @@ -104,6 +110,7 @@ export class AppearanceV2Component implements OnInit {
theme,
enableAnimations,
enableCompactMode,
showQuickCopyActions,
width,
});

Expand Down Expand Up @@ -139,6 +146,12 @@ export class AppearanceV2Component implements OnInit {
void this.updateCompactMode(enableCompactMode);
});

this.appearanceForm.controls.showQuickCopyActions.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((showQuickCopyActions) => {
void this.updateQuickCopyActions(showQuickCopyActions);
});

this.appearanceForm.controls.width.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((width) => {
Expand Down Expand Up @@ -167,6 +180,10 @@ export class AppearanceV2Component implements OnInit {
await this.compactModeService.setEnabled(enableCompactMode);
}

async updateQuickCopyActions(showQuickCopyActions: boolean) {
await this.copyButtonsService.setShowQuickCopyActions(showQuickCopyActions);
}

async updateWidth(width: PopupWidthOption) {
await this.popupWidthService.setWidth(width);
}
Expand Down
1 change: 1 addition & 0 deletions libs/common/src/platform/state/state-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,4 @@ export const NEW_DEVICE_VERIFICATION_NOTICE = new StateDefinition(
"newDeviceVerificationNotice",
"disk",
);
export const VAULT_APPEARANCE = new StateDefinition("vaultAppearance", "disk");

0 comments on commit fbf87e7

Please sign in to comment.