mirror of
https://github.com/frappe/books.git
synced 2024-11-10 07:40:55 +00:00
fix: minor refactor shortcuts, dont maintain stack
- better dialog messages - use Shortcut for escape in modal
This commit is contained in:
parent
08343ae18e
commit
1e8b1152bb
@ -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);
|
||||
} else {
|
||||
document.removeEventListener('keyup', this.escapeEventListener);
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
escapeEventListener(event: KeyboardEvent) {
|
||||
if (event.code !== 'Escape') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.shortcuts?.set(this.context, ['Escape'], () => {
|
||||
this.$emit('closemodal');
|
||||
});
|
||||
} else {
|
||||
this.shortcuts?.delete(this.context);
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
context(): string {
|
||||
return `Modal-` + Math.random().toString(36).slice(2, 6);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user