mirror of
https://github.com/frappe/books.git
synced 2024-11-10 07:40:55 +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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { getColorClass } from 'src/utils/colors';
|
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';
|
import FeatherIcon from './FeatherIcon.vue';
|
||||||
|
|
||||||
export default {
|
type TimeoutId = ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
FeatherIcon,
|
FeatherIcon,
|
||||||
},
|
},
|
||||||
@ -46,16 +51,21 @@ export default {
|
|||||||
return {
|
return {
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
show: true,
|
show: true,
|
||||||
opacityTimeoutId: -1,
|
opacityTimeoutId: null,
|
||||||
cleanupTimeoutId: -1,
|
cleanupTimeoutId: null,
|
||||||
|
} as {
|
||||||
|
opacity: number;
|
||||||
|
show: boolean;
|
||||||
|
opacityTimeoutId: null | TimeoutId;
|
||||||
|
cleanupTimeoutId: null | TimeoutId;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
message: { type: String, required: true },
|
message: { type: String, required: true },
|
||||||
action: { type: Function, default: () => {} },
|
action: { type: Function, default: () => {} },
|
||||||
actionText: { type: String, default: '' },
|
actionText: { type: String, default: '' },
|
||||||
type: { type: String, default: 'info' },
|
type: { type: String as PropType<ToastType>, default: 'info' },
|
||||||
duration: { type: Number, default: 5000 },
|
duration: { type: String as PropType<ToastDuration>, default: 'long' },
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
iconName() {
|
iconName() {
|
||||||
@ -77,22 +87,23 @@ export default {
|
|||||||
}[this.type];
|
}[this.type];
|
||||||
},
|
},
|
||||||
iconColor() {
|
iconColor() {
|
||||||
return getColorClass(this.color, 'text', 400);
|
return getColorClass(this.color ?? 'gray', 'text', 400);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
const duration = toastDurationMap[this.duration];
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.opacity = 1;
|
this.opacity = 1;
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
this.opacityTimeoutId = setTimeout(() => {
|
this.opacityTimeoutId = setTimeout(() => {
|
||||||
this.opacity = 0;
|
this.opacity = 0;
|
||||||
}, this.duration);
|
}, duration);
|
||||||
|
|
||||||
this.cleanupTimeoutId = setTimeout(() => {
|
this.cleanupTimeoutId = setTimeout(() => {
|
||||||
this.show = false;
|
this.show = false;
|
||||||
this.cleanup();
|
this.cleanup();
|
||||||
}, this.duration + 300);
|
}, duration + 300);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
actionClicked() {
|
actionClicked() {
|
||||||
@ -100,8 +111,12 @@ export default {
|
|||||||
this.closeToast();
|
this.closeToast();
|
||||||
},
|
},
|
||||||
closeToast() {
|
closeToast() {
|
||||||
clearTimeout(this.opacityTimeoutId);
|
if (this.opacityTimeoutId != null) {
|
||||||
clearTimeout(this.cleanupTimeoutId);
|
clearTimeout(this.opacityTimeoutId);
|
||||||
|
}
|
||||||
|
if (this.cleanupTimeoutId != null) {
|
||||||
|
clearTimeout(this.cleanupTimeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
this.opacity = 0;
|
this.opacity = 0;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -110,11 +125,16 @@ export default {
|
|||||||
}, 300);
|
}, 300);
|
||||||
},
|
},
|
||||||
cleanup() {
|
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)
|
.filter((el) => !el.innerHTML)
|
||||||
.splice(1)
|
.splice(1)
|
||||||
.forEach((el) => el.remove());
|
.forEach((el) => el.remove());
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #header v-if="hasDoc">
|
<template #header v-if="hasDoc">
|
||||||
<Button
|
<Button
|
||||||
v-if="!doc.isCancelled && !doc.dirty && isPrintable"
|
v-if="canPrint"
|
||||||
|
ref="printButton"
|
||||||
:icon="true"
|
:icon="true"
|
||||||
@click="routeTo(`/print/${doc.schemaName}/${doc.name}`)"
|
@click="routeTo(`/print/${doc.schemaName}/${doc.name}`)"
|
||||||
>
|
>
|
||||||
@ -122,7 +123,7 @@ import StatusBadge from 'src/components/StatusBadge.vue';
|
|||||||
import { getErrorMessage } from 'src/utils';
|
import { getErrorMessage } from 'src/utils';
|
||||||
import { docsPathMap } from 'src/utils/misc';
|
import { docsPathMap } from 'src/utils/misc';
|
||||||
import { docsPathRef } from 'src/utils/refs';
|
import { docsPathRef } from 'src/utils/refs';
|
||||||
import { ActionGroup, UIGroupedFields } from 'src/utils/types';
|
import { ActionGroup, DocRef, UIGroupedFields } from 'src/utils/types';
|
||||||
import {
|
import {
|
||||||
commonDocSubmit,
|
commonDocSubmit,
|
||||||
commonDocSync,
|
commonDocSync,
|
||||||
@ -135,12 +136,31 @@ import {
|
|||||||
import { computed, defineComponent, nextTick } from 'vue';
|
import { computed, defineComponent, nextTick } from 'vue';
|
||||||
import QuickEditForm from '../QuickEditForm.vue';
|
import QuickEditForm from '../QuickEditForm.vue';
|
||||||
import CommonFormSection from './CommonFormSection.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({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
name: { type: String, default: '' },
|
name: { type: String, default: '' },
|
||||||
schemaName: { type: String, default: ModelNameEnum.SalesInvoice },
|
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() {
|
provide() {
|
||||||
return {
|
return {
|
||||||
schemaName: computed(() => this.docOrNull?.schemaName),
|
schemaName: computed(() => this.docOrNull?.schemaName),
|
||||||
@ -151,14 +171,12 @@ export default defineComponent({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
errors: {},
|
errors: {},
|
||||||
docOrNull: null,
|
|
||||||
activeTab: this.t`Default`,
|
activeTab: this.t`Default`,
|
||||||
groupedFields: null,
|
groupedFields: null,
|
||||||
quickEditDoc: null,
|
quickEditDoc: null,
|
||||||
isPrintable: false,
|
isPrintable: false,
|
||||||
} as {
|
} as {
|
||||||
errors: Record<string, string>;
|
errors: Record<string, string>;
|
||||||
docOrNull: null | Doc;
|
|
||||||
activeTab: string;
|
activeTab: string;
|
||||||
groupedFields: null | UIGroupedFields;
|
groupedFields: null | UIGroupedFields;
|
||||||
quickEditDoc: null | Doc;
|
quickEditDoc: null | Doc;
|
||||||
@ -180,16 +198,30 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
activated(): void {
|
activated(): void {
|
||||||
docsPathRef.value = docsPathMap[this.schemaName] ?? '';
|
docsPathRef.value = docsPathMap[this.schemaName] ?? '';
|
||||||
|
this.shortcuts?.pmod.set(this.context, ['KeyP'], () => {
|
||||||
|
if (!this.canPrint) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.printButton?.$el.click();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
deactivated(): void {
|
deactivated(): void {
|
||||||
docsPathRef.value = '';
|
docsPathRef.value = '';
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
canPrint(): boolean {
|
||||||
|
if (!this.hasDoc) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !this.doc.isCancelled && !this.doc.dirty && this.isPrintable;
|
||||||
|
},
|
||||||
hasDoc(): boolean {
|
hasDoc(): boolean {
|
||||||
return !!this.docOrNull;
|
return this.docOrNull instanceof Doc;
|
||||||
},
|
},
|
||||||
hasQeDoc(): boolean {
|
hasQeDoc(): boolean {
|
||||||
return !!this.quickEditDoc;
|
return this.quickEditDoc instanceof Doc;
|
||||||
},
|
},
|
||||||
status(): string {
|
status(): string {
|
||||||
if (!this.hasDoc) {
|
if (!this.hasDoc) {
|
||||||
@ -265,8 +297,8 @@ export default defineComponent({
|
|||||||
this.doc
|
this.doc
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
async sync() {
|
async sync(useDialog?: boolean) {
|
||||||
if (await commonDocSync(this.doc)) {
|
if (await commonDocSync(this.doc, useDialog)) {
|
||||||
this.updateGroupedFields();
|
this.updateGroupedFields();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -21,7 +21,8 @@
|
|||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
v-if="!doc.isCancelled && !doc.dirty"
|
v-if="canPrint"
|
||||||
|
ref="printButton"
|
||||||
:icon="true"
|
:icon="true"
|
||||||
@click="routeTo(`/print/${doc.schemaName}/${doc.name}`)"
|
@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 StatusBadge from 'src/components/StatusBadge.vue';
|
||||||
import LinkedEntryWidget from 'src/components/Widgets/LinkedEntryWidget.vue';
|
import LinkedEntryWidget from 'src/components/Widgets/LinkedEntryWidget.vue';
|
||||||
import { fyo } from 'src/initFyo';
|
import { fyo } from 'src/initFyo';
|
||||||
|
import { shortcutsKey } from 'src/utils/injectionKeys';
|
||||||
import { docsPathMap } from 'src/utils/misc';
|
import { docsPathMap } from 'src/utils/misc';
|
||||||
import { docsPathRef } from 'src/utils/refs';
|
import { docsPathRef } from 'src/utils/refs';
|
||||||
import {
|
import {
|
||||||
@ -321,7 +323,8 @@ import {
|
|||||||
getGroupedActionsForDoc,
|
getGroupedActionsForDoc,
|
||||||
routeTo,
|
routeTo,
|
||||||
} from 'src/utils/ui';
|
} 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 { handleErrorWithDialog } from '../errorHandling';
|
||||||
import QuickEditForm from './QuickEditForm.vue';
|
import QuickEditForm from './QuickEditForm.vue';
|
||||||
|
|
||||||
@ -341,6 +344,22 @@ export default {
|
|||||||
LinkedEntryWidget,
|
LinkedEntryWidget,
|
||||||
Barcode,
|
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() {
|
provide() {
|
||||||
return {
|
return {
|
||||||
schemaName: this.schemaName,
|
schemaName: this.schemaName,
|
||||||
@ -351,7 +370,6 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
chstatus: false,
|
chstatus: false,
|
||||||
doc: null,
|
|
||||||
quickEditDoc: null,
|
quickEditDoc: null,
|
||||||
quickEditFields: [],
|
quickEditFields: [],
|
||||||
color: null,
|
color: null,
|
||||||
@ -364,6 +382,9 @@ export default {
|
|||||||
this.chstatus = !this.chstatus;
|
this.chstatus = !this.chstatus;
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
canPrint() {
|
||||||
|
return !this.doc.isCancelled && !this.doc.dirty;
|
||||||
|
},
|
||||||
stockTransferText() {
|
stockTransferText() {
|
||||||
if (!this.fyo.singles.AccountingSettings.enableInventory) {
|
if (!this.fyo.singles.AccountingSettings.enableInventory) {
|
||||||
return '';
|
return '';
|
||||||
@ -458,6 +479,13 @@ export default {
|
|||||||
},
|
},
|
||||||
activated() {
|
activated() {
|
||||||
docsPathRef.value = docsPathMap[this.schemaName];
|
docsPathRef.value = docsPathMap[this.schemaName];
|
||||||
|
this.shortcuts?.pmod.set(this.context, ['KeyP'], () => {
|
||||||
|
if (!this.canPrint) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.printButton?.$el.click();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
deactivated() {
|
deactivated() {
|
||||||
docsPathRef.value = '';
|
docsPathRef.value = '';
|
||||||
|
@ -116,7 +116,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
deactivated() {
|
deactivated() {
|
||||||
docsPathRef.value = '';
|
docsPathRef.value = '';
|
||||||
this.shortcuts?.delete(this);
|
this.shortcuts?.delete(this.context);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setShortcuts() {
|
setShortcuts() {
|
||||||
@ -124,10 +124,10 @@ export default defineComponent({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.shortcuts.pmod.set(this, ['KeyN'], () =>
|
this.shortcuts.pmod.set(this.context, ['KeyN'], () =>
|
||||||
this.makeNewDocButton?.$el.click()
|
this.makeNewDocButton?.$el.click()
|
||||||
);
|
);
|
||||||
this.shortcuts.pmod.set(this, ['KeyE'], () =>
|
this.shortcuts.pmod.set(this.context, ['KeyE'], () =>
|
||||||
this.exportButton?.$el.click()
|
this.exportButton?.$el.click()
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -194,6 +194,9 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
context(): string {
|
||||||
|
return 'ListView-' + this.schemaName;
|
||||||
|
},
|
||||||
title(): string {
|
title(): string {
|
||||||
if (this.pageTitle) {
|
if (this.pageTitle) {
|
||||||
return this.pageTitle;
|
return this.pageTitle;
|
||||||
|
@ -113,14 +113,17 @@ import DropdownWithActions from 'src/components/DropdownWithActions.vue';
|
|||||||
import StatusBadge from 'src/components/StatusBadge.vue';
|
import StatusBadge from 'src/components/StatusBadge.vue';
|
||||||
import TwoColumnForm from 'src/components/TwoColumnForm.vue';
|
import TwoColumnForm from 'src/components/TwoColumnForm.vue';
|
||||||
import { fyo } from 'src/initFyo';
|
import { fyo } from 'src/initFyo';
|
||||||
|
import { shortcutsKey } from 'src/utils/injectionKeys';
|
||||||
import { getQuickEditWidget } from 'src/utils/quickEditWidgets';
|
import { getQuickEditWidget } from 'src/utils/quickEditWidgets';
|
||||||
import {
|
import {
|
||||||
commonDocSubmit,
|
commonDocSubmit,
|
||||||
commonDocSync,
|
commonDocSync,
|
||||||
focusOrSelectFormControl,
|
focusOrSelectFormControl,
|
||||||
getActionsForDoc,
|
getActionsForDoc,
|
||||||
openQuickEdit
|
openQuickEdit,
|
||||||
} from 'src/utils/ui';
|
} from 'src/utils/ui';
|
||||||
|
import { useDocShortcuts } from 'src/utils/vueUtils';
|
||||||
|
import { ref, inject } from 'vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'QuickEditForm',
|
name: 'QuickEditForm',
|
||||||
@ -146,6 +149,21 @@ export default {
|
|||||||
DropdownWithActions,
|
DropdownWithActions,
|
||||||
},
|
},
|
||||||
emits: ['close'],
|
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() {
|
provide() {
|
||||||
return {
|
return {
|
||||||
schemaName: this.schemaName,
|
schemaName: this.schemaName,
|
||||||
@ -155,13 +173,17 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
doc: null,
|
|
||||||
values: null,
|
values: null,
|
||||||
titleField: null,
|
titleField: null,
|
||||||
imageField: null,
|
imageField: null,
|
||||||
statusText: null,
|
statusText: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
activated() {
|
||||||
|
this.shortcuts.set(this.context, ['Escape'], () => {
|
||||||
|
this.routeToPrevious();
|
||||||
|
});
|
||||||
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
if (this.defaults) {
|
if (this.defaults) {
|
||||||
this.values = JSON.parse(this.defaults);
|
this.values = JSON.parse(this.defaults);
|
||||||
|
@ -228,7 +228,7 @@ import {
|
|||||||
getPrintTemplatePropValues,
|
getPrintTemplatePropValues,
|
||||||
} from 'src/utils/printTemplates';
|
} from 'src/utils/printTemplates';
|
||||||
import { docsPathRef, showSidebar } from 'src/utils/refs';
|
import { docsPathRef, showSidebar } from 'src/utils/refs';
|
||||||
import { PrintValues } from 'src/utils/types';
|
import { DocRef, PrintValues } from 'src/utils/types';
|
||||||
import {
|
import {
|
||||||
focusOrSelectFormControl,
|
focusOrSelectFormControl,
|
||||||
getActionsForDoc,
|
getActionsForDoc,
|
||||||
@ -239,14 +239,12 @@ import {
|
|||||||
showMessageDialog,
|
showMessageDialog,
|
||||||
showToast,
|
showToast,
|
||||||
} from 'src/utils/ui';
|
} from 'src/utils/ui';
|
||||||
|
import { useDocShortcuts } from 'src/utils/vueUtils';
|
||||||
import { getMapFromList } from 'utils/index';
|
import { getMapFromList } from 'utils/index';
|
||||||
import { computed, defineComponent } from 'vue';
|
import { computed, defineComponent, inject, ref } from 'vue';
|
||||||
import PrintContainer from './PrintContainer.vue';
|
import PrintContainer from './PrintContainer.vue';
|
||||||
import TemplateBuilderHint from './TemplateBuilderHint.vue';
|
import TemplateBuilderHint from './TemplateBuilderHint.vue';
|
||||||
import TemplateEditor from './TemplateEditor.vue';
|
import TemplateEditor from './TemplateEditor.vue';
|
||||||
import { inject } from 'vue';
|
|
||||||
|
|
||||||
const COMPONENT_NAME = 'TemplateBuilder';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: { name: String },
|
props: { name: String },
|
||||||
@ -262,8 +260,18 @@ export default defineComponent({
|
|||||||
ShortcutKeys,
|
ShortcutKeys,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
|
const doc = ref(null) as DocRef<PrintTemplate>;
|
||||||
|
const shortcuts = inject(shortcutsKey);
|
||||||
|
|
||||||
|
let context = 'TemplateBuilder';
|
||||||
|
if (shortcuts) {
|
||||||
|
context = useDocShortcuts(shortcuts, doc, context, false);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
shortcuts: inject(shortcutsKey),
|
doc,
|
||||||
|
context,
|
||||||
|
shortcuts,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
provide() {
|
provide() {
|
||||||
@ -271,7 +279,6 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
doc: null,
|
|
||||||
editMode: false,
|
editMode: false,
|
||||||
showHints: false,
|
showHints: false,
|
||||||
hints: undefined,
|
hints: undefined,
|
||||||
@ -290,7 +297,6 @@ export default defineComponent({
|
|||||||
showHints: boolean;
|
showHints: boolean;
|
||||||
hints?: Record<string, unknown>;
|
hints?: Record<string, unknown>;
|
||||||
values: null | PrintValues;
|
values: null | PrintValues;
|
||||||
doc: PrintTemplate | null;
|
|
||||||
displayDoc: PrintTemplate | null;
|
displayDoc: PrintTemplate | null;
|
||||||
scale: number;
|
scale: number;
|
||||||
panelWidth: number;
|
panelWidth: number;
|
||||||
@ -311,25 +317,27 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
async activated(): Promise<void> {
|
async activated(): Promise<void> {
|
||||||
docsPathRef.value = docsPathMap.PrintTemplate ?? '';
|
docsPathRef.value = docsPathMap.PrintTemplate ?? '';
|
||||||
this.setShortcuts;
|
this.setShortcuts();
|
||||||
},
|
},
|
||||||
deactivated(): void {
|
deactivated(): void {
|
||||||
docsPathRef.value = '';
|
docsPathRef.value = '';
|
||||||
this.shortcuts?.delete(COMPONENT_NAME);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setShortcuts() {
|
setShortcuts() {
|
||||||
|
/**
|
||||||
|
* Node: Doc Save and Delete shortcuts are in the setup.
|
||||||
|
*/
|
||||||
if (!this.shortcuts) {
|
if (!this.shortcuts) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.shortcuts.ctrl.set(COMPONENT_NAME, ['Enter'], this.setTemplate);
|
this.shortcuts.ctrl.set(this.context, ['Enter'], this.setTemplate);
|
||||||
this.shortcuts.ctrl.set(COMPONENT_NAME, ['KeyE'], this.toggleEditMode);
|
this.shortcuts.ctrl.set(this.context, ['KeyE'], this.toggleEditMode);
|
||||||
this.shortcuts.ctrl.set(COMPONENT_NAME, ['KeyH'], this.toggleShowHints);
|
this.shortcuts.ctrl.set(this.context, ['KeyH'], this.toggleShowHints);
|
||||||
this.shortcuts.ctrl.set(COMPONENT_NAME, ['Equal'], () =>
|
this.shortcuts.ctrl.set(this.context, ['Equal'], () =>
|
||||||
this.setScale(this.scale + 0.1)
|
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)
|
this.setScale(this.scale - 0.1)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -385,7 +393,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
let message = this.t`Please set a Display Doc`;
|
let message = this.t`Please set a Display Doc`;
|
||||||
if (!this.displayDoc) {
|
if (!this.displayDoc) {
|
||||||
return showToast({ type: 'warning', message, duration: 1000 });
|
return showToast({ type: 'warning', message, duration: 'short' });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.editMode = !this.editMode;
|
this.editMode = !this.editMode;
|
||||||
|
@ -23,6 +23,20 @@ type ShortcutMap = Map<Context, Map<string, ShortcutConfig>>;
|
|||||||
|
|
||||||
const mods: Readonly<Mod[]> = ['alt', 'ctrl', 'meta', 'repeat', 'shift'];
|
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 {
|
export class Shortcuts {
|
||||||
keys: Keys;
|
keys: Keys;
|
||||||
isMac: boolean;
|
isMac: boolean;
|
||||||
@ -64,9 +78,9 @@ export class Shortcuts {
|
|||||||
*
|
*
|
||||||
* @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
|
* @returns boolean indicating presence
|
||||||
*/
|
*/
|
||||||
has(context: Context, shortcut?: string[]) {
|
has(context: Context, shortcut?: string[]): boolean {
|
||||||
if (!shortcut) {
|
if (!shortcut) {
|
||||||
return this.shortcuts.has(context);
|
return this.shortcuts.has(context);
|
||||||
}
|
}
|
||||||
@ -86,7 +100,7 @@ export class Shortcuts {
|
|||||||
* @param context context object to which the shortcut belongs
|
* @param context context object to which the shortcut belongs
|
||||||
* @param shortcut keyboard event codes used as shortcut chord
|
* @param shortcut keyboard event codes used as shortcut chord
|
||||||
* @param callback function to be called when the shortcut is pressed
|
* @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
|
* @param removeIfSet whether to delete the set shortcut
|
||||||
*/
|
*/
|
||||||
set(
|
set(
|
||||||
@ -95,18 +109,14 @@ export class Shortcuts {
|
|||||||
callback: ShortcutFunction,
|
callback: ShortcutFunction,
|
||||||
propagate: boolean = false,
|
propagate: boolean = false,
|
||||||
removeIfSet: boolean = true
|
removeIfSet: boolean = true
|
||||||
) {
|
): void {
|
||||||
if (!this.shortcuts.has(context)) {
|
if (!this.shortcuts.has(context)) {
|
||||||
this.shortcuts.set(context, new Map());
|
this.shortcuts.set(context, new Map());
|
||||||
}
|
}
|
||||||
const contextualShortcuts = this.shortcuts.get(context)!;
|
|
||||||
|
|
||||||
const key = this.getKey(shortcut);
|
const key = this.getKey(shortcut);
|
||||||
if (removeIfSet) {
|
const contextualShortcuts = this.shortcuts.get(context)!;
|
||||||
contextualShortcuts.delete(key);
|
if (contextualShortcuts.has(key) && !removeIfSet) {
|
||||||
}
|
|
||||||
|
|
||||||
if (contextualShortcuts.has(key)) {
|
|
||||||
throw new Error(`Shortcut ${key} already exists.`);
|
throw new Error(`Shortcut ${key} already exists.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
import type { Doc } from 'fyo/model/doc';
|
import type { Doc } from 'fyo/model/doc';
|
||||||
import type { Action } from 'fyo/model/types';
|
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 { Field, FieldType } from 'schemas/types';
|
||||||
import type { QueryFilter } from 'utils/db/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 {
|
export interface MessageDialogButton {
|
||||||
label: string;
|
label: string;
|
||||||
@ -17,8 +24,8 @@ export interface MessageDialogOptions {
|
|||||||
|
|
||||||
export interface ToastOptions {
|
export interface ToastOptions {
|
||||||
message: string;
|
message: string;
|
||||||
type?: 'info' | 'warning' | 'error' | 'success';
|
type?: ToastType;
|
||||||
duration?: number;
|
duration?: ToastDuration;
|
||||||
action?: () => void;
|
action?: () => void;
|
||||||
actionText?: string;
|
actionText?: string;
|
||||||
}
|
}
|
||||||
|
121
src/utils/ui.ts
121
src/utils/ui.ts
@ -33,6 +33,8 @@ import {
|
|||||||
UIGroupedFields,
|
UIGroupedFields,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
|
export const toastDurationMap = { short: 2_500, long: 5_000 } as const;
|
||||||
|
|
||||||
export async function openQuickEdit({
|
export async function openQuickEdit({
|
||||||
doc,
|
doc,
|
||||||
schemaName,
|
schemaName,
|
||||||
@ -318,12 +320,7 @@ function getCancelAction(doc: Doc): Action {
|
|||||||
},
|
},
|
||||||
condition: (doc: Doc) => doc.canCancel,
|
condition: (doc: Doc) => doc.canCancel,
|
||||||
async action() {
|
async action() {
|
||||||
const res = await cancelDocWithPrompt(doc);
|
await commonDocCancel(doc);
|
||||||
if (!res) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showActionToast(doc, 'cancel');
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -336,13 +333,7 @@ function getDeleteAction(doc: Doc): Action {
|
|||||||
},
|
},
|
||||||
condition: (doc: Doc) => doc.canDelete,
|
condition: (doc: Doc) => doc.canDelete,
|
||||||
async action() {
|
async action() {
|
||||||
const res = await deleteDocWithPrompt(doc);
|
await commongDocDelete(doc);
|
||||||
if (!res) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showActionToast(doc, 'delete');
|
|
||||||
router.back();
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -569,11 +560,39 @@ export function getShortcutKeyMap(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function commonDocSync(doc: Doc): Promise<boolean> {
|
export async function commongDocDelete(doc: Doc): Promise<boolean> {
|
||||||
try {
|
const res = await deleteDocWithPrompt(doc);
|
||||||
await doc.sync();
|
if (!res) {
|
||||||
} catch (error) {
|
return false;
|
||||||
handleErrorWithDialog(error, doc);
|
}
|
||||||
|
|
||||||
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -581,8 +600,19 @@ export async function commonDocSync(doc: Doc): Promise<boolean> {
|
|||||||
return true;
|
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> {
|
export async function commonDocSubmit(doc: Doc): Promise<boolean> {
|
||||||
const success = await showSubmitDialog(doc);
|
const success = await showSubmitOrSyncDialog(doc, 'submit');
|
||||||
if (!success) {
|
if (!success) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -591,12 +621,16 @@ export async function commonDocSubmit(doc: Doc): Promise<boolean> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function showSubmitDialog(doc: Doc) {
|
async function showSubmitOrSyncDialog(doc: Doc, type: 'submit' | 'sync') {
|
||||||
const label = doc.schema.label ?? doc.schemaName;
|
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 () => {
|
const yesAction = async () => {
|
||||||
try {
|
try {
|
||||||
await doc.submit();
|
await doc[type]();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleErrorWithDialog(error, doc);
|
handleErrorWithDialog(error, doc);
|
||||||
return false;
|
return false;
|
||||||
@ -630,7 +664,7 @@ function showActionToast(doc: Doc, type: 'sync' | 'cancel' | 'delete') {
|
|||||||
delete: t`${label} deleted`,
|
delete: t`${label} deleted`,
|
||||||
}[type];
|
}[type];
|
||||||
|
|
||||||
showToast({ type: 'success', message, duration: 2500 });
|
showToast({ type: 'success', message, duration: 'short' });
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSubmitToast(doc: Doc) {
|
function showSubmitToast(doc: Doc) {
|
||||||
@ -639,21 +673,12 @@ function showSubmitToast(doc: Doc) {
|
|||||||
const toastOption: ToastOptions = {
|
const toastOption: ToastOptions = {
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message,
|
message,
|
||||||
duration: 5000,
|
duration: 'long',
|
||||||
...getSubmitSuccessToastAction(doc),
|
...getSubmitSuccessToastAction(doc),
|
||||||
};
|
};
|
||||||
showToast(toastOption);
|
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) {
|
function getSubmitSuccessToastAction(doc: Doc) {
|
||||||
const isStockTransfer = doc instanceof Transfer;
|
const isStockTransfer = doc instanceof Transfer;
|
||||||
const isTransactional = doc instanceof Transactional;
|
const isTransactional = doc instanceof Transactional;
|
||||||
@ -680,3 +705,33 @@ function getSubmitSuccessToastAction(doc: Doc) {
|
|||||||
|
|
||||||
return {};
|
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 { 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 { getIsMac } from './misc';
|
||||||
|
import { Shortcuts } from './shortcuts';
|
||||||
|
import { DocRef } from './types';
|
||||||
|
import {
|
||||||
|
commonDocCancel,
|
||||||
|
commonDocSubmit,
|
||||||
|
commonDocSync,
|
||||||
|
commongDocDelete,
|
||||||
|
showCannotCancelOrDeleteToast,
|
||||||
|
showCannotSaveOrSubmitToast,
|
||||||
|
} from './ui';
|
||||||
|
|
||||||
export function useKeys() {
|
export function useKeys() {
|
||||||
const isMac = getIsMac();
|
const isMac = getIsMac();
|
||||||
@ -72,3 +89,60 @@ export function useMouseLocation() {
|
|||||||
|
|
||||||
return loc;
|
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