2
0
mirror of https://github.com/frappe/books.git synced 2024-09-20 11:29:00 +00:00

fix: minor refactor shortcuts, dont maintain stack

- better dialog messages
- use Shortcut for escape in modal
This commit is contained in:
18alantom 2023-03-23 14:28:15 +05:30 committed by Alan
parent 08343ae18e
commit 1e8b1152bb
4 changed files with 78 additions and 63 deletions

View File

@ -32,9 +32,13 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { shortcutsKey } from 'src/utils/injectionKeys';
import { defineComponent, inject } from 'vue';
export default defineComponent({ export default defineComponent({
setup() {
return { shortcuts: inject(shortcutsKey) };
},
props: { props: {
openModal: { openModal: {
default: false, default: false,
@ -49,19 +53,17 @@ export default defineComponent({
watch: { watch: {
openModal(value: boolean) { openModal(value: boolean) {
if (value) { if (value) {
document.addEventListener('keyup', this.escapeEventListener); this.shortcuts?.set(this.context, ['Escape'], () => {
} else {
document.removeEventListener('keyup', this.escapeEventListener);
}
},
},
methods: {
escapeEventListener(event: KeyboardEvent) {
if (event.code !== 'Escape') {
return;
}
this.$emit('closemodal'); this.$emit('closemodal');
});
} else {
this.shortcuts?.delete(this.context);
}
},
},
computed: {
context(): string {
return `Modal-` + Math.random().toString(36).slice(2, 6);
}, },
}, },
}); });

View File

@ -1,7 +1,6 @@
import { Keys } from 'utils/types'; import { Keys } from 'utils/types';
import { watch } from 'vue'; import { watch } from 'vue';
import { getIsMac } from './misc'; import { getIsMac } from './misc';
import { useKeys } from './vueUtils';
interface ModMap { interface ModMap {
alt: boolean; alt: boolean;
@ -40,32 +39,40 @@ const mods: Readonly<Mod[]> = ['alt', 'ctrl', 'meta', 'repeat', 'shift'];
export class Shortcuts { export class Shortcuts {
keys: Keys; keys: Keys;
isMac: boolean; isMac: boolean;
contextStack: Context[];
shortcuts: ShortcutMap; shortcuts: ShortcutMap;
modMap: Partial<Record<Mod, boolean>>; modMap: Partial<Record<Mod, boolean>>;
keySet: Set<string>;
constructor(keys?: Keys) { constructor(keys: Keys) {
this.contextStack = [];
this.modMap = {}; this.modMap = {};
this.keys = keys ?? useKeys(); this.keySet = new Set();
this.keys = keys;
this.shortcuts = new Map(); this.shortcuts = new Map();
this.isMac = getIsMac(); this.isMac = getIsMac();
watch(this.keys, (keys) => { watch(this.keys, (keys) => {
this.#trigger(keys); const key = this.getKey(Array.from(keys.pressed), keys);
if (!key) {
return false;
}
if (!key || !this.keySet.has(key)) {
return;
}
this.#trigger(key);
}); });
} }
#trigger(keys: Keys) { #trigger(key: string) {
const key = this.getKey(Array.from(keys.pressed), keys); const configList = Array.from(this.shortcuts.keys())
for (const context of this.contextStack.reverse()) { .map((cxt) => this.shortcuts.get(cxt)?.get(key))
const obj = this.shortcuts.get(context)?.get(key); .filter(Boolean)
if (!obj) { .reverse() as ShortcutConfig[];
continue;
}
obj.callback(); for (const config of configList) {
if (!obj.propagate) { config.callback();
if (!config.propagate) {
break; break;
} }
} }
@ -75,7 +82,6 @@ export class Shortcuts {
* Check if a context is present or if a shortcut * Check if a context is present or if a shortcut
* is present in a context. * is present in a context.
* *
*
* @param context context in which the shortcut is to be checked * @param context context in which the shortcut is to be checked
* @param shortcut shortcut that is to be checked * @param shortcut shortcut that is to be checked
* @returns boolean indicating presence * @returns boolean indicating presence
@ -91,6 +97,10 @@ export class Shortcuts {
} }
const key = this.getKey(shortcut); const key = this.getKey(shortcut);
if (!key) {
return false;
}
return contextualShortcuts.has(key); return contextualShortcuts.has(key);
} }
@ -110,18 +120,30 @@ export class Shortcuts {
propagate: boolean = false, propagate: boolean = false,
removeIfSet: boolean = true removeIfSet: boolean = true
): void { ): void {
if (!this.shortcuts.has(context)) { const key = this.getKey(shortcut);
this.shortcuts.set(context, new Map()); if (!key) {
return;
}
let contextualShortcuts = this.shortcuts.get(context);
/**
* Maintain context order.
*/
if (!contextualShortcuts) {
contextualShortcuts = new Map();
} else {
this.shortcuts.delete(contextualShortcuts);
} }
const key = this.getKey(shortcut);
const contextualShortcuts = this.shortcuts.get(context)!;
if (contextualShortcuts.has(key) && !removeIfSet) { if (contextualShortcuts.has(key) && !removeIfSet) {
this.shortcuts.set(context, contextualShortcuts);
throw new Error(`Shortcut ${key} already exists.`); throw new Error(`Shortcut ${key} already exists.`);
} }
this.#pushContext(context); this.keySet.add(key);
contextualShortcuts.set(key, { callback, propagate }); contextualShortcuts.set(key, { callback, propagate });
this.shortcuts.set(context, contextualShortcuts);
} }
/** /**
@ -134,7 +156,6 @@ export class Shortcuts {
*/ */
delete(context: Context, shortcut?: string[]): boolean { delete(context: Context, shortcut?: string[]): boolean {
if (!shortcut) { if (!shortcut) {
this.#removeContext(context);
return this.shortcuts.delete(context); return this.shortcuts.delete(context);
} }
@ -144,6 +165,10 @@ export class Shortcuts {
} }
const key = this.getKey(shortcut); const key = this.getKey(shortcut);
if (!key) {
return false;
}
return contextualShortcuts.delete(key); return contextualShortcuts.delete(key);
} }
@ -155,7 +180,7 @@ export class Shortcuts {
* @param modMap boolean map of mod keys to be used * @param modMap boolean map of mod keys to be used
* @returns string to be used as the shortcut Map key * @returns string to be used as the shortcut Map key
*/ */
getKey(shortcut: string[], modMap?: Partial<ModMap>): string { getKey(shortcut: string[], modMap?: Partial<ModMap>): string | null {
const _modMap = modMap || this.modMap; const _modMap = modMap || this.modMap;
this.modMap = {}; this.modMap = {};
@ -173,24 +198,7 @@ export class Shortcuts {
return modString; return modString;
} }
return ''; return null;
}
#pushContext(context: Context) {
this.#removeContext(context);
this.contextStack.push(context);
}
#removeContext(context: Context) {
const index = this.contextStack.indexOf(context);
if (index === -1) {
return;
}
this.contextStack = [
this.contextStack.slice(0, index),
this.contextStack.slice(index + 1),
].flat();
} }
/** /**

View File

@ -172,11 +172,11 @@ export async function deleteDocWithPrompt(doc: Doc) {
} }
return await showMessageDialog({ return await showMessageDialog({
message: t`Delete ${schemaLabel} ${doc.name!}?`, message: t`Delete ${getActionLabel(doc)}?`,
detail, detail,
buttons: [ buttons: [
{ {
label: t`Delete`, label: t`Yes`,
async action() { async action() {
try { try {
await doc.delete(); await doc.delete();
@ -196,7 +196,7 @@ export async function deleteDocWithPrompt(doc: Doc) {
}, },
}, },
{ {
label: t`Cancel`, label: t`No`,
action() { action() {
return false; return false;
}, },
@ -237,9 +237,8 @@ export async function cancelDocWithPrompt(doc: Doc) {
} }
} }
const schemaLabel = fyo.schemaMap[doc.schemaName]!.label;
return await showMessageDialog({ return await showMessageDialog({
message: t`Cancel ${schemaLabel} ${doc.name!}?`, message: t`Cancel ${getActionLabel(doc)}?`,
detail, detail,
buttons: [ buttons: [
{ {
@ -622,7 +621,7 @@ export async function commonDocSubmit(doc: Doc): Promise<boolean> {
} }
async function showSubmitOrSyncDialog(doc: Doc, type: 'submit' | 'sync') { async function showSubmitOrSyncDialog(doc: Doc, type: 'submit' | 'sync') {
const label = doc.schema.label ?? doc.schemaName; const label = getActionLabel(doc);
let message = t`Submit ${label}?`; let message = t`Submit ${label}?`;
if (type === 'sync') { if (type === 'sync') {
message = t`Save ${label}?`; message = t`Save ${label}?`;
@ -657,7 +656,7 @@ async function showSubmitOrSyncDialog(doc: Doc, type: 'submit' | 'sync') {
} }
function showActionToast(doc: Doc, type: 'sync' | 'cancel' | 'delete') { function showActionToast(doc: Doc, type: 'sync' | 'cancel' | 'delete') {
const label = getToastLabel(doc); const label = getActionLabel(doc);
const message = { const message = {
sync: t`${label} saved`, sync: t`${label} saved`,
cancel: t`${label} cancelled`, cancel: t`${label} cancelled`,
@ -668,7 +667,7 @@ function showActionToast(doc: Doc, type: 'sync' | 'cancel' | 'delete') {
} }
function showSubmitToast(doc: Doc) { function showSubmitToast(doc: Doc) {
const label = getToastLabel(doc); const label = getActionLabel(doc);
const message = t`${label} submitted`; const message = t`${label} submitted`;
const toastOption: ToastOptions = { const toastOption: ToastOptions = {
type: 'success', type: 'success',
@ -707,7 +706,7 @@ function getSubmitSuccessToastAction(doc: Doc) {
} }
export function showCannotSaveOrSubmitToast(doc: Doc) { export function showCannotSaveOrSubmitToast(doc: Doc) {
const label = getToastLabel(doc); const label = getActionLabel(doc);
let message = t`${label} already saved`; let message = t`${label} already saved`;
if (doc.schema.isSubmittable && doc.isSubmitted) { if (doc.schema.isSubmittable && doc.isSubmitted) {
@ -718,7 +717,7 @@ export function showCannotSaveOrSubmitToast(doc: Doc) {
} }
export function showCannotCancelOrDeleteToast(doc: Doc) { export function showCannotCancelOrDeleteToast(doc: Doc) {
const label = getToastLabel(doc); const label = getActionLabel(doc);
let message = t`${label} cannot be deleted`; let message = t`${label} cannot be deleted`;
if (doc.schema.isSubmittable && !doc.isCancelled) { if (doc.schema.isSubmittable && !doc.isCancelled) {
message = t`${label} cannot be cancelled`; message = t`${label} cannot be cancelled`;
@ -727,7 +726,7 @@ export function showCannotCancelOrDeleteToast(doc: Doc) {
showToast({ type: 'warning', message, duration: 'short' }); showToast({ type: 'warning', message, duration: 'short' });
} }
function getToastLabel(doc: Doc) { function getActionLabel(doc: Doc) {
const label = doc.schema.label || doc.schemaName; const label = doc.schema.label || doc.schemaName;
if (doc.schema.naming === 'random') { if (doc.schema.naming === 'random') {
return label; return label;

View File

@ -53,7 +53,13 @@ export function useKeys() {
const keyupListener = (e: KeyboardEvent) => { const keyupListener = (e: KeyboardEvent) => {
const { code } = e; const { code } = e;
if (code.startsWith('Meta') && isMac) { if (code.startsWith('Meta') && isMac) {
return keys.pressed.clear(); keys.alt = false;
keys.ctrl = false;
keys.meta = false;
keys.shift = false;
keys.repeat = false;
keys.pressed.clear();
return;
} }
keys.pressed.delete(code); keys.pressed.delete(code);