From 67f88795512b6215abf50ba76bd6036426946885 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Fri, 20 Jan 2023 16:31:09 +0530 Subject: [PATCH 1/4] incr: add mods to shortcut class --- src/App.vue | 5 +- src/components/SearchBar.vue | 19 ++++--- src/pages/QuickEditForm.vue | 1 + src/utils/vueUtils.ts | 104 ++++++++++++++++++++++++++++------- 4 files changed, 98 insertions(+), 31 deletions(-) diff --git a/src/App.vue b/src/App.vue index 01361390..daa81054 100644 --- a/src/App.vue +++ b/src/App.vue @@ -55,7 +55,7 @@ import { checkForUpdates } from './utils/ipcCalls'; import { updateConfigFiles } from './utils/misc'; import { Search } from './utils/search'; import { routeTo, systemLanguage } from './utils/ui'; -import { Shortcuts, useKeys } from './utils/vueUtils'; +import { getModKeyCode, Shortcuts, useKeys } from './utils/vueUtils'; export default { name: 'App', @@ -69,6 +69,7 @@ export default { companyName: '', searcher: null, shortcuts: null, + modKey: '', }; }, provide() { @@ -76,6 +77,7 @@ export default { languageDirection: computed(() => this.languageDirection), searcher: computed(() => this.searcher), shortcuts: computed(() => this.shortcuts), + modKey: computed(() => this.modKey), keys: computed(() => this.keys), }; }, @@ -86,6 +88,7 @@ export default { WindowsTitleBar, }, async mounted() { + this.modKey = getModKeyCode(this.platform); this.shortcuts = new Shortcuts(this.keys); const lastSelectedFilePath = fyo.config.get( ConfigKeys.LastSelectedFilePath, diff --git a/src/components/SearchBar.vue b/src/components/SearchBar.vue index a4e02cae..62842358 100644 --- a/src/components/SearchBar.vue +++ b/src/components/SearchBar.vue @@ -5,7 +5,7 @@

{{ t`Search` }}

- {{ modKey('k') }} + {{ modKeyText('k') }}
@@ -195,7 +195,6 @@ import { getBgTextColorClass } from 'src/utils/colors'; import { openLink } from 'src/utils/ipcCalls'; import { docsPathMap } from 'src/utils/misc'; import { getGroupLabelMap, searchGroups } from 'src/utils/search'; -import { getModKeyCode } from 'src/utils/vueUtils'; import { nextTick } from 'vue'; import Button from './Button.vue'; import Modal from './Modal.vue'; @@ -212,7 +211,7 @@ export default { allowedLimits: [50, 100, 500, -1], }; }, - inject: ['searcher', 'shortcuts'], + inject: ['searcher', 'shortcuts', 'modKey'], components: { Modal, Button }, async mounted() { if (fyo.store.isDevelopment) { @@ -233,18 +232,20 @@ export default { openLink('https://docs.frappebooks.com/' + docsPathMap.Search); }, getShortcuts() { - const modKey = getModKeyCode(this.platform); const ifOpen = (cb) => () => this.openModal && cb(); const ifClose = (cb) => () => !this.openModal && cb(); const shortcuts = [ - { shortcut: ['KeyK', modKey], callback: ifClose(() => this.open()) }, + { + shortcut: ['KeyK', this.modKey], + callback: ifClose(() => this.open()), + }, { shortcut: ['Escape'], callback: ifOpen(() => this.close()) }, ]; for (const i in searchGroups) { shortcuts.push({ - shortcut: [modKey, `Digit${Number(i) + 1}`], + shortcut: [this.modKey, `Digit${Number(i) + 1}`], callback: ifOpen(() => { const group = searchGroups[i]; const value = this.searcher.filters.groupFilters[group]; @@ -261,15 +262,15 @@ export default { }, setShortcuts() { for (const { shortcut, callback } of this.getShortcuts()) { - this.shortcuts.set(shortcut, callback); + this.shortcuts.meta.set(shortcut, callback); } }, deleteShortcuts() { for (const { shortcut } of this.getShortcuts()) { - this.shortcuts.delete(shortcut); + this.shortcuts.meta.delete(shortcut); } }, - modKey(key) { + modKeyText(key) { key = key.toUpperCase(); if (this.platform === 'Mac') { return `⌘ ${key}`; diff --git a/src/pages/QuickEditForm.vue b/src/pages/QuickEditForm.vue index 4eae317b..80b386d2 100644 --- a/src/pages/QuickEditForm.vue +++ b/src/pages/QuickEditForm.vue @@ -131,6 +131,7 @@ export default { DropdownWithActions, }, emits: ['close'], + inject: ['shortcuts'], provide() { return { schemaName: this.schemaName, diff --git a/src/utils/vueUtils.ts b/src/utils/vueUtils.ts index 4a266ddd..2ad3281f 100644 --- a/src/utils/vueUtils.ts +++ b/src/utils/vueUtils.ts @@ -1,7 +1,6 @@ -import { onMounted, onUnmounted, Ref, ref, watch } from 'vue'; +import { onMounted, onUnmounted, reactive, ref, unref, watch } from 'vue'; -interface Keys { - pressed: Set; +interface ModMap { alt: boolean; ctrl: boolean; meta: boolean; @@ -9,11 +8,23 @@ interface Keys { repeat: boolean; } -export class Shortcuts { - keys: Ref; - shortcuts: Map; +type Mod = keyof ModMap; - constructor(keys?: Ref) { +interface Keys extends ModMap { + pressed: Set; +} + +type ShortcutFunction = () => void; + +const mods: Readonly = ['alt', 'ctrl', 'meta', 'repeat', 'shift']; + +export class Shortcuts { + keys: Keys; + shortcuts: Map; + modMap: Partial>; + + constructor(keys?: Keys) { + this.modMap = {}; this.keys = keys ?? useKeys(); this.shortcuts = new Map(); @@ -23,17 +34,22 @@ export class Shortcuts { } #trigger(keys: Keys) { - const key = Array.from(keys.pressed).sort().join('+'); + const key = this.getKey(Array.from(keys.pressed), keys); this.shortcuts.get(key)?.(); } has(shortcut: string[]) { - const key = shortcut.sort().join('+'); + const key = this.getKey(shortcut); return this.shortcuts.has(key); } - set(shortcut: string[], callback: Function, removeIfSet: boolean = true) { - const key = shortcut.sort().join('+'); + set( + shortcut: string[], + callback: ShortcutFunction, + removeIfSet: boolean = true + ) { + const key = this.getKey(shortcut); + if (removeIfSet) { this.shortcuts.delete(key); } @@ -46,13 +62,59 @@ export class Shortcuts { } delete(shortcut: string[]) { - const key = shortcut.sort().join('+'); + const key = this.getKey(shortcut); this.shortcuts.delete(key); } + + getKey(shortcut: string[], modMap?: Partial): string { + const _modMap = modMap || this.modMap; + this.modMap = {}; + + const shortcutString = shortcut.sort().join('+'); + const modString = mods.filter((k) => _modMap[k]).join('+'); + if (shortcutString && modString) { + return modString + '+' + shortcutString; + } + + if (!modString) { + return shortcutString; + } + + if (!shortcutString) { + return modString; + } + + return ''; + } + + get alt() { + this.modMap['alt'] = true; + return this; + } + + get ctrl() { + this.modMap['ctrl'] = true; + return this; + } + + get meta() { + this.modMap['meta'] = true; + return this; + } + + get shift() { + this.modMap['shift'] = true; + return this; + } + + get repeat() { + this.modMap['repeat'] = true; + return this; + } } export function useKeys() { - const keys: Ref = ref({ + const keys: Keys = reactive({ pressed: new Set(), alt: false, ctrl: false, @@ -62,20 +124,20 @@ export function useKeys() { }); const keydownListener = (e: KeyboardEvent) => { - keys.value.pressed.add(e.code); - keys.value.alt = e.altKey; - keys.value.ctrl = e.ctrlKey; - keys.value.meta = e.metaKey; - keys.value.shift = e.shiftKey; - keys.value.repeat = e.repeat; + keys.pressed.add(e.code); + keys.alt = e.altKey; + keys.ctrl = e.ctrlKey; + keys.meta = e.metaKey; + keys.shift = e.shiftKey; + keys.repeat = e.repeat; }; const keyupListener = (e: KeyboardEvent) => { - keys.value.pressed.delete(e.code); + keys.pressed.delete(e.code); // Key up won't trigger on macOS for other keys. if (e.code === 'MetaLeft') { - keys.value.pressed.clear(); + keys.pressed.clear(); } }; From e4fbecc91480794a79986f7efacc8924a9056320 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 30 Jan 2023 12:27:21 +0530 Subject: [PATCH 2/4] incr: mods not added to pressed --- src/components/SearchBar.vue | 19 +++++++++++++------ src/utils/vueUtils.ts | 26 +++++++++++++++++++------- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/components/SearchBar.vue b/src/components/SearchBar.vue index 62842358..d54f3181 100644 --- a/src/components/SearchBar.vue +++ b/src/components/SearchBar.vue @@ -237,15 +237,14 @@ export default { const shortcuts = [ { - shortcut: ['KeyK', this.modKey], + shortcut: 'KeyK', callback: ifClose(() => this.open()), }, - { shortcut: ['Escape'], callback: ifOpen(() => this.close()) }, ]; for (const i in searchGroups) { shortcuts.push({ - shortcut: [this.modKey, `Digit${Number(i) + 1}`], + shortcut: `Digit${Number(i) + 1}`, callback: ifOpen(() => { const group = searchGroups[i]; const value = this.searcher.filters.groupFilters[group]; @@ -262,12 +261,20 @@ export default { }, setShortcuts() { for (const { shortcut, callback } of this.getShortcuts()) { - this.shortcuts.meta.set(shortcut, callback); + if (this.platform === 'Mac') { + this.shortcuts.meta.set([shortcut], callback); + } else { + this.shortcuts.ctrl.set([shortcut], callback); + } } }, deleteShortcuts() { for (const { shortcut } of this.getShortcuts()) { - this.shortcuts.meta.delete(shortcut); + if (this.platform === 'Mac') { + this.shortcuts.meta.delete([shortcut]); + } else { + this.shortcuts.ctrl.delete([shortcut]); + } } }, modKeyText(key) { @@ -305,7 +312,7 @@ export default { }, select(idx) { this.idx = idx ?? this.idx; - this.suggestions[this.idx]?.action(); + this.suggestionsthis.idx?.action(); this.close(); }, scrollToHighlighted() { diff --git a/src/utils/vueUtils.ts b/src/utils/vueUtils.ts index 2ad3281f..cdeca992 100644 --- a/src/utils/vueUtils.ts +++ b/src/utils/vueUtils.ts @@ -1,4 +1,4 @@ -import { onMounted, onUnmounted, reactive, ref, unref, watch } from 'vue'; +import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'; interface ModMap { alt: boolean; @@ -114,6 +114,7 @@ export class Shortcuts { } export function useKeys() { + const isMac = navigator.userAgent.indexOf('Mac') !== -1; const keys: Keys = reactive({ pressed: new Set(), alt: false, @@ -124,21 +125,32 @@ export function useKeys() { }); const keydownListener = (e: KeyboardEvent) => { - keys.pressed.add(e.code); keys.alt = e.altKey; keys.ctrl = e.ctrlKey; keys.meta = e.metaKey; keys.shift = e.shiftKey; keys.repeat = e.repeat; + + const { code } = e; + if ( + code.startsWith('Alt') || + code.startsWith('Ctrl') || + code.startsWith('Meta') || + code.startsWith('Shift') + ) { + return; + } + + keys.pressed.add(code); }; const keyupListener = (e: KeyboardEvent) => { - keys.pressed.delete(e.code); - - // Key up won't trigger on macOS for other keys. - if (e.code === 'MetaLeft') { - keys.pressed.clear(); + const { code } = e; + if (code.startsWith('Meta') && isMac) { + return keys.pressed.clear(); } + + keys.pressed.delete(code); }; onMounted(() => { From 974c95aafc3b55735e7eb5844c9768c1f2f62650 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 30 Jan 2023 16:20:40 +0530 Subject: [PATCH 3/4] incr: add shortcuts to go back and - save, submit, cancel and delete a focused doc - fix search filter issue --- src/App.vue | 16 +- src/components/FormContainer.vue | 2 +- src/components/Modal.vue | 9 +- src/components/SearchBar.vue | 335 +++++++++++++++--------------- src/components/Sidebar.vue | 5 +- src/pages/ChartOfAccounts.vue | 7 +- src/pages/Dashboard/Dashboard.vue | 6 +- src/pages/DataImport.vue | 7 +- src/pages/GeneralForm.vue | 10 +- src/pages/InvoiceForm.vue | 11 +- src/pages/JournalEntryForm.vue | 9 +- src/pages/ListView/ListView.vue | 7 +- src/pages/QuickEditForm.vue | 16 +- src/pages/Report.vue | 6 +- src/pages/Settings/Settings.vue | 7 +- src/styles/index.css | 1 + src/utils/language.ts | 5 +- src/utils/misc.ts | 43 ++++ src/utils/refs.ts | 8 + src/utils/search.ts | 5 +- src/utils/shortcuts.ts | 78 +++++++ src/utils/ui.ts | 5 +- src/utils/vueUtils.ts | 20 +- 23 files changed, 384 insertions(+), 234 deletions(-) create mode 100644 src/utils/refs.ts create mode 100644 src/utils/shortcuts.ts diff --git a/src/App.vue b/src/App.vue index daa81054..f3312327 100644 --- a/src/App.vue +++ b/src/App.vue @@ -41,6 +41,7 @@ import { ConfigKeys } from 'fyo/core/types'; import { RTL_LANGUAGES } from 'fyo/utils/consts'; import { ModelNameEnum } from 'models/types'; +import { systemLanguageRef } from 'src/utils/refs'; import { computed } from 'vue'; import WindowsTitleBar from './components/WindowsTitleBar.vue'; import { handleErrorWithDialog } from './errorHandling'; @@ -54,8 +55,9 @@ import { initializeInstance } from './utils/initialization'; import { checkForUpdates } from './utils/ipcCalls'; import { updateConfigFiles } from './utils/misc'; import { Search } from './utils/search'; -import { routeTo, systemLanguage } from './utils/ui'; -import { getModKeyCode, Shortcuts, useKeys } from './utils/vueUtils'; +import { setGlobalShortcuts } from './utils/shortcuts'; +import { routeTo } from './utils/ui'; +import { Shortcuts, useKeys } from './utils/vueUtils'; export default { name: 'App', @@ -69,7 +71,6 @@ export default { companyName: '', searcher: null, shortcuts: null, - modKey: '', }; }, provide() { @@ -77,7 +78,6 @@ export default { languageDirection: computed(() => this.languageDirection), searcher: computed(() => this.searcher), shortcuts: computed(() => this.shortcuts), - modKey: computed(() => this.modKey), keys: computed(() => this.keys), }; }, @@ -88,8 +88,8 @@ export default { WindowsTitleBar, }, async mounted() { - this.modKey = getModKeyCode(this.platform); - this.shortcuts = new Shortcuts(this.keys); + const shortcuts = new Shortcuts(this.keys); + this.shortcuts = shortcuts; const lastSelectedFilePath = fyo.config.get( ConfigKeys.LastSelectedFilePath, null @@ -105,10 +105,12 @@ export default { await handleErrorWithDialog(err, undefined, true, true); await this.showDbSelector(); } + + setGlobalShortcuts(shortcuts); }, computed: { language() { - return systemLanguage.value; + return systemLanguageRef.value; }, languageDirection() { return RTL_LANGUAGES.includes(this.language) ? 'rtl' : 'ltr'; diff --git a/src/components/FormContainer.vue b/src/components/FormContainer.vue index dddc9cb2..2ad11f78 100644 --- a/src/components/FormContainer.vue +++ b/src/components/FormContainer.vue @@ -1,5 +1,5 @@