2
0
mirror of https://github.com/frappe/books.git synced 2024-09-19 19:19:02 +00:00

fix(ux): handle directory does not exist

- type DatabaseSelector
- fix dialog button width, varios types, injection
This commit is contained in:
18alantom 2023-04-03 13:51:46 +05:30 committed by Alan
parent 6f11d6e6c2
commit d0fe62946c
9 changed files with 149 additions and 27 deletions

View File

@ -63,7 +63,7 @@ export type ListsMap = Record<string, ListFunction | undefined>;
export interface Action { export interface Action {
label: string; label: string;
action: (doc: Doc, router: Router) => Promise<void> | void; action: (doc: Doc, router: Router) => Promise<void> | void | unknown;
condition?: (doc: Doc) => boolean; condition?: (doc: Doc) => boolean;
group?: string; group?: string;
type?: 'primary' | 'secondary'; type?: 'primary' | 'secondary';

View File

@ -5,10 +5,7 @@ import { Main } from 'main';
import config from 'utils/config'; import config from 'utils/config';
import { BackendResponse } from 'utils/ipc/types'; import { BackendResponse } from 'utils/ipc/types';
import { IPC_CHANNELS } from 'utils/messages'; import { IPC_CHANNELS } from 'utils/messages';
import type { ConfigFilesWithModified } from 'utils/types';
interface ConfigFilesWithModified extends ConfigFile {
modified: string;
}
export async function setAndGetCleanedConfigFiles() { export async function setAndGetCleanedConfigFiles() {
const files = config.get(ConfigKeys.Files, []) as ConfigFile[]; const files = config.get(ConfigKeys.Files, []) as ConfigFile[];

View File

@ -17,6 +17,7 @@
@change-db-file="showDbSelector" @change-db-file="showDbSelector"
/> />
<DatabaseSelector <DatabaseSelector
ref="databaseSelector"
v-if="activeScreen === 'DatabaseSelector'" v-if="activeScreen === 'DatabaseSelector'"
@file-selected="fileSelected" @file-selected="fileSelected"
/> />
@ -49,6 +50,7 @@ import SetupWizard from './pages/SetupWizard/SetupWizard.vue';
import setupInstance from './setup/setupInstance'; import setupInstance from './setup/setupInstance';
import { SetupWizardOptions } from './setup/types'; import { SetupWizardOptions } from './setup/types';
import './styles/index.css'; import './styles/index.css';
import { connectToDatabase, dbErrorActionSymbols } from './utils/db';
import { initializeInstance } from './utils/initialization'; import { initializeInstance } from './utils/initialization';
import * as injectionKeys from './utils/injectionKeys'; import * as injectionKeys from './utils/injectionKeys';
import { checkForUpdates } from './utils/ipcCalls'; import { checkForUpdates } from './utils/ipcCalls';
@ -80,7 +82,17 @@ export default defineComponent({
provide(injectionKeys.shortcutsKey, shortcuts); provide(injectionKeys.shortcutsKey, shortcuts);
provide(injectionKeys.languageDirectionKey, languageDirection); provide(injectionKeys.languageDirectionKey, languageDirection);
return { keys, searcher, shortcuts, languageDirection }; const databaseSelector = ref<InstanceType<typeof DatabaseSelector> | null>(
null
);
return {
keys,
searcher,
shortcuts,
languageDirection,
databaseSelector,
};
}, },
data() { data() {
return { return {
@ -170,7 +182,15 @@ export default defineComponent({
await this.setDesk(filePath); await this.setDesk(filePath);
}, },
async showSetupWizardOrDesk(filePath: string): Promise<void> { async showSetupWizardOrDesk(filePath: string): Promise<void> {
const countryCode = await fyo.db.connectToDatabase(filePath); const { countryCode, error, actionSymbol } = await connectToDatabase(
this.fyo,
filePath
);
if (!countryCode && error && actionSymbol) {
return await this.handleConnectionFailed(error, actionSymbol);
}
const setupComplete = await fyo.getValue( const setupComplete = await fyo.getValue(
ModelNameEnum.AccountingSettings, ModelNameEnum.AccountingSettings,
'setupComplete' 'setupComplete'
@ -185,6 +205,19 @@ export default defineComponent({
await updatePrintTemplates(fyo); await updatePrintTemplates(fyo);
await this.setDesk(filePath); await this.setDesk(filePath);
}, },
async handleConnectionFailed(error: Error, actionSymbol: symbol) {
await this.showDbSelector();
if (actionSymbol === dbErrorActionSymbols.CancelSelection) {
return;
}
if (actionSymbol === dbErrorActionSymbols.SelectFile) {
return await this.databaseSelector?.existingDatabase();
}
throw error;
},
async setDeskRoute(): Promise<void> { async setDeskRoute(): Promise<void> {
const { onboardingComplete } = await fyo.doc.getDoc('GetStarted'); const { onboardingComplete } = await fyo.doc.getDoc('GetStarted');
const { hideGetStarted } = await fyo.doc.getDoc('SystemSettings'); const { hideGetStarted } = await fyo.doc.getDoc('SystemSettings');

View File

@ -32,7 +32,7 @@
v-for="(b, index) of buttons" v-for="(b, index) of buttons"
:ref="b.isPrimary ? 'primary' : 'secondary'" :ref="b.isPrimary ? 'primary' : 'secondary'"
:key="b.label" :key="b.label"
class="w-20" style="min-width: 5rem"
:type="b.isPrimary ? 'primary' : 'secondary'" :type="b.isPrimary ? 'primary' : 'secondary'"
@click="() => handleClick(index)" @click="() => handleClick(index)"
> >

View File

@ -32,7 +32,10 @@ export default defineComponent({
icon: { type: Boolean, default: true }, icon: { type: Boolean, default: true },
}, },
inject: { inject: {
injectedDoc: { from: 'doc' }, injectedDoc: {
from: 'doc',
default: undefined,
},
}, },
components: { components: {
Dropdown, Dropdown,

View File

@ -212,7 +212,7 @@
</Modal> </Modal>
</div> </div>
</template> </template>
<script> <script lang="ts">
import { setupDummyInstance } from 'dummy'; import { setupDummyInstance } from 'dummy';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import { t } from 'fyo'; import { t } from 'fyo';
@ -227,8 +227,10 @@ 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 { IPC_ACTIONS } from 'utils/messages'; import { IPC_ACTIONS } from 'utils/messages';
import type { ConfigFilesWithModified } from 'utils/types';
import { defineComponent } from 'vue';
export default { export default defineComponent({
name: 'DatabaseSelector', name: 'DatabaseSelector',
emits: ['file-selected'], emits: ['file-selected'],
data() { data() {
@ -240,27 +242,36 @@ export default {
creatingDemo: false, creatingDemo: false,
loadingDatabase: false, loadingDatabase: false,
files: [], files: [],
} as {
openModal: boolean;
baseCount: number;
creationMessage: string;
creationPercent: number;
creatingDemo: boolean;
loadingDatabase: boolean;
files: ConfigFilesWithModified[];
}; };
}, },
async mounted() { async mounted() {
await this.setFiles(); await this.setFiles();
if (fyo.store.isDevelopment) { if (fyo.store.isDevelopment) {
// @ts-ignore
window.ds = this; window.ds = this;
} }
}, },
methods: { methods: {
truncate(value) { truncate(value: string) {
if (value.length < 72) { if (value.length < 72) {
return value; return value;
} }
return '...' + value.slice(value.length - 72); return '...' + value.slice(value.length - 72);
}, },
formatDate(isoDate) { formatDate(isoDate: string) {
return DateTime.fromISO(isoDate).toRelative(); return DateTime.fromISO(isoDate).toRelative();
}, },
async deleteDb(i) { async deleteDb(i: number) {
const file = this.files[i]; const file = this.files[i];
const vm = this; const vm = this;
@ -317,7 +328,11 @@ export default {
this.creatingDemo = false; this.creatingDemo = false;
}, },
async setFiles() { async setFiles() {
this.files = (await ipcRenderer.invoke(IPC_ACTIONS.GET_DB_LIST))?.sort( const dbList: ConfigFilesWithModified[] = await ipcRenderer.invoke(
IPC_ACTIONS.GET_DB_LIST
);
this.files = dbList?.sort(
(a, b) => Date.parse(b.modified) - Date.parse(a.modified) (a, b) => Date.parse(b.modified) - Date.parse(a.modified)
); );
}, },
@ -331,7 +346,7 @@ export default {
return; return;
} }
this.connectToDatabase(filePath, true); this.emitFileSelected(filePath, true);
}, },
async existingDatabase() { async existingDatabase() {
if (this.creatingDemo) { if (this.creatingDemo) {
@ -345,16 +360,16 @@ export default {
filters: [{ name: 'SQLite DB File', extensions: ['db'] }], filters: [{ name: 'SQLite DB File', extensions: ['db'] }],
}) })
)?.filePaths?.[0]; )?.filePaths?.[0];
this.connectToDatabase(filePath); this.emitFileSelected(filePath);
}, },
async selectFile(file) { async selectFile(file: ConfigFilesWithModified) {
if (this.creatingDemo) { if (this.creatingDemo) {
return; return;
} }
await this.connectToDatabase(file.dbPath); await this.emitFileSelected(file.dbPath);
}, },
async connectToDatabase(filePath, isNew) { async emitFileSelected(filePath: string, isNew?: boolean) {
if (!filePath) { if (!filePath) {
return; return;
} }
@ -374,5 +389,5 @@ export default {
Modal, Modal,
Button, Button,
}, },
}; });
</script> </script>

View File

@ -123,7 +123,7 @@
:border="true" :border="true"
:key="index" :key="index"
:df="gridColumnTitleDf" :df="gridColumnTitleDf"
:value="importer.assignedTemplateFields[index]" :value="importer.assignedTemplateFields[index]!"
@change="(value: string | null) => importer.setTemplateField(index, value)" @change="(value: string | null) => importer.setTemplateField(index, value)"
/> />
</div> </div>
@ -160,6 +160,7 @@
v-if="!importer.assignedTemplateFields[cidx]" v-if="!importer.assignedTemplateFields[cidx]"
:title="getFieldTitle(val)" :title="getFieldTitle(val)"
:df="{ :df="{
fieldtype: 'Data',
fieldname: 'tempField', fieldname: 'tempField',
label: t`Temporary`, label: t`Temporary`,
placeholder: t`Select column`, placeholder: t`Select column`,
@ -244,6 +245,7 @@
> >
<Check <Check
:df="{ :df="{
fieldtype: 'Check',
fieldname: tf.fieldname, fieldname: tf.fieldname,
label: tf.label, label: tf.label,
}" }"
@ -367,7 +369,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { DocValue } from 'fyo/core/types'; import { DocValue } from 'fyo/core/types';
import { Action as BaseAction } from 'fyo/model/types'; import { Action } from 'fyo/model/types';
import { ValidationError } from 'fyo/utils/errors'; import { ValidationError } from 'fyo/utils/errors';
import { ModelNameEnum } from 'models/types'; import { ModelNameEnum } from 'models/types';
import { OptionField, RawValue, SelectOption } from 'schemas/types'; import { OptionField, RawValue, SelectOption } from 'schemas/types';
@ -391,10 +393,6 @@ 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';
type Action = Pick<BaseAction, 'condition' | 'component'> & {
action: Function;
};
type ImportWizardData = { type ImportWizardData = {
showColumnPicker: boolean; showColumnPicker: boolean;
complete: boolean; complete: boolean;
@ -612,6 +610,7 @@ export default defineComponent({
if (this.canImportData) { if (this.canImportData) {
actions.push({ actions.push({
label: selectFileLabel,
component: { component: {
template: `<span>{{ "${selectFileLabel}" }}</span>`, template: `<span>{{ "${selectFileLabel}" }}</span>`,
}, },
@ -620,6 +619,7 @@ export default defineComponent({
} }
const pickColumnsAction = { const pickColumnsAction = {
label: this.t`Pick Import Columns`,
component: { component: {
template: '<span>{{ t`Pick Import Columns` }}</span>', template: '<span>{{ t`Pick Import Columns` }}</span>',
}, },
@ -627,6 +627,7 @@ export default defineComponent({
}; };
const cancelAction = { const cancelAction = {
label: this.t`Cancel`,
component: { component: {
template: '<span class="text-red-700" >{{ t`Cancel` }}</span>', template: '<span class="text-red-700" >{{ t`Cancel` }}</span>',
}, },

67
src/utils/db.ts Normal file
View File

@ -0,0 +1,67 @@
import { Fyo, t } from 'fyo';
export const dbErrorActionSymbols = {
SelectFile: Symbol('select-file'),
CancelSelection: Symbol('cancel-selection'),
} as const;
const dbErrors = {
DirectoryDoesNotExist: 'directory does not exist',
} as const;
type Conn = {
countryCode: string;
error?: Error;
actionSymbol?: typeof dbErrorActionSymbols[keyof typeof dbErrorActionSymbols];
};
export async function connectToDatabase(
fyo: Fyo,
dbPath: string,
countryCode?: string
): Promise<Conn> {
try {
return { countryCode: await fyo.db.connectToDatabase(dbPath, countryCode) };
} catch (error) {
if (!(error instanceof Error)) {
throw error;
}
const actionSymbol = await handleDatabaseConnectionError(error, dbPath);
return { countryCode: '', error, actionSymbol };
}
}
async function handleDatabaseConnectionError(error: Error, dbPath: string) {
if (error.message?.includes(dbErrors.DirectoryDoesNotExist)) {
return await handleDirectoryDoesNotExist(dbPath);
}
throw error;
}
async function handleDirectoryDoesNotExist(dbPath: string) {
const { showDialog } = await import('src/utils/interactive');
return await showDialog({
type: 'error',
title: t`Cannot Open File`,
detail: t`Directory for file ${dbPath} does not exist`,
buttons: [
{
label: t`Select File`,
action() {
return dbErrorActionSymbols.SelectFile;
},
isPrimary: true,
},
{
label: t`Cancel`,
action() {
return dbErrorActionSymbols.CancelSelection;
},
isEscape: true,
},
],
});
}

View File

@ -1,3 +1,5 @@
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>;
@ -66,3 +68,7 @@ interface ModMap {
shift: boolean; shift: boolean;
repeat: boolean; repeat: boolean;
} }
export interface ConfigFilesWithModified extends ConfigFile {
modified: string;
}