import {class_to_image} from "./icon-helper"; import * as electron from "electron"; import * as mbar from "tc-shared/ui/frames/MenuBar"; import {Arguments, process_args} from "../shared/process-arguments"; import ipcRenderer = electron.ipcRenderer; import {Icon} from "tc-shared/FileManager"; namespace native { import ipcRenderer = electron.ipcRenderer; let _item_index = 1; abstract class NativeMenuBase { protected _handle: NativeMenuBar; protected _click: () => any; id: string; protected constructor(handle: NativeMenuBar, id?: string) { this._handle = handle; this.id = id || ("item_" + (_item_index++)); } abstract build() : electron.MenuItemConstructorOptions; abstract items(): (mbar.MenuItem | mbar.HRItem)[]; trigger_click() { if(this._click) this._click(); } } class NativeMenuItem extends NativeMenuBase implements mbar.MenuItem { private _items: (NativeMenuItem | NativeHrItem)[] = []; private _label: string; private _enabled: boolean = true; private _visible: boolean = true; private _icon_data: string; constructor(handle: NativeMenuBar) { super(handle); } append_hr(): mbar.HRItem { const item = new NativeHrItem(this._handle); this._items.push(item); return item; } append_item(label: string): mbar.MenuItem { const item = new NativeMenuItem(this._handle); item.label(label); this._items.push(item); return item; } click(callback: () => any): this { this._click = callback; return this; } delete_item(item: mbar.MenuItem | mbar.HRItem) { const i_index = this._items.indexOf(item as any); if(i_index < 0) return; this._items.splice(i_index, 1); } disabled(value?: boolean): boolean { if(typeof(value) === "boolean") this._enabled = !value; return !this._enabled; } icon(klass?: string | Promise | Icon): string { if(typeof(klass) === "string") { const buffer = class_to_image(klass); if(buffer) this._icon_data = buffer.toDataURL(); } return ""; } items(): (mbar.MenuItem | mbar.HRItem)[] { return this._items; } label(value?: string): string { if(typeof(value) === "string") this._label = value; return this._label; } visible(value?: boolean): boolean { if(typeof(value) === "boolean") this._visible = value; return this._visible; } build(): Electron.MenuItemConstructorOptions { return { id: this.id, label: this._label || "", submenu: this._items.length > 0 ? this._items.map(e => e.build()) : undefined, enabled: this._enabled, visible: this._visible, icon: this._icon_data } } } class NativeHrItem extends NativeMenuBase implements mbar.HRItem { constructor(handle: NativeMenuBar) { super(handle); } build(): Electron.MenuItemConstructorOptions { return { type: 'separator', id: this.id } } items(): (mbar.MenuItem | mbar.HRItem)[] { return []; } } function is_similar_deep(a, b) { if(typeof(a) !== typeof(b)) return false; if(typeof(a) !== "object") return a === b; const aProps = Object.keys(a); const bProps = Object.keys(b); if (aProps.length != bProps.length) return false; for (let i = 0; i < aProps.length; i++) { const propName = aProps[i]; if(!is_similar_deep(a[propName], b[propName])) return false; } return true; } export class NativeMenuBar implements mbar.MenuBarDriver { private static _instance: NativeMenuBar; private menu: electron.Menu; private _items: NativeMenuItem[] = []; private _current_menu: electron.MenuItemConstructorOptions[]; public static instance() : NativeMenuBar { if(!this._instance) this._instance = new NativeMenuBar(); return this._instance; } append_item(label: string): mbar.MenuItem { const item = new NativeMenuItem(this); item.label(label); this._items.push(item); return item; } delete_item(item: mbar.MenuItem) { const i_index = this._items.indexOf(item as any); if(i_index < 0) return; this._items.splice(i_index, 1); } flush_changes() { const target_menu = this.build_menu(); if(is_similar_deep(target_menu, this._current_menu)) return; this._current_menu = target_menu; ipcRenderer.send('top-menu', target_menu); } private build_menu() : electron.MenuItemConstructorOptions[] { return this._items.map(e => e.build()); } items(): mbar.MenuItem[] { return this._items; } initialize() { this.menu = new electron.remote.Menu(); ipcRenderer.on('top-menu', (event, clicked_item) => { console.log("Item %o clicked", clicked_item); const check_item = (item: NativeMenuBase) => { if(item.id == clicked_item) { item.trigger_click(); return true; } for(const child of item.items()) if(check_item(child as NativeMenuBase)) return true; }; for(const item of this._items) if(check_item(item)) return; }); } } } mbar.set_driver(native.NativeMenuBar.instance()); // @ts-ignore mbar.native_actions = { open_change_log() { call_basic_action("open-changelog"); }, check_native_update() { call_basic_action("check-native-update"); }, quit() { call_basic_action("quit"); }, open_dev_tools() { call_basic_action("open-dev-tools"); }, reload_page() { call_basic_action("reload-window") }, show_dev_tools() { return process_args.has_flag(Arguments.DEV_TOOLS); } }; const call_basic_action = (name: string, ...args: any[]) => ipcRenderer.send('basic-action', name, ...args);