/ src / main / MainRunner.ts
MainRunner.ts
  1  import {
  2    app,
  3    BrowserWindow,
  4    RenderProcessGoneDetails,
  5    BrowserWindowConstructorOptions
  6  } from 'electron'
  7  import Constants, { TrayOptions } from './utils/Constants'
  8  import IPCs, { registerIpcHandlers } from './IPCs'
  9  import { createTray, hideWindow, showWindow } from './tray'
 10  
 11  import { loadConfig } from './mcp/init'
 12  
 13  const options = {
 14    width: Constants.IS_DEV_ENV ? 1500 : 1280,
 15    height: 1080,
 16    minWidth: 375,
 17    minHeight: 480,
 18    tray: {
 19      // all optional values from DEFAULT_TRAY_OPTIONS can de defined here
 20      enabled: true,
 21      menu: true, // true, to use a tray menu ; false to toggle visibility on click on tray icon
 22      trayWindow: false // true, to use a tray floating window attached to top try icon
 23    }
 24  }
 25  
 26  const exitApp = (mainWindow: BrowserWindow): void => {
 27    if (mainWindow && !mainWindow.isDestroyed()) {
 28      mainWindow.hide()
 29    }
 30    mainWindow.destroy()
 31    app.exit()
 32  }
 33  
 34  export const createSplashWindow = async (): Promise<BrowserWindow> => {
 35    const splashWindow = new BrowserWindow({
 36      width: 400,
 37      height: 300,
 38      frame: false,
 39      alwaysOnTop: true,
 40      resizable: false,
 41      show: true,
 42      skipTaskbar: true,
 43      transparent: true
 44    })
 45  
 46    if (Constants.IS_DEV_ENV) {
 47      await splashWindow.loadURL(Constants.APP_SPLASH_URL_DEV)
 48    } else {
 49      await splashWindow.loadFile(Constants.APP_SPLASH_URL_PROD)
 50    }
 51  
 52    return splashWindow
 53  }
 54  
 55  export const createMainWindow = async (): Promise<BrowserWindow> => {
 56    let opt: BrowserWindowConstructorOptions = {
 57      title: Constants.APP_NAME,
 58      show: false,
 59      width: options.width,
 60      height: options.height,
 61      minWidth: options.minWidth,
 62      minHeight: options.minHeight,
 63      useContentSize: true,
 64      webPreferences: Constants.DEFAULT_WEB_PREFERENCES,
 65      frame: true,
 66      ...(process.platform == 'win32' || process.platform == 'linux' || process.platform == 'darwin'
 67        ? {
 68            titleBarStyle: 'hidden',
 69            titleBarOverlay: {
 70              color: '#344767',
 71              symbolColor: 'white',
 72              height: 36
 73            }
 74          }
 75        : {})
 76    }
 77    const trayOptions: TrayOptions = options.tray?.enabled
 78      ? {
 79          ...Constants.DEFAULT_TRAY_OPTIONS,
 80          ...options.tray
 81        }
 82      : {
 83          ...Constants.DEFAULT_TRAY_OPTIONS,
 84          enabled: false
 85        }
 86  
 87    // trayWindow requires tray.enabled=true
 88    if (trayOptions.enabled && trayOptions.trayWindow) {
 89      opt = {
 90        ...opt,
 91        width: options.width,
 92        height: options.height,
 93        maxWidth: options.width,
 94        maxHeight: options.height,
 95        show: false,
 96        frame: false,
 97        fullscreenable: false,
 98        hiddenInMissionControl: true,
 99        resizable: false,
100        transparent: true,
101        alwaysOnTop: true,
102        webPreferences: {
103          ...Constants.DEFAULT_WEB_PREFERENCES,
104          backgroundThrottling: false
105        }
106      }
107    }
108    const mainWindow = new BrowserWindow(opt)
109  
110    // This will disable dev-tool as well
111    mainWindow.setMenu(null)
112  
113    mainWindow.on('close', (event: Event): void => {
114      event.preventDefault()
115      exitApp(mainWindow)
116    })
117  
118    mainWindow.webContents.on('did-frame-finish-load', (): void => {
119      if (Constants.IS_DEV_ENV && Constants.IS_DEVTOOLS) {
120        mainWindow.webContents.openDevTools()
121      }
122    })
123  
124    if (trayOptions.enabled) {
125      createTray(mainWindow, trayOptions)
126    }
127  
128    if (trayOptions.enabled && trayOptions.trayWindow) {
129      hideWindow(mainWindow)
130      if (trayOptions.showAtStartup) {
131        showWindow(mainWindow)
132      }
133    } else {
134      mainWindow.once('ready-to-show', (): void => {
135        mainWindow.setAlwaysOnTop(true)
136        mainWindow.show()
137        mainWindow.focus()
138        mainWindow.setAlwaysOnTop(false)
139      })
140    }
141  
142    // Initialize IPC Communication
143    IPCs.initialize()
144  
145    const configs = await loadConfig()
146  
147    const features = configs.map((params) => {
148      return registerIpcHandlers(params)
149    })
150  
151    IPCs.initializeMCP(features)
152  
153    if (Constants.IS_DEV_ENV) {
154      await mainWindow.loadURL(Constants.APP_INDEX_URL_DEV)
155    } else {
156      await mainWindow.loadFile(Constants.APP_INDEX_URL_PROD)
157    }
158  
159    return mainWindow
160  }
161  
162  export const createErrorWindow = async (
163    errorWindow: BrowserWindow,
164    mainWindow: BrowserWindow,
165    _details?: RenderProcessGoneDetails
166  ): Promise<BrowserWindow> => {
167    if (!Constants.IS_DEV_ENV) {
168      mainWindow?.hide()
169    }
170  
171    errorWindow = new BrowserWindow({
172      title: Constants.APP_NAME,
173      show: false,
174      resizable: Constants.IS_DEV_ENV,
175      webPreferences: Constants.DEFAULT_WEB_PREFERENCES
176    })
177  
178    errorWindow.setMenu(null)
179  
180    if (Constants.IS_DEV_ENV) {
181      await errorWindow.loadURL(`${Constants.APP_INDEX_URL_DEV}#/error`)
182    } else {
183      await errorWindow.loadFile(Constants.APP_INDEX_URL_PROD, { hash: 'error' })
184    }
185  
186    errorWindow.on('ready-to-show', (): void => {
187      if (!Constants.IS_DEV_ENV && mainWindow && !mainWindow.isDestroyed()) {
188        mainWindow.destroy()
189      }
190      errorWindow.show()
191      errorWindow.focus()
192    })
193  
194    errorWindow.webContents.on('did-frame-finish-load', (): void => {
195      if (Constants.IS_DEV_ENV) {
196        errorWindow.webContents.openDevTools()
197      }
198    })
199  
200    return errorWindow
201  }