From c426eb98990c5ce575623f6ea48d347de2cfb881 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 27 Jul 2021 11:20:08 -0400 Subject: [PATCH] attempt 1 to fix opening bug Signed-off-by: Sebastian Malton --- .../catalog-entities/kubernetes-cluster.ts | 10 +- src/common/cluster-frames.ts | 40 +++++++- src/common/cluster-ipc.ts | 1 + src/common/ipc/ipc.ts | 10 +- src/common/utils/index.ts | 6 ++ src/main/index.ts | 3 + src/main/initializers/ipc.ts | 28 ++++-- src/main/menu.ts | 7 ++ src/main/window-manager.ts | 95 +++++++++++++++---- 9 files changed, 161 insertions(+), 39 deletions(-) diff --git a/src/common/catalog-entities/kubernetes-cluster.ts b/src/common/catalog-entities/kubernetes-cluster.ts index 8ec324d155..15f7843f2f 100644 --- a/src/common/catalog-entities/kubernetes-cluster.ts +++ b/src/common/catalog-entities/kubernetes-cluster.ts @@ -20,8 +20,8 @@ */ import { catalogCategoryRegistry } from "../catalog/catalog-category-registry"; -import { CatalogEntity, CatalogEntityActionContext, CatalogEntityAddMenuContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog"; -import { clusterActivateHandler, clusterDeleteHandler, clusterDisconnectHandler } from "../cluster-ipc"; +import { CatalogEntity, CatalogEntityAddMenuContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog"; +import { clusterActivateHandler, clusterDeleteHandler, clusterDisconnectHandler, navigateToClusterHandler } from "../cluster-ipc"; import { ClusterStore } from "../cluster-store"; import { onNewWindowForClusterHandler, requestMain } from "../ipc"; import { CatalogCategory, CatalogCategorySpec } from "../catalog"; @@ -90,8 +90,8 @@ export class KubernetesCluster extends CatalogEntity this.onRun(context), + onClick: () => this.onRun(), }, { title: "Open in new window", diff --git a/src/common/cluster-frames.ts b/src/common/cluster-frames.ts index 88dc2d3163..81ec53558b 100644 --- a/src/common/cluster-frames.ts +++ b/src/common/cluster-frames.ts @@ -19,11 +19,45 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { observable } from "mobx"; +import { action, observable } from "mobx"; +import type { ClusterId } from "./cluster-store"; +import { iter, Singleton } from "./utils"; export interface ClusterFrameInfo { frameId: number; - processId: number + processId: number; + windowId: number; } -export const clusterFrameMap = observable.map(); +export class ClusterFrames extends Singleton { + private mapping = observable.map(); + + public getAllFrameInfo(): ClusterFrameInfo[] { + return [...this.mapping.values()]; + } + + public set(clusterId: ClusterId, info: ClusterFrameInfo): void { + this.mapping.set(clusterId, info); + } + + public getFrameInfoByClusterId(clusterId: ClusterId): ClusterFrameInfo | undefined { + return this.mapping.get(clusterId); + } + + public getFrameInfoByFrameId(frameId: number): ClusterFrameInfo | undefined { + return iter.find(this.mapping.values(), frameInfo => frameInfo.frameId === frameId); + } + + public clearInfoForCluster(clusterId: ClusterId): void { + this.mapping.delete(clusterId); + } + + @action + public clearInfoForWindow(windowId: number): void { + for (const [clusterId, frameInfo] of this.mapping) { + if (frameInfo.windowId === windowId) { + this.mapping.delete(clusterId); + } + } + } +} diff --git a/src/common/cluster-ipc.ts b/src/common/cluster-ipc.ts index 9191d93048..18330db0b7 100644 --- a/src/common/cluster-ipc.ts +++ b/src/common/cluster-ipc.ts @@ -19,6 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +export const navigateToClusterHandler = "navigate:to-cluster"; export const clusterActivateHandler = "cluster:activate"; export const clusterSetFrameIdHandler = "cluster:set-frame-id"; export const clusterVisibilityHandler = "cluster:visibility"; diff --git a/src/common/ipc/ipc.ts b/src/common/ipc/ipc.ts index 074d73176c..57604d1577 100644 --- a/src/common/ipc/ipc.ts +++ b/src/common/ipc/ipc.ts @@ -26,7 +26,7 @@ import { ipcMain, ipcRenderer, remote, webContents } from "electron"; import { toJS } from "../utils/toJS"; import logger from "../../main/logger"; -import { ClusterFrameInfo, clusterFrameMap } from "../cluster-frames"; +import { ClusterFrames } from "../cluster-frames"; import type { Disposer } from "../utils"; const subFramesChannel = "ipc:get-sub-frames"; @@ -41,10 +41,6 @@ export function ipcMainHandle(channel: string, listener: (event: Electron.IpcMai }); } -function getSubFrames(): ClusterFrameInfo[] { - return Array.from(clusterFrameMap.values()); -} - export function broadcastMessage(channel: string, ...args: any[]) { const views = (webContents || remote?.webContents)?.getAllWebContents(); @@ -56,7 +52,7 @@ export function broadcastMessage(channel: string, ...args: any[]) { const subFramesP = ipcRenderer ? requestMain(subFramesChannel) - : Promise.resolve(getSubFrames()); + : Promise.resolve(ClusterFrames.getInstance().getAllFrameInfo()); subFramesP .then(subFrames => { @@ -88,7 +84,7 @@ export function ipcRendererOn(channel: string, listener: (event: Electron.IpcRen } export function bindBroadcastHandlers() { - ipcMainHandle(subFramesChannel, () => getSubFrames()); + ipcMainHandle(subFramesChannel, () => ClusterFrames.getInstance().getAllFrameInfo()); } /** diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index 1644c07f36..220c106444 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -55,3 +55,9 @@ export * from "./type-narrowing"; import * as iter from "./iter"; export { iter }; + +export type NeverPartial = { + [P in keyof T]?: never; +}; + +export type OnlyOneOf = { [K in keyof T]-?: Required> & NeverPartial>>; }[keyof T]; diff --git a/src/main/index.ts b/src/main/index.ts index 3fe18a5f46..dd72b46482 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -62,6 +62,7 @@ import { FilesystemProvisionerStore } from "./extension-filesystem"; import { SentryInit } from "../common/sentry"; import { initMenu } from "./menu"; import { initTray } from "./tray"; +import { ClusterFrames } from "../common/cluster-frames"; // This has to be called before start using winton-based logger // For example, before any logger.log @@ -121,6 +122,8 @@ app.on("second-instance", (event, argv) => { }); app.on("ready", async () => { + ClusterFrames.createInstance(); + logger.info(`🚀 Starting ${productName} from "${workingDir}"`); logger.info("🐚 Syncing shell environment"); await shellSync(); diff --git a/src/main/initializers/ipc.ts b/src/main/initializers/ipc.ts index eb4b2843a1..c966407516 100644 --- a/src/main/initializers/ipc.ts +++ b/src/main/initializers/ipc.ts @@ -21,8 +21,8 @@ import type { IpcMainInvokeEvent } from "electron"; import { KubernetesCluster } from "../../common/catalog-entities"; -import { clusterFrameMap } from "../../common/cluster-frames"; -import { clusterActivateHandler, clusterSetFrameIdHandler, clusterVisibilityHandler, clusterRefreshHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterDeleteHandler } from "../../common/cluster-ipc"; +import { ClusterFrames } from "../../common/cluster-frames"; +import { clusterActivateHandler, clusterSetFrameIdHandler, clusterVisibilityHandler, clusterRefreshHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterDeleteHandler, navigateToClusterHandler } from "../../common/cluster-ipc"; import { ClusterId, ClusterStore } from "../../common/cluster-store"; import { appEventBus } from "../../common/event-bus"; import { ipcMainHandle, onNewWindowForClusterHandler } from "../../common/ipc"; @@ -46,11 +46,25 @@ export function initIpcMainHandlers() { const cluster = ClusterStore.getInstance().getById(clusterId); if (cluster) { - clusterFrameMap.set(cluster.id, { frameId: event.frameId, processId: event.processId }); + ClusterFrames.getInstance().set(cluster.id, { + frameId: event.frameId, + processId: event.processId, + windowId: event.sender.getProcessId(), + }); cluster.pushState(); } }); + ipcMainHandle(navigateToClusterHandler, async (event, clusterId: ClusterId) => { + const cluster = ClusterStore.getInstance().getById(clusterId); + + if (!cluster) { + return void logger.warn("[NAVIGATE-TO-CLUSTER]: unknown cluster", { clusterId }); + } + + await WindowManager.getInstance().navigate(`/cluster/${clusterId}`, { clusterId }, { windowId: event.sender.getProcessId() }); + }); + ipcMainHandle(clusterVisibilityHandler, (event: IpcMainInvokeEvent, clusterId: ClusterId, visible: boolean) => { const entity = catalogEntityRegistry.getById(clusterId); @@ -75,7 +89,7 @@ export function initIpcMainHandlers() { if (cluster) { cluster.disconnect(); - clusterFrameMap.delete(cluster.id); + ClusterFrames.getInstance().clearInfoForCluster(cluster.id); } }); @@ -89,7 +103,7 @@ export function initIpcMainHandlers() { ClusterManager.getInstance().deleting.add(clusterId); cluster.disconnect(); - clusterFrameMap.delete(cluster.id); + ClusterFrames.getInstance().clearInfoForCluster(cluster.id); const kubectlPath = bundledKubectlPath(); const args = ["config", "delete-context", cluster.contextName, "--kubeconfig", cluster.kubeConfigPath]; @@ -148,10 +162,8 @@ export function initIpcMainHandlers() { 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 wm = WindowManager.getInstance(); const window = await wm.openNewWindow(); window.webContents.send(IpcRendererNavigationEvents.NAVIGATE_IN_APP, `/cluster/${clusterId}`); diff --git a/src/main/menu.ts b/src/main/menu.ts index b1ffff6a57..2565948908 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -118,6 +118,13 @@ export function buildMenu(windowManager: WindowManager) { navigate(addClusterURL()); } }, + { + label: "New Window", + accelerator: "CmdOrCtrl+N", + click() { + windowManager.openNewWindow(); + } + }, ...ignoreOnMac([ { type: "separator" }, { diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index 5871c1547d..736fabe6ae 100644 --- a/src/main/window-manager.ts +++ b/src/main/window-manager.ts @@ -22,8 +22,8 @@ import { app, BrowserWindow, dialog, ipcMain, shell, webContents } from "electron"; import windowStateKeeper from "electron-window-state"; import { appEventBus } from "../common/event-bus"; -import { delay, iter, Singleton } from "../common/utils"; -import { ClusterFrameInfo, clusterFrameMap } from "../common/cluster-frames"; +import { delay, iter, Singleton, toJS } from "../common/utils"; +import { ClusterFrameInfo, ClusterFrames } from "../common/cluster-frames"; import { IpcRendererNavigationEvents } from "../renderer/navigation/events"; import logger from "./logger"; import { productName } from "../common/vars"; @@ -40,6 +40,12 @@ export interface SendToViewArgs { data?: any[]; } +export interface NavigateFrameInfoSpecifier { + windowId?: number; + clusterId?: number; + frameId?: number; +} + export class WindowManager extends Singleton { protected splashWindow: BrowserWindow; protected windows = new Map(); @@ -93,7 +99,9 @@ export class WindowManager extends Singleton { // clean up windowState.unmanage(); this.windows.delete(windowId); + ClusterFrames.getInstance().clearInfoForWindow(windowId); + this.splashWindow?.close(); this.splashWindow = null; }) .webContents @@ -117,11 +125,7 @@ export class WindowManager extends Singleton { const windowId = browserWindow.webContents.getProcessId(); try { - if (!this.hasVisibleWindow()) { - await this.showSplash(); - } - - console.log(this.windows); + await this.showSplash(); logger.info(`[WINDOW-MANAGER]: Loading window from url: ${this.mainUrl} ...`, { windowId }); @@ -139,9 +143,9 @@ export class WindowManager extends Singleton { await browserWindow.loadURL(this.mainUrl); await viewHasLoaded; - browserWindow.show(); this.splashWindow?.close(); this.splashWindow = undefined; + browserWindow.show(); setTimeout(() => { appEventBus.emit({ name: "app", action: "start" }); @@ -154,7 +158,7 @@ export class WindowManager extends Singleton { return browserWindow; } - async ensureWindow(): Promise { + async ensureWindow(windowId?: number): Promise { // This needs to be ready to hear the IPC message before the window is loaded let viewHasLoaded = Promise.resolve(); let browserWindow: BrowserWindow; @@ -174,18 +178,20 @@ export class WindowManager extends Singleton { logger.error("Loading window failed", { error }); dialog.showErrorBox("ERROR!", error.toString()); } - } else { - browserWindow = iter.first(this.windows.values())[0]; + } else if (typeof windowId === "number") { + browserWindow = (this.windows.get(windowId) ?? iter.first(this.windows.values()))[0]; } + 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]: Window has reported that it has loaded", { windowId: browserWindow.webContents.getProcessId() }); - browserWindow.show(); this.splashWindow?.close(); this.splashWindow = undefined; + browserWindow.show(); setTimeout(() => { appEventBus.emit({ name: "app", action: "start" }); }, 1000); @@ -217,7 +223,7 @@ export class WindowManager extends Singleton { async navigateExtension(extId: string, pageId?: string, params?: Record, frameId?: number) { const browserWindow = await this.ensureWindow(); - const frameInfo = iter.find(clusterFrameMap.values(), frameInfo => frameInfo.frameId === frameId); + const frameInfo = ClusterFrames.getInstance().getFrameInfoByFrameId(frameId); this.sendToView(browserWindow, { channel: "extension:navigate", @@ -226,9 +232,66 @@ export class WindowManager extends Singleton { }); } - async navigate(url: string, frameId?: number) { - const browserWindow = await this.ensureWindow(); - const frameInfo = iter.find(clusterFrameMap.values(), frameInfo => frameInfo.frameId === frameId); + /** + * Get the naviate target + * @param specifics The fallback options for specifying a target + */ + private getNavigateTarget(specifics: NavigateFrameInfoSpecifier[]): [ClusterFrameInfo | undefined, number | undefined] { + function helper(): ClusterFrameInfo | undefined | number { + const clusterFrames = ClusterFrames.getInstance(); + + for (const fallback of specifics) { + if (typeof fallback.clusterId === "string") { + const res = clusterFrames.getFrameInfoByClusterId(fallback.clusterId); + + if (res == null) { // intentional + return res; + } else { + continue; + } + } + + if (typeof fallback.frameId === "number") { + const res = clusterFrames.getFrameInfoByFrameId(fallback.frameId); + + if (res == null) { // intentional + return res; + } else { + continue; + } + } + + if (typeof fallback.windowId === "number") { + return fallback.windowId; + } + } + + return undefined; + } + + const target = helper(); + + if (target == null) { // intentional + return [undefined, undefined]; + } + + if (typeof target === "number") { + return [undefined, target]; + } + + return [target, target.windowId]; + } + + /** + * Navigate to `url` on a specific window or frame + * @param url The url to navigate to + * @param specifics Data for specifying a specific window or iframe + */ + async navigate(url: string, ...specifics: NavigateFrameInfoSpecifier[]): Promise { + const [frameInfo, windowId] = this.getNavigateTarget(specifics); + + console.log("[WINDOW-MANAGER]: navigate to", url, "with", specifics, toJS(frameInfo), { windowId }); + const browserWindow = await this.ensureWindow(windowId); const channel = frameInfo ? IpcRendererNavigationEvents.NAVIGATE_IN_CLUSTER : IpcRendererNavigationEvents.NAVIGATE_IN_APP;