import frappe from 'frappejs'; import fs from 'fs'; import { _ } from 'frappejs/utils'; import migrate from './migrate'; import { remote, shell, ipcRenderer } from 'electron'; import SQLite from 'frappejs/backends/sqlite'; import postStart from '../server/postStart'; import router from '@/router'; import Avatar from '@/components/Avatar'; import config from '@/config'; export async function createNewDatabase() { const options = { title: _('Select folder'), defaultPath: 'frappe-books.db' }; let { filePath } = await remote.dialog.showSaveDialog(options); if (filePath) { if (!filePath.endsWith('.db')) { filePath = filePath + '.db'; } if (fs.existsSync(filePath)) { showMessageDialog({ // prettier-ignore message: _('A file exists with the same name and it will be overwritten. Are you sure you want to continue?'), buttons: [ { label: _('Overwrite'), action() { fs.unlinkSync(filePath); return filePath; } }, { label: _('Cancel'), action() {} } ] }); } else { return filePath; } } } export async function loadExistingDatabase() { const options = { title: _('Select file'), properties: ['openFile'], filters: [{ name: 'SQLite DB File', extensions: ['db'] }] }; let { filePaths } = await remote.dialog.showOpenDialog(options); if (filePaths && filePaths[0]) { return filePaths[0]; } } export async function connectToLocalDatabase(filepath) { frappe.login('Administrator'); frappe.db = new SQLite({ dbPath: filepath }); await frappe.db.connect(); await migrate(); await postStart(); // set file info in config let files = config.get('files') || []; if (!files.find(file => file.filePath === filepath)) { files = [ { companyName: frappe.AccountingSettings.companyName, filePath: filepath }, ...files ]; config.set('files', files); } // set last selected file config.set('lastSelectedFilePath', filepath); } export async function showMessageDialog({ message, description, buttons = [] }) { let buttonLabels = buttons.map(a => a.label); const { response } = await remote.dialog.showMessageBox( remote.getCurrentWindow(), { message, detail: description, buttons: buttonLabels } ); let button = buttons[response]; if (button && button.action) { button.action(); } } export function deleteDocWithPrompt(doc) { return new Promise(resolve => { showMessageDialog({ message: _('Are you sure you want to delete {0} "{1}"?', [ doc.doctype, doc.name ]), description: _('This action is permanent'), buttons: [ { label: _('Delete'), action: () => { doc .delete() .then(() => resolve(true)) .catch(e => { handleErrorWithDialog(e, doc); }); } }, { label: _('Cancel'), action() { resolve(false); } } ] }); }); } export function partyWithAvatar(party) { return { data() { return { imageURL: null, label: null }; }, components: { Avatar }, async mounted() { this.imageURL = await frappe.db.getValue('Party', party, 'image'); this.label = party; }, template: `
{{ label }}
` }; } export function openQuickEdit({ doctype, name, hideFields, defaults = {} }) { let currentRoute = router.currentRoute; let query = currentRoute.query; let method = 'push'; if (query.edit && query.doctype === doctype) { // replace the current route if we are // editing another document of the same doctype method = 'replace'; } router[method]({ query: { edit: 1, doctype, name, hideFields, values: defaults, lastRoute: currentRoute } }); } export function getErrorMessage(e, doc) { let errorMessage = e.message || _('An error occurred'); if (e.type === frappe.errors.LinkValidationError) { errorMessage = _('{0} {1} is linked with existing records.', [ doc.doctype, doc.name ]); } else if (e.type === frappe.errors.DuplicateEntryError) { errorMessage = _('{0} {1} already exists.', [doc.doctype, doc.name]); } return errorMessage; } export function handleErrorWithDialog(e, doc) { let errorMessage = getErrorMessage(e, doc); showMessageDialog({ message: errorMessage }); throw e; } // NOTE: a hack to find all the css from the current document and inject it to the print version // remove this if you are able to fix and get the default css loading on the page function injectCSS(contents) { const styles = document.getElementsByTagName('style'); for (let style of styles) { contents.insertCSS(style.innerHTML); } } export function makePDF(html, destination) { const { BrowserWindow } = remote; let printWindow = new BrowserWindow({ width: 595, height: 842, show: false, webPreferences: { nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION, enableRemoteModule: true } }); let webpackDevServerURL = remote.getGlobal('WEBPACK_DEV_SERVER_URL'); if (webpackDevServerURL) { // Load the url of the dev server if in development mode printWindow.loadURL(webpackDevServerURL + 'print'); } else { // Load the index.html when not in development printWindow.loadURL(`app://./print.html`); } printWindow.on('closed', () => { printWindow = null; }); const code = ` document.body.innerHTML = \`${html}\`; `; printWindow.webContents.executeJavaScript(code); const printOptions = { marginsType: 1, // no margin pageSize: 'A4', printBackground: true, printBackgrounds: true, printSelectionOnly: false }; return new Promise(resolve => { printWindow.webContents.on('did-finish-load', () => { injectCSS(printWindow.webContents); const sleep = m => new Promise(r => setTimeout(r, m)); (async () => { await sleep(3000); printWindow.webContents.printToPDF(printOptions).then(data => { printWindow.close(); fs.writeFile(destination, data, error => { if (error) throw error; resolve(shell.openItem(destination)); }); }); })(); }); }); } export function getActionsForDocument(doc) { if (!doc) return []; let deleteAction = { component: { template: `{{ _('Delete') }}` }, condition: doc => !doc.isNew() && !doc.submitted && !doc.meta.isSingle, action: () => deleteDocWithPrompt(doc).then(res => { if (res) { router.push(`/list/${doc.doctype}`); } }) }; let actions = [...(doc.meta.actions || []), deleteAction] .filter(d => (d.condition ? d.condition(doc) : true)) .map(d => { return { label: d.label, component: d.component, action: d.action.bind(this, doc, router) }; }); return actions; } export function openSettings(tab = 'General') { ipcRenderer.send('open-settings-window', tab); }