2
0
mirror of https://github.com/frappe/books.git synced 2024-09-20 03:29:00 +00:00

refactor: deprecate use of remote; remove bg transparency on macOS

This commit is contained in:
18alantom 2021-11-04 15:03:51 +05:30
parent 864d07fd1b
commit f0424d0ee4
20 changed files with 422 additions and 312 deletions

View File

@ -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"

View File

@ -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');
}
}
},
},
};
</script>

View File

@ -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();
}

View File

@ -41,8 +41,9 @@
<script>
import frappe from 'frappejs';
import { remote } from 'electron';
import { ipcRenderer } from 'electron';
import Base from './Base';
import { IPC_ACTIONS } from '@/messages'
export default {
name: 'AttachImage',
@ -61,7 +62,7 @@ export default {
filters: [{ name: 'Image', extensions: ['png', 'jpg', 'jpeg', 'webp'] }]
};
const { filePaths } = await remote.dialog.showOpenDialog(options);
const { filePaths } = await ipcRenderer.invoke(IPC_ACTIONS.GET_OPEN_FILEPATH, options);
if (filePaths && filePaths[0]) {
let dataURL = await this.getDataURL(filePaths[0]);
this.triggerChange(dataURL);

View File

@ -1,8 +1,5 @@
<template>
<div
class="pt-6 pb-2 px-2 h-full block window-drag flex justify-between flex-col bg-gray-100"
:style="sidebarBackground"
>
<div class="pt-6 pb-2 px-2 h-full block window-drag flex justify-between flex-col bg-gray-100" >
<div class="window-no-drag">
<WindowControls v-if="platform === 'Mac'" class="px-3 mb-6" />
<div class="px-3">
@ -47,7 +44,6 @@
</div>
</template>
<script>
import { remote } from 'electron';
import sidebarConfig from '../sidebarConfig';
import WindowControls from './WindowControls';
@ -60,15 +56,8 @@ export default {
};
},
computed: {
sidebarBackground() {
return this.platform === 'Mac'
? {
'background-color': 'rgba(255, 255, 255, 0.6)'
}
: null;
},
appVersion() {
return remote.app.getVersion();
return frappe.store.appVersion;
}
},
components: {

View File

@ -19,32 +19,29 @@
</template>
<script>
import electron from 'electron';
import { runWindowAction } from '@/utils';
import { ipcRenderer } from 'electron';
export default {
name: 'WindowControls',
props: {
buttons: {
type: Array,
default: () => ['close', 'minimize', 'maximize']
}
default: () => ['close', 'minimize', 'maximize'],
},
},
methods: {
action(name) {
async action(name) {
if (this.buttons.includes(name)) {
let window = electron.remote.getCurrentWindow();
if (name === 'maximize' && window.isMaximized()) {
name = 'unmaximize';
}
this.$emit(name);
window[name]();
const actionRan = await runWindowAction(name);
this.$emit(actionRan);
}
},
getColorClasses(name) {
let classes = {
close: 'bg-red-500 hover:bg-red-700',
minimize: 'bg-yellow-500 hover:bg-yellow-700',
maximize: 'bg-green-500 hover:bg-green-700'
maximize: 'bg-green-500 hover:bg-green-700',
}[name];
if (this.buttons.includes(name)) {
@ -52,7 +49,7 @@ export default {
}
return 'bg-gray-500';
}
}
},
},
};
</script>

View File

@ -30,23 +30,20 @@
</template>
<script>
import electron from 'electron';
import { openMenu } from '@/menu';
import { ipcRenderer } from 'electron';
import { runWindowAction } from '@/utils';
import { IPC_MESSAGES } from '@/messages';
export default {
name: 'WindowsTitleBar',
methods: {
action(name) {
let window = electron.remote.getCurrentWindow();
if (name === 'maximize' && window.isMaximized()) {
name = 'unmaximize';
}
this.$emit(name);
window[name]();
async action(name) {
const actionRan = await runWindowAction(name);
this.$emit(actionRan);
},
openMenu() {
openMenu();
}
}
ipcRenderer.send(IPC_MESSAGES.OPEN_MENU);
},
},
};
</script>

View File

@ -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: '<App/>'
template: '<App/>',
});
})();

View File

@ -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
};

18
src/messages.js Normal file
View File

@ -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'
};

View File

@ -4,24 +4,28 @@
<h4 class="pb-2">{{ _('Data Import') }}</h4>
<frappe-control
:docfield="{
fieldtype: 'Select',
fieldname: 'referenceDoctype',
options: ['Select...', 'Item', 'Party', 'Account']
}"
@change="doctype => showTable(doctype)"
fieldtype: 'Select',
fieldname: 'referenceDoctype',
options: ['Select...', 'Item', 'Party', 'Account'],
}"
@change="(doctype) => showTable(doctype)"
/>
<f-button secondary v-if="doctype" primary @click="uploadCSV">Upload CSV</f-button>
<f-button secondary v-if="doctype" primary @click="downloadCSV">Download CSV Template</f-button>
<f-button secondary v-if="doctype" primary @click="uploadCSV"
>Upload CSV</f-button
>
<f-button secondary v-if="doctype" primary @click="downloadCSV"
>Download CSV Template</f-button
>
<f-button primary @click="importData">Submit</f-button>
<frappe-control
v-if="doctype"
ref="fileInput"
style="position: absolute; display: none;"
style="position: absolute; display: none"
:docfield="{
fieldtype: 'File',
fieldname: 'CSV File',
}"
fieldtype: 'File',
fieldname: 'CSV File',
}"
@change="uploadCSV"
/>
<div class="pt-2" ref="datatable" v-once></div>
@ -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`,
},
});
}
}
},
},
};
</script>

View File

@ -21,7 +21,12 @@
>
<div
class="h-full shadow-lg mb-12 absolute"
style="width: 21cm; min-height: 29.7cm; height: max-content; transform: scale(0.755);"
style="
width: 21cm;
min-height: 29.7cm;
height: max-content;
transform: scale(0.755);
"
ref="printContainer"
>
<component
@ -52,7 +57,8 @@ import Button from '@/components/Button';
import BackLink from '@/components/BackLink';
import TwoColumnForm from '@/components/TwoColumnForm';
import { makePDF } from '@/utils';
import { remote } from 'electron';
import { ipcRenderer } from 'electron';
import { IPC_ACTIONS } from '@/messages';
export default {
name: 'PrintView',
@ -63,13 +69,13 @@ export default {
DropdownWithAction,
Button,
BackLink,
TwoColumnForm
TwoColumnForm,
},
data() {
return {
doc: null,
showCustomiser: false,
printSettings: null
printSettings: null,
};
},
async mounted() {
@ -82,21 +88,27 @@ export default {
},
printTemplate() {
return this.meta.printTemplate;
}
},
},
methods: {
async makePDF() {
let destination = await this.getSavePath();
let html = this.$refs.printContainer.innerHTML;
makePDF(html, destination);
const savePath = await this.getSavePath();
if (!savePath) return;
const html = this.$refs.printContainer.innerHTML;
makePDF(html, savePath);
},
async getSavePath() {
const options = {
title: this._('Select folder'),
defaultPath: `${this.name}.pdf`
defaultPath: `${this.name}.pdf`,
};
let { filePath } = await remote.dialog.showSaveDialog(options);
let { filePath } = await ipcRenderer.invoke(
IPC_ACTIONS.GET_SAVE_FILEPATH,
options
);
if (filePath) {
if (!filePath.endsWith('.pdf')) {
filePath = filePath + '.pdf';
@ -104,7 +116,7 @@ export default {
}
return filePath;
}
}
},
},
};
</script>

View File

@ -27,8 +27,6 @@
</div>
</template>
<script>
import frappe from 'frappejs';
import { remote } from 'electron';
import { _ } from 'frappejs/utils';
import WindowControls from '@/components/WindowControls';
import TabGeneral from './TabGeneral.vue';
@ -80,11 +78,6 @@ export default {
if (index !== -1) {
this.activeTab = index;
}
let currentWindow = remote.getCurrentWindow();
currentWindow.on('close', () => {
frappe.events.trigger('reload-main-window');
});
},
methods: {
getIconComponent(tab) {

View File

@ -5,7 +5,7 @@
:df="meta.getField('logo')"
:value="doc.logo"
@change="
value => {
(value) => {
doc.set('logo', value);
doc.update();
}
@ -24,7 +24,7 @@
:value="doc.displayLogo"
:show-label="true"
@change="
value => {
(value) => {
doc.set('displayLogo', value);
doc.update();
}
@ -38,27 +38,28 @@
</template>
<script>
import frappe from 'frappejs';
import { remote } from 'electron';
import { dialog, ipcRenderer } from 'electron';
import TwoColumnForm from '@/components/TwoColumnForm';
import FormControl from '@/components/Controls/FormControl';
import { IPC_ACTIONS } from '@/messages';
export default {
name: 'TabInvoice',
components: {
TwoColumnForm,
FormControl
FormControl,
},
provide() {
return {
doctype: 'PrintSettings',
name: 'PrintSettings'
name: 'PrintSettings',
};
},
data() {
return {
companyName: null,
doc: null,
showEdit: false
showEdit: false,
};
},
async mounted() {
@ -79,27 +80,23 @@ export default {
'email',
'phone',
'address',
'gstin'
].map(field => this.meta.getField(field));
}
'gstin',
].map((field) => this.meta.getField(field));
},
},
methods: {
openFileSelector() {
remote.dialog.showOpenDialog(
remote.getCurrentWindow(),
{
title: frappe._('Select Logo'),
properties: ['openFile'],
filters: [{ name: 'Invoice Logo', extensions: ['png', 'jpg', 'svg'] }]
},
files => {
if (files && files[0]) {
this.doc.set('logo', `file://${files[0]}`);
this.doc.update();
}
}
);
}
}
async openFileSelector() {
const options = {
title: frappe._('Select Logo'),
properties: ['openFile'],
filters: [{ name: 'Invoice Logo', extensions: ['png', 'jpg', 'svg'] }],
};
const { filePaths } = await ipcRenderer.invoke(IPC_ACTIONS.GET_OPEN_FILEPATH, options);
if (filePaths[0] !== undefined) {
this.doc.set('logo', `file://${files[0]}`);
this.doc.update;
}
},
},
};
</script>

View File

@ -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: {

54
src/saveHtmlAsPdf.js Normal file
View File

@ -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;
}

View File

@ -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 };
}

View File

@ -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) {
<Avatar class="flex-shrink-0" :imageURL="imageURL" :label="label" size="sm" />
<span class="ml-2 truncate">{{ label }}</span>
</div>
`
`,
};
}
@ -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: `<span class="text-red-700">{{ _('Delete') }}</span>`
template: `<span class="text-red-700">{{ _('Delete') }}</span>`,
},
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;
}

View File

@ -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'],
};

View File

@ -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==