import { merge } from '@overflow/core/merge'; import { MenuIDs } from '../type'; import { IAppState } from './app-state'; import { TipState } from './tip'; import { AppMenu, MenuItem } from './app-menu'; export interface IMenuItemState { readonly enabled?: boolean; } /** * Utility class for coalescing updates to menu items */ class MenuStateBuilder { private readonly _state: Map; public constructor(state: Map = new Map()) { this._state = state; } /** * Returns an Map where each key is a MenuID and the values * are IMenuItemState instances containing information about * whether a particular menu item should be enabled/disabled or * visible/hidden. */ public get state() { return new Map(this._state); } private updateMenuItem( id: MenuIDs, state: Pick ) { const currentState = this._state.get(id) || {}; this._state.set(id, merge(currentState, state)); } /** Set the state of the given menu item id to enabled */ public enable(id: MenuIDs): this { this.updateMenuItem(id, { enabled: true }); return this; } /** Set the state of the given menu item id to disabled */ public disable(id: MenuIDs): this { this.updateMenuItem(id, { enabled: false }); return this; } /** Set the enabledness of the given menu item id */ public setEnabled(id: MenuIDs, enabled: boolean): this { this.updateMenuItem(id, { enabled }); return this; } /** * Create a new state builder by merging the current state with the state from * the other state builder. This will replace values in `this` with values * from `other`. */ public merge(other: MenuStateBuilder): MenuStateBuilder { const merged = new Map(this._state); for (const [key, value] of other._state) { merged.set(key, value); } return new MenuStateBuilder(merged); } } function menuItemStateEqual(state: IMenuItemState, menuItem: MenuItem) { if ( state.enabled !== undefined && menuItem.type !== 'separator' && menuItem.enabled !== state.enabled ) { return false; } return true; } const allMenuIds: ReadonlyArray = [ 'save', 'preferences', 'about', 'export-csv', 'print', 'language-english', 'language-korean', ]; function getAllMenusDisabledBuilder(): MenuStateBuilder { const menuStateBuilder = new MenuStateBuilder(); for (const menuId of allMenuIds) { menuStateBuilder.disable(menuId); } return menuStateBuilder; } function getMenuState(state: IAppState): Map { if (state.currentPopup) { return getAllMenusDisabledBuilder().state; } return getAllMenusEnabledBuilder() .merge(getInWelcomeFlowBuilder(state.showWelcomeFlow)).state; } function getAllMenusEnabledBuilder(): MenuStateBuilder { const menuStateBuilder = new MenuStateBuilder(); for (const menuId of allMenuIds) { menuStateBuilder.enable(menuId); } return menuStateBuilder; } function getInWelcomeFlowBuilder(inWelcomeFlow: boolean): MenuStateBuilder { const welcomeScopedIds: ReadonlyArray = [ 'preferences', 'about', ]; const menuStateBuilder = new MenuStateBuilder(); if (inWelcomeFlow) { for (const id of welcomeScopedIds) { menuStateBuilder.disable(id); } } else { for (const id of welcomeScopedIds) { menuStateBuilder.enable(id); } } return menuStateBuilder; } /** * Update the menu state in the main process. * * This function will set the enabledness and visibility of menu items * in the main process based on the AppState. All changes will be * batched together into one ipc message. */ export function updateMenuState( state: IAppState, currentAppMenu: AppMenu | null ) { const menuState = getMenuState(state); // Try to avoid updating sending the IPC message at all // if we have a current app menu that we can compare against. if (currentAppMenu) { for (const [id, menuItemState] of menuState.entries()) { const appMenuItem = currentAppMenu.getItemById(id); if (appMenuItem && menuItemStateEqual(menuItemState, appMenuItem)) { menuState.delete(id); } } } if (menuState.size === 0) { return; } // because we can't send Map over the wire, we need to convert // the remaining entries into an array that can be serialized const array = new Array<{ id: MenuIDs; state: IMenuItemState }>(); menuState.forEach((value, key) => array.push({ id: key, state: value })); // ipcUpdateMenuState(array); }