mirror of
https://github.com/frappe/books.git
synced 2024-11-15 09:54:04 +00:00
refactor: use composable for doc shortcuts in Forms
- type Toast.vue, use constants for duration
This commit is contained in:
parent
e1b502b138
commit
08343ae18e
@ -34,11 +34,16 @@
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { getColorClass } from 'src/utils/colors';
|
||||
import { ToastDuration, ToastType } from 'src/utils/types';
|
||||
import { toastDurationMap } from 'src/utils/ui';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import FeatherIcon from './FeatherIcon.vue';
|
||||
|
||||
export default {
|
||||
type TimeoutId = ReturnType<typeof setTimeout>;
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FeatherIcon,
|
||||
},
|
||||
@ -46,16 +51,21 @@ export default {
|
||||
return {
|
||||
opacity: 0,
|
||||
show: true,
|
||||
opacityTimeoutId: -1,
|
||||
cleanupTimeoutId: -1,
|
||||
opacityTimeoutId: null,
|
||||
cleanupTimeoutId: null,
|
||||
} as {
|
||||
opacity: number;
|
||||
show: boolean;
|
||||
opacityTimeoutId: null | TimeoutId;
|
||||
cleanupTimeoutId: null | TimeoutId;
|
||||
};
|
||||
},
|
||||
props: {
|
||||
message: { type: String, required: true },
|
||||
action: { type: Function, default: () => {} },
|
||||
actionText: { type: String, default: '' },
|
||||
type: { type: String, default: 'info' },
|
||||
duration: { type: Number, default: 5000 },
|
||||
type: { type: String as PropType<ToastType>, default: 'info' },
|
||||
duration: { type: String as PropType<ToastDuration>, default: 'long' },
|
||||
},
|
||||
computed: {
|
||||
iconName() {
|
||||
@ -77,22 +87,23 @@ export default {
|
||||
}[this.type];
|
||||
},
|
||||
iconColor() {
|
||||
return getColorClass(this.color, 'text', 400);
|
||||
return getColorClass(this.color ?? 'gray', 'text', 400);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const duration = toastDurationMap[this.duration];
|
||||
setTimeout(() => {
|
||||
this.opacity = 1;
|
||||
}, 50);
|
||||
|
||||
this.opacityTimeoutId = setTimeout(() => {
|
||||
this.opacity = 0;
|
||||
}, this.duration);
|
||||
}, duration);
|
||||
|
||||
this.cleanupTimeoutId = setTimeout(() => {
|
||||
this.show = false;
|
||||
this.cleanup();
|
||||
}, this.duration + 300);
|
||||
}, duration + 300);
|
||||
},
|
||||
methods: {
|
||||
actionClicked() {
|
||||
@ -100,8 +111,12 @@ export default {
|
||||
this.closeToast();
|
||||
},
|
||||
closeToast() {
|
||||
clearTimeout(this.opacityTimeoutId);
|
||||
clearTimeout(this.cleanupTimeoutId);
|
||||
if (this.opacityTimeoutId != null) {
|
||||
clearTimeout(this.opacityTimeoutId);
|
||||
}
|
||||
if (this.cleanupTimeoutId != null) {
|
||||
clearTimeout(this.cleanupTimeoutId);
|
||||
}
|
||||
|
||||
this.opacity = 0;
|
||||
setTimeout(() => {
|
||||
@ -110,11 +125,16 @@ export default {
|
||||
}, 300);
|
||||
},
|
||||
cleanup() {
|
||||
Array.from(this.$el.parentElement?.children ?? [])
|
||||
const element = this.$el;
|
||||
if (!(element instanceof Element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Array.from(element.parentElement?.children ?? [])
|
||||
.filter((el) => !el.innerHTML)
|
||||
.splice(1)
|
||||
.forEach((el) => el.remove());
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
@ -5,7 +5,8 @@
|
||||
</template>
|
||||
<template #header v-if="hasDoc">
|
||||
<Button
|
||||
v-if="!doc.isCancelled && !doc.dirty && isPrintable"
|
||||
v-if="canPrint"
|
||||
ref="printButton"
|
||||
:icon="true"
|
||||
@click="routeTo(`/print/${doc.schemaName}/${doc.name}`)"
|
||||
>
|
||||
@ -122,7 +123,7 @@ import StatusBadge from 'src/components/StatusBadge.vue';
|
||||
import { getErrorMessage } from 'src/utils';
|
||||
import { docsPathMap } from 'src/utils/misc';
|
||||
import { docsPathRef } from 'src/utils/refs';
|
||||
import { ActionGroup, UIGroupedFields } from 'src/utils/types';
|
||||
import { ActionGroup, DocRef, UIGroupedFields } from 'src/utils/types';
|
||||
import {
|
||||
commonDocSubmit,
|
||||
commonDocSync,
|
||||
@ -135,12 +136,31 @@ import {
|
||||
import { computed, defineComponent, nextTick } from 'vue';
|
||||
import QuickEditForm from '../QuickEditForm.vue';
|
||||
import CommonFormSection from './CommonFormSection.vue';
|
||||
import { inject } from 'vue';
|
||||
import { shortcutsKey } from 'src/utils/injectionKeys';
|
||||
import { ref } from 'vue';
|
||||
import { useDocShortcuts } from 'src/utils/vueUtils';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
name: { type: String, default: '' },
|
||||
schemaName: { type: String, default: ModelNameEnum.SalesInvoice },
|
||||
},
|
||||
setup() {
|
||||
const shortcuts = inject(shortcutsKey);
|
||||
const docOrNull = ref(null) as DocRef;
|
||||
let context = 'CommonForm';
|
||||
if (shortcuts) {
|
||||
context = useDocShortcuts(shortcuts, docOrNull, 'CommonForm', true);
|
||||
}
|
||||
|
||||
return {
|
||||
docOrNull,
|
||||
shortcuts,
|
||||
context,
|
||||
printButton: ref<InstanceType<typeof Button> | null>(null),
|
||||
};
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
schemaName: computed(() => this.docOrNull?.schemaName),
|
||||
@ -151,14 +171,12 @@ export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
errors: {},
|
||||
docOrNull: null,
|
||||
activeTab: this.t`Default`,
|
||||
groupedFields: null,
|
||||
quickEditDoc: null,
|
||||
isPrintable: false,
|
||||
} as {
|
||||
errors: Record<string, string>;
|
||||
docOrNull: null | Doc;
|
||||
activeTab: string;
|
||||
groupedFields: null | UIGroupedFields;
|
||||
quickEditDoc: null | Doc;
|
||||
@ -180,16 +198,30 @@ export default defineComponent({
|
||||
},
|
||||
activated(): void {
|
||||
docsPathRef.value = docsPathMap[this.schemaName] ?? '';
|
||||
this.shortcuts?.pmod.set(this.context, ['KeyP'], () => {
|
||||
if (!this.canPrint) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.printButton?.$el.click();
|
||||
});
|
||||
},
|
||||
deactivated(): void {
|
||||
docsPathRef.value = '';
|
||||
},
|
||||
computed: {
|
||||
canPrint(): boolean {
|
||||
if (!this.hasDoc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !this.doc.isCancelled && !this.doc.dirty && this.isPrintable;
|
||||
},
|
||||
hasDoc(): boolean {
|
||||
return !!this.docOrNull;
|
||||
return this.docOrNull instanceof Doc;
|
||||
},
|
||||
hasQeDoc(): boolean {
|
||||
return !!this.quickEditDoc;
|
||||
return this.quickEditDoc instanceof Doc;
|
||||
},
|
||||
status(): string {
|
||||
if (!this.hasDoc) {
|
||||
@ -265,8 +297,8 @@ export default defineComponent({
|
||||
this.doc
|
||||
);
|
||||
},
|
||||
async sync() {
|
||||
if (await commonDocSync(this.doc)) {
|
||||
async sync(useDialog?: boolean) {
|
||||
if (await commonDocSync(this.doc, useDialog)) {
|
||||
this.updateGroupedFields();
|
||||
}
|
||||
},
|
||||
|
@ -21,7 +21,8 @@
|
||||
"
|
||||
/>
|
||||
<Button
|
||||
v-if="!doc.isCancelled && !doc.dirty"
|
||||
v-if="canPrint"
|
||||
ref="printButton"
|
||||
:icon="true"
|
||||
@click="routeTo(`/print/${doc.schemaName}/${doc.name}`)"
|
||||
>
|
||||
@ -313,6 +314,7 @@ import FormHeader from 'src/components/FormHeader.vue';
|
||||
import StatusBadge from 'src/components/StatusBadge.vue';
|
||||
import LinkedEntryWidget from 'src/components/Widgets/LinkedEntryWidget.vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { shortcutsKey } from 'src/utils/injectionKeys';
|
||||
import { docsPathMap } from 'src/utils/misc';
|
||||
import { docsPathRef } from 'src/utils/refs';
|
||||
import {
|
||||
@ -321,7 +323,8 @@ import {
|
||||
getGroupedActionsForDoc,
|
||||
routeTo,
|
||||
} from 'src/utils/ui';
|
||||
import { nextTick } from 'vue';
|
||||
import { useDocShortcuts } from 'src/utils/vueUtils';
|
||||
import { inject, nextTick, ref } from 'vue';
|
||||
import { handleErrorWithDialog } from '../errorHandling';
|
||||
import QuickEditForm from './QuickEditForm.vue';
|
||||
|
||||
@ -341,6 +344,22 @@ export default {
|
||||
LinkedEntryWidget,
|
||||
Barcode,
|
||||
},
|
||||
setup() {
|
||||
const doc = ref(null);
|
||||
const shortcuts = inject(shortcutsKey);
|
||||
|
||||
let context = 'InvoiceForm';
|
||||
if (shortcuts) {
|
||||
context = useDocShortcuts(shortcuts, doc, context, true);
|
||||
}
|
||||
|
||||
return {
|
||||
doc,
|
||||
context,
|
||||
shortcuts,
|
||||
printButton: null,
|
||||
};
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
schemaName: this.schemaName,
|
||||
@ -351,7 +370,6 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
chstatus: false,
|
||||
doc: null,
|
||||
quickEditDoc: null,
|
||||
quickEditFields: [],
|
||||
color: null,
|
||||
@ -364,6 +382,9 @@ export default {
|
||||
this.chstatus = !this.chstatus;
|
||||
},
|
||||
computed: {
|
||||
canPrint() {
|
||||
return !this.doc.isCancelled && !this.doc.dirty;
|
||||
},
|
||||
stockTransferText() {
|
||||
if (!this.fyo.singles.AccountingSettings.enableInventory) {
|
||||
return '';
|
||||
@ -458,6 +479,13 @@ export default {
|
||||
},
|
||||
activated() {
|
||||
docsPathRef.value = docsPathMap[this.schemaName];
|
||||
this.shortcuts?.pmod.set(this.context, ['KeyP'], () => {
|
||||
if (!this.canPrint) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.printButton?.$el.click();
|
||||
});
|
||||
},
|
||||
deactivated() {
|
||||
docsPathRef.value = '';
|
||||
|
@ -116,7 +116,7 @@ export default defineComponent({
|
||||
},
|
||||
deactivated() {
|
||||
docsPathRef.value = '';
|
||||
this.shortcuts?.delete(this);
|
||||
this.shortcuts?.delete(this.context);
|
||||
},
|
||||
methods: {
|
||||
setShortcuts() {
|
||||
@ -124,10 +124,10 @@ export default defineComponent({
|
||||
return;
|
||||
}
|
||||
|
||||
this.shortcuts.pmod.set(this, ['KeyN'], () =>
|
||||
this.shortcuts.pmod.set(this.context, ['KeyN'], () =>
|
||||
this.makeNewDocButton?.$el.click()
|
||||
);
|
||||
this.shortcuts.pmod.set(this, ['KeyE'], () =>
|
||||
this.shortcuts.pmod.set(this.context, ['KeyE'], () =>
|
||||
this.exportButton?.$el.click()
|
||||
);
|
||||
},
|
||||
@ -194,6 +194,9 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
context(): string {
|
||||
return 'ListView-' + this.schemaName;
|
||||
},
|
||||
title(): string {
|
||||
if (this.pageTitle) {
|
||||
return this.pageTitle;
|
||||
|
@ -113,14 +113,17 @@ import DropdownWithActions from 'src/components/DropdownWithActions.vue';
|
||||
import StatusBadge from 'src/components/StatusBadge.vue';
|
||||
import TwoColumnForm from 'src/components/TwoColumnForm.vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { shortcutsKey } from 'src/utils/injectionKeys';
|
||||
import { getQuickEditWidget } from 'src/utils/quickEditWidgets';
|
||||
import {
|
||||
commonDocSubmit,
|
||||
commonDocSync,
|
||||
focusOrSelectFormControl,
|
||||
getActionsForDoc,
|
||||
openQuickEdit
|
||||
commonDocSubmit,
|
||||
commonDocSync,
|
||||
focusOrSelectFormControl,
|
||||
getActionsForDoc,
|
||||
openQuickEdit,
|
||||
} from 'src/utils/ui';
|
||||
import { useDocShortcuts } from 'src/utils/vueUtils';
|
||||
import { ref, inject } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'QuickEditForm',
|
||||
@ -146,6 +149,21 @@ export default {
|
||||
DropdownWithActions,
|
||||
},
|
||||
emits: ['close'],
|
||||
setup() {
|
||||
const doc = ref(null);
|
||||
const shortcuts = inject(shortcutsKey);
|
||||
|
||||
let context = 'QuickEditForm';
|
||||
if (shortcuts) {
|
||||
context = useDocShortcuts(shortcuts, doc, context, true);
|
||||
}
|
||||
|
||||
return {
|
||||
doc,
|
||||
context,
|
||||
shortcuts,
|
||||
};
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
schemaName: this.schemaName,
|
||||
@ -155,13 +173,17 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
doc: null,
|
||||
values: null,
|
||||
titleField: null,
|
||||
imageField: null,
|
||||
statusText: null,
|
||||
};
|
||||
},
|
||||
activated() {
|
||||
this.shortcuts.set(this.context, ['Escape'], () => {
|
||||
this.routeToPrevious();
|
||||
});
|
||||
},
|
||||
async mounted() {
|
||||
if (this.defaults) {
|
||||
this.values = JSON.parse(this.defaults);
|
||||
|
@ -228,7 +228,7 @@ import {
|
||||
getPrintTemplatePropValues,
|
||||
} from 'src/utils/printTemplates';
|
||||
import { docsPathRef, showSidebar } from 'src/utils/refs';
|
||||
import { PrintValues } from 'src/utils/types';
|
||||
import { DocRef, PrintValues } from 'src/utils/types';
|
||||
import {
|
||||
focusOrSelectFormControl,
|
||||
getActionsForDoc,
|
||||
@ -239,14 +239,12 @@ import {
|
||||
showMessageDialog,
|
||||
showToast,
|
||||
} from 'src/utils/ui';
|
||||
import { useDocShortcuts } from 'src/utils/vueUtils';
|
||||
import { getMapFromList } from 'utils/index';
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { computed, defineComponent, inject, ref } from 'vue';
|
||||
import PrintContainer from './PrintContainer.vue';
|
||||
import TemplateBuilderHint from './TemplateBuilderHint.vue';
|
||||
import TemplateEditor from './TemplateEditor.vue';
|
||||
import { inject } from 'vue';
|
||||
|
||||
const COMPONENT_NAME = 'TemplateBuilder';
|
||||
|
||||
export default defineComponent({
|
||||
props: { name: String },
|
||||
@ -262,8 +260,18 @@ export default defineComponent({
|
||||
ShortcutKeys,
|
||||
},
|
||||
setup() {
|
||||
const doc = ref(null) as DocRef<PrintTemplate>;
|
||||
const shortcuts = inject(shortcutsKey);
|
||||
|
||||
let context = 'TemplateBuilder';
|
||||
if (shortcuts) {
|
||||
context = useDocShortcuts(shortcuts, doc, context, false);
|
||||
}
|
||||
|
||||
return {
|
||||
shortcuts: inject(shortcutsKey),
|
||||
doc,
|
||||
context,
|
||||
shortcuts,
|
||||
};
|
||||
},
|
||||
provide() {
|
||||
@ -271,7 +279,6 @@ export default defineComponent({
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
doc: null,
|
||||
editMode: false,
|
||||
showHints: false,
|
||||
hints: undefined,
|
||||
@ -290,7 +297,6 @@ export default defineComponent({
|
||||
showHints: boolean;
|
||||
hints?: Record<string, unknown>;
|
||||
values: null | PrintValues;
|
||||
doc: PrintTemplate | null;
|
||||
displayDoc: PrintTemplate | null;
|
||||
scale: number;
|
||||
panelWidth: number;
|
||||
@ -311,25 +317,27 @@ export default defineComponent({
|
||||
},
|
||||
async activated(): Promise<void> {
|
||||
docsPathRef.value = docsPathMap.PrintTemplate ?? '';
|
||||
this.setShortcuts;
|
||||
this.setShortcuts();
|
||||
},
|
||||
deactivated(): void {
|
||||
docsPathRef.value = '';
|
||||
this.shortcuts?.delete(COMPONENT_NAME);
|
||||
},
|
||||
methods: {
|
||||
setShortcuts() {
|
||||
/**
|
||||
* Node: Doc Save and Delete shortcuts are in the setup.
|
||||
*/
|
||||
if (!this.shortcuts) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.shortcuts.ctrl.set(COMPONENT_NAME, ['Enter'], this.setTemplate);
|
||||
this.shortcuts.ctrl.set(COMPONENT_NAME, ['KeyE'], this.toggleEditMode);
|
||||
this.shortcuts.ctrl.set(COMPONENT_NAME, ['KeyH'], this.toggleShowHints);
|
||||
this.shortcuts.ctrl.set(COMPONENT_NAME, ['Equal'], () =>
|
||||
this.shortcuts.ctrl.set(this.context, ['Enter'], this.setTemplate);
|
||||
this.shortcuts.ctrl.set(this.context, ['KeyE'], this.toggleEditMode);
|
||||
this.shortcuts.ctrl.set(this.context, ['KeyH'], this.toggleShowHints);
|
||||
this.shortcuts.ctrl.set(this.context, ['Equal'], () =>
|
||||
this.setScale(this.scale + 0.1)
|
||||
);
|
||||
this.shortcuts.ctrl.set(COMPONENT_NAME, ['Minus'], () =>
|
||||
this.shortcuts.ctrl.set(this.context, ['Minus'], () =>
|
||||
this.setScale(this.scale - 0.1)
|
||||
);
|
||||
},
|
||||
@ -385,7 +393,7 @@ export default defineComponent({
|
||||
|
||||
let message = this.t`Please set a Display Doc`;
|
||||
if (!this.displayDoc) {
|
||||
return showToast({ type: 'warning', message, duration: 1000 });
|
||||
return showToast({ type: 'warning', message, duration: 'short' });
|
||||
}
|
||||
|
||||
this.editMode = !this.editMode;
|
||||
|
@ -23,6 +23,20 @@ type ShortcutMap = Map<Context, Map<string, ShortcutConfig>>;
|
||||
|
||||
const mods: Readonly<Mod[]> = ['alt', 'ctrl', 'meta', 'repeat', 'shift'];
|
||||
|
||||
/**
|
||||
* Used to add shortcuts based on **context**.
|
||||
*
|
||||
* **Context** is a identifier for where the shortcut belongs. For instance
|
||||
* a _Form_ component having shortcuts for _Submit Form_.
|
||||
*
|
||||
* In the above example an app can have multiple instances of the _Form_
|
||||
* component active at the same time, so the passed context should be a
|
||||
* unique identifier such as the component object.
|
||||
*
|
||||
* If only one instance of a component is meant to be active at a time
|
||||
* (for example a _Sidebar_ component) then do not use objects, use some
|
||||
* primitive datatype (`string`).
|
||||
*/
|
||||
export class Shortcuts {
|
||||
keys: Keys;
|
||||
isMac: boolean;
|
||||
@ -64,9 +78,9 @@ export class Shortcuts {
|
||||
*
|
||||
* @param context context in which the shortcut is to be checked
|
||||
* @param shortcut shortcut that is to be checked
|
||||
* @returns
|
||||
* @returns boolean indicating presence
|
||||
*/
|
||||
has(context: Context, shortcut?: string[]) {
|
||||
has(context: Context, shortcut?: string[]): boolean {
|
||||
if (!shortcut) {
|
||||
return this.shortcuts.has(context);
|
||||
}
|
||||
@ -86,7 +100,7 @@ export class Shortcuts {
|
||||
* @param context context object to which the shortcut belongs
|
||||
* @param shortcut keyboard event codes used as shortcut chord
|
||||
* @param callback function to be called when the shortcut is pressed
|
||||
* @param propagate whether to check and executs shortcuts in parent contexts
|
||||
* @param propagate whether to check and execute shortcuts in earlier contexts
|
||||
* @param removeIfSet whether to delete the set shortcut
|
||||
*/
|
||||
set(
|
||||
@ -95,18 +109,14 @@ export class Shortcuts {
|
||||
callback: ShortcutFunction,
|
||||
propagate: boolean = false,
|
||||
removeIfSet: boolean = true
|
||||
) {
|
||||
): void {
|
||||
if (!this.shortcuts.has(context)) {
|
||||
this.shortcuts.set(context, new Map());
|
||||
}
|
||||
const contextualShortcuts = this.shortcuts.get(context)!;
|
||||
|
||||
const key = this.getKey(shortcut);
|
||||
if (removeIfSet) {
|
||||
contextualShortcuts.delete(key);
|
||||
}
|
||||
|
||||
if (contextualShortcuts.has(key)) {
|
||||
const contextualShortcuts = this.shortcuts.get(context)!;
|
||||
if (contextualShortcuts.has(key) && !removeIfSet) {
|
||||
throw new Error(`Shortcut ${key} already exists.`);
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,15 @@
|
||||
import type { Doc } from 'fyo/model/doc';
|
||||
import type { Action } from 'fyo/model/types';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import type { ModelNameEnum } from 'models/types';
|
||||
import type { Field, FieldType } from 'schemas/types';
|
||||
import type { QueryFilter } from 'utils/db/types';
|
||||
import type { Ref } from 'vue';
|
||||
import type { toastDurationMap } from './ui';
|
||||
|
||||
export type DocRef<D extends Doc = Doc> = Ref<D | null>;
|
||||
|
||||
export type ToastType = 'info' | 'warning' | 'error' | 'success';
|
||||
export type ToastDuration = keyof typeof toastDurationMap;
|
||||
|
||||
export interface MessageDialogButton {
|
||||
label: string;
|
||||
@ -17,8 +24,8 @@ export interface MessageDialogOptions {
|
||||
|
||||
export interface ToastOptions {
|
||||
message: string;
|
||||
type?: 'info' | 'warning' | 'error' | 'success';
|
||||
duration?: number;
|
||||
type?: ToastType;
|
||||
duration?: ToastDuration;
|
||||
action?: () => void;
|
||||
actionText?: string;
|
||||
}
|
||||
|
121
src/utils/ui.ts
121
src/utils/ui.ts
@ -33,6 +33,8 @@ import {
|
||||
UIGroupedFields,
|
||||
} from './types';
|
||||
|
||||
export const toastDurationMap = { short: 2_500, long: 5_000 } as const;
|
||||
|
||||
export async function openQuickEdit({
|
||||
doc,
|
||||
schemaName,
|
||||
@ -318,12 +320,7 @@ function getCancelAction(doc: Doc): Action {
|
||||
},
|
||||
condition: (doc: Doc) => doc.canCancel,
|
||||
async action() {
|
||||
const res = await cancelDocWithPrompt(doc);
|
||||
if (!res) {
|
||||
return;
|
||||
}
|
||||
|
||||
showActionToast(doc, 'cancel');
|
||||
await commonDocCancel(doc);
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -336,13 +333,7 @@ function getDeleteAction(doc: Doc): Action {
|
||||
},
|
||||
condition: (doc: Doc) => doc.canDelete,
|
||||
async action() {
|
||||
const res = await deleteDocWithPrompt(doc);
|
||||
if (!res) {
|
||||
return;
|
||||
}
|
||||
|
||||
showActionToast(doc, 'delete');
|
||||
router.back();
|
||||
await commongDocDelete(doc);
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -569,11 +560,39 @@ export function getShortcutKeyMap(
|
||||
};
|
||||
}
|
||||
|
||||
export async function commonDocSync(doc: Doc): Promise<boolean> {
|
||||
try {
|
||||
await doc.sync();
|
||||
} catch (error) {
|
||||
handleErrorWithDialog(error, doc);
|
||||
export async function commongDocDelete(doc: Doc): Promise<boolean> {
|
||||
const res = await deleteDocWithPrompt(doc);
|
||||
if (!res) {
|
||||
return false;
|
||||
}
|
||||
|
||||
showActionToast(doc, 'delete');
|
||||
router.back();
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function commonDocCancel(doc: Doc): Promise<boolean> {
|
||||
const res = await cancelDocWithPrompt(doc);
|
||||
if (!res) {
|
||||
return false;
|
||||
}
|
||||
|
||||
showActionToast(doc, 'cancel');
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function commonDocSync(
|
||||
doc: Doc,
|
||||
useDialog: boolean = false
|
||||
): Promise<boolean> {
|
||||
let success: boolean;
|
||||
if (useDialog) {
|
||||
success = !!(await showSubmitOrSyncDialog(doc, 'sync'));
|
||||
} else {
|
||||
success = await syncWithoutDialog(doc);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -581,8 +600,19 @@ export async function commonDocSync(doc: Doc): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
async function syncWithoutDialog(doc: Doc): Promise<boolean> {
|
||||
try {
|
||||
await doc.sync();
|
||||
} catch (error) {
|
||||
handleErrorWithDialog(error, doc);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function commonDocSubmit(doc: Doc): Promise<boolean> {
|
||||
const success = await showSubmitDialog(doc);
|
||||
const success = await showSubmitOrSyncDialog(doc, 'submit');
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
@ -591,12 +621,16 @@ export async function commonDocSubmit(doc: Doc): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
async function showSubmitDialog(doc: Doc) {
|
||||
async function showSubmitOrSyncDialog(doc: Doc, type: 'submit' | 'sync') {
|
||||
const label = doc.schema.label ?? doc.schemaName;
|
||||
const message = t`Submit ${label}?`;
|
||||
let message = t`Submit ${label}?`;
|
||||
if (type === 'sync') {
|
||||
message = t`Save ${label}?`;
|
||||
}
|
||||
|
||||
const yesAction = async () => {
|
||||
try {
|
||||
await doc.submit();
|
||||
await doc[type]();
|
||||
} catch (error) {
|
||||
handleErrorWithDialog(error, doc);
|
||||
return false;
|
||||
@ -630,7 +664,7 @@ function showActionToast(doc: Doc, type: 'sync' | 'cancel' | 'delete') {
|
||||
delete: t`${label} deleted`,
|
||||
}[type];
|
||||
|
||||
showToast({ type: 'success', message, duration: 2500 });
|
||||
showToast({ type: 'success', message, duration: 'short' });
|
||||
}
|
||||
|
||||
function showSubmitToast(doc: Doc) {
|
||||
@ -639,21 +673,12 @@ function showSubmitToast(doc: Doc) {
|
||||
const toastOption: ToastOptions = {
|
||||
type: 'success',
|
||||
message,
|
||||
duration: 5000,
|
||||
duration: 'long',
|
||||
...getSubmitSuccessToastAction(doc),
|
||||
};
|
||||
showToast(toastOption);
|
||||
}
|
||||
|
||||
function getToastLabel(doc: Doc) {
|
||||
const label = doc.schema.label ?? doc.schemaName;
|
||||
if (doc.schema.naming === 'random') {
|
||||
return label;
|
||||
}
|
||||
|
||||
return doc.name ?? label;
|
||||
}
|
||||
|
||||
function getSubmitSuccessToastAction(doc: Doc) {
|
||||
const isStockTransfer = doc instanceof Transfer;
|
||||
const isTransactional = doc instanceof Transactional;
|
||||
@ -680,3 +705,33 @@ function getSubmitSuccessToastAction(doc: Doc) {
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
export function showCannotSaveOrSubmitToast(doc: Doc) {
|
||||
const label = getToastLabel(doc);
|
||||
let message = t`${label} already saved`;
|
||||
|
||||
if (doc.schema.isSubmittable && doc.isSubmitted) {
|
||||
message = t`${label} already submitted`;
|
||||
}
|
||||
|
||||
showToast({ type: 'warning', message, duration: 'short' });
|
||||
}
|
||||
|
||||
export function showCannotCancelOrDeleteToast(doc: Doc) {
|
||||
const label = getToastLabel(doc);
|
||||
let message = t`${label} cannot be deleted`;
|
||||
if (doc.schema.isSubmittable && !doc.isCancelled) {
|
||||
message = t`${label} cannot be cancelled`;
|
||||
}
|
||||
|
||||
showToast({ type: 'warning', message, duration: 'short' });
|
||||
}
|
||||
|
||||
function getToastLabel(doc: Doc) {
|
||||
const label = doc.schema.label || doc.schemaName;
|
||||
if (doc.schema.naming === 'random') {
|
||||
return label;
|
||||
}
|
||||
|
||||
return doc.name || label;
|
||||
}
|
||||
|
@ -1,6 +1,23 @@
|
||||
import { Keys } from 'utils/types';
|
||||
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||
import {
|
||||
onActivated,
|
||||
onDeactivated,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
reactive,
|
||||
ref,
|
||||
} from 'vue';
|
||||
import { getIsMac } from './misc';
|
||||
import { Shortcuts } from './shortcuts';
|
||||
import { DocRef } from './types';
|
||||
import {
|
||||
commonDocCancel,
|
||||
commonDocSubmit,
|
||||
commonDocSync,
|
||||
commongDocDelete,
|
||||
showCannotCancelOrDeleteToast,
|
||||
showCannotSaveOrSubmitToast,
|
||||
} from './ui';
|
||||
|
||||
export function useKeys() {
|
||||
const isMac = getIsMac();
|
||||
@ -72,3 +89,60 @@ export function useMouseLocation() {
|
||||
|
||||
return loc;
|
||||
}
|
||||
|
||||
export function useDocShortcuts(
|
||||
shortcuts: Shortcuts,
|
||||
docRef: DocRef,
|
||||
name: string,
|
||||
isMultiple: boolean = true
|
||||
) {
|
||||
let context = name;
|
||||
if (isMultiple) {
|
||||
context = name + '-' + Math.random().toString(36).slice(2, 6);
|
||||
}
|
||||
|
||||
const syncOrSubmitCallback = () => {
|
||||
const doc = docRef.value;
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (doc.canSave) {
|
||||
return commonDocSync(doc, true);
|
||||
}
|
||||
|
||||
if (doc.canSubmit) {
|
||||
return commonDocSubmit(doc);
|
||||
}
|
||||
|
||||
showCannotSaveOrSubmitToast(doc);
|
||||
};
|
||||
|
||||
const cancelOrDeleteCallback = () => {
|
||||
const doc = docRef.value;
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (doc.canCancel) {
|
||||
return commonDocCancel(doc);
|
||||
}
|
||||
|
||||
if (doc.canDelete) {
|
||||
return commongDocDelete(doc);
|
||||
}
|
||||
|
||||
showCannotCancelOrDeleteToast(doc);
|
||||
};
|
||||
|
||||
onActivated(() => {
|
||||
shortcuts.pmod.set(context, ['KeyS'], syncOrSubmitCallback, false);
|
||||
shortcuts.pmod.set(context, ['Backspace'], cancelOrDeleteCallback, false);
|
||||
});
|
||||
|
||||
onDeactivated(() => {
|
||||
shortcuts.delete(context);
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user