global shortcuts

This commit is contained in:
antelle 2021-07-04 17:12:28 +02:00
parent 48c4f28d5d
commit b572876099
No known key found for this signature in database
GPG Key ID: 63C9777AAB7C563C
7 changed files with 297 additions and 153 deletions

View File

@ -31,10 +31,9 @@ const AllGlobalShortcuts = {
restoreApp: undefined
};
const GlobalShortcutAppSettingsFields: Record<
keyof typeof AllGlobalShortcuts,
AppSettingsFieldName
> = {
export type GlobalShortcutType = keyof typeof AllGlobalShortcuts;
const GlobalShortcutAppSettingsFields: Record<GlobalShortcutType, AppSettingsFieldName> = {
copyPassword: 'globalShortcutCopyPassword',
copyUser: 'globalShortcutCopyUser',
copyUrl: 'globalShortcutCopyUrl',
@ -108,11 +107,11 @@ const Shortcuts = {
return formatting ? `${shortcut} + ` : `${shortcut}+`;
},
globalShortcutText(type: keyof typeof AllGlobalShortcuts, formatting?: boolean): string {
globalShortcutText(type: GlobalShortcutType, formatting?: boolean): string {
return this.presentShortcut(this.globalShortcut(type), formatting);
},
globalShortcut(type: keyof typeof AllGlobalShortcuts): string | undefined {
globalShortcut(type: GlobalShortcutType): string | undefined {
const setting = GlobalShortcutAppSettingsFields[type];
const appSettingsShortcut = AppSettings[setting];
if (typeof appSettingsShortcut === 'string') {
@ -128,7 +127,7 @@ const Shortcuts = {
return undefined;
},
setGlobalShortcut(type: keyof typeof AllGlobalShortcuts, value: string): void {
setGlobalShortcut(type: GlobalShortcutType, value: string | null): void {
if (!AllGlobalShortcuts[type]) {
throw new Error('Bad shortcut: ' + type);
}

View File

@ -1,108 +0,0 @@
import { View } from 'framework/views/view';
import { Shortcuts } from 'comp/app/shortcuts';
import { Launcher } from 'comp/launcher';
import { Keys } from 'const/keys';
import { Features } from 'util/features';
import { Locale } from 'util/locale';
import template from 'templates/settings/settings-shortcuts.hbs';
class SettingsShortcutsView extends View {
template = template;
systemShortcuts = [
'Meta+A',
'Alt+A',
'Alt+C',
'Alt+D',
'Meta+F',
'Meta+C',
'Meta+B',
'Meta+U',
'Meta+T',
'Alt+N',
'Meta+O',
'Meta+S',
'Meta+G',
'Meta+,',
'Meta+L'
];
events = {
'click button.shortcut': 'shortcutClick'
};
render() {
super.render({
cmd: Shortcuts.actionShortcutSymbol(true),
alt: Shortcuts.altShortcutSymbol(true),
globalIsLarge: !Features.isMac,
autoTypeSupported: !!Launcher,
globalShortcuts: Launcher
? {
autoType: Shortcuts.globalShortcutText('autoType', true),
copyPassword: Shortcuts.globalShortcutText('copyPassword', true),
copyUser: Shortcuts.globalShortcutText('copyUser', true),
copyUrl: Shortcuts.globalShortcutText('copyUrl', true),
copyOtp: Shortcuts.globalShortcutText('copyOtp', true),
restoreApp: Shortcuts.globalShortcutText('restoreApp', true)
}
: undefined
});
}
shortcutClick(e) {
const globalShortcutType = e.target.dataset.shortcut;
const existing = $(`.shortcut__editor[data-shortcut=${globalShortcutType}]`);
if (existing.length) {
existing.remove();
return;
}
const shortcutEditor = $('<div/>')
.addClass('shortcut__editor')
.attr('data-shortcut', globalShortcutType);
$('<div/>').text(Locale.setShEdit).appendTo(shortcutEditor);
const shortcutEditorInput = $('<input/>')
.addClass('shortcut__editor-input')
.val(Shortcuts.globalShortcutText(globalShortcutType))
.appendTo(shortcutEditor);
if (!Features.isMac) {
shortcutEditorInput.addClass('shortcut__editor-input--large');
}
shortcutEditor.insertAfter($(e.target).parent());
shortcutEditorInput.focus();
shortcutEditorInput.on('keypress', (e) => e.preventDefault());
shortcutEditorInput.on('keydown', (e) => {
e.preventDefault();
e.stopImmediatePropagation();
if (e.which === Keys.DOM_VK_DELETE || e.which === Keys.DOM_VK_BACK_SPACE) {
Shortcuts.setGlobalShortcut(globalShortcutType, undefined);
this.render();
return;
}
if (e.which === Keys.DOM_VK_ESCAPE) {
shortcutEditorInput.blur();
return;
}
const shortcut = Shortcuts.keyEventToShortcut(e);
const presentableShortcutText = Shortcuts.presentShortcut(shortcut.value);
shortcutEditorInput.val(presentableShortcutText);
const exists = this.systemShortcuts.includes(shortcut.text);
shortcutEditorInput.toggleClass('input--error', exists);
const isValid = shortcut.valid && !exists;
if (isValid) {
Shortcuts.setGlobalShortcut(globalShortcutType, shortcut.value);
this.render();
}
});
}
}
export { SettingsShortcutsView };

View File

@ -0,0 +1,76 @@
import { FunctionComponent, h } from 'preact';
import { GlobalShortcutDef, SettingsShortcutsView } from 'views/settings/settings-shortcuts-view';
import { Features } from 'util/features';
import { Launcher } from 'comp/launcher';
import { GlobalShortcutType, Shortcuts } from 'comp/browser/shortcuts';
import { Locale } from 'util/locale';
import { useModelWatcher } from 'util/ui/hooks';
import { AppSettings } from 'models/app-settings';
import { useState } from 'preact/hooks';
function getGlobalShortcuts(): GlobalShortcutDef[] {
return [
{
id: 'autoType',
title: Locale.setShAutoTypeGlobal,
value: Shortcuts.globalShortcutText('autoType', true)
},
{
id: 'copyPassword',
title: Locale.setShCopyPassOnly,
value: Shortcuts.globalShortcutText('copyPassword', true)
},
{
id: 'copyUser',
title: Locale.setShCopyUser,
value: Shortcuts.globalShortcutText('copyUser', true)
},
{
id: 'copyUrl',
title: Locale.setShCopyUrl,
value: Shortcuts.globalShortcutText('copyUrl', true)
},
{
id: 'copyOtp',
title: Locale.setShCopyOtp,
value: Shortcuts.globalShortcutText('copyOtp', true)
},
{
id: 'restoreApp',
title: Locale.setShRestoreApp.with('KeeWeb'),
value: Shortcuts.globalShortcutText('restoreApp', true)
}
];
}
export const SettingsShortcuts: FunctionComponent = () => {
useModelWatcher(AppSettings);
const [editedShortcut, setEditedShortcut] = useState('');
const shortcutClicked = (id: GlobalShortcutType) => {
setEditedShortcut(editedShortcut === id ? '' : id);
};
const shortcutChanged = (id: GlobalShortcutType, shortcut: string) => {
Shortcuts.setGlobalShortcut(id, shortcut);
setEditedShortcut('');
};
const shortcutReset = (id: GlobalShortcutType) => {
Shortcuts.setGlobalShortcut(id, null);
setEditedShortcut('');
};
return h(SettingsShortcutsView, {
cmd: Shortcuts.actionShortcutSymbol(true),
alt: Shortcuts.altShortcutSymbol(true),
globalIsLarge: !Features.isMac,
autoTypeSupported: !!Launcher,
globalShortcuts: Launcher ? getGlobalShortcuts() : undefined,
editedShortcut,
shortcutClicked,
shortcutChanged,
shortcutReset
});
};

View File

@ -0,0 +1,84 @@
import { FunctionComponent } from 'preact';
import { Locale } from 'util/locale';
import { classes } from 'util/ui/classes';
import { useLayoutEffect, useRef, useState } from 'preact/hooks';
import { Keys } from 'const/keys';
import { Shortcuts } from 'comp/browser/shortcuts';
const SystemShortcuts = [
'Meta+A',
'Alt+A',
'Alt+C',
'Alt+D',
'Meta+F',
'Meta+C',
'Meta+B',
'Meta+U',
'Meta+T',
'Alt+N',
'Meta+O',
'Meta+S',
'Meta+G',
'Meta+,',
'Meta+L'
];
export const ShortcutEditor: FunctionComponent<{
shortcut: string;
large: boolean;
onSet: (shortcut: string) => void;
onClear: () => void;
}> = ({ shortcut, large, onSet, onClear }) => {
const shortcutInput = useRef<HTMLInputElement>();
const [shortcutExists, setShortcutExists] = useState(false);
useLayoutEffect(() => {
shortcutInput.current.focus();
}, []);
const inputKeyDown = (e: KeyboardEvent) => {
e.preventDefault();
e.stopImmediatePropagation();
if (e.which === Keys.DOM_VK_DELETE || e.which === Keys.DOM_VK_BACK_SPACE) {
onClear();
return;
}
if (e.which === Keys.DOM_VK_ESCAPE) {
shortcutInput.current.blur();
return;
}
const shortcut = Shortcuts.keyEventToShortcut(e);
const presentableShortcutText = Shortcuts.presentShortcut(shortcut.value);
shortcutInput.current.value = presentableShortcutText;
const exists = SystemShortcuts.includes(shortcut.value);
setShortcutExists(exists);
const isValid = shortcut.valid && !exists;
if (isValid) {
onSet(shortcut.value);
}
};
return (
<div class="shortcut__editor">
<div>{Locale.setShEdit}</div>
<input
ref={shortcutInput}
class={classes({
'shortcut__editor-input': true,
'shortcut__editor-input--large': large,
'input--error': shortcutExists
})}
value={shortcut}
onKeyPress={(e) => e.preventDefault()}
onKeyDown={inputKeyDown}
/>
</div>
);
};

View File

@ -0,0 +1,129 @@
import { FunctionComponent } from 'preact';
import { Locale } from 'util/locale';
import { classes } from 'util/ui/classes';
import { ShortcutEditor } from 'views/components/shortcut-editor';
import { GlobalShortcutType } from 'comp/browser/shortcuts';
export interface GlobalShortcutDef {
id: GlobalShortcutType;
value: string;
title: string;
}
export const SettingsShortcutsView: FunctionComponent<{
cmd: string;
alt: string;
autoTypeSupported: boolean;
globalShortcuts?: GlobalShortcutDef[];
globalIsLarge: boolean;
editedShortcut?: string;
shortcutClicked: (id: GlobalShortcutType) => void;
shortcutChanged: (id: GlobalShortcutType, shortcut: string) => void;
shortcutReset: (id: GlobalShortcutType) => void;
}> = ({
cmd,
alt,
autoTypeSupported,
globalShortcuts,
globalIsLarge,
editedShortcut,
shortcutClicked,
shortcutChanged,
shortcutReset
}) => {
return (
<div class="settings__content">
<h1>
<i class="fa fa-keyboard settings__head-icon" /> {Locale.setShTitle}
</h1>
<div>
<span class="shortcut">{cmd}A</span> {Locale.or}{' '}
<span class="shortcut">{alt}A</span> {Locale.setShShowAll}
</div>
<div>
<span class="shortcut">{alt}C</span> {Locale.setShColors}
</div>
<div>
<span class="shortcut">{alt}D</span> {Locale.setShTrash}
</div>
<div>
<span class="shortcut">{cmd}F</span> {Locale.setShFind}
</div>
<div>
<span class="shortcut">esc</span> {Locale.setShClearSearch}
</div>
<div>
<span class="shortcut">{cmd}C</span> {Locale.setShCopyPass}
</div>
<div>
<span class="shortcut">{cmd}B</span> {Locale.setShCopyUser}
</div>
<div>
<span class="shortcut">{cmd}U</span> {Locale.setShCopyUrl}
</div>
<div>
<span class="shortcut">{alt}2</span> {Locale.setShCopyOtp}
</div>
{autoTypeSupported ? (
<div>
<span class="shortcut">{cmd}T</span> {Locale.setShAutoType}
</div>
) : null}
<div>
<span class="shortcut">&uarr;</span> {Locale.setShPrev}
</div>
<div>
<span class="shortcut">&darr;</span> {Locale.setShNext}
</div>
<div>
<span class="shortcut">{alt}N</span> {Locale.setShCreateEntry}
</div>
<div>
<span class="shortcut">{cmd}O</span> {Locale.setShOpen}
</div>
<div>
<span class="shortcut">{cmd}S</span> {Locale.setShSave}
</div>
<div>
<span class="shortcut">{cmd}G</span> {Locale.setShGen}
</div>
<div>
<span class="shortcut">{cmd},</span> {Locale.setShSet}
</div>
<div>
<span class="shortcut">{cmd}L</span> {Locale.setShLock}
</div>
{globalShortcuts ? (
<>
<p>{Locale.setShGlobal}</p>
{globalShortcuts.map((sh) => (
<div key={sh.id}>
<button
class={classes({
'shortcut': true,
'btn-silent': true,
'shortcut-large': globalIsLarge
})}
onClick={() => shortcutClicked(sh.id)}
>
{sh.value}
</button>{' '}
{sh.title}
{sh.id === editedShortcut ? (
<ShortcutEditor
shortcut={sh.value}
large={globalIsLarge}
onSet={(shortcut) => shortcutChanged(sh.id, shortcut)}
onClear={() => shortcutReset(sh.id)}
/>
) : null}
</div>
))}
</>
) : null}
</div>
);
};

View File

@ -3,6 +3,7 @@ import { SettingsPage } from 'models/workspace';
import { Scrollable } from 'views/components/scrollable';
import { Locale } from 'util/locale';
import { SettingsGeneral } from 'ui/settings/settings-general';
import { SettingsShortcuts } from 'ui/settings/settings-shortcuts';
import { SettingsAbout } from 'ui/settings/settings-about';
import { SettingsHelp } from 'ui/settings/settings-help';
@ -19,6 +20,7 @@ export const SettingsView: FunctionComponent<{
</div>
<Scrollable>
{page === 'general' ? <SettingsGeneral /> : null}
{page === 'shortcuts' ? <SettingsShortcuts /> : null}
{page === 'about' ? <SettingsAbout /> : null}
{page === 'help' ? <SettingsHelp /> : null}
</Scrollable>

View File

@ -1,38 +0,0 @@
<div class="settings__content">
<h1><i class="fa fa-keyboard settings__head-icon"></i> {{res 'setShTitle'}}</h1>
<div><span class="shortcut">{{cmd}}A</span> {{res 'or'}} <span class="shortcut">{{alt}}A</span> {{res 'setShShowAll'}}</div>
<div><span class="shortcut">{{alt}}C</span> {{res 'setShColors'}}</div>
<div><span class="shortcut">{{alt}}D</span> {{res 'setShTrash'}}</div>
<div><span class="shortcut">{{cmd}}F</span> {{res 'setShFind'}}</div>
<div><span class="shortcut">esc</span> {{res 'setShClearSearch'}}</div>
<div><span class="shortcut">{{cmd}}C</span> {{res 'setShCopyPass'}}</div>
<div><span class="shortcut">{{cmd}}B</span> {{res 'setShCopyUser'}}</div>
<div><span class="shortcut">{{cmd}}U</span> {{res 'setShCopyUrl'}}</div>
<div><span class="shortcut">{{alt}}2</span> {{res 'setShCopyOtp'}}</div>
{{#if autoTypeSupported}}
<div><span class="shortcut">{{cmd}}T</span> {{res 'setShAutoType'}}</div>
{{/if}}
<div><span class="shortcut">&uarr;</span> {{res 'setShPrev'}}</div>
<div><span class="shortcut">&darr;</span> {{res 'setShNext'}}</div>
<div><span class="shortcut">{{alt}}N</span> {{res 'setShCreateEntry'}}</div>
<div><span class="shortcut">{{cmd}}O</span> {{res 'setShOpen'}}</div>
<div><span class="shortcut">{{cmd}}S</span> {{res 'setShSave'}}</div>
<div><span class="shortcut">{{cmd}}G</span> {{res 'setShGen'}}</div>
<div><span class="shortcut">{{cmd}},</span> {{res 'setShSet'}}</div>
<div><span class="shortcut">{{cmd}}L</span> {{res 'setShLock'}}</div>
{{#if globalShortcuts}}
<p>{{res 'setShGlobal'}}</p>
<div><button class="shortcut btn-silent {{#if globalIsLarge}}shortcut-large{{/if}}"
data-shortcut="autoType">{{globalShortcuts.autoType}}</button> {{res 'setShAutoTypeGlobal'}}</div>
<div><button class="shortcut btn-silent {{#if globalIsLarge}}shortcut-large{{/if}}"
data-shortcut="copyPassword">{{globalShortcuts.copyPassword}}</button> {{res 'setShCopyPassOnly'}}</div>
<div><button class="shortcut btn-silent {{#if globalIsLarge}}shortcut-large{{/if}}"
data-shortcut="copyUser">{{globalShortcuts.copyUser}}</button> {{res 'setShCopyUser'}}</div>
<div><button class="shortcut btn-silent {{#if globalIsLarge}}shortcut-large{{/if}}"
data-shortcut="copyUrl">{{globalShortcuts.copyUrl}}</button> {{res 'setShCopyUrl'}}</div>
<div><button class="shortcut btn-silent {{#if globalIsLarge}}shortcut-large{{/if}}"
data-shortcut="copyOtp">{{globalShortcuts.copyOtp}}</button> {{res 'setShCopyOtp'}}</div>
<div><button class="shortcut btn-silent {{#if globalIsLarge}}shortcut-large{{/if}}"
data-shortcut="restoreApp">{{globalShortcuts.restoreApp}}</button> {{#res 'setShRestoreApp'}}KeeWeb{{/res}}</div>
{{/if}}
</div>