mirror of
https://github.com/frappe/books.git
synced 2025-01-03 15:17:30 +00:00
refactor(ui): use showDialog for FB Dialog Component
- remove showMessageDialog using native component - add icons to the Dialog component
This commit is contained in:
parent
524111418c
commit
080eaa5e4f
@ -4,8 +4,8 @@ import { DateTime } from 'luxon';
|
|||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import { codeStateMap } from 'regional/in';
|
import { codeStateMap } from 'regional/in';
|
||||||
import { ExportExtention } from 'reports/types';
|
import { ExportExtention } from 'reports/types';
|
||||||
|
import { showDialog } from 'src/utils/interactive';
|
||||||
import { getSavePath } from 'src/utils/ipcCalls';
|
import { getSavePath } from 'src/utils/ipcCalls';
|
||||||
import { showMessageDialog } from 'src/utils/ui';
|
|
||||||
import { invertMap } from 'utils';
|
import { invertMap } from 'utils';
|
||||||
import { getCsvData, saveExportData } from '../commonExporter';
|
import { getCsvData, saveExportData } from '../commonExporter';
|
||||||
import { BaseGSTR } from './BaseGSTR';
|
import { BaseGSTR } from './BaseGSTR';
|
||||||
@ -175,9 +175,10 @@ async function getCanExport(report: BaseGSTR) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
showMessageDialog({
|
showDialog({
|
||||||
message: 'Cannot Export',
|
title: report.fyo.t`Cannot Export`,
|
||||||
detail: 'Please set GSTIN in General Settings.',
|
detail: report.fyo.t`Please set GSTIN in General Settings.`,
|
||||||
|
type: 'error',
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -18,9 +18,16 @@
|
|||||||
inner
|
inner
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
<h1 class="font-semibold">{{ title }}</h1>
|
<h1 class="font-semibold">{{ title }}</h1>
|
||||||
<p v-if="description" class="text-base">{{ description }}</p>
|
<FeatherIcon
|
||||||
<div class="flex justify-end gap-2">
|
:name="config.iconName"
|
||||||
|
class="w-6 h-6"
|
||||||
|
:class="config.iconColor"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p v-if="detail" class="text-base">{{ detail }}</p>
|
||||||
|
<div class="flex justify-end gap-4 mt-4">
|
||||||
<Button
|
<Button
|
||||||
v-for="(b, index) of buttons"
|
v-for="(b, index) of buttons"
|
||||||
:ref="b.isPrimary ? 'primary' : 'secondary'"
|
:ref="b.isPrimary ? 'primary' : 'secondary'"
|
||||||
@ -38,9 +45,11 @@
|
|||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DialogButton } from 'src/utils/types';
|
import { getIconConfig } from 'src/utils/interactive';
|
||||||
|
import { DialogButton, ToastType } from 'src/utils/types';
|
||||||
import { defineComponent, nextTick, PropType, ref } from 'vue';
|
import { defineComponent, nextTick, PropType, ref } from 'vue';
|
||||||
import Button from './Button.vue';
|
import Button from './Button.vue';
|
||||||
|
import FeatherIcon from './FeatherIcon.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
@ -53,8 +62,9 @@ export default defineComponent({
|
|||||||
return { open: false };
|
return { open: false };
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
type: { type: String as PropType<ToastType>, default: 'info' },
|
||||||
title: { type: String, required: true },
|
title: { type: String, required: true },
|
||||||
description: {
|
detail: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
@ -79,6 +89,11 @@ export default defineComponent({
|
|||||||
|
|
||||||
this.focusButton();
|
this.focusButton();
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
config() {
|
||||||
|
return getIconConfig(this.type);
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
focusButton() {
|
focusButton() {
|
||||||
let button = this.primary?.[0];
|
let button = this.primary?.[0];
|
||||||
@ -104,10 +119,7 @@ export default defineComponent({
|
|||||||
return this.handleClick(0);
|
return this.handleClick(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const index = this.buttons.findIndex(
|
const index = this.buttons.findIndex(({ isEscape }) => isEscape);
|
||||||
({ isPrimary, isEscape }) =>
|
|
||||||
isEscape || (this.buttons.length === 2 && !isPrimary)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
return;
|
return;
|
||||||
@ -117,11 +129,11 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
handleClick(index: number) {
|
handleClick(index: number) {
|
||||||
const button = this.buttons[index];
|
const button = this.buttons[index];
|
||||||
button.handler();
|
button.action();
|
||||||
this.open = false;
|
this.open = false;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: { Button },
|
components: { Button, FeatherIcon },
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -20,9 +20,9 @@
|
|||||||
style="pointer-events: auto"
|
style="pointer-events: auto"
|
||||||
>
|
>
|
||||||
<feather-icon
|
<feather-icon
|
||||||
:name="iconName"
|
:name="config.iconName"
|
||||||
class="w-6 h-6 me-3"
|
class="w-6 h-6 me-3"
|
||||||
:class="iconColor"
|
:class="config.iconColor"
|
||||||
/>
|
/>
|
||||||
<div @click="actionClicked" :class="actionText ? 'cursor-pointer' : ''">
|
<div @click="actionClicked" :class="actionText ? 'cursor-pointer' : ''">
|
||||||
<p class="text-base">{{ message }}</p>
|
<p class="text-base">{{ message }}</p>
|
||||||
@ -51,6 +51,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getColorClass } from 'src/utils/colors';
|
import { getColorClass } from 'src/utils/colors';
|
||||||
|
import { getIconConfig } from 'src/utils/interactive';
|
||||||
import { ToastDuration, ToastType } from 'src/utils/types';
|
import { ToastDuration, ToastType } from 'src/utils/types';
|
||||||
import { toastDurationMap } from 'src/utils/ui';
|
import { toastDurationMap } from 'src/utils/ui';
|
||||||
import { defineComponent, nextTick, PropType } from 'vue';
|
import { defineComponent, nextTick, PropType } from 'vue';
|
||||||
@ -73,26 +74,8 @@ export default defineComponent({
|
|||||||
duration: { type: String as PropType<ToastDuration>, default: 'long' },
|
duration: { type: String as PropType<ToastDuration>, default: 'long' },
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
iconName() {
|
config() {
|
||||||
switch (this.type) {
|
return getIconConfig(this.type);
|
||||||
case 'warning':
|
|
||||||
return 'alert-triangle';
|
|
||||||
case 'success':
|
|
||||||
return 'check-circle';
|
|
||||||
default:
|
|
||||||
return 'alert-circle';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
color() {
|
|
||||||
return {
|
|
||||||
info: 'blue',
|
|
||||||
warning: 'orange',
|
|
||||||
error: 'red',
|
|
||||||
success: 'green',
|
|
||||||
}[this.type];
|
|
||||||
},
|
|
||||||
iconColor() {
|
|
||||||
return getColorClass(this.color ?? 'gray', 'text', 400);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
@ -5,12 +5,12 @@ import { Doc } from 'fyo/model/doc';
|
|||||||
import { BaseError } from 'fyo/utils/errors';
|
import { BaseError } from 'fyo/utils/errors';
|
||||||
import { ErrorLog } from 'fyo/utils/types';
|
import { ErrorLog } from 'fyo/utils/types';
|
||||||
import { truncate } from 'lodash';
|
import { truncate } from 'lodash';
|
||||||
|
import { showDialog } from 'src/utils/interactive';
|
||||||
import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages';
|
import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages';
|
||||||
import { fyo } from './initFyo';
|
import { fyo } from './initFyo';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
import { getErrorMessage, stringifyCircular } from './utils';
|
import { getErrorMessage, stringifyCircular } from './utils';
|
||||||
import { MessageDialogOptions, ToastOptions } from './utils/types';
|
import { DialogOptions, ToastOptions } from './utils/types';
|
||||||
import { showMessageDialog } from './utils/ui';
|
|
||||||
|
|
||||||
function shouldNotStore(error: Error) {
|
function shouldNotStore(error: Error) {
|
||||||
const shouldLog = (error as BaseError).shouldStore ?? true;
|
const shouldLog = (error as BaseError).shouldStore ?? true;
|
||||||
@ -108,9 +108,10 @@ export async function handleErrorWithDialog(
|
|||||||
await handleError(false, error, { errorMessage, doc });
|
await handleError(false, error, { errorMessage, doc });
|
||||||
|
|
||||||
const label = getErrorLabel(error);
|
const label = getErrorLabel(error);
|
||||||
const options: MessageDialogOptions = {
|
const options: DialogOptions = {
|
||||||
message: label,
|
title: label,
|
||||||
detail: errorMessage,
|
detail: errorMessage,
|
||||||
|
type: 'error',
|
||||||
};
|
};
|
||||||
|
|
||||||
if (reportError) {
|
if (reportError) {
|
||||||
@ -121,12 +122,13 @@ export async function handleErrorWithDialog(
|
|||||||
action() {
|
action() {
|
||||||
reportIssue(getErrorLogObject(error, { errorMessage }));
|
reportIssue(getErrorLogObject(error, { errorMessage }));
|
||||||
},
|
},
|
||||||
|
isPrimary: true,
|
||||||
},
|
},
|
||||||
{ label: t`Cancel`, action() {} },
|
{ label: t`Cancel`, action() {}, isEscape: true },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
await showMessageDialog(options);
|
await showDialog(options);
|
||||||
if (dontThrow) {
|
if (dontThrow) {
|
||||||
if (fyo.store.isDevelopment) {
|
if (fyo.store.isDevelopment) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -223,9 +223,9 @@ import FeatherIcon from 'src/components/FeatherIcon.vue';
|
|||||||
import Loading from 'src/components/Loading.vue';
|
import Loading from 'src/components/Loading.vue';
|
||||||
import Modal from 'src/components/Modal.vue';
|
import Modal from 'src/components/Modal.vue';
|
||||||
import { fyo } from 'src/initFyo';
|
import { fyo } from 'src/initFyo';
|
||||||
|
import { showDialog } from 'src/utils/interactive';
|
||||||
import { deleteDb, getSavePath } from 'src/utils/ipcCalls';
|
import { deleteDb, getSavePath } from 'src/utils/ipcCalls';
|
||||||
import { updateConfigFiles } from 'src/utils/misc';
|
import { updateConfigFiles } from 'src/utils/misc';
|
||||||
import { showMessageDialog } from 'src/utils/ui';
|
|
||||||
import { IPC_ACTIONS } from 'utils/messages';
|
import { IPC_ACTIONS } from 'utils/messages';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -264,9 +264,10 @@ export default {
|
|||||||
const file = this.files[i];
|
const file = this.files[i];
|
||||||
const vm = this;
|
const vm = this;
|
||||||
|
|
||||||
await showMessageDialog({
|
await showDialog({
|
||||||
message: t`Delete ${file.companyName}?`,
|
title: t`Delete ${file.companyName}?`,
|
||||||
detail: t`Database file: ${file.dbPath}`,
|
detail: t`Database file: ${file.dbPath}`,
|
||||||
|
type: 'warning',
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
label: this.t`Yes`,
|
label: this.t`Yes`,
|
||||||
@ -274,10 +275,12 @@ export default {
|
|||||||
await deleteDb(file.dbPath);
|
await deleteDb(file.dbPath);
|
||||||
await vm.setFiles();
|
await vm.setFiles();
|
||||||
},
|
},
|
||||||
|
isPrimary: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: this.t`No`,
|
label: this.t`No`,
|
||||||
action() {},
|
action() {},
|
||||||
|
isEscape: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@ -383,10 +383,11 @@ import Modal from 'src/components/Modal.vue';
|
|||||||
import PageHeader from 'src/components/PageHeader.vue';
|
import PageHeader from 'src/components/PageHeader.vue';
|
||||||
import { getColumnLabel, Importer, TemplateField } from 'src/importer';
|
import { getColumnLabel, Importer, TemplateField } from 'src/importer';
|
||||||
import { fyo } from 'src/initFyo';
|
import { fyo } from 'src/initFyo';
|
||||||
|
import { showDialog } from 'src/utils/interactive';
|
||||||
import { getSavePath, saveData, selectFile } from 'src/utils/ipcCalls';
|
import { getSavePath, saveData, selectFile } from 'src/utils/ipcCalls';
|
||||||
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 { selectTextFile, showMessageDialog } from 'src/utils/ui';
|
import { selectTextFile } from 'src/utils/ui';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import Loading from '../components/Loading.vue';
|
import Loading from '../components/Loading.vue';
|
||||||
|
|
||||||
@ -780,10 +781,11 @@ export default defineComponent({
|
|||||||
await saveData(template, filePath);
|
await saveData(template, filePath);
|
||||||
},
|
},
|
||||||
async preImportValidations(): Promise<boolean> {
|
async preImportValidations(): Promise<boolean> {
|
||||||
const message = this.t`Cannot Import`;
|
const title = this.t`Cannot Import`;
|
||||||
if (this.errorMessage.length) {
|
if (this.errorMessage.length) {
|
||||||
await showMessageDialog({
|
await showDialog({
|
||||||
message,
|
title,
|
||||||
|
type: 'error',
|
||||||
detail: this.errorMessage,
|
detail: this.errorMessage,
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
@ -791,8 +793,9 @@ export default defineComponent({
|
|||||||
|
|
||||||
const cellErrors = this.importer.checkCellErrors();
|
const cellErrors = this.importer.checkCellErrors();
|
||||||
if (cellErrors.length) {
|
if (cellErrors.length) {
|
||||||
await showMessageDialog({
|
await showDialog({
|
||||||
message,
|
title,
|
||||||
|
type: 'error',
|
||||||
detail: this.t`Following cells have errors: ${cellErrors.join(', ')}`,
|
detail: this.t`Following cells have errors: ${cellErrors.join(', ')}`,
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
@ -800,8 +803,9 @@ export default defineComponent({
|
|||||||
|
|
||||||
const absentLinks = await this.importer.checkLinks();
|
const absentLinks = await this.importer.checkLinks();
|
||||||
if (absentLinks.length) {
|
if (absentLinks.length) {
|
||||||
await showMessageDialog({
|
await showDialog({
|
||||||
message,
|
title,
|
||||||
|
type: 'error',
|
||||||
detail: this.t`Following links do not exist: ${absentLinks
|
detail: this.t`Following links do not exist: ${absentLinks
|
||||||
.map((l) => `(${l.schemaLabel}, ${l.name})`)
|
.map((l) => `(${l.schemaLabel}, ${l.name})`)
|
||||||
.join(', ')}`,
|
.join(', ')}`,
|
||||||
@ -851,18 +855,22 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let shouldSubmit = false;
|
let shouldSubmit = false;
|
||||||
await showMessageDialog({
|
await showDialog({
|
||||||
message: this.t`Should entries be submitted after syncing?`,
|
title: this.t`Submit entries?`,
|
||||||
|
type: 'info',
|
||||||
|
details: this.t`Should entries be submitted after syncing?`,
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
label: this.t`Yes`,
|
label: this.t`Yes`,
|
||||||
action() {
|
action() {
|
||||||
shouldSubmit = true;
|
shouldSubmit = true;
|
||||||
},
|
},
|
||||||
|
isPrimary: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: this.t`No`,
|
label: this.t`No`,
|
||||||
action() {},
|
action() {},
|
||||||
|
isEscape: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -916,9 +924,10 @@ export default defineComponent({
|
|||||||
|
|
||||||
const isValid = this.importer.selectFile(text);
|
const isValid = this.importer.selectFile(text);
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
await showMessageDialog({
|
await showDialog({
|
||||||
message: this.t`Bad import data`,
|
title: this.t`Cannot read file`,
|
||||||
detail: this.t`Could not read file`,
|
detail: this.t`Bad import data, could not read file`,
|
||||||
|
type: 'error',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -76,11 +76,9 @@ import Button from 'src/components/Button.vue';
|
|||||||
import FormContainer from 'src/components/FormContainer.vue';
|
import FormContainer from 'src/components/FormContainer.vue';
|
||||||
import FormHeader from 'src/components/FormHeader.vue';
|
import FormHeader from 'src/components/FormHeader.vue';
|
||||||
import { getErrorMessage } from 'src/utils';
|
import { getErrorMessage } from 'src/utils';
|
||||||
|
import { showDialog } from 'src/utils/interactive';
|
||||||
import { getSetupWizardDoc } from 'src/utils/misc';
|
import { getSetupWizardDoc } from 'src/utils/misc';
|
||||||
import {
|
import { getFieldsGroupedByTabAndSection } from 'src/utils/ui';
|
||||||
getFieldsGroupedByTabAndSection,
|
|
||||||
showMessageDialog,
|
|
||||||
} from 'src/utils/ui';
|
|
||||||
import { computed, defineComponent } from 'vue';
|
import { computed, defineComponent } from 'vue';
|
||||||
import CommonFormSection from '../CommonForm/CommonFormSection.vue';
|
import CommonFormSection from '../CommonForm/CommonFormSection.vue';
|
||||||
|
|
||||||
@ -155,8 +153,10 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.areAllValuesFilled) {
|
if (!this.areAllValuesFilled) {
|
||||||
return await showMessageDialog({
|
return await showDialog({
|
||||||
message: this.t`Please fill all values`,
|
title: this.t`Mandatory Error`,
|
||||||
|
detail: this.t`Please fill all values`,
|
||||||
|
type: 'error',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +220,7 @@ import PageHeader from 'src/components/PageHeader.vue';
|
|||||||
import ShortcutKeys from 'src/components/ShortcutKeys.vue';
|
import ShortcutKeys from 'src/components/ShortcutKeys.vue';
|
||||||
import { handleErrorWithDialog } from 'src/errorHandling';
|
import { handleErrorWithDialog } from 'src/errorHandling';
|
||||||
import { shortcutsKey } from 'src/utils/injectionKeys';
|
import { shortcutsKey } from 'src/utils/injectionKeys';
|
||||||
import { showToast } from 'src/utils/interactive';
|
import { showDialog, showToast } from 'src/utils/interactive';
|
||||||
import { getSavePath } from 'src/utils/ipcCalls';
|
import { getSavePath } from 'src/utils/ipcCalls';
|
||||||
import { docsPathMap } from 'src/utils/misc';
|
import { docsPathMap } from 'src/utils/misc';
|
||||||
import {
|
import {
|
||||||
@ -237,7 +237,6 @@ import {
|
|||||||
openSettings,
|
openSettings,
|
||||||
selectTextFile,
|
selectTextFile,
|
||||||
ShortcutKey,
|
ShortcutKey,
|
||||||
showMessageDialog,
|
|
||||||
} from 'src/utils/ui';
|
} from 'src/utils/ui';
|
||||||
import { useDocShortcuts } from 'src/utils/vueUtils';
|
import { useDocShortcuts } from 'src/utils/vueUtils';
|
||||||
import { getMapFromList } from 'utils/index';
|
import { getMapFromList } from 'utils/index';
|
||||||
@ -460,10 +459,11 @@ export default defineComponent({
|
|||||||
const name = names[0]?.name;
|
const name = names[0]?.name;
|
||||||
if (!name) {
|
if (!name) {
|
||||||
const label = this.fyo.schemaMap[schemaName]?.label ?? schemaName;
|
const label = this.fyo.schemaMap[schemaName]?.label ?? schemaName;
|
||||||
await showMessageDialog({
|
await showDialog({
|
||||||
message: this.t`No Display Entries Found`,
|
title: this.t`No Display Entries Found`,
|
||||||
detail: this
|
detail: this
|
||||||
.t`Please create a ${label} entry to view Template Preview`,
|
.t`Please create a ${label} entry to view Template Preview`,
|
||||||
|
type: 'warning',
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -1,36 +1,38 @@
|
|||||||
|
import { t } from 'fyo';
|
||||||
import Dialog from 'src/components/Dialog.vue';
|
import Dialog from 'src/components/Dialog.vue';
|
||||||
import Toast from 'src/components/Toast.vue';
|
import Toast from 'src/components/Toast.vue';
|
||||||
import { t } from 'fyo';
|
|
||||||
import { App, createApp, h } from 'vue';
|
import { App, createApp, h } from 'vue';
|
||||||
import { DialogButton, DialogOptions, ToastOptions } from './types';
|
import { getColorClass } from './colors';
|
||||||
|
import { DialogButton, DialogOptions, ToastOptions, ToastType } from './types';
|
||||||
|
|
||||||
type DialogReturn<DO extends DialogOptions> = DO['buttons'] extends {
|
type DialogReturn<DO extends DialogOptions> = DO['buttons'] extends {
|
||||||
handler: () => Promise<infer O> | infer O;
|
action: () => Promise<infer O> | infer O;
|
||||||
}[]
|
}[]
|
||||||
? O
|
? O
|
||||||
: void;
|
: void;
|
||||||
|
|
||||||
export async function showDialog<DO extends DialogOptions>(options: DO) {
|
export async function showDialog<DO extends DialogOptions>(options: DO) {
|
||||||
const { title, description } = options;
|
|
||||||
|
|
||||||
const preWrappedButtons: DialogButton[] = options.buttons ?? [
|
const preWrappedButtons: DialogButton[] = options.buttons ?? [
|
||||||
{ label: t`Okay`, handler: () => {} },
|
{ label: t`Okay`, action: () => {}, isEscape: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
const buttons = preWrappedButtons!.map(({ label, handler, isPrimary }) => {
|
const buttons = preWrappedButtons!.map((config) => {
|
||||||
return {
|
return {
|
||||||
label,
|
...config,
|
||||||
handler: async () => {
|
action: async () => {
|
||||||
resolve(await handler());
|
try {
|
||||||
|
resolve(await config.action());
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
isPrimary,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const dialogApp = createApp({
|
const dialogApp = createApp({
|
||||||
render() {
|
render() {
|
||||||
return h(Dialog, { title, description, buttons });
|
return h(Dialog, { ...options, buttons });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -55,3 +57,23 @@ function fragmentMountComponent(app: App<Element>) {
|
|||||||
app.mount(fragment);
|
app.mount(fragment);
|
||||||
document.body.append(fragment);
|
document.body.append(fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getIconConfig(type: ToastType) {
|
||||||
|
let iconName = 'alert-circle';
|
||||||
|
if (type === 'warning') {
|
||||||
|
iconName = 'alert-triangle';
|
||||||
|
} else if (type === 'success') {
|
||||||
|
iconName = 'check-circle';
|
||||||
|
}
|
||||||
|
|
||||||
|
const color = {
|
||||||
|
info: 'blue',
|
||||||
|
warning: 'orange',
|
||||||
|
error: 'red',
|
||||||
|
success: 'green',
|
||||||
|
}[type];
|
||||||
|
|
||||||
|
const iconColor = getColorClass(color ?? 'gray', 'text', 400);
|
||||||
|
|
||||||
|
return { iconName, color, iconColor };
|
||||||
|
}
|
||||||
|
@ -7,9 +7,8 @@ import { BaseError } from 'fyo/utils/errors';
|
|||||||
import { BackendResponse } from 'utils/ipc/types';
|
import { BackendResponse } from 'utils/ipc/types';
|
||||||
import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages';
|
import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages';
|
||||||
import { SelectFileOptions, SelectFileReturn, TemplateFile } from 'utils/types';
|
import { SelectFileOptions, SelectFileReturn, TemplateFile } from 'utils/types';
|
||||||
import { showToast } from './interactive';
|
import { showDialog, showToast } from './interactive';
|
||||||
import { setLanguageMap } from './language';
|
import { setLanguageMap } from './language';
|
||||||
import { showMessageDialog } from './ui';
|
|
||||||
|
|
||||||
export function reloadWindow() {
|
export function reloadWindow() {
|
||||||
return ipcRenderer.send(IPC_MESSAGES.RELOAD_MAIN_WINDOW);
|
return ipcRenderer.send(IPC_MESSAGES.RELOAD_MAIN_WINDOW);
|
||||||
@ -41,19 +40,22 @@ export async function deleteDb(filePath: string) {
|
|||||||
)) as BackendResponse;
|
)) as BackendResponse;
|
||||||
|
|
||||||
if (error?.code === 'EBUSY') {
|
if (error?.code === 'EBUSY') {
|
||||||
showMessageDialog({
|
showDialog({
|
||||||
message: t`Delete Failed`,
|
title: t`Delete Failed`,
|
||||||
detail: t`Please restart and try again`,
|
detail: t`Please restart and try again`,
|
||||||
|
type: 'error',
|
||||||
});
|
});
|
||||||
} else if (error?.code === 'ENOENT') {
|
} else if (error?.code === 'ENOENT') {
|
||||||
showMessageDialog({
|
showDialog({
|
||||||
message: t`Delete Failed`,
|
title: t`Delete Failed`,
|
||||||
detail: t`File ${filePath} does not exist`,
|
detail: t`File ${filePath} does not exist`,
|
||||||
|
type: 'error',
|
||||||
});
|
});
|
||||||
} else if (error?.code === 'EPERM') {
|
} else if (error?.code === 'EPERM') {
|
||||||
showMessageDialog({
|
showDialog({
|
||||||
message: t`Cannot Delete`,
|
title: t`Cannot Delete`,
|
||||||
detail: t`Close Frappe Books and try manually`,
|
detail: t`Close Frappe Books and try manually`,
|
||||||
|
type: 'error',
|
||||||
});
|
});
|
||||||
} else if (error) {
|
} else if (error) {
|
||||||
const err = new BaseError(500, error.message);
|
const err = new BaseError(500, error.message);
|
||||||
|
@ -101,13 +101,14 @@ export type PrintValues = {
|
|||||||
|
|
||||||
export interface DialogOptions {
|
export interface DialogOptions {
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
type?: ToastType;
|
||||||
|
detail?: string;
|
||||||
buttons?: DialogButton[];
|
buttons?: DialogButton[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DialogButton = {
|
export type DialogButton = {
|
||||||
label: string;
|
label: string;
|
||||||
handler: () => any;
|
action: () => any;
|
||||||
isPrimary?: boolean;
|
isPrimary?: boolean;
|
||||||
isEscape?: boolean;
|
isEscape?: boolean;
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
* Utils to do UI stuff such as opening dialogs, toasts, etc.
|
* Utils to do UI stuff such as opening dialogs, toasts, etc.
|
||||||
* Basically anything that may directly or indirectly import a Vue file.
|
* Basically anything that may directly or indirectly import a Vue file.
|
||||||
*/
|
*/
|
||||||
import { ipcRenderer } from 'electron';
|
|
||||||
import { t } from 'fyo';
|
import { t } from 'fyo';
|
||||||
import type { Doc } from 'fyo/model/doc';
|
import type { Doc } from 'fyo/model/doc';
|
||||||
import { Action } from 'fyo/model/types';
|
import { Action } from 'fyo/model/types';
|
||||||
@ -16,17 +15,16 @@ import { Schema } from 'schemas/types';
|
|||||||
import { handleErrorWithDialog } from 'src/errorHandling';
|
import { handleErrorWithDialog } from 'src/errorHandling';
|
||||||
import { fyo } from 'src/initFyo';
|
import { fyo } from 'src/initFyo';
|
||||||
import router from 'src/router';
|
import router from 'src/router';
|
||||||
import { IPC_ACTIONS } from 'utils/messages';
|
|
||||||
import { SelectFileOptions } from 'utils/types';
|
import { SelectFileOptions } from 'utils/types';
|
||||||
import { RouteLocationRaw } from 'vue-router';
|
import { RouteLocationRaw } from 'vue-router';
|
||||||
import { stringifyCircular } from './';
|
import { stringifyCircular } from './';
|
||||||
import { evaluateHidden } from './doc';
|
import { evaluateHidden } from './doc';
|
||||||
import { showToast } from './interactive';
|
import { showDialog, showToast } from './interactive';
|
||||||
import { selectFile } from './ipcCalls';
|
import { selectFile } from './ipcCalls';
|
||||||
import { showSidebar } from './refs';
|
import { showSidebar } from './refs';
|
||||||
import {
|
import {
|
||||||
ActionGroup,
|
ActionGroup,
|
||||||
MessageDialogOptions,
|
DialogButton,
|
||||||
QuickEditOptions,
|
QuickEditOptions,
|
||||||
SettingsTab,
|
SettingsTab,
|
||||||
ToastOptions,
|
ToastOptions,
|
||||||
@ -96,33 +94,6 @@ export async function openQuickEdit({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
window.openqe = openQuickEdit;
|
|
||||||
|
|
||||||
export async function showMessageDialog({
|
|
||||||
message,
|
|
||||||
detail,
|
|
||||||
buttons = [],
|
|
||||||
}: MessageDialogOptions) {
|
|
||||||
const options = {
|
|
||||||
message,
|
|
||||||
detail,
|
|
||||||
buttons: buttons.map((a) => a.label),
|
|
||||||
};
|
|
||||||
|
|
||||||
const { response } = (await ipcRenderer.invoke(
|
|
||||||
IPC_ACTIONS.GET_DIALOG_RESPONSE,
|
|
||||||
options
|
|
||||||
)) as { response: number };
|
|
||||||
|
|
||||||
const button = buttons[response];
|
|
||||||
if (!button?.action) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await button.action();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function openSettings(tab: SettingsTab) {
|
export async function openSettings(tab: SettingsTab) {
|
||||||
await routeTo({ path: '/settings', query: { tab } });
|
await routeTo({ path: '/settings', query: { tab } });
|
||||||
}
|
}
|
||||||
@ -145,21 +116,22 @@ export async function deleteDocWithPrompt(doc: Doc) {
|
|||||||
detail = t`This action is permanent and will delete associated ledger entries.`;
|
detail = t`This action is permanent and will delete associated ledger entries.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await showMessageDialog({
|
return await showDialog({
|
||||||
message: t`Delete ${getActionLabel(doc)}?`,
|
title: t`Delete ${getActionLabel(doc)}?`,
|
||||||
detail,
|
detail,
|
||||||
|
type: 'warning',
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
label: t`Yes`,
|
label: t`Yes`,
|
||||||
async action() {
|
async action() {
|
||||||
try {
|
try {
|
||||||
await doc.delete();
|
await doc.delete();
|
||||||
return true;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (getDbError(err as Error) === LinkValidationError) {
|
if (getDbError(err as Error) === LinkValidationError) {
|
||||||
showMessageDialog({
|
showDialog({
|
||||||
message: t`Delete Failed`,
|
title: t`Delete Failed`,
|
||||||
detail: t`Cannot delete ${schemaLabel} ${doc.name!} because of linked entries.`,
|
detail: t`Cannot delete ${schemaLabel} ${doc.name!} because of linked entries.`,
|
||||||
|
type: 'error',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
handleErrorWithDialog(err as Error, doc);
|
handleErrorWithDialog(err as Error, doc);
|
||||||
@ -167,13 +139,17 @@ export async function deleteDocWithPrompt(doc: Doc) {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
|
isPrimary: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`No`,
|
label: t`No`,
|
||||||
action() {
|
action() {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
isEscape: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -211,27 +187,31 @@ export async function cancelDocWithPrompt(doc: Doc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await showMessageDialog({
|
return await showDialog({
|
||||||
message: t`Cancel ${getActionLabel(doc)}?`,
|
title: t`Cancel ${getActionLabel(doc)}?`,
|
||||||
detail,
|
detail,
|
||||||
|
type: 'warning',
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
label: t`Yes`,
|
label: t`Yes`,
|
||||||
async action() {
|
async action() {
|
||||||
try {
|
try {
|
||||||
await doc.cancel();
|
await doc.cancel();
|
||||||
return true;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
handleErrorWithDialog(err as Error, doc);
|
handleErrorWithDialog(err as Error, doc);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
|
isPrimary: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`No`,
|
label: t`No`,
|
||||||
action() {
|
action() {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
isEscape: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -596,9 +576,14 @@ 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 = getActionLabel(doc);
|
const label = getActionLabel(doc);
|
||||||
let message = t`Submit ${label}?`;
|
let title = t`Submit ${label}?`;
|
||||||
if (type === 'sync') {
|
if (type === 'sync') {
|
||||||
message = t`Save ${label}?`;
|
title = t`Save ${label}?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let detail = t`Mark ${doc.schema.label} as submitted`;
|
||||||
|
if (type === 'sync') {
|
||||||
|
detail = t`Save ${doc.schema.label} to database`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const yesAction = async () => {
|
const yesAction = async () => {
|
||||||
@ -612,19 +597,22 @@ async function showSubmitOrSyncDialog(doc: Doc, type: 'submit' | 'sync') {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttons = [
|
const buttons: DialogButton[] = [
|
||||||
{
|
{
|
||||||
label: t`Yes`,
|
label: t`Yes`,
|
||||||
action: yesAction,
|
action: yesAction,
|
||||||
|
isPrimary: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`No`,
|
label: t`No`,
|
||||||
action: () => false,
|
action: () => false,
|
||||||
|
isEscape: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return await showMessageDialog({
|
return await showDialog({
|
||||||
message,
|
title,
|
||||||
|
detail,
|
||||||
buttons,
|
buttons,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
onMounted,
|
onMounted,
|
||||||
onUnmounted,
|
onUnmounted,
|
||||||
reactive,
|
reactive,
|
||||||
ref,
|
ref
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { getIsMac } from './misc';
|
import { getIsMac } from './misc';
|
||||||
import { Shortcuts } from './shortcuts';
|
import { Shortcuts } from './shortcuts';
|
||||||
@ -16,7 +16,7 @@ import {
|
|||||||
commonDocSync,
|
commonDocSync,
|
||||||
commongDocDelete,
|
commongDocDelete,
|
||||||
showCannotCancelOrDeleteToast,
|
showCannotCancelOrDeleteToast,
|
||||||
showCannotSaveOrSubmitToast,
|
showCannotSaveOrSubmitToast
|
||||||
} from './ui';
|
} from './ui';
|
||||||
|
|
||||||
export function useKeys() {
|
export function useKeys() {
|
||||||
@ -107,35 +107,35 @@ export function useDocShortcuts(
|
|||||||
context = name + '-' + Math.random().toString(36).slice(2, 6);
|
context = name + '-' + Math.random().toString(36).slice(2, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncOrSubmitCallback = () => {
|
const syncOrSubmitCallback = async () => {
|
||||||
const doc = docRef.value;
|
const doc = docRef.value;
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc.canSave) {
|
if (doc.canSave) {
|
||||||
return commonDocSync(doc, true);
|
return await commonDocSync(doc, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc.canSubmit) {
|
if (doc.canSubmit) {
|
||||||
return commonDocSubmit(doc);
|
return await commonDocSubmit(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
showCannotSaveOrSubmitToast(doc);
|
showCannotSaveOrSubmitToast(doc);
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelOrDeleteCallback = () => {
|
const cancelOrDeleteCallback = async () => {
|
||||||
const doc = docRef.value;
|
const doc = docRef.value;
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc.canCancel) {
|
if (doc.canCancel) {
|
||||||
return commonDocCancel(doc);
|
return await commonDocCancel(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc.canDelete) {
|
if (doc.canDelete) {
|
||||||
return commongDocDelete(doc);
|
return await commongDocDelete(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
showCannotCancelOrDeleteToast(doc);
|
showCannotCancelOrDeleteToast(doc);
|
||||||
|
Loading…
Reference in New Issue
Block a user