2
0
mirror of https://github.com/frappe/books.git synced 2024-12-23 03:19:01 +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>
<script lang="ts">
import { defineComponent } from 'vue';
import { shortcutsKey } from 'src/utils/injectionKeys';
import { defineComponent, inject } from 'vue';
export default defineComponent({
setup() {
return { shortcuts: inject(shortcutsKey) };
},
props: {
openModal: {
default: false,
@ -49,19 +53,17 @@ export default defineComponent({
watch: {
openModal(value: boolean) {
if (value) {
document.addEventListener('keyup', this.escapeEventListener);
this.shortcuts?.set(this.context, ['Escape'], () => {
this.$emit('closemodal');
});
} else {
document.removeEventListener('keyup', this.escapeEventListener);
this.shortcuts?.delete(this.context);
}
},
},
methods: {
escapeEventListener(event: KeyboardEvent) {
if (event.code !== 'Escape') {
return;
}
this.$emit('closemodal');
computed: {
context(): string {
return `Modal-` + Math.random().toString(36).slice(2, 6);
},
},
});

View File

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

View File

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

View File

@ -53,7 +53,13 @@ export function useKeys() {
const keyupListener = (e: KeyboardEvent) => {
const { code } = e;
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);