2022-10-20 18:47:20 +00:00
/* sheetjs (C) 2013-present SheetJS -- https://sheetjs.com */
2025-05-02 16:35:16 +00:00
const { app , BrowserWindow , ipcMain , dialog } = require ( 'electron' ) ;
const path = require ( 'path' ) ;
const XLSX = require ( 'xlsx' ) ;
2022-08-04 03:00:20 +00:00
2025-04-30 18:15:48 +00:00
const EXT _REGEX = /\.(xls|xlsx|xlsm|xlsb|xml|csv|txt|dif|sylk|slk|prn|ods|fods|htm|html|numbers)$/i ;
2025-05-02 16:35:16 +00:00
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.
/ * I n e l e c t r o n , t h e m a i n p r o c e s s i s t h e o n l y p r o c e s s t h a t c a n d i r e c t l y i n t e r f a c e w i t h t h e o p e r a t i n g s y s t e m .
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 */
/* ----------------------------------------------------------------------------- */
/ * T h e s e t h r e e f u n c t i o n s c a n b e u s e d t o i n t e r f a c e w i t h t h e o p e r a t i n g s y s t e m f r o m t h e r e n d e r e r p r o c e s s .
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 } )
) ;
2025-04-30 18:15:48 +00:00
2025-05-02 16:35:16 +00:00
ipcMain . handle ( 'dialog:message' , ( _e , msg ) =>
dialog . showMessageBox ( { message : msg , buttons : [ 'OK' ] } )
) ;
2025-04-30 18:15:48 +00:00
2025-05-02 16:35:16 +00:00
/* ----------------------------------------------------------------------------- */
/* Utility functions */
/* ----------------------------------------------------------------------------- */
function sendToRenderer ( fp ) {
if ( win && win . webContents ) win . webContents . send ( 'file-opened' , fp ) ;
else pendingPaths . push ( fp ) ;
2025-04-30 18:15:48 +00:00
}
2025-05-02 16:35:16 +00:00
/ *
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 .
* /
2025-04-30 18:15:48 +00:00
function firstSpreadsheetFromArgv ( ) {
2025-05-02 16:35:16 +00:00
const args = process . defaultApp ? process . argv . slice ( 2 ) : process . argv . slice ( 1 ) ;
return args . find ( ( a ) => EXT _REGEX . test ( a ) ) ;
2025-04-30 18:15:48 +00:00
}
2025-05-02 16:35:16 +00:00
/* ----------------------------------------------------------------------------- */
/* 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 ( ) ;
2025-04-30 18:15:48 +00:00
else {
2025-05-02 16:35:16 +00:00
app . on ( 'second-instance' , ( _e , argv ) => {
const fp = argv . find ( ( a ) => EXT _REGEX . test ( a ) ) ;
2025-04-30 18:15:48 +00:00
if ( fp ) sendToRenderer ( fp ) ;
if ( win ) { win . show ( ) ; win . focus ( ) ; }
} ) ;
}
2025-05-02 16:35:16 +00:00
// 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://' , '' ) ) ; } ) ;
2025-04-30 18:15:48 +00:00
2025-05-02 16:35:16 +00:00
/* ----------------------------------------------------------------------------- */
/* Create the window */
/* ----------------------------------------------------------------------------- */
2022-08-04 03:00:20 +00:00
function createWindow ( ) {
2022-08-08 06:59:57 +00:00
if ( win ) return ;
2025-05-02 16:35:16 +00:00
win = new BrowserWindow ( {
width : 800 ,
height : 600 ,
2022-08-08 06:59:57 +00:00
webPreferences : {
2025-05-02 16:35:16 +00:00
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
2022-08-08 06:59:57 +00:00
}
} ) ;
2025-05-02 16:35:16 +00:00
win . loadFile ( 'index.html' ) ;
if ( process . env . NODE _ENV === 'development' ) win . webContents . openDevTools ( ) ;
win . on ( 'closed' , ( ) => { win = null ; } ) ;
2025-04-30 18:15:48 +00:00
win . webContents . once ( 'did-finish-load' , ( ) => {
pendingPaths . splice ( 0 ) . forEach ( sendToRenderer ) ;
} ) ;
2022-08-04 03:00:20 +00:00
}
2025-04-30 18:15:48 +00:00
2025-05-02 16:35:16 +00:00
/* ----------------------------------------------------------------------------- */
/* 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'
} ) ;
}
2022-08-04 03:00:20 +00:00
app . on ( 'activate' , createWindow ) ;
2025-05-02 16:35:16 +00:00
app . on ( 'window-all-closed' , ( ) => { if ( process . platform !== 'darwin' ) app . quit ( ) ; } ) ;