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:
parent
08343ae18e
commit
1e8b1152bb
@ -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);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user