diff --git a/package.json b/package.json
index 8c7df897..577cd7f0 100644
--- a/package.json
+++ b/package.json
@@ -39,7 +39,7 @@
"@vue/cli-plugin-router": "^4.5.0",
"@vue/cli-service": "^4.5.0",
"autoprefixer": "^9",
- "electron": "^8.0.0",
+ "electron": "^8.5.5",
"electron-devtools-installer": "^3.2.0",
"electron-notarize": "^1.1.1",
"electron-updater": "^4.3.9",
@@ -53,8 +53,7 @@
"raw-loader": "^4.0.2",
"tailwindcss": "npm:@tailwindcss/postcss7-compat",
"vue-cli-plugin-electron-builder": "^2.0.0",
- "vue-template-compiler": "^2.6.10",
- "webpack": "^5.61.0"
+ "vue-template-compiler": "^2.6.10"
},
"gitHooks": {
"pre-commit": "lint-staged"
diff --git a/src/App.vue b/src/App.vue
index 5befd873..f00053c9 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -30,43 +30,43 @@ import SetupWizard from './pages/SetupWizard/SetupWizard';
import DatabaseSelector from './pages/DatabaseSelector';
import Settings from '@/pages/Settings/Settings.vue';
import WindowsTitleBar from '@/components/WindowsTitleBar';
-import { remote } from 'electron';
+import { ipcRenderer } from 'electron';
import config from '@/config';
import { connectToLocalDatabase } from '@/utils';
-import { getMainWindowSize } from '@/screenSize';
+import { IPC_MESSAGES, IPC_ACTIONS } from '@/messages';
export default {
name: 'App',
data() {
return {
- activeScreen: null
+ activeScreen: null,
};
},
watch: {
- activeScreen(value) {
+ async activeScreen(value) {
if (!value) return;
- let { width, height } = getMainWindowSize();
+ const { width, height } = ipcRenderer.invoke(
+ IPC_ACTIONS.GET_PRIMARY_DISPLAY_SIZE
+ );
let size = {
Desk: [width, height],
DatabaseSelector: [600, 600],
SetupWizard: [600, 600],
- Settings: [460, 577]
+ Settings: [460, 577],
}[value];
let resizable = value === 'Desk';
- let win = remote.getCurrentWindow();
- if (size.length) {
- win.setSize(...size);
- win.setResizable(resizable);
+ if (size.length && value != 'Settings') {
+ ipcRenderer.send(IPC_MESSAGES.RESIZE_MAIN_WINDOW, size, resizable);
}
- }
+ },
},
components: {
Desk,
SetupWizard,
DatabaseSelector,
Settings,
- WindowsTitleBar
+ WindowsTitleBar,
},
async mounted() {
let lastSelectedFilePath = config.get('lastSelectedFilePath', null);
@@ -99,7 +99,7 @@ export default {
},
checkForUpdates() {
frappe.events.trigger('check-for-updates');
- }
- }
+ },
+ },
};
diff --git a/src/background.js b/src/background.js
index d30a7560..a9a22c4f 100644
--- a/src/background.js
+++ b/src/background.js
@@ -1,32 +1,54 @@
'use strict';
-import { app, protocol, BrowserWindow, ipcMain } from 'electron';
+import electron, {
+ app,
+ dialog,
+ protocol,
+ BrowserWindow,
+ ipcMain,
+ Menu,
+} from 'electron';
import { autoUpdater } from 'electron-updater';
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer';
-import {
- createProtocol
- // installVueDevtools
-} from 'vue-cli-plugin-electron-builder/lib';
+import { createProtocol } from 'vue-cli-plugin-electron-builder/lib';
+
+import saveHtmlAsPdf from './saveHtmlAsPdf';
+import { IPC_MESSAGES, IPC_ACTIONS } from './messages';
import theme from '@/theme';
-import { getMainWindowSize } from './screenSize';
const isDevelopment = process.env.NODE_ENV !== 'production';
const isMac = process.platform === 'darwin';
const isLinux = process.platform === 'linux';
-// Keep a global reference of the window object, if you don't, the window will
-// be closed automatically when the JavaScript object is garbage collected.
+// Global ref to prevent garbage collection.
let mainWindow;
let winURL;
let checkedForUpdate = false;
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
- { scheme: 'app', privileges: { secure: true, standard: true } }
+ { scheme: 'app', privileges: { secure: true, standard: true } },
]);
+/* -----------------------------
+ * Main process helper functions
+ * -----------------------------*/
+
+function getMainWindowSize() {
+ let height;
+ if (app.isReady()) {
+ // const screen = require('electron').screen;
+ const screen = electron.screen;
+ height = screen.getPrimaryDisplay().workAreaSize.height;
+ height = height > 907 ? 907 : height;
+ } else {
+ height = 907;
+ }
+ const width = Math.ceil(1.323 * height);
+ return { height, width };
+}
+
function createWindow() {
- // Create the browser window.
let { width, height } = getMainWindowSize();
mainWindow = new BrowserWindow({
vibrancy: 'sidebar',
@@ -35,10 +57,10 @@ function createWindow() {
width,
height,
webPreferences: {
- nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION
+ nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
},
frame: isLinux,
- resizable: true
+ resizable: true,
});
if (process.env.WEBPACK_DEV_SERVER_URL) {
@@ -58,6 +80,12 @@ function createWindow() {
mainWindow.on('closed', () => {
mainWindow = null;
});
+
+ mainWindow.webContents.on('did-finish-load', () => {
+ mainWindow.webContents.send('store-on-window', {
+ appVersion: app.getVersion(),
+ });
+ });
}
function createSettingsWindow(tab = 'General') {
@@ -68,49 +96,111 @@ function createSettingsWindow(tab = 'General') {
height: 577,
backgroundColor: theme.backgroundColor.gray['200'],
webPreferences: {
- nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION
+ nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
},
- resizable: false
+ resizable: false,
});
settingsWindow.loadURL(`${winURL}#/settings/${tab}`);
+ settingsWindow.on('close', () => {
+ mainWindow.reload();
+ });
}
-ipcMain.on('check-for-updates', () => {
+/* ---------------------------------
+ * Register ipcMain message handlers
+ * ---------------------------------*/
+
+ipcMain.on(IPC_MESSAGES.CHECK_FOR_UPDATES, () => {
if (!isDevelopment && !checkedForUpdate) {
autoUpdater.checkForUpdatesAndNotify();
checkedForUpdate = true;
}
});
-ipcMain.on('open-settings-window', (event, tab) => {
+ipcMain.on(IPC_MESSAGES.OPEN_SETTINGS, (event, tab) => {
createSettingsWindow(tab);
});
-ipcMain.on('reload-main-window', () => {
+ipcMain.on(IPC_MESSAGES.OPEN_SETTINGS, (event) => {
+ const window = event.sender.getOwnerBrowserWindow();
+ const menu = Menu.getApplicationMenu();
+ menu.popup({ window });
+});
+
+ipcMain.on(IPC_MESSAGES.RELOAD_MAIN_WINDOW, () => {
mainWindow.reload();
});
-// Quit when all windows are closed.
+ipcMain.on(IPC_MESSAGES.RESIZE_MAIN_WINDOW, (event, size, resizable) => {
+ const [width, height] = size;
+ if (!width || !height) return;
+ mainWindow.setSize(width, height);
+ mainWindow.setResizable(resizable);
+});
+
+ipcMain.on(IPC_MESSAGES.CLOSE_CURRENT_WINDOW, (event) => {
+ event.sender.getOwnerBrowserWindow().close();
+});
+
+ipcMain.on(IPC_MESSAGES.MINIMIZE_CURRENT_WINDOW, (event) => {
+ event.sender.getOwnerBrowserWindow().minimize();
+});
+
+/* ----------------------------------
+ * Register ipcMain function handlers
+ * ----------------------------------*/
+
+ipcMain.handle(IPC_ACTIONS.TOGGLE_MAXIMIZE_CURRENT_WINDOW, (event) => {
+ const window = event.sender.getOwnerBrowserWindow();
+ const maximizing = !window.isMaximized();
+ if (maximizing) {
+ window.maximize();
+ } else {
+ window.unmaximize();
+ }
+ return maximizing;
+});
+
+ipcMain.handle(IPC_ACTIONS.GET_OPEN_FILEPATH, async (event, options) => {
+ const window = event.sender.getOwnerBrowserWindow();
+ return await dialog.showOpenDialog(window, options);
+});
+
+ipcMain.handle(IPC_ACTIONS.GET_SAVE_FILEPATH, async (event, options) => {
+ const window = event.sender.getOwnerBrowserWindow();
+ return await dialog.showSaveDialog(window, options);
+});
+
+ipcMain.handle(IPC_ACTIONS.GET_PRIMARY_DISPLAY_SIZE, (event) => {
+ return getMainWindowSize();
+});
+
+ipcMain.handle(IPC_ACTIONS.GET_DIALOG_RESPONSE, async (event, options) => {
+ const window = event.sender.getOwnerBrowserWindow();
+ return await dialog.showMessageBox(window, options);
+});
+
+ipcMain.handle(IPC_ACTIONS.SAVE_HTML_AS_PDF, async (event, html, savePath) => {
+ return await saveHtmlAsPdf(html, savePath);
+});
+
+/* ------------------------------
+ * Register app lifecycle methods
+ * ------------------------------*/
+
app.on('window-all-closed', () => {
- // On macOS it is common for applications and their menu bar
- // to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
- // On macOS it's common to re-create a window in the app when the
- // dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow();
}
});
-// This method will be called when Electron has finished
-// initialization and is ready to create browser windows.
-// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools
@@ -128,10 +218,13 @@ app.on('ready', async () => {
createWindow();
});
-// Exit cleanly on request from parent process in development mode.
+/* ------------------------------
+ * Register node#process messages
+ * ------------------------------*/
+
if (isDevelopment) {
if (process.platform === 'win32') {
- process.on('message', data => {
+ process.on('message', (data) => {
if (data === 'graceful-exit') {
app.quit();
}
diff --git a/src/components/Controls/AttachImage.vue b/src/components/Controls/AttachImage.vue
index 40ebe250..e8d6e100 100644
--- a/src/components/Controls/AttachImage.vue
+++ b/src/components/Controls/AttachImage.vue
@@ -41,8 +41,9 @@
diff --git a/src/components/WindowsTitleBar.vue b/src/components/WindowsTitleBar.vue
index e98514bf..3f054115 100644
--- a/src/components/WindowsTitleBar.vue
+++ b/src/components/WindowsTitleBar.vue
@@ -30,23 +30,20 @@
diff --git a/src/main.js b/src/main.js
index e18f3f23..a8a05d04 100644
--- a/src/main.js
+++ b/src/main.js
@@ -14,6 +14,7 @@ import router from './router';
// other imports
import { ipcRenderer } from 'electron';
+import { IPC_MESSAGES } from './messages';
import Store from 'electron-store';
(async () => {
@@ -26,17 +27,22 @@ import Store from 'electron-store';
frappe.fetch = window.fetch.bind();
frappe.events.on('reload-main-window', () => {
- ipcRenderer.send('reload-main-window');
+ ipcRenderer.send(IPC_MESSAGES.RELOAD_MAIN_WINDOW);
});
frappe.events.on('check-for-updates', () => {
let { autoUpdate } = frappe.AccountingSettings;
if (autoUpdate == null || autoUpdate === 1) {
- ipcRenderer.send('check-for-updates');
+ ipcRenderer.send(IPC_MESSAGES.CHECK_FOR_UPDATES);
}
});
window.frappe = frappe;
+ window.frappe.store = {};
+
+ ipcRenderer.on('store-on-window', (event, message) => {
+ Object.assign(window.frappe.store, message);
+ });
Vue.config.productionTip = false;
Vue.component('feather-icon', FeatherIcon);
@@ -51,22 +57,22 @@ import Store from 'electron-store';
return {
win32: 'Windows',
darwin: 'Mac',
- linux: 'Linux'
+ linux: 'Linux',
}[process.platform];
- }
+ },
},
methods: {
_(...args) {
return frappe._(...args);
- }
- }
+ },
+ },
});
Vue.config.errorHandler = (err, vm, info) => {
console.error(err, vm, info);
};
- process.on('unhandledRejection', error => {
+ process.on('unhandledRejection', (error) => {
console.error(error);
});
@@ -75,8 +81,8 @@ import Store from 'electron-store';
el: '#app',
router,
components: {
- App
+ App,
},
- template: ''
+ template: '',
});
})();
diff --git a/src/menu.js b/src/menu.js
deleted file mode 100644
index 0e87332f..00000000
--- a/src/menu.js
+++ /dev/null
@@ -1,11 +0,0 @@
-const { remote } = require('electron');
-const { Menu } = remote;
-
-function openMenu() {
- const menu = Menu.getApplicationMenu();
- menu.popup({ window: remote.getCurrentWindow() });
-}
-
-module.exports = {
- openMenu
-};
diff --git a/src/messages.js b/src/messages.js
new file mode 100644
index 00000000..fb2d258f
--- /dev/null
+++ b/src/messages.js
@@ -0,0 +1,18 @@
+export const IPC_MESSAGES = {
+ OPEN_MENU: 'open-menu',
+ OPEN_SETTINGS: 'open-settings',
+ CHECK_FOR_UPDATES: 'check-for-updates',
+ RELOAD_MAIN_WINDOW: 'reload-main-window',
+ RESIZE_MAIN_WINDOW: 'resize-main-window',
+ CLOSE_CURRENT_WINDOW: 'close-current-window',
+ MINIMIZE_CURRENT_WINDOW: 'minimize-current-window',
+};
+
+export const IPC_ACTIONS = {
+ TOGGLE_MAXIMIZE_CURRENT_WINDOW: 'toggle-maximize-current-window',
+ GET_OPEN_FILEPATH: 'open-dialog',
+ GET_SAVE_FILEPATH: 'save-dialog',
+ GET_DIALOG_RESPONSE: 'show-message-box',
+ GET_PRIMARY_DISPLAY_SIZE: 'get-primary-display-size',
+ SAVE_HTML_AS_PDF: 'save-html-as-pdf'
+};
diff --git a/src/pages/DataImport/index.vue b/src/pages/DataImport/index.vue
index bb1d6ed9..bf5064f0 100644
--- a/src/pages/DataImport/index.vue
+++ b/src/pages/DataImport/index.vue
@@ -4,24 +4,28 @@
{{ _('Data Import') }}
showTable(doctype)"
+ fieldtype: 'Select',
+ fieldname: 'referenceDoctype',
+ options: ['Select...', 'Item', 'Party', 'Account'],
+ }"
+ @change="(doctype) => showTable(doctype)"
/>
- Upload CSV
- Download CSV Template
+ Upload CSV
+ Download CSV Template
Submit
@@ -35,13 +39,12 @@ import { convertFieldsToDatatableColumns } from 'frappejs/client/ui/utils';
import { writeFile } from 'frappejs/server/utils';
import path from 'path';
import csv2json from 'csvjson-csv2json';
-const { remote } = require('electron');
export default {
data() {
return {
doctype: undefined,
- fileUploaded: false
+ fileUploaded: false,
};
},
methods: {
@@ -59,7 +62,7 @@ export default {
this.datatable = new DataTable(this.$refs.datatable, {
columns,
data: [[]],
- pasteFromClipboard: true
+ pasteFromClipboard: true,
});
},
async downloadCSV() {
@@ -72,19 +75,26 @@ export default {
const documentsPath =
process.env.NODE_ENV === 'development'
? path.resolve('.')
- : remote.getGlobal('documentsPath');
+ : frappe.store.documentsPath;
- await writeFile(
- path.resolve(documentsPath + `/frappe-accounting/${this.doctype}.csv`),
- csvString
- );
+ let title = frappe._('Message');
+ let message = frappe._('Template saved successfully.');
+
+ if (documentsPath === undefined) {
+ title = frappe._('Error');
+ message = frappe._('Template could not be saved.');
+ } else {
+ await writeFile(
+ path.resolve(
+ documentsPath + `/frappe-accounting/${this.doctype}.csv`
+ ),
+ csvString
+ );
+ }
frappe.call({
method: 'show-dialog',
- args: {
- title: 'Message',
- message: `Template Saved Successfully`
- }
+ args: { title, message },
});
},
uploadCSV(file) {
@@ -93,9 +103,9 @@ export default {
reader.onload = () => {
const meta = frappe.getMeta(this.doctype);
let header = reader.result.split('\n')[0];
- header = header.split(',').map(label => {
+ header = header.split(',').map((label) => {
let fieldname;
- meta.fields.some(field => {
+ meta.fields.some((field) => {
if (field.label === label.trim()) {
fieldname = field.fieldname;
return true;
@@ -120,19 +130,19 @@ export default {
importData() {
const rows = this.datatable.datamanager.getRows();
- const data = rows.map(row => {
+ const data = rows.map((row) => {
return row.slice(1).reduce((prev, curr) => {
prev[curr.column.field.fieldname] = curr.content;
return prev;
}, {});
});
- data.forEach(async d => {
+ data.forEach(async (d) => {
try {
await frappe
.newDoc(
Object.assign(d, {
- doctype: this.doctype
+ doctype: this.doctype,
})
)
.insert();
@@ -144,10 +154,10 @@ export default {
method: 'show-dialog',
args: {
title: 'Message',
- message: `Data Imported Successfully`
- }
+ message: `Data Imported Successfully`,
+ },
});
- }
- }
+ },
+ },
};
diff --git a/src/pages/PrintView/PrintView.vue b/src/pages/PrintView/PrintView.vue
index a279b4aa..96cf4461 100644
--- a/src/pages/PrintView/PrintView.vue
+++ b/src/pages/PrintView/PrintView.vue
@@ -21,7 +21,12 @@
>
diff --git a/src/pages/Settings/Settings.vue b/src/pages/Settings/Settings.vue
index 9b065454..1f307aac 100644
--- a/src/pages/Settings/Settings.vue
+++ b/src/pages/Settings/Settings.vue
@@ -27,8 +27,6 @@
diff --git a/src/pages/Settings/TabSystem.vue b/src/pages/Settings/TabSystem.vue
index 0a80dac3..446fdec1 100644
--- a/src/pages/Settings/TabSystem.vue
+++ b/src/pages/Settings/TabSystem.vue
@@ -51,7 +51,8 @@ import TwoColumnForm from '@/components/TwoColumnForm';
import FormControl from '@/components/Controls/FormControl';
import Button from '@/components/Button';
import config from '@/config';
-import { remote } from 'electron';
+import { ipcRenderer } from 'electron';
+import { IPC_MESSAGES } from '@/messages';
export default {
name: 'TabSystem',
@@ -74,7 +75,7 @@ export default {
changeFile() {
config.set('lastSelectedFilePath', null);
frappe.events.trigger('reload-main-window');
- remote.getCurrentWindow().close();
+ ipcRenderer.send(IPC_MESSAGES.CLOSE_CURRENT_WINDOW);
}
},
computed: {
diff --git a/src/saveHtmlAsPdf.js b/src/saveHtmlAsPdf.js
new file mode 100644
index 00000000..e12bbbeb
--- /dev/null
+++ b/src/saveHtmlAsPdf.js
@@ -0,0 +1,54 @@
+import fs from 'fs';
+import { shell, BrowserWindow } from 'electron';
+
+const PRINT_OPTIONS = {
+ marginsType: 1, // no margin
+ pageSize: 'A4',
+ printBackground: true,
+ printBackgrounds: true,
+ printSelectionOnly: false,
+};
+
+export default async function makePDF(html, savePath) {
+ const printWindow = getInitializedPrintWindow();
+
+ printWindow.webContents.executeJavaScript(`
+ document.body.innerHTML = \`${html}\`;
+ `);
+
+ const sleep = (m) => new Promise((r) => setTimeout(r, m));
+ // TODO: Check if event 'paint' works after bumping electron.
+ printWindow.webContents.on('did-finish-load', async () => {
+ await sleep(1000); // Required else pdf'll be blank.
+ printWindow.webContents.printToPDF(PRINT_OPTIONS).then((data) => {
+ printWindow.destroy();
+ fs.writeFile(savePath, data, (error) => {
+ if (error) throw error;
+ return shell.openItem(savePath);
+ });
+ });
+ });
+}
+
+function getInitializedPrintWindow() {
+ const printWindow = new BrowserWindow({
+ width: 595,
+ height: 842,
+ show: false,
+ webPreferences: {
+ nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
+ },
+ });
+ printWindow.loadURL(getPrintWindowUrl());
+ return printWindow;
+}
+
+function getPrintWindowUrl() {
+ let url = global.WEBPACK_DEV_SERVER_URL;
+ if (url) {
+ url = url + 'print';
+ } else {
+ url = 'app://./print.html';
+ }
+ return url;
+}
diff --git a/src/screenSize.js b/src/screenSize.js
deleted file mode 100644
index 70892f1f..00000000
--- a/src/screenSize.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import electron from 'electron';
-
-export function getMainWindowSize() {
- let screen = electron.screen || electron.remote.screen;
- let { height } = screen.getPrimaryDisplay().workAreaSize;
- let width;
-
- if (height > 907) {
- height = 907;
- }
- width = Math.ceil(1.323 * height);
-
- return { width, height };
-}
diff --git a/src/utils.js b/src/utils.js
index 1a07d009..86d05945 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -2,7 +2,8 @@ import frappe from 'frappejs';
import fs from 'fs';
import { _ } from 'frappejs/utils';
import migrate from './migrate';
-import { remote, shell, ipcRenderer } from 'electron';
+import { ipcRenderer } from 'electron';
+import { IPC_MESSAGES, IPC_ACTIONS } from './messages';
import SQLite from 'frappejs/backends/sqlite';
import postStart from '../server/postStart';
import router from '@/router';
@@ -12,10 +13,13 @@ import config from '@/config';
export async function createNewDatabase() {
const options = {
title: _('Select folder'),
- defaultPath: 'frappe-books.db'
+ defaultPath: 'frappe-books.db',
};
- let { filePath } = await remote.dialog.showSaveDialog(options);
+ let { filePath } = await ipcRenderer.invoke(
+ IPC_ACTIONS.GET_SAVE_FILEPATH,
+ options
+ );
if (filePath) {
if (!filePath.endsWith('.db')) {
filePath = filePath + '.db';
@@ -30,10 +34,10 @@ export async function createNewDatabase() {
action() {
fs.unlinkSync(filePath);
return filePath;
- }
+ },
},
- { label: _('Cancel'), action() {} }
- ]
+ { label: _('Cancel'), action() {} },
+ ],
});
} else {
return filePath;
@@ -45,10 +49,13 @@ export async function loadExistingDatabase() {
const options = {
title: _('Select file'),
properties: ['openFile'],
- filters: [{ name: 'SQLite DB File', extensions: ['db'] }]
+ filters: [{ name: 'SQLite DB File', extensions: ['db'] }],
};
- let { filePaths } = await remote.dialog.showOpenDialog(options);
+ const { filePaths } = await ipcRenderer.invoke(
+ IPC_ACTIONS.GET_OPEN_FILEPATH,
+ options
+ );
if (filePaths && filePaths[0]) {
return filePaths[0];
@@ -58,7 +65,7 @@ export async function loadExistingDatabase() {
export async function connectToLocalDatabase(filepath) {
frappe.login('Administrator');
frappe.db = new SQLite({
- dbPath: filepath
+ dbPath: filepath,
});
await frappe.db.connect();
await migrate();
@@ -66,13 +73,13 @@ export async function connectToLocalDatabase(filepath) {
// set file info in config
let files = config.get('files') || [];
- if (!files.find(file => file.filePath === filepath)) {
+ if (!files.find((file) => file.filePath === filepath)) {
files = [
{
companyName: frappe.AccountingSettings.companyName,
- filePath: filepath
+ filePath: filepath,
},
- ...files
+ ...files,
];
config.set('files', files);
}
@@ -84,16 +91,17 @@ export async function connectToLocalDatabase(filepath) {
export async function showMessageDialog({
message,
description,
- buttons = []
+ buttons = [],
}) {
- let buttonLabels = buttons.map(a => a.label);
- const { response } = await remote.dialog.showMessageBox(
- remote.getCurrentWindow(),
- {
- message,
- detail: description,
- buttons: buttonLabels
- }
+ const options = {
+ message,
+ detail: description,
+ buttons: buttons.map((a) => a.label),
+ };
+
+ const { response } = await ipcRenderer.invoke(
+ IPC_ACTIONS.GET_DIALOG_RESPONSE,
+ options
);
let button = buttons[response];
@@ -103,11 +111,11 @@ export async function showMessageDialog({
}
export function deleteDocWithPrompt(doc) {
- return new Promise(resolve => {
+ return new Promise((resolve) => {
showMessageDialog({
message: _('Are you sure you want to delete {0} "{1}"?', [
doc.doctype,
- doc.name
+ doc.name,
]),
description: _('This action is permanent'),
buttons: [
@@ -117,18 +125,18 @@ export function deleteDocWithPrompt(doc) {
doc
.delete()
.then(() => resolve(true))
- .catch(e => {
+ .catch((e) => {
handleErrorWithDialog(e, doc);
});
- }
+ },
},
{
label: _('Cancel'),
action() {
resolve(false);
- }
- }
- ]
+ },
+ },
+ ],
});
});
}
@@ -138,11 +146,11 @@ export function partyWithAvatar(party) {
data() {
return {
imageURL: null,
- label: null
+ label: null,
};
},
components: {
- Avatar
+ Avatar,
},
async mounted() {
this.imageURL = await frappe.db.getValue('Party', party, 'image');
@@ -153,7 +161,7 @@ export function partyWithAvatar(party) {
{{ label }}
- `
+ `,
};
}
@@ -173,8 +181,8 @@ export function openQuickEdit({ doctype, name, hideFields, defaults = {} }) {
name,
hideFields,
values: defaults,
- lastRoute: currentRoute
- }
+ lastRoute: currentRoute,
+ },
});
}
@@ -183,7 +191,7 @@ export function getErrorMessage(e, doc) {
if (e.type === frappe.errors.LinkValidationError) {
errorMessage = _('{0} {1} is linked with existing records.', [
doc.doctype,
- doc.name
+ doc.name,
]);
} else if (e.type === frappe.errors.DuplicateEntryError) {
errorMessage = _('{0} {1} already exists.', [doc.doctype, doc.name]);
@@ -197,69 +205,8 @@ export function handleErrorWithDialog(e, doc) {
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 async 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
- };
-
- const sleep = m => new Promise(r => setTimeout(r, m));
-
- printWindow.webContents.on('did-finish-load', async () => {
- injectCSS(printWindow.webContents);
- await sleep(1000);
- printWindow.webContents.printToPDF(printOptions).then(data => {
- printWindow.close();
- fs.writeFile(destination, data, error => {
- if (error) throw error;
- return (shell.openItem(destination));
- });
- });
- });
+export async function makePDF(html, savePath) {
+ ipcRenderer.invoke(IPC_ACTIONS.SAVE_HTML_AS_PDF, html, savePath);
}
export function getActionsForDocument(doc) {
@@ -267,24 +214,24 @@ export function getActionsForDocument(doc) {
let deleteAction = {
component: {
- template: `{{ _('Delete') }}`
+ template: `{{ _('Delete') }}`,
},
- condition: doc => !doc.isNew() && !doc.submitted && !doc.meta.isSingle,
+ condition: (doc) => !doc.isNew() && !doc.submitted && !doc.meta.isSingle,
action: () =>
- deleteDocWithPrompt(doc).then(res => {
+ 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 => {
+ .filter((d) => (d.condition ? d.condition(doc) : true))
+ .map((d) => {
return {
label: d.label,
component: d.component,
- action: d.action.bind(this, doc, router)
+ action: d.action.bind(this, doc, router),
};
});
@@ -292,5 +239,23 @@ export function getActionsForDocument(doc) {
}
export function openSettings(tab = 'General') {
- ipcRenderer.send('open-settings-window', tab);
+ ipcRenderer.send(IPC_MESSAGES.OPEN_SETTINGS, tab);
+}
+
+export async function runWindowAction(name) {
+ switch (name) {
+ case 'close':
+ ipcRenderer.send(IPC_MESSAGES.CLOSE_CURRENT_WINDOW);
+ break;
+ case 'minimize':
+ ipcRenderer.send(IPC_MESSAGES.MINIMIZE_CURRENT_WINDOW);
+ break;
+ case 'maximize':
+ const maximizing = await ipcRenderer.invoke(
+ IPC_ACTIONS.TOGGLE_MAXIMIZE_CURRENT_WINDOW
+ );
+ name = maximizing ? name : 'unmaximize';
+ break;
+ }
+ return name;
}
diff --git a/vue.config.js b/vue.config.js
index 7e0901f1..afa8f5d3 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -4,18 +4,21 @@ const webpack = require('webpack');
module.exports = {
pluginOptions: {
electronBuilder: {
- nodeIntegration: true
- }
+ nodeIntegration: true,
+ chainWebpackRendererProcess: (config) => {
+ config.target('electron-renderer');
+ },
+ },
},
pages: {
index: {
entry: 'src/main.js',
- filename: 'index.html'
+ filename: 'index.html',
},
print: {
entry: 'src/print.js',
- filename: 'print.html'
- }
+ filename: 'print.html',
+ },
},
runtimeCompiler: true,
lintOnSave: process.env.NODE_ENV !== 'production',
@@ -23,7 +26,7 @@ module.exports = {
Object.assign(config.resolve.alias, {
deepmerge$: 'deepmerge/dist/umd.js',
'frappe-charts$': 'frappe-charts/dist/frappe-charts.esm.js',
- '~': path.resolve('.')
+ '~': path.resolve('.'),
});
config.plugins.push(
@@ -36,10 +39,10 @@ module.exports = {
config.module.rules.push({
test: /\.txt$/i,
- use: 'raw-loader'
+ use: 'raw-loader',
});
config.devtool = 'source-map';
},
- transpileDependencies: ['frappejs']
+ transpileDependencies: ['frappejs'],
};
diff --git a/yarn.lock b/yarn.lock
index c9196801..2d2ccdb1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5024,7 +5024,7 @@ electron@5.0.0:
electron-download "^4.1.0"
extract-zip "^1.0.3"
-electron@^8.0.0:
+electron@^8.5.5:
version "8.5.5"
resolved "https://registry.yarnpkg.com/electron/-/electron-8.5.5.tgz#17b12bd70139c0099f750fc5de0d480bf03acb96"
integrity sha512-e355H+tRDial0m+X2v+l+0SnaATAPw4sNjv9qmdk/6MJz/glteVJwVJEnxTjPfEELIJSChrBWDBVpjdDvoBF4Q==
@@ -12759,7 +12759,7 @@ webpack@^4.0.0, webpack@^4.16.1, webpack@^4.18.0:
watchpack "^1.7.4"
webpack-sources "^1.4.1"
-webpack@^5.22.0, webpack@^5.61.0:
+webpack@^5.22.0:
version "5.61.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.61.0.tgz#fa827f0ee9bdfd141dd73c3e891e955ebd52fe7f"
integrity sha512-fPdTuaYZ/GMGFm4WrPi2KRCqS1vDp773kj9S0iI5Uc//5cszsFEDgHNaX4Rj1vobUiU1dFIV3mA9k1eHeluFpw==