forked from sheetjs/docs.sheetjs.com
120 lines
5.5 KiB
JavaScript
120 lines
5.5 KiB
JavaScript
/* sheetjs (C) 2013-present SheetJS -- https://sheetjs.com */
|
||
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
|
||
const path = require('path');
|
||
const XLSX = require('xlsx');
|
||
|
||
const EXT_REGEX = /\.(xls|xlsx|xlsm|xlsb|xml|csv|txt|dif|sylk|slk|prn|ods|fods|htm|html|numbers)$/i;
|
||
const pendingPaths = []; // paths of files that were opened before the window was created. These are queued and loaded once the window is created.
|
||
|
||
let win = null; // reference to the main window, to make sure we don't create multiple windows.
|
||
|
||
/* In electron, the main process is the only process that can directly interface with the operating system.
|
||
The renderer process is sandboxed and cannot run any non-browser code.
|
||
To allow the renderer process to interface with the operating system, we use the contextBridge API to expose the API to the renderer process.
|
||
https://www.electronjs.org/docs/latest/api/context-bridge
|
||
*/
|
||
|
||
/* ----------------------------------------------------------------------------- */
|
||
/* IPC handlers that allow communication between main and renderer processes */
|
||
/* ----------------------------------------------------------------------------- */
|
||
|
||
/* These three functions can be used to interface with the operating system from the renderer process.
|
||
the ipcMain.handle() function is used to register a handler for a specific event.
|
||
when the renderer process calls the corresponding function, the main process will receive the event and execute the handler.
|
||
|
||
In this case, we are listening to events which allow the renderer process to open/save file dialogs and show modal messages.
|
||
*/
|
||
ipcMain.handle('dialog:openFile', (_e, filters) =>
|
||
dialog.showOpenDialog({ title: 'Select a file', filters, properties: ['openFile'] })
|
||
);
|
||
|
||
ipcMain.handle('dialog:saveFile', (_e, filters) =>
|
||
dialog.showSaveDialog({ title: 'Save file as', filters })
|
||
);
|
||
|
||
ipcMain.handle('dialog:message', (_e, msg) =>
|
||
dialog.showMessageBox({ message: msg, buttons: ['OK'] })
|
||
);
|
||
|
||
/* ----------------------------------------------------------------------------- */
|
||
/* Utility functions */
|
||
/* ----------------------------------------------------------------------------- */
|
||
function sendToRenderer(fp) {
|
||
if (win && win.webContents) win.webContents.send('file-opened', fp);
|
||
else pendingPaths.push(fp);
|
||
}
|
||
|
||
/*
|
||
On Windows and Linux, opening a file using the "open with" menu option or `open` command will pass the file path as a startup argument to the app.
|
||
We need to parse it, and test if it is a spreadsheet file.
|
||
*/
|
||
function firstSpreadsheetFromArgv() {
|
||
const args = process.defaultApp ? process.argv.slice(2) : process.argv.slice(1);
|
||
return args.find((a) => EXT_REGEX.test(a));
|
||
}
|
||
|
||
/* ----------------------------------------------------------------------------- */
|
||
/* Single-instance guard */
|
||
/* ----------------------------------------------------------------------------- */
|
||
|
||
// Windows and Linux only: If the app is already running, we need to prevent a new instance from launching when opening a file via the "open with" menu option or `open` command.
|
||
if (!app.requestSingleInstanceLock()) app.quit();
|
||
else {
|
||
app.on('second-instance', (_e, argv) => {
|
||
const fp = argv.find((a) => EXT_REGEX.test(a));
|
||
if (fp) sendToRenderer(fp);
|
||
if (win) { win.show(); win.focus(); }
|
||
});
|
||
}
|
||
|
||
// macOS file / url events
|
||
app.on('open-file', (evt, fp) => { evt.preventDefault(); sendToRenderer(fp); });
|
||
app.on('open-url', (evt, url) => { evt.preventDefault(); sendToRenderer(url.replace('file://', '')); });
|
||
|
||
/* ----------------------------------------------------------------------------- */
|
||
/* Create the window */
|
||
/* ----------------------------------------------------------------------------- */
|
||
function createWindow() {
|
||
if (win) return;
|
||
|
||
win = new BrowserWindow({
|
||
width: 800,
|
||
height: 600,
|
||
webPreferences: {
|
||
preload: path.join(__dirname, './preload.js'), // preload script that will be executed in the renderer process before the page is loaded and act as a bridge between the main and renderer processes within a worker thread.
|
||
contextIsolation: true, // isolate and enable bridge, keeping the renderer process sandboxed and separated from the main process.
|
||
nodeIntegration: false, // no Node.js in renderer process.
|
||
nodeIntegrationInWorker: true, // enable Node.js in worker threads.
|
||
worldSafeExecuteJavaScript: true
|
||
}
|
||
});
|
||
|
||
win.loadFile('index.html');
|
||
if (process.env.NODE_ENV === 'development') win.webContents.openDevTools();
|
||
|
||
win.on('closed', () => { win = null; });
|
||
|
||
win.webContents.once('did-finish-load', () => {
|
||
pendingPaths.splice(0).forEach(sendToRenderer);
|
||
});
|
||
}
|
||
|
||
/* ----------------------------------------------------------------------------- */
|
||
/* App lifecycle */
|
||
/* ----------------------------------------------------------------------------- */
|
||
app.whenReady().then(() => {
|
||
const fp = firstSpreadsheetFromArgv();
|
||
if (fp) pendingPaths.push(fp);
|
||
createWindow();
|
||
});
|
||
|
||
if (app.setAboutPanelOptions) {
|
||
app.setAboutPanelOptions({
|
||
applicationName: 'sheetjs-electron',
|
||
applicationVersion: `XLSX ${XLSX.version}`,
|
||
copyright: '(C) 2017‑present SheetJS LLC'
|
||
});
|
||
}
|
||
|
||
app.on('activate', createWindow);
|
||
app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); }); |