import * as loader from "tc-loader"; import {Stage} from "tc-loader"; import { ContextMenuEntry, ContextMenuFactory, setGlobalContextMenuFactory } from "tc-shared/ui/ContextMenu"; import {ChannelMessage, IPCChannel} from "tc-shared/ipc/BrowserIPC"; import * as ipc from "tc-shared/ipc/BrowserIPC"; import {reactContextMenuInstance} from "./ReactRenderer"; import {getIconManager, RemoteIcon} from "tc-shared/file/Icons"; const kIPCContextMenuChannel = "context-menu"; class IPCContextMenu implements ContextMenuFactory { private readonly ipcChannel: IPCChannel; private currentlyFocusedWindow: string; private currentWindowFocused = true; private remoteContextMenuSupplierId: string; private uniqueEntryId: number = 0; private menuCallbacks: {[key: string]: (() => void)} = {}; private closeCallback: () => void; constructor() { this.ipcChannel = ipc.getIpcInstance().createChannel(kIPCContextMenuChannel); this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this); /* if we're just created we're the focused window ;) */ this.currentWindowFocused = false; this.handleWindowFocusReceived(); document.addEventListener("mousedown", () => this.handleWindowFocusReceived()); } private handleWindowFocusReceived() { if(!this.currentWindowFocused) { if(this.closeCallback) { this.closeCallback(); } this.closeCallback = undefined; this.menuCallbacks = {}; this.remoteContextMenuSupplierId = undefined; this.currentlyFocusedWindow = undefined; this.currentWindowFocused = true; this.ipcChannel.sendMessage("notify-focus-taken", {}); } } private wrapMenuEntryForRemote(entry: ContextMenuEntry) : ContextMenuEntry { switch (entry.type) { case "normal": if(entry.subMenu) { entry.subMenu = entry.subMenu.map(entry => this.wrapMenuEntryForRemote(entry)); } if(entry.icon instanceof RemoteIcon) { entry.icon = { iconId: entry.icon.iconId, serverUniqueId: entry.icon.serverUniqueId } as any; } /* fall through wanted! */ case "checkbox": if(!entry.click) { break; } if(!entry.uniqueId) { entry.uniqueId = "r_" + (++this.uniqueEntryId); } this.menuCallbacks[entry.uniqueId] = entry.click; entry.click = undefined; break; } return entry; } private wrapMenuEntryFromRemote(entry: ContextMenuEntry) : ContextMenuEntry { switch (entry.type) { case "normal": if(entry.subMenu) { entry.subMenu = entry.subMenu.map(entry => this.wrapMenuEntryFromRemote(entry)); } if(typeof entry.icon === "object") { const icon = entry.icon as any; entry.icon = getIconManager().resolveIcon(icon.iconId, icon.serverUniqueId); } /* fall through wanted! */ case "checkbox": if(!entry.uniqueId) { break; } entry.click = () => { this.remoteContextMenuSupplierId && this.ipcChannel.sendMessage("notify-entry-click", { id: entry.uniqueId }, this.remoteContextMenuSupplierId); }; break; } return entry; } closeContextMenu() { if(this.currentWindowFocused) { reactContextMenuInstance.closeContextMenu(); } } spawnContextMenu(position: { pageX: number; pageY: number }, entries: ContextMenuEntry[], callbackClose?: () => void) { if(this.currentWindowFocused) { reactContextMenuInstance.spawnContextMenu(position, entries, callbackClose); } else { this.ipcChannel.sendMessage("spawn-context-menu", { position: position, entries: entries.map(entry => this.wrapMenuEntryForRemote(entry)) }); this.closeCallback = callbackClose; } } private handleIpcMessage(remoteId: string, _broadcast: boolean, message: ChannelMessage) { if(message.type === "spawn-context-menu") { if(!this.currentWindowFocused) { return; } reactContextMenuInstance.spawnContextMenu(message.data.position, message.data.entries.map(entry => this.wrapMenuEntryFromRemote(entry)), () => { if(!this.remoteContextMenuSupplierId) { return; } this.ipcChannel.sendMessage("notify-menu-close", {}, this.remoteContextMenuSupplierId); this.remoteContextMenuSupplierId = undefined; }); this.remoteContextMenuSupplierId = remoteId; } else if(message.type === "notify-focus-taken") { this.currentlyFocusedWindow = remoteId; this.currentWindowFocused = false; /* close out context menu if we've any */ reactContextMenuInstance.closeContextMenu(); } else if(message.type === "notify-entry-click") { const callback = this.menuCallbacks[message.data.id]; if(!callback) { return; } callback(); } else if(message.type === "notify-menu-close") { this.menuCallbacks = {}; if(this.closeCallback) { this.closeCallback(); this.closeCallback = undefined; } } } } loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { priority: 80, name: "context menu init", function: async () => { setGlobalContextMenuFactory(new IPCContextMenu()); } })