import { ConfigurationOption , defineConfigOption } from "@nteract/mythic-configuration" ; import { setConfigFile } from "@nteract/mythic-configuration/src/backends/filesystem" ; import { closeWindow , electronBackend , setWindowingBackend , showWindow } from "@nteract/mythic-windowing" ; import { KernelspecInfo } from "@nteract/types" ; import { app , BrowserWindow , dialog , Event , ipcMain as ipc , IpcMainEvent , Menu , nativeTheme , Tray } from "electron" ; import initContextMenu from "electron-context-menu" ; import * as log from "electron-log" ; import { existsSync } from "fs" ; import { join , resolve } from "path" ; import { fromEvent , Observable , Subscriber , zip } from "rxjs" ; import { buffer , first , last , skipUntil , takeUntil } from "rxjs/operators" ; import yargs from "yargs/yargs" ; import { QUITTING_STATE_NOT_STARTED , QUITTING_STATE_QUITTING , setQuittingState } from "./actions" ; import { initAutoUpdater } from "./auto-updater" ; import { defaultKernel } from "./config-options" ; import { bestKernelObservable , kernelSpecs$ } from "./kernel-specs" ; import { launch , launchNewNotebook } from "./launch" ; import { loadFullMenu , loadTrayMenu } from "./menu" ; import prepareEnv from "./prepare-env" ; import configureStore from "./store" ; import installExtension , { REDUX_DEVTOOLS , REACT_DEVELOPER_TOOLS } from 'electron-devtools-installer' ; app . allowRendererProcessReuse = false ; const store = configureStore ( undefined ) ; ( global as any ) . store = store ; const argv = yargs ( ) . version ( ( ( ) => require ( "./../../package.json" ) . version ) ( ) ) . usage ( "Usage: nteract <notebooks> [options]" ) . example ( "nteract notebook1.ipynb notebook2.ipynb" , "Open notebooks" ) . example ( "nteract --kernel javascript" , "Launch a kernel" ) . describe ( "kernel" , "Launch a kernel" ) . alias ( "k" , "kernel" ) . alias ( "v" , "version" ) . alias ( "h" , "help" ) . describe ( "verbose" , "Display debug information" ) . help ( "help" ) . parse ( process . argv . slice ( 1 ) ) ; log . info ( "args" , argv ) ; const notebooks = argv . _ . filter ( ( x ) => /(.ipynb)$/ . test ( x ) ) ; ipc . on ( "transfer-config-options-to-main" , ( _event , options ) => { options . forEach ( each => defineConfigOption ( each ) ) ; } ) ; ipc . on ( "new-kernel" , ( _event , k ) => { launchNewNotebook ( null , k ) ; } ) ; ipc . on ( "open-notebook" , ( _event , filename ) => { launch ( resolve ( filename ) ) ; } ) ; ipc . on ( "reload" , ( event ) => { event . sender . reload ( ) ; event . returnValue = null ; } ) ; ipc . on ( "show-message-box" , async ( event , arg ) => { const response = await dialog . showMessageBox ( arg ) ; event . sender . send ( "show-message-box-response" , response ) ; } ) ; ipc . on ( "kernel_specs_request" , event => kernelSpecs$ . pipe ( last ( ) ) . subscribe ( specs => event . reply ( "kernel_specs_reply" , specs ) ) ) ; app . on ( "ready" , initAutoUpdater ) ; const electronReady$ = new Observable ( ( observer ) => { ( app as any ) . on ( "ready" , ( launchInfo ) => observer . next ( launchInfo ) ) ; } ) ; const windowReady$ = fromEvent ( ipc , "react-ready" ) ; const fullAppReady$ = zip ( electronReady$ , prepareEnv ) . pipe ( first ( ) ) ; const jupyterConfigDir = join ( app . getPath ( "home" ) , ".jupyter" ) ; const nteractConfigFilename = join ( jupyterConfigDir , "nteract.json" ) ; store . dispatch ( setConfigFile ( nteractConfigFilename ) ) ; store . dispatch ( setWindowingBackend . create ( electronBackend ) ) ; const appAndKernelSpecsReady$ = zip ( fullAppReady$ , windowReady$ , kernelSpecs$ ) ; electronReady$ . pipe ( takeUntil ( appAndKernelSpecsReady$ ) ) . subscribe ( ( ) => store . dispatch ( showWindow . create ( { id : "splash" , kind : "splash" , width : 565 , height : 233 , path : join ( __dirname , ".." , "static" , "splash.html" ) } ) ) , ( err ) => console . error ( err ) , ( ) => store . dispatch ( closeWindow . create ( "splash" ) ) ) ; app . on ( "before-quit" , ( e ) => { if ( process . platform === "win32" ) { return ; } const windows = BrowserWindow . getAllWindows ( ) ; if ( windows . filter ( ( win ) => win . isVisible ( ) ) . length > 0 && store . getState ( ) . quittingState === QUITTING_STATE_NOT_STARTED ) { e . preventDefault ( ) ; store . dispatch ( setQuittingState ( QUITTING_STATE_QUITTING ) ) ; windows . forEach ( ( win ) => win . close ( ) ) ; } } ) ; const windowAllClosed = new Observable ( ( observer ) => { app . on ( "window-all-closed" , ( event ) => observer . next ( event ) ) ; } ) ; windowAllClosed . pipe ( skipUntil ( appAndKernelSpecsReady$ ) ) . subscribe ( ( ) => { if ( process . platform !== "darwin" || store . getState ( ) . quittingState === QUITTING_STATE_QUITTING ) { app . quit ( ) ; } } ) ; ipc . on ( "close-notebook-canceled" , ( ) => { store . dispatch ( setQuittingState ( QUITTING_STATE_NOT_STARTED ) ) ; } ) ; const openFile$ = new Observable ( ( observer ) => { const eventName = "open-file" ; const handler = ( event , filename ) => { observer . next ( { event , filename } ) ; } ; app . on ( eventName , handler ) ; return ( ) => app . removeListener ( eventName , handler ) ; } ) ; function openFileFromEvent ( { event , filename } ) { event . preventDefault ( ) ; launch ( resolve ( filename ) ) ; } openFile$ . pipe ( buffer ( fullAppReady$ ) , first ( ) ) . subscribe ( ( buffer ) => { const bestKernel$ = bestKernelObservable ( argv . kernel as string , defaultKernel ( store . getState ( ) ) ) ; if ( notebooks . length <= 0 && buffer . length <= 0 ) { bestKernel$ . subscribe ( spec => launchNewNotebook ( null , spec ) ) ; } else { notebooks . forEach ( ( f ) => { if ( existsSync ( resolve ( f ) ) ) { try { launch ( resolve ( f ) ) ; } catch ( e ) { log . error ( e ) ; console . error ( e ) ; } } else { log . info ( `notebook ${ f } not found, launching as empty notebook` ) ; bestKernel$ . subscribe ( spec => launchNewNotebook ( f , spec ) ) ; } } ) ; } buffer . forEach ( openFileFromEvent ) ; } ) ; openFile$ . pipe ( skipUntil ( fullAppReady$ ) ) . subscribe ( openFileFromEvent ) ; fullAppReady$ . subscribe ( ( ) => { initContextMenu ( ) ; } ) ; appAndKernelSpecsReady$ . subscribe ( ( ) => { const logo = ( nativeTheme . shouldUseDarkColors || process . platform === "darwin" ) ? "logoTemplate" : "logoWhite" ; const tray = new Tray ( join ( __dirname , ".." , "static" , `${ logo }.png` ) ) ; Menu . setApplicationMenu ( loadFullMenu ( ) ) ; tray . setContextMenu ( loadTrayMenu ( ) ) ; } ) ; app . on ( 'ready' , ( ) => { installExtension ( [ REDUX_DEVTOOLS , REACT_DEVELOPER_TOOLS ] , { loadExtensionOptions : { allowFileAccess : true } } ) . then ( ( name ) => console . log ( `Added Extension:  ${ name }` ) ) . catch ( ( err ) => console . log ( 'An error occurred: ' , err ) ) ; } ) ;