2
0
mirror of https://github.com/frappe/books.git synced 2024-11-12 16:36:27 +00:00

chore: fix all fixable eslint errors in src

This commit is contained in:
18alantom 2023-06-22 11:39:32 +05:30
parent 4415c04a88
commit e67e6ae257
19 changed files with 154 additions and 119 deletions

View File

@ -17,8 +17,8 @@ module.exports = {
'@typescript-eslint/ban-ts-comment': 'off', '@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-var-requires': 'off', '@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-floating-promises': 'off', '@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-misused-promises': 'off', '@typescript-eslint/no-misused-promises': 'warn',
}, },
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
parserOptions: { project: true, tsconfigRootDir: __dirname }, parserOptions: { project: true, tsconfigRootDir: __dirname },
@ -28,5 +28,5 @@ module.exports = {
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking', 'plugin:@typescript-eslint/recommended-requiring-type-checking',
], ],
ignorePatterns: ['.eslintrc.js'], ignorePatterns: ['.eslintrc.js', 'tailwind.config.js'],
}; };

View File

@ -9,7 +9,6 @@ import { fyo } from './initFyo';
import router from './router'; import router from './router';
import { getErrorMessage, stringifyCircular } from './utils'; import { getErrorMessage, stringifyCircular } from './utils';
import { DialogOptions, ToastOptions } from './utils/types'; import { DialogOptions, ToastOptions } from './utils/types';
import { UnknownFunction } from 'utils/types';
const { ipcRenderer } = require('electron'); const { ipcRenderer } = require('electron');
function shouldNotStore(error: Error) { function shouldNotStore(error: Error) {
@ -40,6 +39,7 @@ export async function sendError(errorLogObj: ErrorLog) {
}; };
if (fyo.store.isDevelopment) { if (fyo.store.isDevelopment) {
// eslint-disable-next-line no-console
console.log('sendError', body); console.log('sendError', body);
} }
@ -80,6 +80,7 @@ export async function handleError(
notifyUser = true notifyUser = true
) { ) {
if (logToConsole) { if (logToConsole) {
// eslint-disable-next-line no-console
console.error(error); console.error(error);
} }
@ -93,7 +94,7 @@ export async function handleError(
if (notifyUser) { if (notifyUser) {
const toastProps = getToastProps(errorLogObj); const toastProps = getToastProps(errorLogObj);
const { showToast } = await import('src/utils/interactive'); const { showToast } = await import('src/utils/interactive');
await showToast(toastProps); showToast(toastProps);
} }
} }
@ -140,6 +141,7 @@ export async function handleErrorWithDialog(
await showDialog(options); await showDialog(options);
if (dontThrow) { if (dontThrow) {
if (fyo.store.isDevelopment) { if (fyo.store.isDevelopment) {
// eslint-disable-next-line no-console
console.error(error); console.error(error);
} }
return; return;
@ -156,12 +158,14 @@ export async function showErrorDialog(title?: string, content?: string) {
await ipcRenderer.invoke(IPC_ACTIONS.SHOW_ERROR, { title, content }); await ipcRenderer.invoke(IPC_ACTIONS.SHOW_ERROR, { title, content });
} }
// Wrapper Functions // eslint-disable-next-line @typescript-eslint/no-explicit-any
export function getErrorHandled<T extends (...args: any[]) => Promise<any>>(
export function getErrorHandled(func: UnknownFunction) { func: T
return async function errorHandled(...args: unknown[]) { ) {
type Return = ReturnType<T> extends Promise<infer P> ? P : true;
return async function errorHandled(...args: Parameters<T>): Promise<Return> {
try { try {
return await func(...args); return (await func(...args)) as Return;
} catch (error) { } catch (error) {
await handleError(false, error as Error, { await handleError(false, error as Error, {
functionName: func.name, functionName: func.name,
@ -173,16 +177,19 @@ export function getErrorHandled(func: UnknownFunction) {
}; };
} }
export function getErrorHandledSync(func: UnknownFunction) { // eslint-disable-next-line @typescript-eslint/no-explicit-any
return function errorHandledSync(...args: unknown[]) { export function getErrorHandledSync<T extends (...args: any[]) => any>(
func: T
) {
type Return = ReturnType<T> extends Promise<infer P> ? P : ReturnType<T>;
return function errorHandledSync(...args: Parameters<T>) {
try { try {
return func(...args); return func(...args) as Return;
} catch (error) { } catch (error) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
handleError(false, error as Error, { handleError(false, error as Error, {
functionName: func.name, functionName: func.name,
functionArgs: args, functionArgs: args,
}).then(() => {
throw error;
}); });
} }
}; };

View File

@ -199,7 +199,7 @@ export class Importer {
}[]; }[];
const cellErrors = []; const cellErrors = [];
for (const i in this.valueMatrix) { for (let i = 0; i < this.valueMatrix.length; i++) {
const row = this.valueMatrix[i]; const row = this.valueMatrix[i];
for (const { tf, index } of assigned) { for (const { tf, index } of assigned) {
if (!row[index]?.error) { if (!row[index]?.error) {
@ -278,14 +278,14 @@ export class Importer {
return { dataMap, childTableMap }; return { dataMap, childTableMap };
} }
for (const i in this.valueMatrix) { for (let i = 0; i < this.valueMatrix.length; i++) {
const row = this.valueMatrix[i]; const row = this.valueMatrix[i];
const name = row[nameIndex]?.value; const name = row[nameIndex]?.value;
if (typeof name !== 'string') { if (typeof name !== 'string') {
continue; continue;
} }
for (const j in row) { for (let j = 0; j < row.length; j++) {
const key = this.assignedTemplateFields[j]; const key = this.assignedTemplateFields[j];
const tf = this.templateFieldsMap.get(key ?? ''); const tf = this.templateFieldsMap.get(key ?? '');
if (!tf || !key) { if (!tf || !key) {
@ -387,7 +387,7 @@ export class Importer {
pushToValueMatrixFromParsedRow(row: string[]) { pushToValueMatrixFromParsedRow(row: string[]) {
const vmRow: ValueMatrix[number] = []; const vmRow: ValueMatrix[number] = [];
for (const i in row) { for (let i = 0; i < row.length; i++) {
const rawValue = row[i]; const rawValue = row[i];
const index = Number(i); const index = Number(i);
@ -492,7 +492,7 @@ export class Importer {
return false; return false;
} }
for (const i in row) { for (let i = 0; i < row.length; i++) {
const value = row[i]; const value = row[i];
const tf = this.templateFieldsMap.get(value); const tf = this.templateFieldsMap.get(value);
let key: string | null = value; let key: string | null = value;

View File

@ -6,7 +6,12 @@ import { App as VueApp, createApp } from 'vue';
import App from './App.vue'; import App from './App.vue';
import Badge from './components/Badge.vue'; import Badge from './components/Badge.vue';
import FeatherIcon from './components/FeatherIcon.vue'; import FeatherIcon from './components/FeatherIcon.vue';
import { getErrorHandled, handleError, sendError } from './errorHandling'; import {
getErrorHandled,
getErrorHandledSync,
handleError,
sendError,
} from './errorHandling';
import { fyo } from './initFyo'; import { fyo } from './initFyo';
import { outsideClickDirective } from './renderer/helpers'; import { outsideClickDirective } from './renderer/helpers';
import registerIpcRendererListeners from './renderer/registerIpcRendererListeners'; import registerIpcRendererListeners from './renderer/registerIpcRendererListeners';
@ -14,6 +19,7 @@ import router from './router';
import { stringifyCircular } from './utils'; import { stringifyCircular } from './utils';
import { setLanguageMap } from './utils/language'; import { setLanguageMap } from './utils/language';
// eslint-disable-next-line @typescript-eslint/no-floating-promises
(async () => { (async () => {
const language = fyo.config.get('language') as string; const language = fyo.config.get('language') as string;
if (language) { if (language) {
@ -21,8 +27,8 @@ import { setLanguageMap } from './utils/language';
} }
fyo.store.language = language || 'English'; fyo.store.language = language || 'English';
ipcRenderer.send = getErrorHandled(ipcRenderer.send); ipcRenderer.send = getErrorHandledSync(ipcRenderer.send.bind(ipcRenderer));
ipcRenderer.invoke = getErrorHandled(ipcRenderer.invoke); ipcRenderer.invoke = getErrorHandled(ipcRenderer.invoke.bind(ipcRenderer));
registerIpcRendererListeners(); registerIpcRendererListeners();
const { isDevelopment, platform, version } = (await ipcRenderer.invoke( const { isDevelopment, platform, version } = (await ipcRenderer.invoke(
@ -69,6 +75,7 @@ import { setLanguageMap } from './utils/language';
function setErrorHandlers(app: VueApp) { function setErrorHandlers(app: VueApp) {
window.onerror = (message, source, lineno, colno, error) => { window.onerror = (message, source, lineno, colno, error) => {
error = error ?? new Error('triggered in window.onerror'); error = error ?? new Error('triggered in window.onerror');
// eslint-disable-next-line @typescript-eslint/no-floating-promises
handleError(true, error, { message, source, lineno, colno }); handleError(true, error, { message, source, lineno, colno });
}; };
@ -80,11 +87,13 @@ function setErrorHandlers(app: VueApp) {
error = new Error(String(event.reason)); error = new Error(String(event.reason));
} }
// eslint-disable-next-line no-console
handleError(true, error).catch((err) => console.error(err)); handleError(true, error).catch((err) => console.error(err));
}; };
window.addEventListener(CUSTOM_EVENTS.LOG_UNEXPECTED, (event) => { window.addEventListener(CUSTOM_EVENTS.LOG_UNEXPECTED, (event) => {
const details = (event as CustomEvent)?.detail as UnexpectedLogObject; const details = (event as CustomEvent)?.detail as UnexpectedLogObject;
// eslint-disable-next-line @typescript-eslint/no-floating-promises
sendError(details); sendError(details);
}); });
@ -100,7 +109,9 @@ function setErrorHandlers(app: VueApp) {
more.props = stringifyCircular(vm.$props ?? {}, true, true); more.props = stringifyCircular(vm.$props ?? {}, true, true);
} }
// eslint-disable-next-line @typescript-eslint/no-floating-promises
handleError(false, err as Error, more); handleError(false, err as Error, more);
// eslint-disable-next-line no-console
console.error(err, vm, info); console.error(err, vm, info);
}; };
} }

View File

@ -6,7 +6,7 @@ import { IPC_CHANNELS } from 'utils/messages';
export default function registerIpcRendererListeners() { export default function registerIpcRendererListeners() {
ipcRenderer.on( ipcRenderer.on(
IPC_CHANNELS.LOG_MAIN_PROCESS_ERROR, IPC_CHANNELS.LOG_MAIN_PROCESS_ERROR,
async (_, error, more) => { (_, error: unknown, more?: Record<string, unknown>) => {
if (!(error instanceof Error)) { if (!(error instanceof Error)) {
throw error; throw error;
} }
@ -22,7 +22,8 @@ export default function registerIpcRendererListeners() {
more.isMainProcess = true; more.isMainProcess = true;
more.notifyUser ??= true; more.notifyUser ??= true;
await handleError(true, error, more, more.notifyUser); // eslint-disable-next-line @typescript-eslint/no-floating-promises
handleError(true, error, more, !!more.notifyUser);
} }
); );
@ -32,6 +33,7 @@ export default function registerIpcRendererListeners() {
} }
if (fyo.store.isDevelopment) { if (fyo.store.isDevelopment) {
// eslint-disable-next-line no-console
console.log(...stuff); console.log(...stuff);
} }
}); });
@ -39,6 +41,7 @@ export default function registerIpcRendererListeners() {
document.addEventListener('visibilitychange', () => { document.addEventListener('visibilitychange', () => {
const { visibilityState } = document; const { visibilityState } = document;
if (visibilityState === 'visible' && !fyo.telemetry.started) { if (visibilityState === 'visible' && !fyo.telemetry.started) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
fyo.telemetry.start(); fyo.telemetry.start();
} }

View File

@ -38,7 +38,7 @@ export class CreateCOA {
const child = children[rootName]; const child = children[rootName];
if (rootAccount) { if (rootAccount) {
rootType = (child as COARootAccount).rootType as AccountRootType; rootType = (child as COARootAccount).rootType;
} }
const accountType = (child as COAChildAccount).accountType ?? ''; const accountType = (child as COAChildAccount).accountType ?? '';
@ -83,7 +83,7 @@ function identifyIsGroup(child: COARootAccount | COAChildAccount): boolean {
return false; return false;
} }
async function getCOA(chartOfAccounts: string) { async function getCOA(chartOfAccounts: string): Promise<COATree> {
const coaList = getCOAList(); const coaList = getCOAList();
const coa = coaList.find(({ name }) => name === chartOfAccounts); const coa = coaList.find(({ name }) => name === chartOfAccounts);
@ -93,8 +93,9 @@ async function getCOA(chartOfAccounts: string) {
} }
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const countryCoa = (await import(`../../fixtures/verified/${conCode}.json`)) const countryCoa = (await import(`../../fixtures/verified/${conCode}.json`))
.default; .default as { tree: COATree };
return countryCoa.tree; return countryCoa.tree;
} catch (e) { } catch (e) {
return getStandardCOA(); return getStandardCOA();

View File

@ -133,7 +133,7 @@ async function updateSystemSettings(
const systemSettings = await fyo.doc.getDoc('SystemSettings'); const systemSettings = await fyo.doc.getDoc('SystemSettings');
const instanceId = getRandomString(); const instanceId = getRandomString();
systemSettings.setAndSync({ await systemSettings.setAndSync({
locale, locale,
currency, currency,
instanceId, instanceId,
@ -168,10 +168,8 @@ async function createCurrencyRecords(fyo: Fyo) {
}; };
const doc = checkAndCreateDoc('Currency', docObject, fyo); const doc = checkAndCreateDoc('Currency', docObject, fyo);
if (doc) { promises.push(doc);
promises.push(doc); queue.push(currency);
queue.push(currency);
}
} }
return Promise.all(promises); return Promise.all(promises);
} }
@ -256,7 +254,7 @@ async function checkAndCreateDoc(
schemaName: string, schemaName: string,
docObject: DocValueMap, docObject: DocValueMap,
fyo: Fyo fyo: Fyo
) { ): Promise<Doc | undefined> {
const canCreate = await checkIfExactRecordAbsent(schemaName, docObject, fyo); const canCreate = await checkIfExactRecordAbsent(schemaName, docObject, fyo);
if (!canCreate) { if (!canCreate) {
return; return;
@ -373,12 +371,12 @@ async function updateInventorySettings(fyo: Fyo) {
} }
const settingName = accountTypeDefaultMap[accountType]!; const settingName = accountTypeDefaultMap[accountType]!;
inventorySettings.set(settingName, accounts[0].name); await inventorySettings.set(settingName, accounts[0].name);
} }
const location = fyo.t`Stores`; const location = fyo.t`Stores`;
if (await fyo.db.exists(ModelNameEnum.Location, location)) { if (await fyo.db.exists(ModelNameEnum.Location, location)) {
inventorySettings.set('defaultLocation', location); await inventorySettings.set('defaultLocation', location);
} }
await inventorySettings.sync(); await inventorySettings.sync();

11
src/shims-tsx.d.ts vendored
View File

@ -1,13 +1,12 @@
import Vue, { VNode } from 'vue' import Vue, { VNode } from 'vue';
declare global { declare global {
namespace JSX { namespace JSX {
// tslint:disable no-empty-interface type Element = VNode;
type Element = VNode type ElementClass = Vue;
// tslint:disable no-empty-interface
type ElementClass = Vue
interface IntrinsicElements { interface IntrinsicElements {
[elem: string]: any // eslint-disable-next-line @typescript-eslint/no-explicit-any
[elem: string]: any;
} }
} }
} }

View File

@ -70,7 +70,7 @@ async function handleDirectoryDoesNotExist(dbPath: string) {
async function showDbErrorDialog(detail: string) { async function showDbErrorDialog(detail: string) {
const { showDialog } = await import('src/utils/interactive'); const { showDialog } = await import('src/utils/interactive');
return await showDialog({ return showDialog({
type: 'error', type: 'error',
title: t`Cannot Open File`, title: t`Cannot Open File`,
detail, detail,

View File

@ -14,9 +14,7 @@ export function getGetStartedConfig() {
icon: 'general', icon: 'general',
description: t`Set up your company information, email, country and fiscal year`, description: t`Set up your company information, email, country and fiscal year`,
fieldname: 'companySetup', fieldname: 'companySetup',
action() { action: () => openSettings(ModelNameEnum.AccountingSettings),
openSettings(ModelNameEnum.AccountingSettings);
},
}, },
{ {
key: 'Print', key: 'Print',
@ -24,9 +22,7 @@ export function getGetStartedConfig() {
icon: 'invoice', icon: 'invoice',
description: t`Customize your invoices by adding a logo and address details`, description: t`Customize your invoices by adding a logo and address details`,
fieldname: 'printSetup', fieldname: 'printSetup',
action() { action: () => openSettings(ModelNameEnum.PrintSettings),
openSettings(ModelNameEnum.PrintSettings);
},
}, },
{ {
key: 'System', key: 'System',
@ -34,9 +30,7 @@ export function getGetStartedConfig() {
icon: 'system', icon: 'system',
description: t`Setup system defaults like date format and display precision`, description: t`Setup system defaults like date format and display precision`,
fieldname: 'systemSetup', fieldname: 'systemSetup',
action() { action: () => openSettings(ModelNameEnum.SystemSettings),
openSettings(ModelNameEnum.SystemSettings);
},
}, },
], ],
}, },
@ -49,9 +43,7 @@ export function getGetStartedConfig() {
label: t`Review Accounts`, label: t`Review Accounts`,
icon: 'review-ac', icon: 'review-ac',
description: t`Review your chart of accounts, add any account or tax heads as needed`, description: t`Review your chart of accounts, add any account or tax heads as needed`,
action: () => { action: () => routeTo('/chart-of-accounts'),
routeTo('/chart-of-accounts');
},
fieldname: 'chartOfAccountsReviewed', fieldname: 'chartOfAccountsReviewed',
documentation: documentation:
'https://docs.frappebooks.com/setting-up/initial-entries.html#add-additional-bank-accounts', 'https://docs.frappebooks.com/setting-up/initial-entries.html#add-additional-bank-accounts',

View File

@ -16,11 +16,11 @@ export function stringifyCircular(
obj: unknown, obj: unknown,
ignoreCircular = false, ignoreCircular = false,
convertDocument = false convertDocument = false
) { ): string {
const cacheKey: string[] = []; const cacheKey: string[] = [];
const cacheValue: unknown[] = []; const cacheValue: unknown[] = [];
return JSON.stringify(obj, (key, value) => { return JSON.stringify(obj, (key: string, value: unknown) => {
if (typeof value !== 'object' || value === null) { if (typeof value !== 'object' || value === null) {
cacheKey.push(key); cacheKey.push(key);
cacheValue.push(value); cacheValue.push(value);

View File

@ -5,19 +5,13 @@ import { App, createApp, h } from 'vue';
import { getColorClass } from './colors'; import { getColorClass } from './colors';
import { DialogButton, DialogOptions, ToastOptions, ToastType } from './types'; import { DialogButton, DialogOptions, ToastOptions, ToastType } from './types';
type DialogReturn<DO extends DialogOptions> = DO['buttons'] extends {
action: () => Promise<infer O> | infer O;
}[]
? O
: void;
export async function showDialog<DO extends DialogOptions>(options: DO) { export async function showDialog<DO extends DialogOptions>(options: DO) {
const preWrappedButtons: DialogButton[] = options.buttons ?? [ const preWrappedButtons: DialogButton[] = options.buttons ?? [
{ label: t`Okay`, action: () => null, isEscape: true }, { label: t`Okay`, action: () => null, isEscape: true },
]; ];
return new Promise(async (resolve, reject) => { const resultPromise = new Promise((resolve, reject) => {
const buttons = preWrappedButtons!.map((config) => { const buttons = preWrappedButtons.map((config) => {
return { return {
...config, ...config,
action: async () => { action: async () => {
@ -37,10 +31,12 @@ export async function showDialog<DO extends DialogOptions>(options: DO) {
}); });
fragmentMountComponent(dialogApp); fragmentMountComponent(dialogApp);
}) as DialogReturn<DO>; });
return await resultPromise;
} }
export async function showToast(options: ToastOptions) { export function showToast(options: ToastOptions) {
const toastApp = createApp({ const toastApp = createApp({
render() { render() {
return h(Toast, { ...options }); return h(Toast, { ...options });

View File

@ -4,9 +4,14 @@
const { ipcRenderer } = require('electron'); const { ipcRenderer } = require('electron');
import { t } from 'fyo'; import { t } from 'fyo';
import { BaseError } from 'fyo/utils/errors'; import { BaseError } from 'fyo/utils/errors';
import { BackendResponse } from 'utils/ipc/types'; import type { 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 type {
LanguageMap,
SelectFileOptions,
SelectFileReturn,
TemplateFile,
} from 'utils/types';
import { showDialog, showToast } from './interactive'; import { showDialog, showToast } from './interactive';
import { setLanguageMap } from './language'; import { setLanguageMap } from './language';
import type { OpenDialogReturnValue } from 'electron'; import type { OpenDialogReturnValue } from 'electron';
@ -15,22 +20,35 @@ export function reloadWindow() {
return ipcRenderer.send(IPC_MESSAGES.RELOAD_MAIN_WINDOW); return ipcRenderer.send(IPC_MESSAGES.RELOAD_MAIN_WINDOW);
} }
export async function getLanguageMap(code: string) {
return (await ipcRenderer.invoke(IPC_ACTIONS.GET_LANGUAGE_MAP, code)) as {
languageMap: LanguageMap;
success: boolean;
message: string;
};
}
export async function getSelectedFilePath(): Promise<OpenDialogReturnValue> { export async function getSelectedFilePath(): Promise<OpenDialogReturnValue> {
return await ipcRenderer.invoke(IPC_ACTIONS.GET_OPEN_FILEPATH, { return (await ipcRenderer.invoke(IPC_ACTIONS.GET_OPEN_FILEPATH, {
title: this.t`Select file`, title: t`Select file`,
properties: ['openFile'], properties: ['openFile'],
filters: [{ name: 'SQLite DB File', extensions: ['db'] }], filters: [{ name: 'SQLite DB File', extensions: ['db'] }],
}); })) as OpenDialogReturnValue;
} }
export async function getTemplates(): Promise<TemplateFile[]> { export async function getTemplates(): Promise<TemplateFile[]> {
return await ipcRenderer.invoke(IPC_ACTIONS.GET_TEMPLATES); return (await ipcRenderer.invoke(
IPC_ACTIONS.GET_TEMPLATES
)) as TemplateFile[];
} }
export async function selectFile( export async function selectFile(
options: SelectFileOptions options: SelectFileOptions
): Promise<SelectFileReturn> { ): Promise<SelectFileReturn> {
return await ipcRenderer.invoke(IPC_ACTIONS.SELECT_FILE, options); return (await ipcRenderer.invoke(
IPC_ACTIONS.SELECT_FILE,
options
)) as SelectFileReturn;
} }
export async function checkForUpdates() { export async function checkForUpdates() {
@ -38,7 +56,7 @@ export async function checkForUpdates() {
await setLanguageMap(); await setLanguageMap();
} }
export async function openLink(link: string) { export function openLink(link: string) {
ipcRenderer.send(IPC_MESSAGES.OPEN_EXTERNAL, link); ipcRenderer.send(IPC_MESSAGES.OPEN_EXTERNAL, link);
} }
@ -49,19 +67,19 @@ export async function deleteDb(filePath: string) {
)) as BackendResponse; )) as BackendResponse;
if (error?.code === 'EBUSY') { if (error?.code === 'EBUSY') {
showDialog({ await showDialog({
title: t`Delete Failed`, title: t`Delete Failed`,
detail: t`Please restart and try again.`, detail: t`Please restart and try again.`,
type: 'error', type: 'error',
}); });
} else if (error?.code === 'ENOENT') { } else if (error?.code === 'ENOENT') {
showDialog({ await showDialog({
title: t`Delete Failed`, title: t`Delete Failed`,
detail: t`File ${filePath} does not exist.`, detail: t`File ${filePath} does not exist.`,
type: 'error', type: 'error',
}); });
} else if (error?.code === 'EPERM') { } else if (error?.code === 'EPERM') {
showDialog({ await showDialog({
title: 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', type: 'error',
@ -87,14 +105,14 @@ export async function makePDF(
savePath: string, savePath: string,
width: number, width: number,
height: number height: number
) { ): Promise<void> {
const success = await ipcRenderer.invoke( const success = (await ipcRenderer.invoke(
IPC_ACTIONS.SAVE_HTML_AS_PDF, IPC_ACTIONS.SAVE_HTML_AS_PDF,
html, html,
savePath, savePath,
width, width,
height height
); )) as boolean;
if (success) { if (success) {
showExportInFolder(t`Save as PDF Successful`, savePath); showExportInFolder(t`Save as PDF Successful`, savePath);
@ -108,8 +126,8 @@ export function showExportInFolder(message: string, filePath: string) {
message, message,
actionText: t`Open Folder`, actionText: t`Open Folder`,
type: 'success', type: 'success',
action: async () => { action: () => {
await showItemInFolder(filePath); showItemInFolder(filePath);
}, },
}); });
} }

View File

@ -1,9 +1,7 @@
const { ipcRenderer } = require('electron');
import { DEFAULT_LANGUAGE } from 'fyo/utils/consts'; import { DEFAULT_LANGUAGE } from 'fyo/utils/consts';
import { setLanguageMapOnTranslationString } from 'fyo/utils/translation'; import { setLanguageMapOnTranslationString } from 'fyo/utils/translation';
import { fyo } from 'src/initFyo'; import { fyo } from 'src/initFyo';
import { IPC_ACTIONS } from 'utils/messages'; import { getLanguageMap, reloadWindow } from './ipcCalls';
import { reloadWindow } from './ipcCalls';
import { systemLanguageRef } from './refs'; import { systemLanguageRef } from './refs';
// Language: Language Code in books/translations // Language: Language Code in books/translations
@ -65,17 +63,14 @@ function getLanguageCode(initLanguage: string, oldLanguage: string) {
} }
async function fetchAndSetLanguageMap(code: string) { async function fetchAndSetLanguageMap(code: string) {
const { success, message, languageMap } = await ipcRenderer.invoke( const { success, message, languageMap } = await getLanguageMap(code);
IPC_ACTIONS.GET_LANGUAGE_MAP,
code
);
if (!success) { if (!success) {
const { showToast } = await import('src/utils/interactive'); const { showToast } = await import('src/utils/interactive');
showToast({ type: 'error', message }); showToast({ type: 'error', message });
} else { } else {
setLanguageMapOnTranslationString(languageMap); setLanguageMapOnTranslationString(languageMap);
fyo.db.translateSchemaMap(languageMap); await fyo.db.translateSchemaMap(languageMap);
} }
return success; return success;

View File

@ -24,7 +24,7 @@ interface SearchItem {
label: string; label: string;
group: Exclude<SearchGroup, 'Docs'>; group: Exclude<SearchGroup, 'Docs'>;
route?: string; route?: string;
action?: () => void; action?: () => void | Promise<void>;
} }
interface DocSearchItem extends Omit<SearchItem, 'group'> { interface DocSearchItem extends Omit<SearchItem, 'group'> {
@ -319,8 +319,8 @@ function getNonDocSearchList(fyo: Fyo) {
.flat() .flat()
.map((d) => { .map((d) => {
if (d.route && !d.action) { if (d.route && !d.action) {
d.action = () => { d.action = async () => {
routeTo(d.route!); await routeTo(d.route!);
}; };
} }
return d; return d;

View File

@ -114,7 +114,7 @@ export interface DialogOptions {
export type DialogButton = { export type DialogButton = {
label: string; label: string;
action: () => any; action: () => unknown;
isPrimary?: boolean; isPrimary?: boolean;
isEscape?: boolean; isEscape?: boolean;
}; };

View File

@ -7,6 +7,7 @@ import type { Doc } from 'fyo/model/doc';
import { Action } from 'fyo/model/types'; import { Action } from 'fyo/model/types';
import { getActions } from 'fyo/utils'; import { getActions } from 'fyo/utils';
import { getDbError, LinkValidationError, ValueError } from 'fyo/utils/errors'; import { getDbError, LinkValidationError, ValueError } from 'fyo/utils/errors';
import { Invoice } from 'models/baseModels/Invoice/Invoice';
import { PurchaseInvoice } from 'models/baseModels/PurchaseInvoice/PurchaseInvoice'; import { PurchaseInvoice } from 'models/baseModels/PurchaseInvoice/PurchaseInvoice';
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice'; import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
import { getLedgerLink } from 'models/helpers'; import { getLedgerLink } from 'models/helpers';
@ -25,13 +26,12 @@ import { selectFile } from './ipcCalls';
import { showSidebar } from './refs'; import { showSidebar } from './refs';
import { import {
ActionGroup, ActionGroup,
DialogButton,
QuickEditOptions, QuickEditOptions,
SettingsTab, SettingsTab,
ToastOptions, ToastOptions,
UIGroupedFields, UIGroupedFields,
} from './types'; } from './types';
import { Invoice } from 'models/baseModels/Invoice/Invoice'; import { assertIsType } from 'utils/index';
export const toastDurationMap = { short: 2_500, long: 5_000 } as const; export const toastDurationMap = { short: 2_500, long: 5_000 } as const;
@ -56,7 +56,7 @@ export async function openQuickEdit({
showFields, showFields,
hideFields, hideFields,
}; };
router.push({ query }); await router.push({ query });
} }
export async function openSettings(tab: SettingsTab) { export async function openSettings(tab: SettingsTab) {
@ -81,7 +81,7 @@ 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 showDialog({ return (await showDialog({
title: t`Delete ${getDocReferenceLabel(doc)}?`, title: t`Delete ${getDocReferenceLabel(doc)}?`,
detail, detail,
type: 'warning', type: 'warning',
@ -93,13 +93,13 @@ export async function deleteDocWithPrompt(doc: Doc) {
await doc.delete(); await doc.delete();
} catch (err) { } catch (err) {
if (getDbError(err as Error) === LinkValidationError) { if (getDbError(err as Error) === LinkValidationError) {
showDialog({ await showDialog({
title: 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', type: 'error',
}); });
} else { } else {
handleErrorWithDialog(err as Error, doc); await handleErrorWithDialog(err as Error, doc);
} }
return false; return false;
@ -117,7 +117,7 @@ export async function deleteDocWithPrompt(doc: Doc) {
isEscape: true, isEscape: true,
}, },
], ],
}); })) as boolean;
} }
export async function cancelDocWithPrompt(doc: Doc) { export async function cancelDocWithPrompt(doc: Doc) {
@ -152,7 +152,7 @@ export async function cancelDocWithPrompt(doc: Doc) {
} }
} }
return await showDialog({ return (await showDialog({
title: t`Cancel ${getDocReferenceLabel(doc)}?`, title: t`Cancel ${getDocReferenceLabel(doc)}?`,
detail, detail,
type: 'warning', type: 'warning',
@ -163,7 +163,7 @@ export async function cancelDocWithPrompt(doc: Doc) {
try { try {
await doc.cancel(); await doc.cancel();
} catch (err) { } catch (err) {
handleErrorWithDialog(err as Error, doc); await handleErrorWithDialog(err as Error, doc);
return false; return false;
} }
@ -179,7 +179,7 @@ export async function cancelDocWithPrompt(doc: Doc) {
isEscape: true, isEscape: true,
}, },
], ],
}); })) as boolean;
} }
export function getActionsForDoc(doc?: Doc): Action[] { export function getActionsForDoc(doc?: Doc): Action[] {
@ -280,7 +280,7 @@ function getDuplicateAction(doc: Doc): Action {
const dupe = doc.duplicate(); const dupe = doc.duplicate();
await openEdit(dupe); await openEdit(dupe);
} catch (err) { } catch (err) {
handleErrorWithDialog(err as Error, doc); await handleErrorWithDialog(err as Error, doc);
} }
}, },
}; };
@ -385,8 +385,8 @@ export function toggleSidebar(value?: boolean) {
export function focusOrSelectFormControl( export function focusOrSelectFormControl(
doc: Doc, doc: Doc,
ref: any, ref: unknown,
clear= true shouldClear = true
) { ) {
if (!doc?.fyo) { if (!doc?.fyo) {
return; return;
@ -405,7 +405,15 @@ export function focusOrSelectFormControl(
ref = ref[0]; ref = ref[0];
} }
if (!clear && typeof ref?.select === 'function') { if (
!ref ||
typeof ref !== 'object' ||
!assertIsType<Record<string, () => void>>(ref)
) {
return;
}
if (!shouldClear && typeof ref?.select === 'function') {
ref.select(); ref.select();
return; return;
} }
@ -429,7 +437,7 @@ export async function selectTextFile(filters?: SelectFileOptions['filters']) {
const { success, canceled, filePath, data, name } = await selectFile(options); const { success, canceled, filePath, data, name } = await selectFile(options);
if (canceled || !success) { if (canceled || !success) {
await showToast({ showToast({
type: 'error', type: 'error',
message: t`File selection failed`, message: t`File selection failed`,
}); });
@ -438,7 +446,7 @@ export async function selectTextFile(filters?: SelectFileOptions['filters']) {
const text = new TextDecoder().decode(data); const text = new TextDecoder().decode(data);
if (!text) { if (!text) {
await showToast({ showToast({
type: 'error', type: 'error',
message: t`Empty file selected`, message: t`Empty file selected`,
}); });
@ -528,7 +536,7 @@ async function syncWithoutDialog(doc: Doc): Promise<boolean> {
try { try {
await doc.sync(); await doc.sync();
} catch (error) { } catch (error) {
handleErrorWithDialog(error, doc); await handleErrorWithDialog(error, doc);
return false; return false;
} }
@ -563,14 +571,14 @@ async function showSubmitOrSyncDialog(doc: Doc, type: 'submit' | 'sync') {
try { try {
await doc[type](); await doc[type]();
} catch (error) { } catch (error) {
handleErrorWithDialog(error, doc); await handleErrorWithDialog(error, doc);
return false; return false;
} }
return true; return true;
}; };
const buttons: DialogButton[] = [ const buttons = [
{ {
label: t`Yes`, label: t`Yes`,
action: yesAction, action: yesAction,
@ -583,11 +591,13 @@ async function showSubmitOrSyncDialog(doc: Doc, type: 'submit' | 'sync') {
}, },
]; ];
return await showDialog({ const dialogOptions = {
title, title,
detail, detail,
buttons, buttons,
}); };
return (await showDialog(dialogOptions)) as boolean;
} }
function getDocSyncMessage(doc: Doc): string { function getDocSyncMessage(doc: Doc): string {

View File

@ -8,7 +8,7 @@ export function getValueMapFromList<T, K extends keyof T, V extends keyof T>(
list: T[], list: T[],
key: K, key: K,
valueKey: V, valueKey: V,
filterUndefined: boolean = true filterUndefined = true
): Record<string, T[V]> { ): Record<string, T[V]> {
if (filterUndefined) { if (filterUndefined) {
list = list.filter( list = list.filter(
@ -32,7 +32,7 @@ export function getRandomString(): string {
return `${randomNumber}-${currentTime}`; return `${randomNumber}-${currentTime}`;
} }
export async function sleep(durationMilliseconds: number = 1000) { export async function sleep(durationMilliseconds = 1000) {
return new Promise((r) => setTimeout(() => r(null), durationMilliseconds)); return new Promise((r) => setTimeout(() => r(null), durationMilliseconds));
} }
@ -267,3 +267,9 @@ export function objectForEach<T extends object | unknown>(
return func(obj); return func(obj);
} }
/**
* Asserts that `value` is of type T. Use with care.
*/
export const assertIsType = <T>(value: unknown): value is T => true;

View File

@ -3,7 +3,6 @@ import type { ConfigFile } from 'fyo/core/types';
export type UnknownMap = Record<string, unknown>; export type UnknownMap = Record<string, unknown>;
export type Translation = { translation: string; context?: string }; export type Translation = { translation: string; context?: string };
export type LanguageMap = Record<string, Translation>; export type LanguageMap = Record<string, Translation>;
export type UnknownFunction = (...args: unknown[]) => unknown;
export type CountryInfoMap = Record<string, CountryInfo | undefined>; export type CountryInfoMap = Record<string, CountryInfo | undefined>;
export interface CountryInfo { export interface CountryInfo {