import * as Path from 'path' ; import { get } from 'lodash' ; import { app , ipcMain , BrowserWindow , nativeTheme } from 'electron' ; import { getResourceDirectory } from '@lib/helpers/paths' ; import { state } from '@lib/state' ; import { ProjectIdentifier , ProjectOptions , Project } from '@lib/frameworks/project' ; import { applicationMenu } from '@main/menu' ; let windowStateKeeper = null ; const windows = { } ; export class ApplicationWindow { protected window : BrowserWindow ; protected minWidth = 960 ; protected minHeight = 660 ; protected ready = false ; protected closed = false ; protected project : Project | null = null ; protected events = 0 ; public constructor ( identifier ) { if ( ! windowStateKeeper ) { windowStateKeeper = require ( 'electron-window-state' ) ; } const savedWindowState = windowStateKeeper ( { defaultHeight : this . minHeight , defaultWidth : this . minWidth } ) ; const windowOptions = { x : savedWindowState . x , y : savedWindowState . y , width : savedWindowState . width , height : savedWindowState . height , minWidth : this . minWidth , minHeight : this . minHeight , useContentSize : true , backgroundColor : '#F7F7F7' , webPreferences : { disableBlinkFeatures : 'Auxclick' , backgroundThrottling : false , scrollBounce : true , nodeIntegration : false , contextIsolation : true , worldSafeExecuteJavaScript : true , enableRemoteModule : false , safeDialogs : true , preload : Path . resolve ( getResourceDirectory ( ) , 'preload.js' ) } , acceptFirstMouse : true , vibrancy : 'under-window' , transparent : false } ; if ( __DARWIN__ ) { windowOptions . titleBarStyle = 'hiddenInset' ; } else if ( __WIN32__ ) { windowOptions . frame = false ; } else if ( __LINUX__ ) { windowOptions . icon = Path . join ( __static , 'icons/1024x1024.png' ) ; } this . window = new BrowserWindow ( windowOptions ) ; this . window . webContents . on ( 'devtools-focused' , ( ) => { ipcMain . emit ( 'window-set' , this ) ; } ) ; savedWindowState . manage ( this . window ) ; if ( identifier ) { this . setProject ( identifier ) ; } this . load ( ) ; nativeTheme . on ( 'updated' , ( ) => { this . window . webContents . send ( 'theme-updated' , nativeTheme . shouldUseDarkColors ? 'dark' : 'light' ) ; } ) ; } public static init ( identifier ) { const window = new this ( identifier ) ; windows [ window . getChild ( ) . id ] = window ; window . onClosed ( async ( ) => { window . closed = true ; if ( window . isBusy ( ) ) { log . info ( 'Window is busy. Attempting teardown of pending processes.' ) ; try { await window . getProject ( ) ! . stop ( ) ; } catch ( _ ) { } } app . quit ( ) ; } ) ; return window ; } public static getFromWebContents ( webContents ) { const child = BrowserWindow . fromWebContents ( webContents ) ; return child ? windows [ child . id ] : null ; } public static getProjectFromWebContents ( webContents ) { const window = this . getFromWebContents ( webContents ) ; return window ? window . getProject ( ) : null ; } protected load ( ) { this . window . webContents . once ( 'did-finish-load' , ( ) => { if ( process . env . NODE_ENV === 'development' ) { this . window . webContents . openDevTools ( ) ; } } ) ; this . window . webContents . on ( 'did-finish-load' , ( ) => { this . onReady ( ) ; this . window . webContents . send ( 'did-finish-load' , { theme : nativeTheme . shouldUseDarkColors ? 'dark' : 'light' , menu : applicationMenu . getSections ( ) , projectId : get ( this . getProject ( ) , 'id' , null ) , focus : this . window . isFocused ( ) , maximized : this . window . isMaximized ( ) , fullscreen : this . window . isFullScreen ( ) , version : app . getVersion ( ) } ) ; this . window . webContents . setVisualZoomLevelLimits ( 1 , 1 ) ; } ) ; this . window . on ( 'close' , ( ) => { if ( __WIN32__ ) { this . window . destroy ( ) ; } } ) ; this . window . on ( 'focus' , ( ) => { this . window . webContents . send ( 'focus' ) ; ipcMain . emit ( 'window-set' , this ) ; } ) ; this . window . on ( 'blur' , ( ) => this . window . webContents . send ( 'blur' ) ) ; this . window . on ( 'maximize' , ( ) => this . window . webContents . send ( 'maximize' ) ) ; this . window . on ( 'unmaximize' , ( ) => this . window . webContents . send ( 'unmaximize' ) ) ; this . window . on ( 'enter-full-screen' , ( ) => this . window . webContents . send ( 'enter-full-screen' ) ) ; this . window . on ( 'leave-full-screen' , ( ) => this . window . webContents . send ( 'leave-full-screen' ) ) ; this . window . loadURL ( process . env . NODE_ENV === 'development' ? `http://localhost:9080` : `file://${ __dirname }/index.html` ) ; } public send ( event , args = [ ] ) { this . events ++ ; this . window . webContents . send ( event , ... args ) ; } public reload ( ) { this . window . reload ( ) ; } public onClosed ( fn ) { this . window . on ( 'closed' , fn ) ; } public setProject ( identifier ) { this . project = new Project ( this , identifier ) ; this . project . on ( 'ready' , async ( ) => { if ( ! this . ready ) { return ; } this . projectReady ( ) ; } ) . on ( 'progress' , this . updateProgress . bind ( this ) ) ; } public onReady ( ) { this . ready = true ; this . refreshSettings ( ) ; if ( this . project && this . project . isReady ( ) ) { this . projectReady ( ) ; } } public canReceiveEvents ( ) { return ! this . closed ; } public getChild ( ) { return this . window ; } public getWebContents ( ) { return this . window . webContents ; } public getProject ( ) { return this . project ; } public getProjectOptions ( ) { return this . project ? this . project . render ( ) : { } ; } public async projectReady ( ) { await this . project ! . reset ( ) ; this . window . webContents . send ( 'project-ready' , this . getProjectOptions ( ) ) ; this . refreshActiveFramework ( ) ; this . refreshSettings ( ) ; } public refreshActiveFramework ( ) { if ( this . project ) { const { framework , repository } = this . project . getActive ( ) ; this . send ( 'framework-active' , [ framework ? framework . getId ( ) : null , repository ? repository . render ( ) : null ] ) ; } } protected refreshSettings ( ) { this . send ( 'settings-updated' , [ state . get ( ) ] ) ; } protected updateProgress ( progress ) { if ( this . canReceiveEvents ( ) ) { this . window . setProgressBar ( progress === 1 ? - 1 : progress ) ; } } public isBusy ( ) { return ! ! this . project && this . project . isBusy ( ) ; } public clear ( ) { this . project = null ; this . refreshSettings ( ) ; this . send ( 'clear' ) ; } public sendMenuEvent ( properties ) { this . window . show ( ) ; this . window . webContents . send ( 'menu-event' , properties ) ; } }