mirror of
https://github.com/frappe/books.git
synced 2024-11-09 23:30:56 +00:00
fix(ux): handle directory does not exist
- type DatabaseSelector - fix dialog button width, varios types, injection
This commit is contained in:
parent
6f11d6e6c2
commit
d0fe62946c
@ -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';
|
||||||
|
@ -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[];
|
||||||
|
37
src/App.vue
37
src/App.vue
@ -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');
|
||||||
|
@ -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)"
|
||||||
>
|
>
|
||||||
|
@ -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,
|
||||||
|
@ -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>
|
||||||
|
@ -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
67
src/utils/db.ts
Normal 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,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user