From 4b660633dd506bcf2390bd9c11b91c23b129fc7b Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 26 Jul 2021 13:53:05 -0400 Subject: [PATCH] Got multiple windows open Signed-off-by: Sebastian Malton --- .../package-lock.json | 9 +- .../metrics-cluster-feature/package-lock.json | 9 +- extensions/node-menu/package-lock.json | 9 +- extensions/pod-menu/package-lock.json | 9 +- .../catalog-entities/kubernetes-cluster.ts | 15 +- src/common/catalog/catalog-entity.ts | 1 + src/common/ipc/index.ts | 2 + src/common/utils/iter.ts | 23 ++ src/main/index.ts | 11 +- src/main/initializers/ipc.ts | 24 +- src/main/protocol-handler/router.ts | 2 +- src/main/tray.ts | 8 +- src/main/window-manager.ts | 266 ++++++++++-------- .../+catalog/catalog-entity-drawer-menu.tsx | 6 +- src/renderer/components/+catalog/catalog.tsx | 4 + .../components/hotbar/hotbar-entity-icon.tsx | 3 + src/renderer/lens-app.tsx | 20 +- 17 files changed, 281 insertions(+), 140 deletions(-) diff --git a/extensions/kube-object-event-status/package-lock.json b/extensions/kube-object-event-status/package-lock.json index f3b0fe6669..a2c8fd6490 100644 --- a/extensions/kube-object-event-status/package-lock.json +++ b/extensions/kube-object-event-status/package-lock.json @@ -11,7 +11,8 @@ "@material-ui/core": "*", "@types/node": "*", "@types/react-select": "*", - "conf": "^7.0.1" + "conf": "^7.0.1", + "typed-emitter": "^1.3.1" }, "dependencies": { "@babel/runtime": { @@ -675,6 +676,12 @@ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", "dev": true }, + "typed-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-1.3.1.tgz", + "integrity": "sha512-2h7utWyXgd2R2u2IuL8B4yu1gqMxbgUj2VS/MGVbFhEVQNJKXoQQoS5CBMh+eW31zFeSmDfEQ3qQf4xy5SlPVQ==", + "dev": true + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/extensions/metrics-cluster-feature/package-lock.json b/extensions/metrics-cluster-feature/package-lock.json index 15feb302b8..bb3d653b3d 100644 --- a/extensions/metrics-cluster-feature/package-lock.json +++ b/extensions/metrics-cluster-feature/package-lock.json @@ -665,7 +665,8 @@ "@material-ui/core": "*", "@types/node": "*", "@types/react-select": "*", - "conf": "^7.0.1" + "conf": "^7.0.1", + "typed-emitter": "^1.3.1" }, "dependencies": { "@babel/runtime": { @@ -1329,6 +1330,12 @@ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", "dev": true }, + "typed-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-1.3.1.tgz", + "integrity": "sha512-2h7utWyXgd2R2u2IuL8B4yu1gqMxbgUj2VS/MGVbFhEVQNJKXoQQoS5CBMh+eW31zFeSmDfEQ3qQf4xy5SlPVQ==", + "dev": true + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/extensions/node-menu/package-lock.json b/extensions/node-menu/package-lock.json index 3ca2677ed4..f49204c83c 100644 --- a/extensions/node-menu/package-lock.json +++ b/extensions/node-menu/package-lock.json @@ -631,7 +631,8 @@ "@material-ui/core": "*", "@types/node": "*", "@types/react-select": "*", - "conf": "^7.0.1" + "conf": "^7.0.1", + "typed-emitter": "^1.3.1" }, "dependencies": { "@babel/runtime": { @@ -1295,6 +1296,12 @@ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", "dev": true }, + "typed-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-1.3.1.tgz", + "integrity": "sha512-2h7utWyXgd2R2u2IuL8B4yu1gqMxbgUj2VS/MGVbFhEVQNJKXoQQoS5CBMh+eW31zFeSmDfEQ3qQf4xy5SlPVQ==", + "dev": true + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/extensions/pod-menu/package-lock.json b/extensions/pod-menu/package-lock.json index 70154d1055..745ae4e19b 100644 --- a/extensions/pod-menu/package-lock.json +++ b/extensions/pod-menu/package-lock.json @@ -631,7 +631,8 @@ "@material-ui/core": "*", "@types/node": "*", "@types/react-select": "*", - "conf": "^7.0.1" + "conf": "^7.0.1", + "typed-emitter": "^1.3.1" }, "dependencies": { "@babel/runtime": { @@ -1248,6 +1249,12 @@ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", "dev": true }, + "typed-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-1.3.1.tgz", + "integrity": "sha512-2h7utWyXgd2R2u2IuL8B4yu1gqMxbgUj2VS/MGVbFhEVQNJKXoQQoS5CBMh+eW31zFeSmDfEQ3qQf4xy5SlPVQ==", + "dev": true + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/src/common/catalog-entities/kubernetes-cluster.ts b/src/common/catalog-entities/kubernetes-cluster.ts index 2e8fdccf04..8ec324d155 100644 --- a/src/common/catalog-entities/kubernetes-cluster.ts +++ b/src/common/catalog-entities/kubernetes-cluster.ts @@ -23,7 +23,7 @@ import { catalogCategoryRegistry } from "../catalog/catalog-category-registry"; import { CatalogEntity, CatalogEntityActionContext, CatalogEntityAddMenuContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog"; import { clusterActivateHandler, clusterDeleteHandler, clusterDisconnectHandler } from "../cluster-ipc"; import { ClusterStore } from "../cluster-store"; -import { requestMain } from "../ipc"; +import { onNewWindowForClusterHandler, requestMain } from "../ipc"; import { CatalogCategory, CatalogCategorySpec } from "../catalog"; import { addClusterURL } from "../routes"; import { app } from "electron"; @@ -103,6 +103,19 @@ export class KubernetesCluster extends CatalogEntity this.onRun(context), + }, + { + title: "Open in new window", + icon: "launch", + onClick: () => requestMain(onNewWindowForClusterHandler, this.getId()), + }, + ); + if (!this.metadata.source || this.metadata.source === "local") { context.menuItems.push( { diff --git a/src/common/catalog/catalog-entity.ts b/src/common/catalog/catalog-entity.ts index bc3e360a7b..1c2d0a2061 100644 --- a/src/common/catalog/catalog-entity.ts +++ b/src/common/catalog/catalog-entity.ts @@ -135,6 +135,7 @@ export interface CatalogEntitySettingsMenu { export interface CatalogEntityContextMenuContext { navigate: (url: string) => void; + setCommandPaletteContext: (context?: CatalogEntity) => void; menuItems: CatalogEntityContextMenu[]; } diff --git a/src/common/ipc/index.ts b/src/common/ipc/index.ts index f412e656e8..9e4fc18315 100644 --- a/src/common/ipc/index.ts +++ b/src/common/ipc/index.ts @@ -24,3 +24,5 @@ export * from "./invalid-kubeconfig"; export * from "./update-available.ipc"; export * from "./cluster.ipc"; export * from "./type-enforced-ipc"; + +export const onNewWindowForClusterHandler = "window:open-new:cluster"; diff --git a/src/common/utils/iter.ts b/src/common/utils/iter.ts index 59fce41829..aabe7a9cfc 100644 --- a/src/common/utils/iter.ts +++ b/src/common/utils/iter.ts @@ -156,3 +156,26 @@ export function find(src: Iterable, match: (i: T) => any): T | undefined { return void 0; } + +/** + * Get the zero indexed iteration value, or `undefined` if the iterator has finished + * @param src A type that can be iterated over + * @param index The 0-index number of items to skip before returning + */ +export function nth(src: Iterable, index: number): T | undefined { + const iterator = src[Symbol.iterator](); + + for (let i = 0; i < index; i += 1) { + iterator.next(); + } + + return iterator.next().value; +} + +/** + * A special cased version of `nth`. + * @param src A type that can be iterated over + */ +export function first(src: Iterable): T | undefined { + return src[Symbol.iterator]().next().value; +} diff --git a/src/main/index.ts b/src/main/index.ts index a5116cfdb0..3fe18a5f46 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -60,6 +60,8 @@ import { WeblinkStore } from "../common/weblink-store"; import { ExtensionsStore } from "../extensions/extensions-store"; import { FilesystemProvisionerStore } from "./extension-filesystem"; import { SentryInit } from "../common/sentry"; +import { initMenu } from "./menu"; +import { initTray } from "./tray"; // This has to be called before start using winton-based logger // For example, before any logger.log @@ -115,7 +117,7 @@ app.on("second-instance", (event, argv) => { } } - WindowManager.getInstance(false)?.ensureMainWindow(); + WindowManager.getInstance(false)?.ensureWindow(); }); app.on("ready", async () => { @@ -207,9 +209,12 @@ app.on("ready", async () => { installDeveloperTools(); if (!startHidden) { - windowManager.ensureMainWindow(); + windowManager.ensureWindow(); } + initMenu(windowManager); + initTray(windowManager); + ipcMainOn(IpcRendererNavigationEvents.LOADED, () => { cleanup.push(pushCatalogToRenderer(catalogEntityRegistry)); KubeconfigSyncManager.getInstance().startSync(); @@ -251,7 +256,7 @@ app.on("activate", (event, hasVisibleWindows) => { logger.info("APP:ACTIVATE", { hasVisibleWindows }); if (!hasVisibleWindows) { - WindowManager.getInstance(false)?.ensureMainWindow(false); + WindowManager.getInstance(false)?.ensureWindow(); } }); diff --git a/src/main/initializers/ipc.ts b/src/main/initializers/ipc.ts index 3614c602d7..eb4b2843a1 100644 --- a/src/main/initializers/ipc.ts +++ b/src/main/initializers/ipc.ts @@ -25,13 +25,15 @@ import { clusterFrameMap } from "../../common/cluster-frames"; import { clusterActivateHandler, clusterSetFrameIdHandler, clusterVisibilityHandler, clusterRefreshHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterDeleteHandler } from "../../common/cluster-ipc"; import { ClusterId, ClusterStore } from "../../common/cluster-store"; import { appEventBus } from "../../common/event-bus"; -import { ipcMainHandle } from "../../common/ipc"; +import { ipcMainHandle, onNewWindowForClusterHandler } from "../../common/ipc"; +import { IpcRendererNavigationEvents } from "../../renderer/navigation/events"; import { catalogEntityRegistry } from "../catalog"; import { ClusterManager } from "../cluster-manager"; import { bundledKubectlPath } from "../kubectl"; import logger from "../logger"; import { promiseExecFile } from "../promise-exec"; import { ResourceApplier } from "../resource-applier"; +import { WindowManager } from "../window-manager"; export function initIpcMainHandlers() { ipcMainHandle(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => { @@ -137,4 +139,24 @@ export function initIpcMainHandlers() { throw `${clusterId} is not a valid cluster id`; } }); + + ipcMainHandle(onNewWindowForClusterHandler, async (event, clusterId: ClusterId) => { + appEventBus.emit({ name: "cluster", action: "open-new-window" }); + const cluster = ClusterStore.getInstance().getById(clusterId); + + if (!cluster) { + return void logger.info("Cannot open clutser in new window, unknown cluster Id", { clusterId }); + } + + const wm = WindowManager.getInstance(); + + try { + console.log("trying to opening new window", event); + const window = await wm.openNewWindow(); + + window.webContents.send(IpcRendererNavigationEvents.NAVIGATE_IN_APP, `/cluster/${clusterId}`); + } catch (error) { + logger.error("Failed to load url for new cluster window", error); + } + }); } diff --git a/src/main/protocol-handler/router.ts b/src/main/protocol-handler/router.ts index fe178705e7..b890c5b132 100644 --- a/src/main/protocol-handler/router.ts +++ b/src/main/protocol-handler/router.ts @@ -81,7 +81,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter { throw new proto.RoutingError(proto.RoutingErrorType.INVALID_PROTOCOL, url); } - WindowManager.getInstance(false)?.ensureMainWindow().catch(noop); + WindowManager.getInstance(false)?.ensureWindow().catch(noop); const routeInternally = checkHost(url); logger.info(`${proto.LensProtocolRouter.LoggingPrefix}: routing ${url.toString()}`); diff --git a/src/main/tray.ts b/src/main/tray.ts index 3a772503f8..b78af40e55 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -54,7 +54,7 @@ export function initTray(windowManager: WindowManager) { if (isWindows) { tray.on("click", () => { windowManager - .ensureMainWindow() + .ensureWindow() .catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to open lens`, { error })); }); } @@ -84,7 +84,7 @@ function createTrayMenu(windowManager: WindowManager): Menu { label: `Open ${productName}`, click() { windowManager - .ensureMainWindow() + .ensureWindow() .catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to open lens`, { error })); }, }, @@ -103,7 +103,7 @@ function createTrayMenu(windowManager: WindowManager): Menu { label: "Check for updates", click() { checkForUpdates() - .then(() => windowManager.ensureMainWindow()); + .then(() => windowManager.ensureWindow()); }, }); } @@ -112,7 +112,7 @@ function createTrayMenu(windowManager: WindowManager): Menu { { label: `About ${productName}`, click() { - windowManager.ensureMainWindow() + windowManager.ensureWindow() .then(showAbout) .catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to show Lens About view`, { error })); }, diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index 9a56aaafb8..5871c1547d 100644 --- a/src/main/window-manager.ts +++ b/src/main/window-manager.ts @@ -19,20 +19,16 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { ClusterId } from "../common/cluster-store"; -import { makeObservable, observable } from "mobx"; import { app, BrowserWindow, dialog, ipcMain, shell, webContents } from "electron"; import windowStateKeeper from "electron-window-state"; import { appEventBus } from "../common/event-bus"; -import { ipcMainOn } from "../common/ipc"; -import { initMenu } from "./menu"; -import { initTray } from "./tray"; import { delay, iter, Singleton } from "../common/utils"; import { ClusterFrameInfo, clusterFrameMap } from "../common/cluster-frames"; import { IpcRendererNavigationEvents } from "../renderer/navigation/events"; import logger from "./logger"; import { productName } from "../common/vars"; import { LensProxy } from "./proxy/lens-proxy"; +import { reaction } from "mobx"; function isHideable(window: BrowserWindow | null): boolean { return Boolean(window && !window.isDestroyed()); @@ -45,156 +41,185 @@ export interface SendToViewArgs { } export class WindowManager extends Singleton { - protected mainWindow: BrowserWindow; protected splashWindow: BrowserWindow; - protected windowState: windowStateKeeper.State; - protected disposers: Record = {}; - - @observable activeClusterId: ClusterId; + protected windows = new Map(); constructor() { super(); - makeObservable(this); - this.bindEvents(); - this.initMenu(); - this.initTray(); + + reaction(() => this.windows.size, windowCount => { + // show icon in dock (mac-os only) + if (windowCount) { + app.dock?.show(); + } else { + app.dock?.hide(); + } + }); } get mainUrl() { return `http://localhost:${LensProxy.getInstance().port}`; } - private async initMainWindow(showSplash: boolean) { - // Manage main window size and position with state persistence - if (!this.windowState) { - this.windowState = windowStateKeeper({ - defaultHeight: 900, - defaultWidth: 1440, + private async createNewWindow(): Promise { + const windowState = windowStateKeeper({ + defaultHeight: 900, + defaultWidth: 1440, + }); + const { width, height, x, y } = windowState; + const browserWindow = new BrowserWindow({ + x, y, width, height, + title: productName, + show: false, + minWidth: 700, // accommodate 800 x 600 display minimum + minHeight: 500, // accommodate 800 x 600 display minimum + titleBarStyle: "hidden", + backgroundColor: "#1e2124", + webPreferences: { + nodeIntegration: true, + nodeIntegrationInSubFrames: true, + enableRemoteModule: true, + }, + }); + const windowId = browserWindow.webContents.getProcessId(); + + windowState.manage(browserWindow); + this.windows.set(windowId, [browserWindow, windowState]); + + browserWindow + .on("focus", () => appEventBus.emit({ name: "app", action: "focus" })) + .on("blur", () => appEventBus.emit({ name: "app", action: "blur" })) + .on("closed", () => { + // clean up + windowState.unmanage(); + this.windows.delete(windowId); + + this.splashWindow = null; + }) + .webContents + .on("new-window", (event, url) => { + event.preventDefault(); + shell.openExternal(url); + }) + .on("dom-ready", () => appEventBus.emit({ name: "app", action: "dom-ready" })) + .on("did-fail-load", (_event, code, desc) => { + logger.error(`[WINDOW-MANAGER]: Failed to load window`, { windowId, code, desc }); + }) + .on("did-finish-load", () => { + logger.info("[WINDOW-MANAGER]: Window emitted did-finish-load", { windowId }); }); - } - if (!this.mainWindow) { - // show icon in dock (mac-os only) - app.dock?.show(); + return browserWindow; + } - const { width, height, x, y } = this.windowState; - - this.mainWindow = new BrowserWindow({ - x, y, width, height, - title: productName, - show: false, - minWidth: 700, // accommodate 800 x 600 display minimum - minHeight: 500, // accommodate 800 x 600 display minimum - titleBarStyle: "hidden", - backgroundColor: "#1e2124", - webPreferences: { - nodeIntegration: true, - nodeIntegrationInSubFrames: true, - enableRemoteModule: true, - }, - }); - this.windowState.manage(this.mainWindow); - - // open external links in default browser (target=_blank, window.open) - this.mainWindow - .on("focus", () => { - appEventBus.emit({ name: "app", action: "focus" }); - }) - .on("blur", () => { - appEventBus.emit({ name: "app", action: "blur" }); - }) - .on("closed", () => { - // clean up - this.windowState.unmanage(); - this.mainWindow = null; - this.splashWindow = null; - app.dock?.hide(); // hide icon in dock (mac-os) - }) - .webContents - .on("new-window", (event, url) => { - event.preventDefault(); - shell.openExternal(url); - }) - .on("dom-ready", () => { - appEventBus.emit({ name: "app", action: "dom-ready" }); - }) - .on("did-fail-load", (_event, code, desc) => { - logger.error(`[WINDOW-MANAGER]: Failed to load Main window`, { code, desc }); - }) - .on("did-finish-load", () => { - logger.info("[WINDOW-MANAGER]: Main window loaded"); - }); - } + public async openNewWindow(): Promise { + const browserWindow = await this.createNewWindow(); + const windowId = browserWindow.webContents.getProcessId(); try { - if (showSplash) await this.showSplash(); - logger.info(`[WINDOW-MANAGER]: Loading Main window from url: ${this.mainUrl} ...`); - await this.mainWindow.loadURL(this.mainUrl); + if (!this.hasVisibleWindow()) { + await this.showSplash(); + } + + console.log(this.windows); + + logger.info(`[WINDOW-MANAGER]: Loading window from url: ${this.mainUrl} ...`, { windowId }); + + const viewHasLoaded = new Promise(resolve => { + const listener = (event: Electron.IpcMainEvent): void => { + if (event.sender.getProcessId() === browserWindow.webContents.getProcessId()) { + resolve(); + ipcMain.off(IpcRendererNavigationEvents.LOADED, listener); + } + }; + + ipcMain.on(IpcRendererNavigationEvents.LOADED, listener); + }); + + await browserWindow.loadURL(this.mainUrl); + await viewHasLoaded; + + browserWindow.show(); + this.splashWindow?.close(); + this.splashWindow = undefined; + + setTimeout(() => { + appEventBus.emit({ name: "app", action: "start" }); + }, 1000); } catch (error) { - logger.error("Loading main window failed", { error }); + logger.error("Loading window failed", { windowId, error }); dialog.showErrorBox("ERROR!", error.toString()); } + + return browserWindow; } - protected async initMenu() { - this.disposers.menuAutoUpdater = initMenu(this); - } - - protected initTray() { - this.disposers.trayAutoUpdater = initTray(this); - } - - protected bindEvents() { - // track visible cluster from ui - ipcMainOn(IpcRendererNavigationEvents.CLUSTER_VIEW_CURRENT_ID, (event, clusterId: ClusterId) => { - this.activeClusterId = clusterId; - }); - } - - async ensureMainWindow(showSplash = true): Promise { + async ensureWindow(): Promise { // This needs to be ready to hear the IPC message before the window is loaded let viewHasLoaded = Promise.resolve(); + let browserWindow: BrowserWindow; - if (!this.mainWindow) { + if (this.windows.size === 0) { viewHasLoaded = new Promise(resolve => { ipcMain.once(IpcRendererNavigationEvents.LOADED, () => resolve()); }); - await this.initMainWindow(showSplash); + + browserWindow = await this.openNewWindow(); + + try { + await this.showSplash(); + logger.info(`[WINDOW-MANAGER]: Loading window from url: ${this.mainUrl} ...`, { windowId: browserWindow.webContents.getProcessId() }); + await browserWindow.loadURL(this.mainUrl); + } catch (error) { + logger.error("Loading window failed", { error }); + dialog.showErrorBox("ERROR!", error.toString()); + } + } else { + browserWindow = iter.first(this.windows.values())[0]; } try { await viewHasLoaded; await delay(50); // wait just a bit longer to let the first round of rendering happen - logger.info("[WINDOW-MANAGER]: Main window has reported that it has loaded"); + logger.info("[WINDOW-MANAGER]: Window has reported that it has loaded", { windowId: browserWindow.webContents.getProcessId() }); - this.mainWindow.show(); + browserWindow.show(); this.splashWindow?.close(); this.splashWindow = undefined; setTimeout(() => { appEventBus.emit({ name: "app", action: "start" }); }, 1000); } catch (error) { - logger.error(`Showing main window failed: ${error.stack || error}`); + logger.error(`Showing window failed: ${error.stack || error}`); dialog.showErrorBox("ERROR!", error.toString()); } - return this.mainWindow; + return browserWindow; } - private sendToView({ channel, frameInfo, data = [] }: SendToViewArgs) { + public hasVisibleWindow(): boolean { + for (const [window] of this.windows.values()) { + if (window.isVisible()) { + return true; + } + } + + return false; + } + + private sendToView(browserWindow: BrowserWindow, { channel, frameInfo, data = [] }: SendToViewArgs) { if (frameInfo) { - this.mainWindow.webContents.sendToFrame([frameInfo.processId, frameInfo.frameId], channel, ...data); + browserWindow.webContents.sendToFrame([frameInfo.processId, frameInfo.frameId], channel, ...data); } else { - this.mainWindow.webContents.send(channel, ...data); + browserWindow.webContents.send(channel, ...data); } } async navigateExtension(extId: string, pageId?: string, params?: Record, frameId?: number) { - await this.ensureMainWindow(); - + const browserWindow = await this.ensureWindow(); const frameInfo = iter.find(clusterFrameMap.values(), frameInfo => frameInfo.frameId === frameId); - this.sendToView({ + this.sendToView(browserWindow, { channel: "extension:navigate", frameInfo, data: [extId, pageId, params], @@ -202,14 +227,13 @@ export class WindowManager extends Singleton { } async navigate(url: string, frameId?: number) { - await this.ensureMainWindow(); - + const browserWindow = await this.ensureWindow(); const frameInfo = iter.find(clusterFrameMap.values(), frameInfo => frameInfo.frameId === frameId); const channel = frameInfo ? IpcRendererNavigationEvents.NAVIGATE_IN_CLUSTER : IpcRendererNavigationEvents.NAVIGATE_IN_APP; - this.sendToView({ + this.sendToView(browserWindow, { channel, frameInfo, data: [url], @@ -217,16 +241,10 @@ export class WindowManager extends Singleton { } reload() { - const frameInfo = clusterFrameMap.get(this.activeClusterId); - - if (frameInfo) { - this.sendToView({ channel: IpcRendererNavigationEvents.RELOAD_PAGE, frameInfo }); - } else { - webContents.getFocusedWebContents()?.reload(); - } + webContents.getFocusedWebContents()?.reload(); } - async showSplash() { + private async showSplash() { if (!this.splashWindow) { this.splashWindow = new BrowserWindow({ width: 500, @@ -246,8 +264,10 @@ export class WindowManager extends Singleton { } hide() { - if (isHideable(this.mainWindow)) { - this.mainWindow.hide(); + for (const [window] of this.windows.values()) { + if (isHideable(window)) { + window.hide(); + } } if (isHideable(this.splashWindow)) { @@ -256,13 +276,13 @@ export class WindowManager extends Singleton { } destroy() { - this.mainWindow.destroy(); + for (const [window, manager] of this.windows.values()) { + manager.unmanage(); + window.destroy(); + } + + this.windows.clear(); this.splashWindow.destroy(); - this.mainWindow = null; this.splashWindow = null; - Object.entries(this.disposers).forEach(([name, dispose]) => { - dispose(); - delete this.disposers[name]; - }); } } diff --git a/src/renderer/components/+catalog/catalog-entity-drawer-menu.tsx b/src/renderer/components/+catalog/catalog-entity-drawer-menu.tsx index af3104afe2..d3289ca63a 100644 --- a/src/renderer/components/+catalog/catalog-entity-drawer-menu.tsx +++ b/src/renderer/components/+catalog/catalog-entity-drawer-menu.tsx @@ -31,6 +31,7 @@ import { ConfirmDialog } from "../confirm-dialog"; import { HotbarStore } from "../../../common/hotbar-store"; import { Icon } from "../icon"; import type { CatalogEntityItem } from "./catalog-entity.store"; +import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; export interface CatalogEntityDrawerMenuProps extends MenuActionsProps { item: CatalogEntityItem | null | undefined; @@ -49,6 +50,9 @@ export class CatalogEntityDrawerMenu extends React.Comp this.contextMenu = { menuItems: [], navigate: (url: string) => navigate(url), + setCommandPaletteContext: (entity: CatalogEntity) => { + catalogEntityRegistry.activeEntity = entity; + }, }; this.props.item?.onContextMenuOpen(this.contextMenu); } @@ -109,7 +113,7 @@ export class CatalogEntityDrawerMenu extends React.Comp render() { const { className, item: entity, ...menuProps } = this.props; - + if (!this.contextMenu || !entity.enabled) { return null; } diff --git a/src/renderer/components/+catalog/catalog.tsx b/src/renderer/components/+catalog/catalog.tsx index 4256170649..db2d6db29a 100644 --- a/src/renderer/components/+catalog/catalog.tsx +++ b/src/renderer/components/+catalog/catalog.tsx @@ -44,6 +44,7 @@ import { CatalogMenu } from "./catalog-menu"; import { HotbarIcon } from "../hotbar/hotbar-icon"; import { RenderDelay } from "../render-delay/render-delay"; import { CatalogTopbar } from "../cluster-manager/catalog-topbar"; +import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; export const previousActiveTab = createAppStorage("catalog-previous-active-tab", ""); @@ -83,6 +84,9 @@ export class Catalog extends React.Component { this.contextMenu = { menuItems: observable.array([]), navigate: (url: string) => navigate(url), + setCommandPaletteContext: (entity: CatalogEntity) => { + catalogEntityRegistry.activeEntity = entity; + }, }; disposeOnUnmount(this, [ this.catalogEntityStore.watch(), diff --git a/src/renderer/components/hotbar/hotbar-entity-icon.tsx b/src/renderer/components/hotbar/hotbar-entity-icon.tsx index fc6628f3eb..a7b809bff7 100644 --- a/src/renderer/components/hotbar/hotbar-entity-icon.tsx +++ b/src/renderer/components/hotbar/hotbar-entity-icon.tsx @@ -55,6 +55,9 @@ export class HotbarEntityIcon extends React.Component { this.contextMenu = { menuItems: [], navigate: (url: string) => navigate(url), + setCommandPaletteContext: (entity: CatalogEntity) => { + catalogEntityRegistry.activeEntity = entity; + }, }; } diff --git a/src/renderer/lens-app.tsx b/src/renderer/lens-app.tsx index 35151dcee2..5f18627b34 100644 --- a/src/renderer/lens-app.tsx +++ b/src/renderer/lens-app.tsx @@ -37,8 +37,16 @@ import { ipcRenderer } from "electron"; import { IpcRendererNavigationEvents } from "./navigation/events"; import { catalogEntityRegistry } from "./api/catalog-entity-registry"; +interface LensAppState { + mounted: boolean; +} + @observer -export class LensApp extends React.Component { +export class LensApp extends React.Component<{}, LensAppState> { + state = { + mounted: false + }; + static async init() { catalogEntityRegistry.init(); ExtensionLoader.getInstance().loadOnClusterManagerRenderer(); @@ -52,7 +60,15 @@ export class LensApp extends React.Component { } componentDidMount() { - ipcRenderer.send(IpcRendererNavigationEvents.LOADED); + this.setState({ mounted: true }); + } + + componentDidUpdate() { + console.log(process); + + if (this.state.mounted) { + ipcRenderer.send(IpcRendererNavigationEvents.LOADED); + } } render() {