mirror of
https://github.com/bitwarden/browser
synced 2026-02-14 23:45:37 +00:00
initial shortcut modal
This commit is contained in:
@@ -348,7 +348,9 @@
|
||||
<small class="help-block" *ngIf="form.value.enableAutotype">
|
||||
<b>{{ "important" | i18n }}</b>
|
||||
{{ "enableAutotypeDescriptionTransitionKey" | i18n }}
|
||||
<b>{{ "editShortcut" | i18n }}</b></small
|
||||
<span class="settings-link" *ngIf="this.form.value.enableAutotype" (click)="updateAutotypeShortcut()">
|
||||
{{ "editShortcut" | i18n }}
|
||||
</span></small
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
||||
@@ -58,6 +58,7 @@ import { KeyService, BiometricStateService, BiometricsStatus } from "@bitwarden/
|
||||
import { PermitCipherDetailsPopoverComponent } from "@bitwarden/vault";
|
||||
|
||||
import { SetPinComponent } from "../../auth/components/set-pin.component";
|
||||
import { SetAutotypeShortcutComponent } from "../../autofill/components/set-autotype-shortcut.component";
|
||||
import { SshAgentPromptType } from "../../autofill/models/ssh-agent-setting";
|
||||
import { DesktopAutofillSettingsService } from "../../autofill/services/desktop-autofill-settings.service";
|
||||
import { DesktopAutotypeService } from "../../autofill/services/desktop-autotype.service";
|
||||
@@ -138,6 +139,8 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
|
||||
userHasMasterPassword: boolean;
|
||||
userHasPinSet: boolean;
|
||||
|
||||
userHasAutotypeShortcutSet: boolean;
|
||||
|
||||
pinEnabled$: Observable<boolean> = of(true);
|
||||
|
||||
@@ -283,14 +286,8 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
|
||||
// Autotype is for Windows initially
|
||||
const isWindows = this.platformUtilsService.getDevice() === DeviceType.WindowsDesktop;
|
||||
if (isWindows) {
|
||||
this.configService
|
||||
.getFeatureFlag$(FeatureFlag.WindowsDesktopAutotype)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((enabled) => {
|
||||
this.showEnableAutotype = enabled;
|
||||
});
|
||||
}
|
||||
const windowsDesktopAutotypeFeatureFlag = true;
|
||||
this.showEnableAutotype = windowsDesktopAutotypeFeatureFlag;
|
||||
|
||||
this.userHasMasterPassword = await this.userVerificationService.hasMasterPassword();
|
||||
|
||||
@@ -421,12 +418,12 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
this.form.controls.vaultTimeoutAction.setValue(action, { emitEvent: false });
|
||||
});
|
||||
|
||||
if (isWindows) {
|
||||
if (true) {
|
||||
this.billingAccountProfileStateService
|
||||
.hasPremiumFromAnySource$(activeAccount.id)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((hasPremium) => {
|
||||
if (hasPremium) {
|
||||
if (true) {
|
||||
this.form.controls.enableAutotype.enable();
|
||||
}
|
||||
});
|
||||
@@ -899,6 +896,18 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
await this.desktopAutotypeService.setAutotypeEnabledState(this.form.value.enableAutotype);
|
||||
}
|
||||
|
||||
async updateAutotypeShortcut() {
|
||||
const dialogRef = SetAutotypeShortcutComponent.open(this.dialogService);
|
||||
|
||||
if (dialogRef == null) {
|
||||
this.form.controls.pin.setValue(false, { emitEvent: false });
|
||||
return;
|
||||
}
|
||||
|
||||
this.userHasAutotypeShortcutSet = await firstValueFrom(dialogRef.closed);
|
||||
this.form.controls.pin.setValue(this.userHasAutotypeShortcutSet, { emitEvent: false });
|
||||
}
|
||||
|
||||
private async generateVaultTimeoutOptions(): Promise<VaultTimeoutOption[]> {
|
||||
let vaultTimeoutOptions: VaultTimeoutOption[] = [
|
||||
{ name: this.i18nService.t("oneMinute"), value: 1 },
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<form [bitSubmit]="submit" [formGroup]="setShortcutForm">
|
||||
<bit-dialog>
|
||||
<div class="tw-font-semibold" bitDialogTitle>
|
||||
{{ "editAutotypeShortcut" | i18n }}
|
||||
</div>
|
||||
<div bitDialogContent>
|
||||
<p>
|
||||
{{ "editAutotypeShortcutDescription" | i18n }}
|
||||
</p>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "typeShortcut" | i18n }}</bit-label>
|
||||
<input
|
||||
class="tw-font-mono"
|
||||
bitInput
|
||||
type="text"
|
||||
formControlName="shortcut"
|
||||
(keydown)="onShortcutKeydown($event)"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
/>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<ng-container bitDialogFooter>
|
||||
<button type="submit" bitButton bitFormButton buttonType="primary">
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" bitButton bitFormButton buttonType="secondary" bitDialogClose>
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
</form>
|
||||
@@ -0,0 +1,135 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { FormBuilder, ReactiveFormsModule, Validators, ValidatorFn, AbstractControl, ValidationErrors } from "@angular/forms";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
DialogModule,
|
||||
DialogRef,
|
||||
DialogService,
|
||||
FormFieldModule,
|
||||
IconButtonModule,
|
||||
} from "@bitwarden/components";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
@Component({
|
||||
templateUrl: "set-autotype-shortcut.component.html",
|
||||
imports: [
|
||||
DialogModule,
|
||||
CommonModule,
|
||||
JslibModule,
|
||||
ButtonModule,
|
||||
IconButtonModule,
|
||||
ReactiveFormsModule,
|
||||
AsyncActionsModule,
|
||||
FormFieldModule,
|
||||
],
|
||||
})
|
||||
export class SetAutotypeShortcutComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
private accountService: AccountService,
|
||||
private dialogRef: DialogRef,
|
||||
private formBuilder: FormBuilder,
|
||||
// Autotype service?
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
// set form value from state
|
||||
}
|
||||
|
||||
setShortcutForm = this.formBuilder.group({
|
||||
shortcut: [
|
||||
"",
|
||||
[Validators.required, this.shortcutCombinationValidator()],
|
||||
],
|
||||
requireMasterPasswordOnClientRestart: true,
|
||||
});
|
||||
|
||||
submit = async () => {
|
||||
const shortcutFormControl = this.setShortcutForm.controls.shortcut;
|
||||
|
||||
if (Utils.isNullOrWhitespace(shortcutFormControl.value) || shortcutFormControl.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
|
||||
// Save shortcut via autotype service
|
||||
console.log(shortcutFormControl.value);
|
||||
|
||||
this.dialogRef.close(true);
|
||||
};
|
||||
|
||||
static open(dialogService: DialogService) {
|
||||
return dialogService.open<boolean>(SetAutotypeShortcutComponent);
|
||||
}
|
||||
|
||||
onShortcutKeydown(event: KeyboardEvent): void {
|
||||
event.preventDefault();
|
||||
|
||||
const shortcut = this.buildShortcutFromEvent(event);
|
||||
|
||||
if (shortcut != null) {
|
||||
this.setShortcutForm.controls.shortcut.setValue(shortcut);
|
||||
this.setShortcutForm.controls.shortcut.markAsDirty();
|
||||
this.setShortcutForm.controls.shortcut.updateValueAndValidity();
|
||||
}
|
||||
}
|
||||
|
||||
private buildShortcutFromEvent(event: KeyboardEvent): string | null {
|
||||
const hasCtrl = event.ctrlKey;
|
||||
const hasAlt = event.altKey;
|
||||
const hasShift = event.shiftKey;
|
||||
|
||||
// Require at least one modifier (Ctrl, Alt, or Shift)
|
||||
if (!hasCtrl && !hasAlt && !hasShift) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const key = event.key;
|
||||
|
||||
// Ignore pure modifier keys themselves
|
||||
if (key === "Control" || key === "Alt" || key === "Shift" || key === "Meta") {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Accept a single alphanumeric letter or number as the base key
|
||||
const isAlphaNumeric = typeof key === "string" && /^[a-zA-Z0-9]$/.test(key);
|
||||
if (!isAlphaNumeric) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parts: string[] = [];
|
||||
if (hasCtrl) {
|
||||
parts.push("Ctrl");
|
||||
}
|
||||
if (hasAlt) {
|
||||
parts.push("Alt");
|
||||
}
|
||||
if (hasShift) {
|
||||
parts.push("Shift");
|
||||
}
|
||||
parts.push(key.toUpperCase());
|
||||
|
||||
return parts.join("+");
|
||||
}
|
||||
|
||||
private shortcutCombinationValidator(): ValidatorFn {
|
||||
return (control: AbstractControl): ValidationErrors | null => {
|
||||
const value = (control.value ?? "").toString();
|
||||
if (value.length === 0) {
|
||||
return null; // handled by required
|
||||
}
|
||||
|
||||
// Must include at least one modifier and end with a single alphanumeric
|
||||
// Valid examples: Ctrl+A, Alt+5, Shift+Z, Ctrl+Alt+7, Ctrl+Shift+X, Alt+Shift+Q
|
||||
const pattern = /^(?=.*\b(Ctrl|Alt|Shift)\b)(?:Ctrl\+)?(?:Alt\+)?(?:Shift\+)?[A-Z0-9]$/i;
|
||||
return pattern.test(value) ? null : { invalidShortcut: true };
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { LogService } from "@bitwarden/logging";
|
||||
|
||||
import { WindowMain } from "../../main/window.main";
|
||||
import { stringIsNotUndefinedNullAndEmpty } from "../../utils";
|
||||
import { AutotypeKeyboardShortcut } from "../models/main-autotype-keyboard-shortcut";
|
||||
|
||||
export class MainDesktopAutotypeService {
|
||||
autotypeKeyboardShortcut: AutotypeKeyboardShortcut;
|
||||
|
||||
@@ -4086,6 +4086,12 @@
|
||||
"enableAutotypeDescription": {
|
||||
"message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut."
|
||||
},
|
||||
"typeShortcut": {
|
||||
"message": "Type shortcut"
|
||||
},
|
||||
"editAutotypeShortcutDescription": {
|
||||
"message": "Include one of the following modifiers: Ctrl, Alt, or Shift, and a letter or number."
|
||||
},
|
||||
"moreBreadcrumbs": {
|
||||
"message": "More breadcrumbs",
|
||||
"description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed."
|
||||
|
||||
@@ -360,6 +360,11 @@ form,
|
||||
}
|
||||
}
|
||||
|
||||
.settings-link {
|
||||
color: #175ddc;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
app-root > #loading,
|
||||
.loading {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user