mirror of
https://github.com/frappe/books.git
synced 2024-11-09 23:30:56 +00:00
refactor: use App level shortcut
This commit is contained in:
parent
0bc22d847b
commit
a140e82321
@ -52,20 +52,27 @@ import { checkForUpdates } from './utils/ipcCalls';
|
|||||||
import { updateConfigFiles } from './utils/misc';
|
import { updateConfigFiles } from './utils/misc';
|
||||||
import { Search } from './utils/search';
|
import { Search } from './utils/search';
|
||||||
import { routeTo } from './utils/ui';
|
import { routeTo } from './utils/ui';
|
||||||
|
import { Shortcuts, useKeys } from './utils/vueUtils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
|
setup() {
|
||||||
|
return { keys: useKeys() };
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
activeScreen: null,
|
activeScreen: null,
|
||||||
dbPath: '',
|
dbPath: '',
|
||||||
companyName: '',
|
companyName: '',
|
||||||
searcher: null,
|
searcher: null,
|
||||||
|
shortcuts: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
provide() {
|
provide() {
|
||||||
return {
|
return {
|
||||||
searcher: computed(() => this.searcher),
|
searcher: computed(() => this.searcher),
|
||||||
|
shortcuts: computed(() => this.shortcuts),
|
||||||
|
keys: computed(() => this.keys),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
@ -75,6 +82,7 @@ export default {
|
|||||||
WindowsTitleBar,
|
WindowsTitleBar,
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
this.shortcuts = new Shortcuts(this.keys);
|
||||||
const lastSelectedFilePath = fyo.config.get(
|
const lastSelectedFilePath = fyo.config.get(
|
||||||
ConfigKeys.LastSelectedFilePath,
|
ConfigKeys.LastSelectedFilePath,
|
||||||
null
|
null
|
||||||
|
@ -11,7 +11,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Search Modal -->
|
<!-- Search Modal -->
|
||||||
<Modal :open-modal="openModal" @closemodal="close" :set-close-listener="false">
|
<Modal
|
||||||
|
:open-modal="openModal"
|
||||||
|
@closemodal="close"
|
||||||
|
:set-close-listener="false"
|
||||||
|
>
|
||||||
<!-- Search Input -->
|
<!-- Search Input -->
|
||||||
<div class="p-1">
|
<div class="p-1">
|
||||||
<input
|
<input
|
||||||
@ -191,18 +195,13 @@ import { getBgTextColorClass } from 'src/utils/colors';
|
|||||||
import { openLink } from 'src/utils/ipcCalls';
|
import { openLink } from 'src/utils/ipcCalls';
|
||||||
import { docsPathMap } from 'src/utils/misc';
|
import { docsPathMap } from 'src/utils/misc';
|
||||||
import { getGroupLabelMap, searchGroups } from 'src/utils/search';
|
import { getGroupLabelMap, searchGroups } from 'src/utils/search';
|
||||||
import { useKeys } from 'src/utils/vueUtils';
|
import { getModKeyCode } from 'src/utils/vueUtils';
|
||||||
import { getIsNullOrUndef } from 'utils/';
|
|
||||||
import { safeParseInt } from 'utils/index';
|
import { safeParseInt } from 'utils/index';
|
||||||
import { nextTick, watch } from 'vue';
|
import { nextTick, watch } from 'vue';
|
||||||
import Button from './Button.vue';
|
import Button from './Button.vue';
|
||||||
import Modal from './Modal.vue';
|
import Modal from './Modal.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
setup() {
|
|
||||||
const keys = useKeys();
|
|
||||||
return { keys };
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
idx: 0,
|
idx: 0,
|
||||||
@ -214,69 +213,62 @@ export default {
|
|||||||
allowedLimits: [50, 100, 500, -1],
|
allowedLimits: [50, 100, 500, -1],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
inject: ['searcher'],
|
inject: ['searcher', 'shortcuts'],
|
||||||
components: { Modal, Button },
|
components: { Modal, Button },
|
||||||
async mounted() {
|
async mounted() {
|
||||||
if (fyo.store.isDevelopment) {
|
if (fyo.store.isDevelopment) {
|
||||||
window.search = this;
|
window.search = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(this.keys, (keys) => {
|
|
||||||
if (
|
|
||||||
keys.size === 2 &&
|
|
||||||
keys.has('KeyK') &&
|
|
||||||
(keys.has('MetaLeft') || keys.has('ControlLeft'))
|
|
||||||
) {
|
|
||||||
this.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.openModal) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keys.size === 1 && keys.has('Escape')) {
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
const input = this.$refs.input;
|
|
||||||
if (!getIsNullOrUndef(input) && document.activeElement !== input) {
|
|
||||||
input.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setFilter(keys);
|
|
||||||
});
|
|
||||||
this.openModal = false;
|
this.openModal = false;
|
||||||
},
|
},
|
||||||
activated() {
|
activated() {
|
||||||
|
this.setShortcuts();
|
||||||
this.openModal = false;
|
this.openModal = false;
|
||||||
},
|
},
|
||||||
|
deactivated() {
|
||||||
|
this.deleteShortcuts();
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openDocs() {
|
openDocs() {
|
||||||
openLink('https://docs.frappebooks.com/' + docsPathMap.Search);
|
openLink('https://docs.frappebooks.com/' + docsPathMap.Search);
|
||||||
},
|
},
|
||||||
setFilter(keys) {
|
getShortcuts() {
|
||||||
if (!keys.has('MetaLeft') && !keys.has('ControlLeft')) {
|
const modKey = getModKeyCode(this.platform);
|
||||||
return;
|
const ifOpen = (cb) => () => this.openModal && cb();
|
||||||
|
const ifClose = (cb) => () => !this.openModal && cb();
|
||||||
|
|
||||||
|
const shortcuts = [
|
||||||
|
{ shortcut: ['KeyK', modKey], callback: ifClose(() => this.open()) },
|
||||||
|
{ shortcut: ['Escape'], callback: ifOpen(() => this.close()) },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const i in searchGroups) {
|
||||||
|
shortcuts.push({
|
||||||
|
shortcut: [modKey, `Digit${Number(i) + 1}`],
|
||||||
|
callback: ifOpen(() => {
|
||||||
|
const group = searchGroups[i];
|
||||||
|
const value = this.searcher.filters.groupFilters[group];
|
||||||
|
if (typeof value !== 'boolean') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.searcher.set(group, !value);
|
||||||
|
}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!keys.size === 2) {
|
return shortcuts;
|
||||||
return;
|
},
|
||||||
|
setShortcuts() {
|
||||||
|
for (const { shortcut, callback } of this.getShortcuts()) {
|
||||||
|
this.shortcuts.set(shortcut, callback);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
const matches = [...keys].join(',').match(/Digit(\d+)/);
|
deleteShortcuts() {
|
||||||
if (!matches) {
|
for (const { shortcut } of this.getShortcuts()) {
|
||||||
return;
|
this.shortcuts.delete(shortcut);
|
||||||
}
|
}
|
||||||
|
|
||||||
const digit = matches[1];
|
|
||||||
const index = safeParseInt(digit) - 1;
|
|
||||||
const group = searchGroups[index];
|
|
||||||
const value = this.searcher.filters.groupFilters[group];
|
|
||||||
if (!group || typeof value !== 'boolean') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.searcher.set(group, !value);
|
|
||||||
},
|
},
|
||||||
modKey(key) {
|
modKey(key) {
|
||||||
key = key.toUpperCase();
|
key = key.toUpperCase();
|
||||||
|
2
src/shims-vue-custom.d.ts
vendored
2
src/shims-vue-custom.d.ts
vendored
@ -5,6 +5,6 @@ declare module 'vue' {
|
|||||||
interface ComponentCustomProperties {
|
interface ComponentCustomProperties {
|
||||||
t: (...args: TranslationLiteral[]) => string;
|
t: (...args: TranslationLiteral[]) => string;
|
||||||
fyo: Fyo;
|
fyo: Fyo;
|
||||||
platform: string;
|
platform: 'Windows' | 'Linux' | 'Mac';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,81 @@
|
|||||||
import { onMounted, onUnmounted, Ref, ref } from 'vue';
|
import { onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||||
|
|
||||||
export function useKeys(callback?: (keys: Set<string>) => void) {
|
interface Keys {
|
||||||
const keys: Ref<Set<string>> = ref(new Set());
|
pressed: Set<string>;
|
||||||
|
alt: boolean;
|
||||||
|
ctrl: boolean;
|
||||||
|
meta: boolean;
|
||||||
|
shift: boolean;
|
||||||
|
repeat: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Shortcuts {
|
||||||
|
keys: Ref<Keys>;
|
||||||
|
shortcuts: Map<string, Function>;
|
||||||
|
|
||||||
|
constructor(keys?: Ref<Keys>) {
|
||||||
|
this.keys = keys ?? useKeys();
|
||||||
|
this.shortcuts = new Map();
|
||||||
|
|
||||||
|
watch(this.keys, (keys) => {
|
||||||
|
this.#trigger(keys);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#trigger(keys: Keys) {
|
||||||
|
const key = Array.from(keys.pressed).sort().join('+');
|
||||||
|
this.shortcuts.get(key)?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
has(shortcut: string[]) {
|
||||||
|
const key = shortcut.sort().join('+');
|
||||||
|
return this.shortcuts.has(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(shortcut: string[], callback: Function, removeIfSet: boolean = true) {
|
||||||
|
const key = shortcut.sort().join('+');
|
||||||
|
if (removeIfSet) {
|
||||||
|
this.shortcuts.delete(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.shortcuts.has(key)) {
|
||||||
|
throw new Error(`Shortcut ${key} already exists.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.shortcuts.set(key, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(shortcut: string[]) {
|
||||||
|
const key = shortcut.sort().join('+');
|
||||||
|
this.shortcuts.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useKeys() {
|
||||||
|
const keys: Ref<Keys> = ref({
|
||||||
|
pressed: new Set<string>(),
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
meta: false,
|
||||||
|
shift: false,
|
||||||
|
repeat: false,
|
||||||
|
});
|
||||||
|
|
||||||
const keydownListener = (e: KeyboardEvent) => {
|
const keydownListener = (e: KeyboardEvent) => {
|
||||||
keys.value.add(e.code);
|
keys.value.pressed.add(e.code);
|
||||||
callback?.(keys.value);
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
const keyupListener = (e: KeyboardEvent) => {
|
const keyupListener = (e: KeyboardEvent) => {
|
||||||
keys.value.delete(e.code);
|
keys.value.pressed.delete(e.code);
|
||||||
|
|
||||||
// Key up won't trigger on macOS for other keys.
|
// Key up won't trigger on macOS for other keys.
|
||||||
if (e.code === 'MetaLeft') {
|
if (e.code === 'MetaLeft') {
|
||||||
keys.value.clear();
|
keys.value.pressed.clear();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,3 +109,11 @@ export function useMouseLocation() {
|
|||||||
|
|
||||||
return loc;
|
return loc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getModKeyCode(platform: 'Windows' | 'Linux' | 'Mac') {
|
||||||
|
if (platform === 'Mac') {
|
||||||
|
return 'MetaLeft';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'CtrlLeft';
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user