diff --git a/main/MenuTemplate/DebugMenu.ts b/main/MenuTemplate/DebugMenu.ts index 1dc43060..c20a04dd 100644 --- a/main/MenuTemplate/DebugMenu.ts +++ b/main/MenuTemplate/DebugMenu.ts @@ -19,9 +19,7 @@ const DebugMenu: MenuItemConstructorOptions = { { label: 'Toggle DevTools', click() { - if (RendererBridge.registeredWindow) { - RendererBridge.registeredWindow.webContents.toggleDevTools(); - } + RendererBridge.toggleWindowDevtools('main'); }, accelerator: 'CommandOrControl+alt+I', }, @@ -57,9 +55,7 @@ const DebugMenu: MenuItemConstructorOptions = { label: 'Reload', accelerator: 'CommandOrControl+R', click() { - if (RendererBridge.registeredWindow) { - RendererBridge.registeredWindow.reload(); - } + RendererBridge.reloadWindow('main'); }, }, diff --git a/main/MenuTemplate/HelpMenu.ts b/main/MenuTemplate/HelpMenu.ts index 7cdc04a0..27df46c9 100644 --- a/main/MenuTemplate/HelpMenu.ts +++ b/main/MenuTemplate/HelpMenu.ts @@ -11,9 +11,7 @@ const HelpMenu: MenuItemConstructorOptions = { { label: 'Interactive Tutorial', click() { - if (RendererBridge.registeredWindow) { - RendererBridge.registeredWindow.webContents.send('start-interactive-tour'); - } + RendererBridge.dispatch('main', 'start-interactive-tour'); }, accelerator: 'CommandOrControl+T', }, diff --git a/main/RendererBridge.ts b/main/RendererBridge.ts index 7d8e708a..149716ba 100644 --- a/main/RendererBridge.ts +++ b/main/RendererBridge.ts @@ -1,23 +1,81 @@ /** - * RendererBridge connects the main process to the renderer's Redux flow. - * Maintains a real-time copy of the renderer's Redux state in the main process, and - * allows the main process to dispatch redux actions to the renderer. + * RendererBridge connects the main process to children renderer processes and + * allows the main process to dispatch data to the renderer processes. */ import _ from 'lodash'; import { BrowserWindow } from "electron"; +type SingleOrArray = T | T[]; + class RendererBridge { - registeredWindow: BrowserWindow | null = null; + private registeredWindows: Record = {}; + + hasRegisteredWindow = (key: string) => this.registeredWindows[key] ?? false; - registerWindow = (electronWindow: BrowserWindow) => { - this.registeredWindow = electronWindow; + /** Registers the window in the RendererBridge */ + registerWindow = (key: string, electronWindow: BrowserWindow) => { + this.registeredWindows[key] = electronWindow; }; - reduxDispatch = (action: any) => { - if (this.registeredWindow) { - this.registeredWindow.webContents.send('dispatch', action); + /** Unregisters the window from the RendererBridge */ + unregisterWindow = (key: string) => { + delete this.registeredWindows[key]; + } + + /** + * Reloads the window specified by its key in the RendererBridge + * If key specified doesn't exist in the RendererBridge, then nothing will happen. + */ + reloadWindow = (key: string) => { + const registeredWindow = this.registeredWindows[key]; + registeredWindow?.reload(); + } + + /** + * Toggles the window DevTools for window specified by key + * If key specified doesn't exist in the RendererBridge, then nothing will happen. + */ + toggleWindowDevtools = (key: string) => { + const registeredWindow = this.registeredWindows[key]; + registeredWindow?.webContents.toggleDevTools(); + } + + /** + * From windows specified by `windowKeys`, dispatches `data` to `channel`. + * If `windowKeys` is undefined, will send to all registered windows. + * If `windowKeys` is a `string`, then the data will only be dispatched to that registered window. + */ + dispatch = (windowKeys: SingleOrArray | 'all', channel: string, ...data: any[]) => { + if (windowKeys === 'all') { + windowKeys = Object.keys(this.registeredWindows); + } else if (typeof windowKeys === 'string') { + windowKeys = [windowKeys] } + + try { + for (const key of windowKeys) { + const registeredWindow = this.registeredWindows[key]; + + // Special case for dispatching Redux since we can only dispatch one action at a time + if (channel === 'reduxDispatch') { + data = data[0]; + } + + registeredWindow?.webContents.send(channel, data); + } + } catch (e) { + console.log(`[RendererBridge] dispatch caught error:`, e) + } + } + + /** + * More particular usage of `dispatch`. Use this method if windows you are sending to + * have a Redux store. The default window for this method will have key `'main'` which will dispatch + * Redux actions to the main window to avoid large refactors from the current usage of this method. + */ + reduxDispatch = (action: any, windowKeys: SingleOrArray | 'all' | 'main' = 'main') => { + this.dispatch(windowKeys, 'reduxDispatch', action); }; }; diff --git a/main/main-process.ts b/main/main-process.ts index 24bb8011..dadb2309 100644 --- a/main/main-process.ts +++ b/main/main-process.ts @@ -74,7 +74,7 @@ app.on('ready', () => { }); // Binding for the main process to inject into Redux workflow - RendererBridge.registerWindow(mainWindow); + RendererBridge.registerWindow('main', mainWindow); mainWindow.maximize(); mainWindow.loadURL(`file://${__dirname}/../static/index.html`); diff --git a/renderer/utils/sagas.ts b/renderer/utils/sagas.ts index ca78956d..18148219 100644 --- a/renderer/utils/sagas.ts +++ b/renderer/utils/sagas.ts @@ -340,10 +340,10 @@ function runtimeReceiver() { emitter(action); }; // Suscribe listener to dispatches from main process. - ipcRenderer.on('dispatch', listener); + ipcRenderer.on('reduxDispatch', listener); // Return an unsuscribe function. return () => { - ipcRenderer.removeListener('dispatch', listener); + ipcRenderer.removeListener('reduxDispatch', listener); }; }); }