diff --git a/src/common/cluster-frames.injectable.ts b/src/common/cluster-frames.injectable.ts new file mode 100644 index 0000000000..23897012a0 --- /dev/null +++ b/src/common/cluster-frames.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { clusterFrameMap } from "./cluster-frames"; + +const clusterFramesInjectable = getInjectable({ + id: "cluster-frames", + instantiate: () => clusterFrameMap, + causesSideEffects: true, +}); + +export default clusterFramesInjectable; diff --git a/src/common/setupable-injection-token/run-setups.ts b/src/common/setupable-injection-token/run-setups.ts index 246b06b165..cbe037b9d1 100644 --- a/src/common/setupable-injection-token/run-setups.ts +++ b/src/common/setupable-injection-token/run-setups.ts @@ -3,13 +3,13 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import type { DiContainer } from "@ogre-tools/injectable"; -import { beforeApplicationIsReadyInjectionToken } from "../../main/start-main-application/before-application-is-ready/before-application-is-ready-injection-token"; +import { beforeElectronIsReadyInjectionToken } from "../../main/start-main-application/before-electron-is-ready/before-electron-is-ready-injection-token"; import { afterApplicationIsReadyInjectionToken } from "../../main/start-main-application/after-application-is-ready/after-application-is-ready-injection-token"; export const runSetups = async (di: DiContainer) => { await Promise.all( di - .injectMany(beforeApplicationIsReadyInjectionToken) + .injectMany(beforeElectronIsReadyInjectionToken) .map((setupable) => setupable.run()), ); diff --git a/src/extensions/lens-main-extension.ts b/src/extensions/lens-main-extension.ts index 556691d8be..cf4279ea99 100644 --- a/src/extensions/lens-main-extension.ts +++ b/src/extensions/lens-main-extension.ts @@ -10,8 +10,8 @@ import type { MenuRegistration } from "../main/menu/menu-registration"; import type { TrayMenuRegistration } from "../main/tray/tray-menu-registration"; import type { ShellEnvModifier } from "../main/shell-session/shell-env-modifier/shell-env-modifier-registration"; import { Environments, getEnvironmentSpecificLegacyGlobalDiForExtensionApi } from "./as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; -import windowManagerInjectable from "../main/window-manager.injectable"; import catalogEntityRegistryInjectable from "../main/catalog/catalog-entity-registry.injectable"; +import navigateForExtensionInjectable from "../main/start-main-application/lens-window/navigate-for-extension.injectable"; export class LensMainExtension extends LensExtension { appMenus: MenuRegistration[] = []; @@ -35,9 +35,9 @@ export class LensMainExtension extends LensExtension { async navigate(pageId?: string, params?: Record, frameId?: number) { const di = getEnvironmentSpecificLegacyGlobalDiForExtensionApi(Environments.main); - const windowManager = di.inject(windowManagerInjectable); + const navigate = di.inject(navigateForExtensionInjectable); - return windowManager.navigateExtension(this.id, pageId, params, frameId); + return navigate(this.id, pageId, params, frameId); } addCatalogSource(id: string, source: IObservableArray) { diff --git a/src/extensions/main-api/navigation.ts b/src/extensions/main-api/navigation.ts index 156641e7fb..375d2bac74 100644 --- a/src/extensions/main-api/navigation.ts +++ b/src/extensions/main-api/navigation.ts @@ -8,12 +8,12 @@ import { getEnvironmentSpecificLegacyGlobalDiForExtensionApi, } from "../as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; -import windowManagerInjectable from "../../main/window-manager.injectable"; +import navigateInjectable from "../../main/start-main-application/lens-window/navigate.injectable"; export function navigate(url: string) { const di = getEnvironmentSpecificLegacyGlobalDiForExtensionApi(Environments.main); - const windowManager = di.inject(windowManagerInjectable); + const navigate = di.inject(navigateInjectable); - return windowManager.navigate(url); + return navigate(url); } diff --git a/src/main/app-paths/setup-app-paths.injectable.ts b/src/main/app-paths/setup-app-paths.injectable.ts index fc4e3d28ae..8a60a961df 100644 --- a/src/main/app-paths/setup-app-paths.injectable.ts +++ b/src/main/app-paths/setup-app-paths.injectable.ts @@ -16,8 +16,8 @@ import { appPathsIpcChannel } from "../../common/app-paths/app-path-injection-to import registerChannelInjectable from "./register-channel/register-channel.injectable"; import joinPathsInjectable from "../../common/path/join-paths.injectable"; import { - beforeApplicationIsReadyInjectionToken, -} from "../start-main-application/before-application-is-ready/before-application-is-ready-injection-token"; + beforeElectronIsReadyInjectionToken, +} from "../start-main-application/before-electron-is-ready/before-electron-is-ready-injection-token"; const setupAppPathsInjectable = getInjectable({ id: "setup-app-paths", @@ -54,7 +54,7 @@ const setupAppPathsInjectable = getInjectable({ }; }, - injectionToken: beforeApplicationIsReadyInjectionToken, + injectionToken: beforeElectronIsReadyInjectionToken, }); export default setupAppPathsInjectable; diff --git a/src/main/electron-app/after-application-is-ready/setup-deep-linking.injectable.ts b/src/main/electron-app/after-application-is-ready/setup-deep-linking.injectable.ts index 2e22932f32..ea73acff9e 100644 --- a/src/main/electron-app/after-application-is-ready/setup-deep-linking.injectable.ts +++ b/src/main/electron-app/after-application-is-ready/setup-deep-linking.injectable.ts @@ -6,12 +6,11 @@ import { getInjectable } from "@ogre-tools/injectable"; import electronAppInjectable from "../electron-app.injectable"; import openDeepLinkInjectable from "../../protocol-handler/lens-protocol-router-main/open-deep-link-for-url/open-deep-link.injectable"; import loggerInjectable from "../../../common/logger.injectable"; -import { afterApplicationIsReadyInjectionToken } from "../../start-main-application/after-application-is-ready/after-application-is-ready-injection-token"; import commandLineArgumentsInjectable from "../../utils/command-line-arguments.injectable"; import { pipeline } from "@ogre-tools/fp"; import { find, startsWith, toLower, map } from "lodash/fp"; -import ensureMainWindowInjectable from "../../ensure-main-window/ensure-main-window.injectable"; -import enforceSingleApplicationInstanceInjectable from "./enforce-single-application-instance.injectable"; +import { whenApplicationIsLoadingInjectionToken } from "../../start-main-application/when-application-is-loading/when-application-is-loading-injection-token"; +import applicationWindowInjectable from "../../start-main-application/lens-window/application-window/application-window.injectable"; const setupDeepLinkingInjectable = getInjectable({ id: "setup-deep-linking", @@ -20,57 +19,52 @@ const setupDeepLinkingInjectable = getInjectable({ const app = di.inject(electronAppInjectable); const logger = di.inject(loggerInjectable); const openDeepLinkForUrl = di.inject(openDeepLinkInjectable); + const applicationWindow = di.inject(applicationWindowInjectable); const firstInstanceCommandLineArguments = di.inject( commandLineArgumentsInjectable, ); - const ensureMainWindow = di.inject(ensureMainWindowInjectable); - return { - runAfter: di.inject(enforceSingleApplicationInstanceInjectable), - run: async () => { - { - logger.info(`📟 Setting protocol client for lens://`); + logger.info(`📟 Setting protocol client for lens://`); - if (app.setAsDefaultProtocolClient("lens")) { - logger.info("📟 Protocol client register succeeded ✅"); - } else { - logger.info("📟 Protocol client register failed ❗"); - } - - app.on("open-url", async (event, url) => { - event.preventDefault(); - - await openDeepLinkForUrl(url); - }); - - app.on( - "second-instance", - - async (_, secondInstanceCommandLineArguments) => { - const url = getDeepLinkUrl(secondInstanceCommandLineArguments); - - await ensureMainWindow(); - - if (url) { - await openDeepLinkForUrl(url); - } - }, - ); - - const url = getDeepLinkUrl(firstInstanceCommandLineArguments); - - if (url) { - await openDeepLinkForUrl(url); - } + if (app.setAsDefaultProtocolClient("lens")) { + logger.info("📟 Protocol client register succeeded ✅"); + } else { + logger.info("📟 Protocol client register failed ❗"); } + + const url = getDeepLinkUrl(firstInstanceCommandLineArguments); + + if (url) { + await openDeepLinkForUrl(url); + } + + app.on("open-url", async (event, url) => { + event.preventDefault(); + + await openDeepLinkForUrl(url); + }); + + app.on( + "second-instance", + + async (_, secondInstanceCommandLineArguments) => { + const url = getDeepLinkUrl(secondInstanceCommandLineArguments); + + await applicationWindow.show(); + + if (url) { + await openDeepLinkForUrl(url); + } + }, + ); }, }; }, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: whenApplicationIsLoadingInjectionToken, }); export default setupDeepLinkingInjectable; diff --git a/src/main/electron-app/after-application-is-ready/setup-developer-tools-in-development-environment.injectable.ts b/src/main/electron-app/after-application-is-ready/setup-developer-tools-in-development-environment.injectable.ts index 329c287872..70d8b5ca73 100644 --- a/src/main/electron-app/after-application-is-ready/setup-developer-tools-in-development-environment.injectable.ts +++ b/src/main/electron-app/after-application-is-ready/setup-developer-tools-in-development-environment.injectable.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { afterApplicationIsReadyInjectionToken } from "../../start-main-application/after-application-is-ready/after-application-is-ready-injection-token"; import loggerInjectable from "../../../common/logger.injectable"; +import { whenApplicationIsLoadingInjectionToken } from "../../start-main-application/when-application-is-loading/when-application-is-loading-injection-token"; const setupDeveloperToolsInDevelopmentEnvironmentInjectable = getInjectable({ id: "setup-developer-tools-in-development-environment", @@ -34,7 +34,7 @@ const setupDeveloperToolsInDevelopmentEnvironmentInjectable = getInjectable({ }; }, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: whenApplicationIsLoadingInjectionToken, }); export default setupDeveloperToolsInDevelopmentEnvironmentInjectable; diff --git a/src/main/electron-app/after-application-is-ready/setup-device-shutdown.injectable.ts b/src/main/electron-app/after-application-is-ready/setup-device-shutdown.injectable.ts index f665ffda66..14ebbe9768 100644 --- a/src/main/electron-app/after-application-is-ready/setup-device-shutdown.injectable.ts +++ b/src/main/electron-app/after-application-is-ready/setup-device-shutdown.injectable.ts @@ -3,9 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { afterApplicationIsReadyInjectionToken } from "../../start-main-application/after-application-is-ready/after-application-is-ready-injection-token"; import powerMonitorInjectable from "../features/power-monitor.injectable"; import exitAppInjectable from "../features/exit-app.injectable"; +import { whenApplicationIsLoadingInjectionToken } from "../../start-main-application/when-application-is-loading/when-application-is-loading-injection-token"; const setupDeviceShutdownInjectable = getInjectable({ id: "setup-device-shutdown", @@ -23,7 +23,7 @@ const setupDeviceShutdownInjectable = getInjectable({ }; }, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: whenApplicationIsLoadingInjectionToken, }); export default setupDeviceShutdownInjectable; diff --git a/src/main/electron-app/after-application-is-ready/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable.ts b/src/main/electron-app/after-application-is-ready/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable.ts index 04a263c349..a533fb351b 100644 --- a/src/main/electron-app/after-application-is-ready/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable.ts +++ b/src/main/electron-app/after-application-is-ready/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable.ts @@ -9,9 +9,9 @@ import loggerInjectable from "../../../../common/logger.injectable"; import clusterManagerInjectable from "../../../cluster-manager.injectable"; import applicationMenuItemsInjectable from "../../../menu/application-menu-items.injectable"; import getAbsolutePathInjectable from "../../../../common/path/get-absolute-path.injectable"; -import { afterApplicationIsReadyInjectionToken } from "../../../start-main-application/after-application-is-ready/after-application-is-ready-injection-token"; import catalogEntityRegistryInjectable from "../../../catalog/catalog-entity-registry.injectable"; import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable"; +import { whenApplicationIsLoadingInjectionToken } from "../../../start-main-application/when-application-is-loading/when-application-is-loading-injection-token"; const setupIpcMainHandlersInjectable = getInjectable({ id: "setup-ipc-main-handlers", @@ -45,7 +45,7 @@ const setupIpcMainHandlersInjectable = getInjectable({ }; }, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: whenApplicationIsLoadingInjectionToken, }); export default setupIpcMainHandlersInjectable; diff --git a/src/main/electron-app/after-application-is-ready/setup-main-window-visibility-after-activation.injectable.ts b/src/main/electron-app/after-application-is-ready/setup-main-window-visibility-after-activation.injectable.ts index 36c72725e3..f0bb635d55 100644 --- a/src/main/electron-app/after-application-is-ready/setup-main-window-visibility-after-activation.injectable.ts +++ b/src/main/electron-app/after-application-is-ready/setup-main-window-visibility-after-activation.injectable.ts @@ -4,16 +4,16 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import electronAppInjectable from "../electron-app.injectable"; -import { afterApplicationIsReadyInjectionToken } from "../../start-main-application/after-application-is-ready/after-application-is-ready-injection-token"; -import ensureMainWindowInjectable from "../../ensure-main-window/ensure-main-window.injectable"; import loggerInjectable from "../../../common/logger.injectable"; +import { whenApplicationIsLoadingInjectionToken } from "../../start-main-application/when-application-is-loading/when-application-is-loading-injection-token"; +import applicationWindowInjectable from "../../start-main-application/lens-window/application-window/application-window.injectable"; const setupMainWindowVisibilityAfterActivationInjectable = getInjectable({ id: "setup-main-window-visibility-after-activation", instantiate: (di) => { const app = di.inject(electronAppInjectable); - const ensureMainWindow = di.inject(ensureMainWindowInjectable); + const applicationWindow = di.inject(applicationWindowInjectable); const logger = di.inject(loggerInjectable); return { @@ -22,14 +22,14 @@ const setupMainWindowVisibilityAfterActivationInjectable = getInjectable({ logger.info("APP:ACTIVATE", { hasVisibleWindows: windowIsVisible }); if (!windowIsVisible) { - await ensureMainWindow(false); + await applicationWindow.show(); } }); }, }; }, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: whenApplicationIsLoadingInjectionToken, }); export default setupMainWindowVisibilityAfterActivationInjectable; diff --git a/src/main/electron-app/after-application-is-ready/setup-os-theme-updates.injectable.ts b/src/main/electron-app/after-application-is-ready/setup-os-theme-updates.injectable.ts index 9ee5ef9476..f32c803501 100644 --- a/src/main/electron-app/after-application-is-ready/setup-os-theme-updates.injectable.ts +++ b/src/main/electron-app/after-application-is-ready/setup-os-theme-updates.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { broadcastNativeThemeOnUpdate } from "../../native-theme"; -import { afterApplicationIsReadyInjectionToken } from "../../start-main-application/after-application-is-ready/after-application-is-ready-injection-token"; +import { whenApplicationIsLoadingInjectionToken } from "../../start-main-application/when-application-is-loading/when-application-is-loading-injection-token"; const setupOsThemeUpdatesInjectable = getInjectable({ id: "setup-os-theme-updates", @@ -18,7 +18,7 @@ const setupOsThemeUpdatesInjectable = getInjectable({ // Todo: remove explicit usage of IPC to get rid of this. causesSideEffects: true, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: whenApplicationIsLoadingInjectionToken, }); export default setupOsThemeUpdatesInjectable; diff --git a/src/main/electron-app/before-application-is-ready/enforce-single-application-instance.injectable.ts b/src/main/electron-app/before-application-is-ready/enforce-single-application-instance.injectable.ts index cb0a1221ea..8e94569b43 100644 --- a/src/main/electron-app/before-application-is-ready/enforce-single-application-instance.injectable.ts +++ b/src/main/electron-app/before-application-is-ready/enforce-single-application-instance.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { beforeApplicationIsReadyInjectionToken } from "../../start-main-application/before-application-is-ready/before-application-is-ready-injection-token"; +import { beforeElectronIsReadyInjectionToken } from "../../start-main-application/before-electron-is-ready/before-electron-is-ready-injection-token"; import requestSingleInstanceLockInjectable from "../features/request-single-instance-lock.injectable"; import exitAppInjectable from "../features/exit-app.injectable"; @@ -23,7 +23,7 @@ const enforceSingleApplicationInstanceInjectable = getInjectable({ }; }, - injectionToken: beforeApplicationIsReadyInjectionToken, + injectionToken: beforeElectronIsReadyInjectionToken, }); export default enforceSingleApplicationInstanceInjectable; diff --git a/src/main/electron-app/before-application-is-ready/setup-application-name.injectable.ts b/src/main/electron-app/before-application-is-ready/setup-application-name.injectable.ts index 954f79f7da..64742b4d49 100644 --- a/src/main/electron-app/before-application-is-ready/setup-application-name.injectable.ts +++ b/src/main/electron-app/before-application-is-ready/setup-application-name.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import appNameInjectable from "../../app-paths/app-name/app-name.injectable"; -import { beforeApplicationIsReadyInjectionToken } from "../../start-main-application/before-application-is-ready/before-application-is-ready-injection-token"; +import { beforeElectronIsReadyInjectionToken } from "../../start-main-application/before-electron-is-ready/before-electron-is-ready-injection-token"; import electronAppInjectable from "../electron-app.injectable"; const setupApplicationNameInjectable = getInjectable({ @@ -21,7 +21,7 @@ const setupApplicationNameInjectable = getInjectable({ }; }, - injectionToken: beforeApplicationIsReadyInjectionToken, + injectionToken: beforeElectronIsReadyInjectionToken, }); export default setupApplicationNameInjectable; diff --git a/src/main/electron-app/before-application-is-ready/setup-runnables-before-closing-of-application.injectable.ts b/src/main/electron-app/before-application-is-ready/setup-runnables-before-closing-of-application.injectable.ts index 750b9ecfab..38be51fdbb 100644 --- a/src/main/electron-app/before-application-is-ready/setup-runnables-before-closing-of-application.injectable.ts +++ b/src/main/electron-app/before-application-is-ready/setup-runnables-before-closing-of-application.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { beforeApplicationIsReadyInjectionToken } from "../../start-main-application/before-application-is-ready/before-application-is-ready-injection-token"; +import { beforeElectronIsReadyInjectionToken } from "../../start-main-application/before-electron-is-ready/before-electron-is-ready-injection-token"; import { beforeQuitOfFrontEndInjectionToken } from "../../start-main-application/before-quit-of-front-end/before-quit-of-front-end-injection-token"; import { beforeQuitOfBackEndInjectionToken } from "../../start-main-application/before-quit-of-back-end/before-quit-of-back-end-injection-token"; import electronAppInjectable from "../electron-app.injectable"; @@ -54,7 +54,7 @@ const setupRunnablesBeforeClosingOfApplicationInjectable = getInjectable({ }; }, - injectionToken: beforeApplicationIsReadyInjectionToken, + injectionToken: beforeElectronIsReadyInjectionToken, }); export default setupRunnablesBeforeClosingOfApplicationInjectable; diff --git a/src/main/electron-app/features/show-message-popup.injectable.ts b/src/main/electron-app/features/show-message-popup.injectable.ts new file mode 100644 index 0000000000..7ad2f53c6f --- /dev/null +++ b/src/main/electron-app/features/show-message-popup.injectable.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import electronDialogInjectable from "./electron-dialog.injectable"; + +export type ShowMessagePopup = (title: string, message: string, detail: string) => void; + +const showMessagePopupInjectable = getInjectable({ + id: "show-message-popup", + + instantiate: (di): ShowMessagePopup => { + const dialog = di.inject(electronDialogInjectable); + + return async (title, message, detail) => { + await dialog.showMessageBox({ + title, + message, + detail, + type: "info", + buttons: ["Close"], + }); + }; + }, +}); + +export default showMessagePopupInjectable; diff --git a/src/main/ensure-main-window/ensure-main-window.injectable.ts b/src/main/ensure-main-window/ensure-main-window.injectable.ts deleted file mode 100644 index f1a8e3cabb..0000000000 --- a/src/main/ensure-main-window/ensure-main-window.injectable.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import windowManagerInjectable from "../window-manager.injectable"; - -const ensureMainWindowInjectable = getInjectable({ - id: "ensure-main-window", - - instantiate: (di) => { - const windowManager = di.inject(windowManagerInjectable); - - return async (showSplash = true) => { - await windowManager.ensureMainWindow(showSplash); - }; - }, -}); - -export default ensureMainWindowInjectable; diff --git a/src/main/getDiForUnitTesting.ts b/src/main/getDiForUnitTesting.ts index 2d40ef3dbb..65db823e4c 100644 --- a/src/main/getDiForUnitTesting.ts +++ b/src/main/getDiForUnitTesting.ts @@ -38,25 +38,24 @@ import appEventBusInjectable from "../common/app-event-bus/app-event-bus.injecta import { EventEmitter } from "../common/event-emitter"; import type { AppEvent } from "../common/app-event-bus/event-bus"; import commandLineArgumentsInjectable from "./utils/command-line-arguments.injectable"; -import initializeExtensionsInjectable from "./start-main-application/after-application-is-ready/implementations/initialize-extensions.injectable"; +import initializeExtensionsInjectable from "./start-main-application/when-application-is-loading/implementations/initialize-extensions.injectable"; import lensResourcesDirInjectable from "../common/vars/lens-resources-dir.injectable"; import registerFileProtocolInjectable from "./electron-app/features/register-file-protocol.injectable"; import environmentVariablesInjectable from "../common/utils/environment-variables.injectable"; import { CatalogCategoryRegistry } from "../common/catalog"; import catalogCategoryRegistryInjectable from "../common/catalog/catalog-category-registry.injectable"; import setupIpcMainHandlersInjectable from "./electron-app/after-application-is-ready/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable"; -import setupLensProxyInjectable from "./start-main-application/after-application-is-ready/implementations/setup-lens-proxy.injectable"; -import setupRunnablesForAfterRootFrameIsReadyInjectable from "./start-main-application/after-application-is-ready/implementations/setup-runnables-for-after-root-frame-is-ready.injectable"; +import setupLensProxyInjectable from "./start-main-application/before-application-is-loading/implementations/setup-lens-proxy.injectable"; +import setupRunnablesForAfterRootFrameIsReadyInjectable from "./start-main-application/when-application-is-loading/implementations/setup-runnables-for-after-root-frame-is-ready.injectable"; import setupOsThemeUpdatesInjectable from "./electron-app/after-application-is-ready/setup-os-theme-updates.injectable"; -import setupSentryInjectable from "./start-main-application/after-application-is-ready/implementations/setup-sentry.injectable"; -import setupShellInjectable from "./start-main-application/after-application-is-ready/implementations/setup-shell.injectable"; -import setupSyncingOfWeblinksInjectable from "./start-main-application/after-application-is-ready/implementations/setup-syncing-of-weblinks.injectable"; +import setupSentryInjectable from "./start-main-application/when-application-is-loading/implementations/setup-sentry.injectable"; +import setupShellInjectable from "./start-main-application/when-application-is-loading/implementations/setup-shell.injectable"; +import setupSyncingOfWeblinksInjectable from "./start-main-application/when-application-is-loading/implementations/setup-syncing-of-weblinks.injectable"; import stopServicesAndExitAppInjectable from "./stop-services-and-exit-app.injectable"; import trayInjectable from "./tray/tray.injectable"; import applicationMenuInjectable from "./menu/application-menu.injectable"; -import windowManagerInjectable from "./window-manager.injectable"; import isDevelopmentInjectable from "../common/vars/is-development.injectable"; -import setupSystemCaInjectable from "./start-main-application/after-application-is-ready/implementations/setup-system-ca.injectable"; +import setupSystemCaInjectable from "./start-main-application/before-application-is-loading/implementations/setup-system-ca.injectable"; import setupDeepLinkingInjectable from "./electron-app/after-application-is-ready/setup-deep-linking.injectable"; import exitAppInjectable from "./electron-app/features/exit-app.injectable"; import getCommandLineSwitchInjectable from "./electron-app/features/get-command-line-switch.injectable"; @@ -69,6 +68,12 @@ import setupMainWindowVisibilityAfterActivationInjectable from "./electron-app/a import setupDeviceShutdownInjectable from "./electron-app/after-application-is-ready/setup-device-shutdown.injectable"; import setupApplicationNameInjectable from "./electron-app/before-application-is-ready/setup-application-name.injectable"; import setupRunnablesBeforeClosingOfApplicationInjectable from "./electron-app/before-application-is-ready/setup-runnables-before-closing-of-application.injectable"; +import showMessagePopupInjectable from "./electron-app/features/show-message-popup.injectable"; +import clusterFramesInjectable from "../common/cluster-frames.injectable"; +import type { ClusterFrameInfo } from "../common/cluster-frames"; +import { observable } from "mobx"; +import createBrowserWindowInjectable from "./start-main-application/lens-window/application-window/create-browser-window.injectable"; +import type { BrowserWindow } from "electron"; export const getDiForUnitTesting = ( { doGeneralOverrides } = { doGeneralOverrides: false }, @@ -98,9 +103,10 @@ export const getDiForUnitTesting = ( di.override(environmentVariablesInjectable, () => ({})); di.override(commandLineArgumentsInjectable, () => []); + di.override(clusterFramesInjectable, () => observable.map()); + di.override(stopServicesAndExitAppInjectable, () => () => {}); di.override(lensResourcesDirInjectable, () => "/irrelevant"); - di.override(windowManagerInjectable, () => ({ ensureMainWindow: () => Promise.resolve(null) })); di.override(trayInjectable, () => ({ start: () => {}, stop: () => {} })); di.override(applicationMenuInjectable, () => ({ start: () => {}, stop: () => {} })); @@ -204,6 +210,12 @@ const overrideElectronFeatures = (di: DiContainer) => { di.override(requestSingleInstanceLockInjectable, () => () => true); di.override(disableHardwareAccelerationInjectable, () => () => {}); di.override(shouldStartHiddenInjectable, () => true); + di.override(showMessagePopupInjectable, () => () => {}); + + di.override(createBrowserWindowInjectable, () => async () => ({ + show: () => {}, + hide: () => {}, + }) as unknown as BrowserWindow); di.override( getElectronAppPathInjectable, diff --git a/src/main/menu/application-menu-items.injectable.ts b/src/main/menu/application-menu-items.injectable.ts index e566ad215c..d10b8a398c 100644 --- a/src/main/menu/application-menu-items.injectable.ts +++ b/src/main/menu/application-menu-items.injectable.ts @@ -7,12 +7,8 @@ import { checkForUpdates } from "../app-updater"; import { docsUrl, productName, supportUrl } from "../../common/vars"; import { broadcastMessage } from "../../common/ipc"; import { openBrowser } from "../../common/utils"; -import { showAbout } from "./menu"; -import windowManagerInjectable from "../window-manager.injectable"; import type { MenuItemConstructorOptions } from "electron"; -import { - webContents, -} from "electron"; +import { webContents } from "electron"; import loggerInjectable from "../../common/logger.injectable"; import appNameInjectable from "../app-paths/app-name/app-name.injectable"; import electronMenuItemsInjectable from "./electron-menu-items.injectable"; @@ -25,6 +21,9 @@ import navigateToAddClusterInjectable from "../../common/front-end-routing/route import stopServicesAndExitAppInjectable from "../stop-services-and-exit-app.injectable"; import isMacInjectable from "../../common/vars/is-mac.injectable"; import { computed } from "mobx"; +import showAboutInjectable from "./show-about.injectable"; +import applicationWindowInjectable from "../start-main-application/lens-window/application-window/application-window.injectable"; +import reloadWindowInjectable from "../start-main-application/lens-window/reload-window.injectable"; function ignoreIf(check: boolean, menuItems: MenuItemConstructorOptions[]) { return check ? [] : menuItems; @@ -43,12 +42,14 @@ const applicationMenuItemsInjectable = getInjectable({ const isMac = di.inject(isMacInjectable); const isAutoUpdateEnabled = di.inject(isAutoUpdateEnabledInjectable); const electronMenuItems = di.inject(electronMenuItemsInjectable); + const showAbout = di.inject(showAboutInjectable); + const applicationWindow = di.inject(applicationWindowInjectable); + const reloadApplicationWindow = di.inject(reloadWindowInjectable, applicationWindow); return computed((): MenuItemOpts[] => { // TODO: These injects should happen outside of the computed. // TODO: Remove temporal dependencies in WindowManager to make sure timing is correct. - const windowManager = di.inject(windowManagerInjectable); const stopServicesAndExitApp = di.inject(stopServicesAndExitAppInjectable); const navigateToPreferences = di.inject(navigateToPreferencesInjectable); const navigateToExtensions = di.inject(navigateToExtensionsInjectable); @@ -75,7 +76,7 @@ const applicationMenuItemsInjectable = getInjectable({ { label: "Check for updates", click() { - checkForUpdates().then(() => windowManager.ensureMainWindow()); + checkForUpdates().then(() => applicationWindow.show()); }, }, ]), @@ -239,7 +240,7 @@ const applicationMenuItemsInjectable = getInjectable({ accelerator: "CmdOrCtrl+R", id: "reload", click() { - windowManager.reload(); + reloadApplicationWindow(); }, }, { role: "toggleDevTools" }, @@ -293,7 +294,7 @@ const applicationMenuItemsInjectable = getInjectable({ label: "Check for updates", click() { checkForUpdates().then(() => - windowManager.ensureMainWindow(), + applicationWindow.show(), ); }, }, diff --git a/src/main/menu/menu.ts b/src/main/menu/menu.ts index 8765b150c0..feb243a297 100644 --- a/src/main/menu/menu.ts +++ b/src/main/menu/menu.ts @@ -2,14 +2,19 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { app, dialog, Menu } from "electron"; +import { app, Menu } from "electron"; import { appName, isWindows, productName } from "../../common/vars"; import packageJson from "../../../package.json"; import type { MenuItemOpts } from "./application-menu-items.injectable"; +import type { ShowMessagePopup } from "../electron-app/features/show-message-popup.injectable"; export type MenuTopId = "mac" | "file" | "edit" | "view" | "help"; -export function showAbout() { +interface Dependencies { + showMessagePopup: ShowMessagePopup; +} + +export const showAbout = ({ showMessagePopup }: Dependencies) => async () => { const appInfo = [ `${appName}: ${app.getVersion()}`, `Electron: ${process.versions.electron}`, @@ -18,14 +23,12 @@ export function showAbout() { packageJson.copyright, ]; - dialog.showMessageBoxSync({ - title: `${isWindows ? " ".repeat(2) : ""}${appName}`, - type: "info", - buttons: ["Close"], - message: productName, - detail: appInfo.join("\r\n"), - }); -} + await showMessagePopup( + `${isWindows ? " ".repeat(2) : ""}${appName}`, + productName, + appInfo.join("\r\n"), + ); +}; export function buildMenu( applicationMenuItems: MenuItemOpts[], diff --git a/src/main/menu/show-about.injectable.ts b/src/main/menu/show-about.injectable.ts new file mode 100644 index 0000000000..e79923d4c2 --- /dev/null +++ b/src/main/menu/show-about.injectable.ts @@ -0,0 +1,16 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { showAbout } from "./menu"; +import showMessagePopupInjectable from "../electron-app/features/show-message-popup.injectable"; + +const showAboutInjectable = getInjectable({ + id: "show-about", + + instantiate: (di) => + showAbout({ showMessagePopup: di.inject(showMessagePopupInjectable) }), +}); + +export default showAboutInjectable; diff --git a/src/main/menu/start-application-menu.injectable.ts b/src/main/menu/start-application-menu.injectable.ts index 34845d4d75..2d0fbff62c 100644 --- a/src/main/menu/start-application-menu.injectable.ts +++ b/src/main/menu/start-application-menu.injectable.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { afterApplicationIsReadyInjectionToken } from "../start-main-application/after-application-is-ready/after-application-is-ready-injection-token"; import applicationMenuInjectable from "./application-menu.injectable"; +import { whenApplicationIsLoadingInjectionToken } from "../start-main-application/when-application-is-loading/when-application-is-loading-injection-token"; const startApplicationMenuInjectable = getInjectable({ id: "start-application-menu", @@ -21,7 +21,7 @@ const startApplicationMenuInjectable = getInjectable({ }; }, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: whenApplicationIsLoadingInjectionToken, }); export default startApplicationMenuInjectable; diff --git a/src/main/navigate-to-url/navigate-to-url.injectable.ts b/src/main/navigate-to-url/navigate-to-url.injectable.ts index 5a3cd2bf49..353cb3cff0 100644 --- a/src/main/navigate-to-url/navigate-to-url.injectable.ts +++ b/src/main/navigate-to-url/navigate-to-url.injectable.ts @@ -3,17 +3,17 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import windowManagerInjectable from "../window-manager.injectable"; import { navigateToUrlInjectionToken } from "../../common/front-end-routing/navigate-to-url-injection-token"; +import navigateInjectable from "../start-main-application/lens-window/navigate.injectable"; const navigateToUrlInjectable = getInjectable({ id: "navigate-to-url", instantiate: (di) => { - const windowManager = di.inject(windowManagerInjectable); + const navigate = di.inject(navigateInjectable); return (url) => { - windowManager.navigateSync(url); + navigate(url); }; }, diff --git a/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts b/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts index 4a72ed6cd9..2c4a899ebd 100644 --- a/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts +++ b/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts @@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable"; import { LensProtocolRouterMain } from "./lens-protocol-router-main"; import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable"; -import windowManagerInjectable from "../../window-manager.injectable"; +import applicationWindowInjectable from "../../start-main-application/lens-window/application-window/application-window.injectable"; const lensProtocolRouterMainInjectable = getInjectable({ id: "lens-protocol-router-main", @@ -15,7 +15,7 @@ const lensProtocolRouterMainInjectable = getInjectable({ new LensProtocolRouterMain({ extensionLoader: di.inject(extensionLoaderInjectable), extensionsStore: di.inject(extensionsStoreInjectable), - windowManager: di.inject(windowManagerInjectable), + applicationWindow: di.inject(applicationWindowInjectable), }), }); diff --git a/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts b/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts index d436a297c1..45aee8d6d0 100644 --- a/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts +++ b/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts @@ -12,9 +12,11 @@ import { observable, when, makeObservable } from "mobx"; import type { RouteAttempt } from "../../../common/protocol-handler"; import { ProtocolHandlerInvalid } from "../../../common/protocol-handler"; import { disposer, noop } from "../../../common/utils"; -import type { WindowManager } from "../../window-manager"; import type { ExtensionLoader } from "../../../extensions/extension-loader"; import type { ExtensionsStore } from "../../../extensions/extensions-store/extensions-store"; +import type { + LensWindow, +} from "../../start-main-application/lens-window/application-window/lens-window-injection-token"; export interface FallbackHandler { (name: string): Promise; @@ -40,7 +42,7 @@ function checkHost(url: URLParse): boolean { interface Dependencies { extensionLoader: ExtensionLoader; extensionsStore: ExtensionsStore; - windowManager: WindowManager; + applicationWindow: LensWindow; } export class LensProtocolRouterMain extends proto.LensProtocolRouter { @@ -77,7 +79,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter { throw new proto.RoutingError(proto.RoutingErrorType.INVALID_PROTOCOL, url); } - this.dependencies.windowManager.ensureMainWindow().catch(noop); + this.dependencies.applicationWindow.show().catch(noop); const routeInternally = checkHost(url); logger.info(`${proto.LensProtocolRouter.LoggingPrefix}: routing ${url.toString()}`); diff --git a/src/main/protocol-handler/lens-protocol-router-main/open-deep-link-for-url/open-deep-link.injectable.ts b/src/main/protocol-handler/lens-protocol-router-main/open-deep-link-for-url/open-deep-link.injectable.ts index 1ad0dab8aa..71e42b9f3c 100644 --- a/src/main/protocol-handler/lens-protocol-router-main/open-deep-link-for-url/open-deep-link.injectable.ts +++ b/src/main/protocol-handler/lens-protocol-router-main/open-deep-link-for-url/open-deep-link.injectable.ts @@ -9,10 +9,10 @@ const openDeepLinkInjectable = getInjectable({ id: "open-deep-link", instantiate: (di) => { - const protocolRouter = di.inject(lensProtocolRouterMainInjectable); + const getProtocolRouter = () => di.inject(lensProtocolRouterMainInjectable); return async (url: string) => { - await protocolRouter.route(url); + await getProtocolRouter().route(url); }; }, }); diff --git a/src/main/start-main-application/after-application-is-loaded/after-application-is-loaded-injection-token.ts b/src/main/start-main-application/after-application-is-loaded/after-application-is-loaded-injection-token.ts new file mode 100644 index 0000000000..abe95f10bd --- /dev/null +++ b/src/main/start-main-application/after-application-is-loaded/after-application-is-loaded-injection-token.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { Runnable } from "../run-many-for"; + +export const afterApplicationIsLoadedInjectionToken = getInjectionToken({ + id: "after-application-is-loaded", +}); diff --git a/src/main/start-main-application/after-application-is-ready/implementations/emit-service-start-to-command-bus.injectable.ts b/src/main/start-main-application/after-application-is-loaded/implementations/emit-service-start-to-command-bus.injectable.ts similarity index 80% rename from src/main/start-main-application/after-application-is-ready/implementations/emit-service-start-to-command-bus.injectable.ts rename to src/main/start-main-application/after-application-is-loaded/implementations/emit-service-start-to-command-bus.injectable.ts index 6ca1d95de7..443d5af49c 100644 --- a/src/main/start-main-application/after-application-is-ready/implementations/emit-service-start-to-command-bus.injectable.ts +++ b/src/main/start-main-application/after-application-is-loaded/implementations/emit-service-start-to-command-bus.injectable.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { afterApplicationIsReadyInjectionToken } from "../after-application-is-ready-injection-token"; import appEventBusInjectable from "../../../../common/app-event-bus/app-event-bus.injectable"; +import { afterApplicationIsLoadedInjectionToken } from "../after-application-is-loaded-injection-token"; const emitServiceStartToCommandBusInjectable = getInjectable({ id: "emit-service-start-to-command-bus", @@ -19,7 +19,7 @@ const emitServiceStartToCommandBusInjectable = getInjectable({ }; }, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: afterApplicationIsLoadedInjectionToken, }); export default emitServiceStartToCommandBusInjectable; diff --git a/src/main/start-main-application/after-application-is-ready/implementations/start-main-window.injectable.ts b/src/main/start-main-application/after-application-is-ready/implementations/start-main-window.injectable.ts deleted file mode 100644 index f7d5c0fff7..0000000000 --- a/src/main/start-main-application/after-application-is-ready/implementations/start-main-window.injectable.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import windowManagerInjectable from "../../../window-manager.injectable"; -import loggerInjectable from "../../../../common/logger.injectable"; -import { afterApplicationIsReadyInjectionToken } from "../after-application-is-ready-injection-token"; -import shouldStartHiddenInjectable from "../../../electron-app/features/should-start-hidden.injectable"; -import setupLensProxyInjectable from "./setup-lens-proxy.injectable"; - -const startMainWindowInjectable = getInjectable({ - id: "start-main-window", - - instantiate: (di) => { - const windowManager = di.inject(windowManagerInjectable); - const logger = di.inject(loggerInjectable); - const shouldStartHidden = di.inject(shouldStartHiddenInjectable); - - return { - runAfter: di.inject(setupLensProxyInjectable), - - run: async () => { - logger.info("🖥️ Starting WindowManager"); - - if (!shouldStartHidden) { - await windowManager.ensureMainWindow(); - } - }, - }; - }, - - injectionToken: afterApplicationIsReadyInjectionToken, -}); - -export default startMainWindowInjectable; diff --git a/src/main/start-main-application/before-application-is-loading/before-application-is-loading-injection-token.ts b/src/main/start-main-application/before-application-is-loading/before-application-is-loading-injection-token.ts new file mode 100644 index 0000000000..9604601ecb --- /dev/null +++ b/src/main/start-main-application/before-application-is-loading/before-application-is-loading-injection-token.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { Runnable } from "../run-many-for"; + +export const beforeApplicationIsLoadingInjectionToken = getInjectionToken({ + id: "before-application-is-loading", +}); diff --git a/src/main/start-main-application/after-application-is-ready/implementations/setup-file-protocol.injectable.ts b/src/main/start-main-application/before-application-is-loading/implementations/setup-file-protocol.injectable.ts similarity index 83% rename from src/main/start-main-application/after-application-is-ready/implementations/setup-file-protocol.injectable.ts rename to src/main/start-main-application/before-application-is-loading/implementations/setup-file-protocol.injectable.ts index eefc4166a1..00da993572 100644 --- a/src/main/start-main-application/after-application-is-ready/implementations/setup-file-protocol.injectable.ts +++ b/src/main/start-main-application/before-application-is-loading/implementations/setup-file-protocol.injectable.ts @@ -3,9 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { afterApplicationIsReadyInjectionToken } from "../after-application-is-ready-injection-token"; import registerFileProtocolInjectable from "../../../electron-app/features/register-file-protocol.injectable"; import staticFilesDirectoryInjectable from "../../../../common/vars/static-files-directory.injectable"; +import { beforeApplicationIsLoadingInjectionToken } from "../before-application-is-loading-injection-token"; const setupFileProtocolInjectable = getInjectable({ id: "setup-file-protocol", @@ -21,7 +21,7 @@ const setupFileProtocolInjectable = getInjectable({ }; }, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: beforeApplicationIsLoadingInjectionToken, }); export default setupFileProtocolInjectable; diff --git a/src/main/start-main-application/after-application-is-ready/implementations/setup-lens-proxy.injectable.ts b/src/main/start-main-application/before-application-is-loading/implementations/setup-lens-proxy.injectable.ts similarity index 94% rename from src/main/start-main-application/after-application-is-ready/implementations/setup-lens-proxy.injectable.ts rename to src/main/start-main-application/before-application-is-loading/implementations/setup-lens-proxy.injectable.ts index 36ba067469..31e0bd72b2 100644 --- a/src/main/start-main-application/after-application-is-ready/implementations/setup-lens-proxy.injectable.ts +++ b/src/main/start-main-application/before-application-is-loading/implementations/setup-lens-proxy.injectable.ts @@ -3,7 +3,6 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { afterApplicationIsReadyInjectionToken } from "../after-application-is-ready-injection-token"; import { getAppVersion, getAppVersionFromProxyServer, @@ -14,6 +13,7 @@ import loggerInjectable from "../../../../common/logger.injectable"; import lensProxyPortNumberStateInjectable from "../../../lens-proxy-port-number-state.injectable"; import isWindowsInjectable from "../../../../common/vars/is-windows.injectable"; import showErrorPopupInjectable from "../../../electron-app/features/show-error-popup.injectable"; +import { beforeApplicationIsLoadingInjectionToken } from "../before-application-is-loading-injection-token"; const setupLensProxyInjectable = getInjectable({ id: "setup-lens-proxy", @@ -74,7 +74,7 @@ const setupLensProxyInjectable = getInjectable({ causesSideEffects: true, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: beforeApplicationIsLoadingInjectionToken, }); export default setupLensProxyInjectable; diff --git a/src/main/start-main-application/after-application-is-ready/implementations/setup-system-ca.injectable.ts b/src/main/start-main-application/before-application-is-loading/implementations/setup-system-ca.injectable.ts similarity index 75% rename from src/main/start-main-application/after-application-is-ready/implementations/setup-system-ca.injectable.ts rename to src/main/start-main-application/before-application-is-loading/implementations/setup-system-ca.injectable.ts index 18aa11cf2c..3528920848 100644 --- a/src/main/start-main-application/after-application-is-ready/implementations/setup-system-ca.injectable.ts +++ b/src/main/start-main-application/before-application-is-loading/implementations/setup-system-ca.injectable.ts @@ -4,7 +4,7 @@ */ import { injectSystemCAs } from "../../../../common/system-ca"; import { getInjectable } from "@ogre-tools/injectable"; -import { afterApplicationIsReadyInjectionToken } from "../after-application-is-ready-injection-token"; +import { beforeApplicationIsLoadingInjectionToken } from "../before-application-is-loading-injection-token"; const setupSystemCaInjectable = getInjectable({ id: "setup-system-ca", @@ -17,7 +17,7 @@ const setupSystemCaInjectable = getInjectable({ causesSideEffects: true, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: beforeApplicationIsLoadingInjectionToken, }); export default setupSystemCaInjectable; diff --git a/src/main/start-main-application/before-application-is-ready/before-application-is-ready-injection-token.ts b/src/main/start-main-application/before-electron-is-ready/before-electron-is-ready-injection-token.ts similarity index 76% rename from src/main/start-main-application/before-application-is-ready/before-application-is-ready-injection-token.ts rename to src/main/start-main-application/before-electron-is-ready/before-electron-is-ready-injection-token.ts index 33320a16bb..05219fd638 100644 --- a/src/main/start-main-application/before-application-is-ready/before-application-is-ready-injection-token.ts +++ b/src/main/start-main-application/before-electron-is-ready/before-electron-is-ready-injection-token.ts @@ -5,7 +5,7 @@ import { getInjectionToken } from "@ogre-tools/injectable"; import type { RunnableSync } from "../run-many-sync-for"; -export const beforeApplicationIsReadyInjectionToken = +export const beforeElectronIsReadyInjectionToken = getInjectionToken({ - id: "before-application-is-ready", + id: "before-electron-is-ready", }); diff --git a/src/main/start-main-application/before-application-is-ready/implementations/setup-hardware-acceleration.injectable.ts b/src/main/start-main-application/before-electron-is-ready/implementations/setup-hardware-acceleration.injectable.ts similarity index 85% rename from src/main/start-main-application/before-application-is-ready/implementations/setup-hardware-acceleration.injectable.ts rename to src/main/start-main-application/before-electron-is-ready/implementations/setup-hardware-acceleration.injectable.ts index e5230f5d50..84d7a5f0f3 100644 --- a/src/main/start-main-application/before-application-is-ready/implementations/setup-hardware-acceleration.injectable.ts +++ b/src/main/start-main-application/before-electron-is-ready/implementations/setup-hardware-acceleration.injectable.ts @@ -5,7 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import environmentVariablesInjectable from "../../../../common/utils/environment-variables.injectable"; import disableHardwareAccelerationInjectable from "../../../electron-app/features/disable-hardware-acceleration.injectable"; -import { beforeApplicationIsReadyInjectionToken } from "../before-application-is-ready-injection-token"; +import { beforeElectronIsReadyInjectionToken } from "../before-electron-is-ready-injection-token"; const setupHardwareAccelerationInjectable = getInjectable({ id: "setup-hardware-acceleration", @@ -23,7 +23,7 @@ const setupHardwareAccelerationInjectable = getInjectable({ }; }, - injectionToken: beforeApplicationIsReadyInjectionToken, + injectionToken: beforeElectronIsReadyInjectionToken, }); export default setupHardwareAccelerationInjectable; diff --git a/src/main/start-main-application/before-application-is-ready/implementations/setup-immer.injectable.ts b/src/main/start-main-application/before-electron-is-ready/implementations/setup-immer.injectable.ts similarity index 79% rename from src/main/start-main-application/before-application-is-ready/implementations/setup-immer.injectable.ts rename to src/main/start-main-application/before-electron-is-ready/implementations/setup-immer.injectable.ts index 27fb7cb87b..d1f3922e62 100644 --- a/src/main/start-main-application/before-application-is-ready/implementations/setup-immer.injectable.ts +++ b/src/main/start-main-application/before-electron-is-ready/implementations/setup-immer.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import * as Immer from "immer"; -import { beforeApplicationIsReadyInjectionToken } from "../before-application-is-ready-injection-token"; +import { beforeElectronIsReadyInjectionToken } from "../before-electron-is-ready-injection-token"; const setupImmerInjectable = getInjectable({ id: "setup-immer", @@ -18,7 +18,7 @@ const setupImmerInjectable = getInjectable({ }, }), - injectionToken: beforeApplicationIsReadyInjectionToken, + injectionToken: beforeElectronIsReadyInjectionToken, }); export default setupImmerInjectable; diff --git a/src/main/start-main-application/before-application-is-ready/implementations/setup-mobx.injectable.ts b/src/main/start-main-application/before-electron-is-ready/implementations/setup-mobx.injectable.ts similarity index 81% rename from src/main/start-main-application/before-application-is-ready/implementations/setup-mobx.injectable.ts rename to src/main/start-main-application/before-electron-is-ready/implementations/setup-mobx.injectable.ts index 166bbc4af2..810c82d094 100644 --- a/src/main/start-main-application/before-application-is-ready/implementations/setup-mobx.injectable.ts +++ b/src/main/start-main-application/before-electron-is-ready/implementations/setup-mobx.injectable.ts @@ -4,7 +4,7 @@ */ import * as Mobx from "mobx"; import { getInjectable } from "@ogre-tools/injectable"; -import { beforeApplicationIsReadyInjectionToken } from "../before-application-is-ready-injection-token"; +import { beforeElectronIsReadyInjectionToken } from "../before-electron-is-ready-injection-token"; const setupMobxInjectable = getInjectable({ id: "setup-mobx", @@ -23,7 +23,7 @@ const setupMobxInjectable = getInjectable({ }, }), - injectionToken: beforeApplicationIsReadyInjectionToken, + injectionToken: beforeElectronIsReadyInjectionToken, }); export default setupMobxInjectable; diff --git a/src/main/start-main-application/before-application-is-ready/implementations/setup-proxy-env.injectable.ts b/src/main/start-main-application/before-electron-is-ready/implementations/setup-proxy-env.injectable.ts similarity index 87% rename from src/main/start-main-application/before-application-is-ready/implementations/setup-proxy-env.injectable.ts rename to src/main/start-main-application/before-electron-is-ready/implementations/setup-proxy-env.injectable.ts index 9f47fb1581..d6f5a91e3a 100644 --- a/src/main/start-main-application/before-application-is-ready/implementations/setup-proxy-env.injectable.ts +++ b/src/main/start-main-application/before-electron-is-ready/implementations/setup-proxy-env.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { beforeApplicationIsReadyInjectionToken } from "../before-application-is-ready-injection-token"; +import { beforeElectronIsReadyInjectionToken } from "../before-electron-is-ready-injection-token"; import getCommandLineSwitchInjectable from "../../../electron-app/features/get-command-line-switch.injectable"; const setupProxyEnvInjectable = getInjectable({ @@ -37,7 +37,7 @@ const setupProxyEnvInjectable = getInjectable({ }; }, - injectionToken: beforeApplicationIsReadyInjectionToken, + injectionToken: beforeElectronIsReadyInjectionToken, }); export default setupProxyEnvInjectable; diff --git a/src/main/start-main-application/lens-window/application-is-loading-window/application-is-loading-window.injectable.ts b/src/main/start-main-application/lens-window/application-is-loading-window/application-is-loading-window.injectable.ts new file mode 100644 index 0000000000..82e92668c6 --- /dev/null +++ b/src/main/start-main-application/lens-window/application-is-loading-window/application-is-loading-window.injectable.ts @@ -0,0 +1,88 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { BrowserWindow } from "electron"; +import type { + SendToViewArgs, +} from "../application-window/lens-window-injection-token"; +import { + lensWindowInjectionToken, +} from "../application-window/lens-window-injection-token"; + +const applicationIsLoadingWindowInjectable = getInjectable({ + id: "application-is-loading-window", + + instantiate: () => { + let loadingWindow: BrowserWindow; + + const hideWindow = () => { + loadingWindow?.hide(); + }; + + return { + show: async () => { + if (!loadingWindow) { + loadingWindow = await createLoadingWindow(); + } + + loadingWindow.show(); + }, + + hide: hideWindow, + + close: () => { + hideWindow(); + + loadingWindow = null; + }, + + send: async ({ channel, frameInfo, data = [] }: SendToViewArgs) => { + if (!loadingWindow) { + loadingWindow = await createLoadingWindow(); + } + + if (frameInfo) { + loadingWindow.webContents.sendToFrame( + [frameInfo.processId, frameInfo.frameId], + channel, + ...data, + ); + + } else { + loadingWindow.webContents.send(channel, ...data); + } + }, + }; + }, + + injectionToken: lensWindowInjectionToken, + + causesSideEffects: true, +}); + +export default applicationIsLoadingWindowInjectable; + +const createLoadingWindow = async () => { + const loadingWindow = new BrowserWindow({ + width: 500, + height: 300, + backgroundColor: "#1e2124", + center: true, + frame: false, + resizable: false, + show: false, + + webPreferences: { + nodeIntegration: true, + contextIsolation: false, + nodeIntegrationInSubFrames: true, + nativeWindowOpen: true, + }, + }); + + await loadingWindow.loadURL("static://splash.html"); + + return loadingWindow; +}; diff --git a/src/main/start-main-application/lens-window/application-window/application-window-state.injectable.ts b/src/main/start-main-application/lens-window/application-window/application-window-state.injectable.ts new file mode 100644 index 0000000000..2ec80b5dc3 --- /dev/null +++ b/src/main/start-main-application/lens-window/application-window/application-window-state.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import windowStateKeeper from "electron-window-state"; + +const applicationWindowStateInjectable = getInjectable({ + id: "application-window-state", + + instantiate: () => { + console.log("asdasd"); + + return windowStateKeeper({ + defaultHeight: 900, + defaultWidth: 1440, + }); + }, +}); + +export default applicationWindowStateInjectable; diff --git a/src/main/start-main-application/lens-window/application-window/application-window.injectable.ts b/src/main/start-main-application/lens-window/application-window/application-window.injectable.ts new file mode 100644 index 0000000000..8029d0fe8d --- /dev/null +++ b/src/main/start-main-application/lens-window/application-window/application-window.injectable.ts @@ -0,0 +1,61 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import createBrowserWindowInjectable from "./create-browser-window.injectable"; +import type { + SendToViewArgs } from "./lens-window-injection-token"; +import { + lensWindowInjectionToken, +} from "./lens-window-injection-token"; +import type { BrowserWindow } from "electron"; +import sendToChannelInElectronBrowserWindowInjectable from "./send-to-channel-in-electron-browser-window.injectable"; + +const applicationWindowInjectable = getInjectable({ + id: "application-window", + + instantiate: (di) => { + const createBrowserWindow = di.inject(createBrowserWindowInjectable); + + let browserWindow: BrowserWindow = null; + + const hideWindow = () => { + browserWindow?.hide(); + }; + + const sendToChannelInLensWindow = di.inject( + sendToChannelInElectronBrowserWindowInjectable, + ); + + return { + show: async () => { + if (!browserWindow) { + browserWindow = await createBrowserWindow("only-application-window"); + } + + browserWindow.show(); + }, + + hide: hideWindow, + + close: () => { + hideWindow(); + + browserWindow = null; + }, + + send: async (args: SendToViewArgs) => { + if (!browserWindow) { + browserWindow = await createBrowserWindow("only-application-window"); + } + + return sendToChannelInLensWindow(browserWindow, args); + }, + }; + }, + + injectionToken: lensWindowInjectionToken, +}); + +export default applicationWindowInjectable; diff --git a/src/main/start-main-application/lens-window/application-window/create-browser-window.injectable.ts b/src/main/start-main-application/lens-window/application-window/create-browser-window.injectable.ts new file mode 100644 index 0000000000..8ad8f26032 --- /dev/null +++ b/src/main/start-main-application/lens-window/application-window/create-browser-window.injectable.ts @@ -0,0 +1,158 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import loggerInjectable from "../../../../common/logger.injectable"; +import appNameInjectable from "../../../app-paths/app-name/app-name.injectable"; +import applicationWindowStateInjectable from "./application-window-state.injectable"; +import lensProxyPortNumberStateInjectable from "../../../lens-proxy-port-number-state.injectable"; +import isMacInjectable from "../../../../common/vars/is-mac.injectable"; +import { BrowserWindow, ipcMain } from "electron"; +import { delay, openBrowser } from "../../../../common/utils"; +import { bundledExtensionsLoaded } from "../../../../common/ipc/extension-handling"; +import electronAppInjectable from "../../../electron-app/electron-app.injectable"; +import appEventBusInjectable from "../../../../common/app-event-bus/app-event-bus.injectable"; + +const createBrowserWindowInjectable = getInjectable({ + id: "create-browser-window", + + instantiate: (di) => { + const logger = di.inject(loggerInjectable); + const applicationName = di.inject(appNameInjectable); + const isMac = di.inject(isMacInjectable); + const appEventBus = di.inject(appEventBusInjectable); + + const lensProxyPortNumberState = di.inject( + lensProxyPortNumberStateInjectable, + ); + + return async (id: string) => { + const applicationWindowState = di.inject(applicationWindowStateInjectable); + const app = di.inject(electronAppInjectable); + + const { width, height, x, y } = applicationWindowState; + + const browserWindow = new BrowserWindow({ + x, + y, + width, + height, + title: applicationName, + show: false, + minWidth: 700, // accommodate 800 x 600 display minimum + minHeight: 500, // accommodate 800 x 600 display minimum + titleBarStyle: isMac ? "hiddenInset" : "hidden", + frame: isMac, + backgroundColor: "#1e2124", + webPreferences: { + nodeIntegration: true, + nodeIntegrationInSubFrames: true, + webviewTag: true, + contextIsolation: false, + nativeWindowOpen: false, + }, + }); + + applicationWindowState.manage(browserWindow); + + // open external links in default browser (target=_blank, window.open) + browserWindow + .on("focus", () => { + appEventBus.emit({ name: "app", action: "focus" }); + }) + + .on("blur", () => { + appEventBus.emit({ name: "app", action: "blur" }); + }) + + .on("closed", () => { + // clean up + applicationWindowState.unmanage(); + // this.mainWindow = null; + // this.splashWindow = null; + app.dock?.hide(); // hide icon in dock (mac-os) + }) + + .webContents.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"); + }) + + .on("will-attach-webview", (event, webPreferences, params) => { + logger.debug("[WINDOW-MANAGER]: Attaching webview"); + // Following is security recommendations because we allow webview tag (webviewTag: true) + // suggested by https://www.electronjs.org/docs/tutorial/security#11-verify-webview-options-before-creation + // and https://www.electronjs.org/docs/tutorial/security#10-do-not-use-allowpopups + + if (webPreferences.preload) { + logger.warn( + "[WINDOW-MANAGER]: Strip away preload scripts of webview", + ); + delete webPreferences.preload; + } + + // @ts-expect-error some electron version uses webPreferences.preloadURL/webPreferences.preload + if (webPreferences.preloadURL) { + logger.warn( + "[WINDOW-MANAGER]: Strip away preload scripts of webview", + ); + delete webPreferences.preload; + } + + if (params.allowpopups) { + logger.warn( + "[WINDOW-MANAGER]: We do not allow allowpopups props, stop webview from renderer", + ); + + // event.preventDefault() will destroy the guest page. + event.preventDefault(); + + return; + } + + // Always disable Node.js integration for all webviews + webPreferences.nodeIntegration = false; + }) + + .setWindowOpenHandler((details) => { + openBrowser(details.url).catch((error) => { + logger.error("[WINDOW-MANAGER]: failed to open browser", { error }); + }); + + return { action: "deny" }; + }); + + const contentUrl = `http://localhost:${lensProxyPortNumberState.get()}`; + + logger.info( + `[WINDOW-MANAGER]: Loading Main window from url: ${contentUrl} ...`, + ); + + await browserWindow.loadURL(contentUrl); + + const viewHasLoaded = new Promise((resolve) => { + ipcMain.once(bundledExtensionsLoaded, () => resolve()); + }); + + await viewHasLoaded; + await delay(50); // wait just a bit longer to let the first round of rendering happen + + return browserWindow; + }; + }, + + causesSideEffects: true, +}); + +export default createBrowserWindowInjectable; diff --git a/src/main/start-main-application/lens-window/application-window/lens-window-injection-token.ts b/src/main/start-main-application/lens-window/application-window/lens-window-injection-token.ts new file mode 100644 index 0000000000..5ca55a49ec --- /dev/null +++ b/src/main/start-main-application/lens-window/application-window/lens-window-injection-token.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { ClusterFrameInfo } from "../../../../common/cluster-frames"; + +export interface SendToViewArgs { + channel: string; + frameInfo?: ClusterFrameInfo; + data?: any[]; +} + +export interface LensWindow { + show: () => Promise; + hide: () => void; + close: () => void; + send: (args: SendToViewArgs) => Promise; +} + +export const lensWindowInjectionToken = getInjectionToken({ + id: "lens-window", +}); diff --git a/src/main/start-main-application/lens-window/application-window/send-to-channel-in-electron-browser-window.injectable.ts b/src/main/start-main-application/lens-window/application-window/send-to-channel-in-electron-browser-window.injectable.ts new file mode 100644 index 0000000000..003315a8cc --- /dev/null +++ b/src/main/start-main-application/lens-window/application-window/send-to-channel-in-electron-browser-window.injectable.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { BrowserWindow } from "electron"; +import type { SendToViewArgs } from "./lens-window-injection-token"; + +const sendToChannelInElectronBrowserWindowInjectable = getInjectable({ + id: "send-to-channel-in-electron-browser-window", + + instantiate: + () => + async ( + browserWindow: BrowserWindow, + { channel, frameInfo, data = [] }: SendToViewArgs, + ) => { + if (frameInfo) { + browserWindow.webContents.sendToFrame( + [frameInfo.processId, frameInfo.frameId], + channel, + ...data, + ); + } else { + browserWindow.webContents.send(channel, ...data); + } + }, + + causesSideEffects: true, +}); + +export default sendToChannelInElectronBrowserWindowInjectable; diff --git a/src/main/start-main-application/lens-window/current-cluster-frame/current-cluster-frame-cluster-id-state.injectable.ts b/src/main/start-main-application/lens-window/current-cluster-frame/current-cluster-frame-cluster-id-state.injectable.ts new file mode 100644 index 0000000000..b45447db5e --- /dev/null +++ b/src/main/start-main-application/lens-window/current-cluster-frame/current-cluster-frame-cluster-id-state.injectable.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { observable } from "mobx"; +import type { ClusterId } from "../../../../common/cluster-types"; + +const currentClusterFrameClusterIdStateInjectable = getInjectable({ + id: "current-cluster-frame-cluster-id-state", + + instantiate: () => observable.box(), +}); + +export default currentClusterFrameClusterIdStateInjectable; diff --git a/src/main/start-main-application/lens-window/current-cluster-frame/current-cluster-frame.injectable.ts b/src/main/start-main-application/lens-window/current-cluster-frame/current-cluster-frame.injectable.ts new file mode 100644 index 0000000000..2749b23253 --- /dev/null +++ b/src/main/start-main-application/lens-window/current-cluster-frame/current-cluster-frame.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import currentClusterFrameClusterIdStateInjectable from "./current-cluster-frame-cluster-id-state.injectable"; +import clusterFramesInjectable from "../../../../common/cluster-frames.injectable"; + +const currentClusterFrameInjectable = getInjectable({ + id: "current-cluster-frame", + + instantiate: (di) => { + const currentClusterFrameState = di.inject(currentClusterFrameClusterIdStateInjectable); + const clusterFrames = di.inject(clusterFramesInjectable); + + return computed(() => { + const clusterId = currentClusterFrameState.get(); + + return clusterFrames.get(clusterId); + }); + }, +}); + +export default currentClusterFrameInjectable; diff --git a/src/main/start-main-application/lens-window/current-cluster-frame/setup-listener-for-current-cluster-frame.injectable.ts b/src/main/start-main-application/lens-window/current-cluster-frame/setup-listener-for-current-cluster-frame.injectable.ts new file mode 100644 index 0000000000..a9441c89b5 --- /dev/null +++ b/src/main/start-main-application/lens-window/current-cluster-frame/setup-listener-for-current-cluster-frame.injectable.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { ipcMainOn } from "../../../../common/ipc"; +import { IpcRendererNavigationEvents } from "../../../../renderer/navigation/events"; +import type { ClusterId } from "../../../../common/cluster-types"; + +import { getInjectable } from "@ogre-tools/injectable"; +import { whenApplicationIsLoadingInjectionToken } from "../../when-application-is-loading/when-application-is-loading-injection-token"; +import currentClusterFrameClusterIdStateInjectable from "./current-cluster-frame-cluster-id-state.injectable"; + +const setupListenerForCurrentClusterFrameInjectable = getInjectable({ + id: "setup-listener-for-current-cluster-frame", + + instantiate: (di) => ({ + run: () => { + const currentClusterFrameState = di.inject(currentClusterFrameClusterIdStateInjectable); + + ipcMainOn( + IpcRendererNavigationEvents.CLUSTER_VIEW_CURRENT_ID, + (event, clusterId: ClusterId) => { + currentClusterFrameState.set(clusterId); + }, + ); + }, + }), + + injectionToken: whenApplicationIsLoadingInjectionToken, +}); + +export default setupListenerForCurrentClusterFrameInjectable; diff --git a/src/main/start-main-application/lens-window/hide-all-windows/hide-all-windows.injectable.ts b/src/main/start-main-application/lens-window/hide-all-windows/hide-all-windows.injectable.ts new file mode 100644 index 0000000000..243c85e574 --- /dev/null +++ b/src/main/start-main-application/lens-window/hide-all-windows/hide-all-windows.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { lensWindowInjectionToken } from "../application-window/lens-window-injection-token"; + +const hideAllWindowsInjectable = getInjectable({ + id: "hide-all-windows", + + instantiate: (di) => () => { + const lensWindows = di.injectMany(lensWindowInjectionToken); + + lensWindows.forEach((lensWindow) => { + lensWindow.hide(); + }); + }, +}); + +export default hideAllWindowsInjectable; diff --git a/src/main/start-main-application/lens-window/navigate-for-extension.injectable.ts b/src/main/start-main-application/lens-window/navigate-for-extension.injectable.ts new file mode 100644 index 0000000000..b61f152272 --- /dev/null +++ b/src/main/start-main-application/lens-window/navigate-for-extension.injectable.ts @@ -0,0 +1,39 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { iter } from "../../../common/utils"; +import applicationWindowInjectable from "./application-window/application-window.injectable"; +import clusterFramesInjectable from "../../../common/cluster-frames.injectable"; + +const navigateForExtensionInjectable = getInjectable({ + id: "navigate-for-extension", + + instantiate: (di) => { + const applicationWindow = di.inject(applicationWindowInjectable); + const clusterFrames = di.inject(clusterFramesInjectable); + + return async ( + extId: string, + pageId?: string, + params?: Record, + frameId?: number, + ) => { + await applicationWindow.show(); + + const frameInfo = iter.find( + clusterFrames.values(), + (frameInfo) => frameInfo.frameId === frameId, + ); + + await applicationWindow.send({ + channel: "extension:navigate", + frameInfo, + data: [extId, pageId, params], + }); + }; + }, +}); + +export default navigateForExtensionInjectable; diff --git a/src/main/start-main-application/lens-window/navigate.injectable.ts b/src/main/start-main-application/lens-window/navigate.injectable.ts new file mode 100644 index 0000000000..8fc6c16eb2 --- /dev/null +++ b/src/main/start-main-application/lens-window/navigate.injectable.ts @@ -0,0 +1,39 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { iter } from "../../../common/utils"; +import applicationWindowInjectable from "./application-window/application-window.injectable"; +import clusterFramesInjectable from "../../../common/cluster-frames.injectable"; +import { IpcRendererNavigationEvents } from "../../../renderer/navigation/events"; + +const navigateInjectable = getInjectable({ + id: "navigate", + + instantiate: (di) => { + const applicationWindow = di.inject(applicationWindowInjectable); + const clusterFrames = di.inject(clusterFramesInjectable); + + return async (url: string, frameId?: number) => { + await applicationWindow.show(); + + const frameInfo = iter.find( + clusterFrames.values(), + (frameInfo) => frameInfo.frameId === frameId, + ); + + const channel = frameInfo + ? IpcRendererNavigationEvents.NAVIGATE_IN_CLUSTER + : IpcRendererNavigationEvents.NAVIGATE_IN_APP; + + await applicationWindow.send({ + channel, + frameInfo, + data: [url], + }); + }; + }, +}); + +export default navigateInjectable; diff --git a/src/main/start-main-application/lens-window/reload-all-windows.injectable.ts b/src/main/start-main-application/lens-window/reload-all-windows.injectable.ts new file mode 100644 index 0000000000..fca4da5e13 --- /dev/null +++ b/src/main/start-main-application/lens-window/reload-all-windows.injectable.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { webContents } from "electron"; + +const reloadAllWindowsInjectable = getInjectable({ + id: "reload-all-windows", + + instantiate: () => () => { + webContents + .getAllWebContents() + .filter((wc) => wc.getType() === "window") + .forEach((wc) => { + wc.reload(); + wc.clearHistory(); + }); + }, + + causesSideEffects: true, +}); + +export default reloadAllWindowsInjectable; diff --git a/src/main/start-main-application/lens-window/reload-window.injectable.ts b/src/main/start-main-application/lens-window/reload-window.injectable.ts new file mode 100644 index 0000000000..99d3f22f14 --- /dev/null +++ b/src/main/start-main-application/lens-window/reload-window.injectable.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import type { LensWindow } from "./application-window/lens-window-injection-token"; +import { IpcRendererNavigationEvents } from "../../../renderer/navigation/events"; +import currentClusterFrameInjectable from "./current-cluster-frame/current-cluster-frame.injectable"; +import reloadAllWindowsInjectable from "./reload-all-windows.injectable"; + +const reloadWindowInjectable = getInjectable({ + id: "reload-window", + + instantiate: (di, lensWindow: LensWindow) => () => { + const currentClusterIframe = di.inject(currentClusterFrameInjectable); + const reloadAllWindows = di.inject(reloadAllWindowsInjectable); + + const frameInfo = currentClusterIframe.get(); + + if (frameInfo) { + lensWindow.send({ + channel: IpcRendererNavigationEvents.RELOAD_PAGE, + frameInfo, + }); + } else { + reloadAllWindows(); + } + }, + + lifecycle: lifecycleEnum.transient, +}); + +export default reloadWindowInjectable; diff --git a/src/main/start-main-application/start-main-application.injectable.ts b/src/main/start-main-application/start-main-application.injectable.ts index 26450828d1..e5ea1c16bc 100644 --- a/src/main/start-main-application/start-main-application.injectable.ts +++ b/src/main/start-main-application/start-main-application.injectable.ts @@ -5,10 +5,20 @@ import { getInjectable } from "@ogre-tools/injectable"; import electronAppInjectable from "../electron-app/electron-app.injectable"; -import { beforeApplicationIsReadyInjectionToken } from "./before-application-is-ready/before-application-is-ready-injection-token"; -import { afterApplicationIsReadyInjectionToken } from "./after-application-is-ready/after-application-is-ready-injection-token"; import { runManyFor } from "./run-many-for"; import { runManySyncFor } from "./run-many-sync-for"; +import { beforeElectronIsReadyInjectionToken } from "./before-electron-is-ready/before-electron-is-ready-injection-token"; +import { beforeApplicationIsLoadingInjectionToken } from "./before-application-is-loading/before-application-is-loading-injection-token"; +import { whenApplicationIsLoadingInjectionToken } from "./when-application-is-loading/when-application-is-loading-injection-token"; +import { afterApplicationIsLoadedInjectionToken } from "./after-application-is-loaded/after-application-is-loaded-injection-token"; +import applicationIsLoadingWindowInjectable from "./lens-window/application-is-loading-window/application-is-loading-window.injectable"; + +import applicationWindowInjectable from "./lens-window/application-window/application-window.injectable"; +import shouldStartHiddenInjectable from "../electron-app/features/should-start-hidden.injectable"; +import openDeepLinkInjectable from "../protocol-handler/lens-protocol-router-main/open-deep-link-for-url/open-deep-link.injectable"; +import { pipeline } from "@ogre-tools/fp"; +import { find, map, startsWith, toLower } from "lodash/fp"; +import commandLineArgumentsInjectable from "../utils/command-line-arguments.injectable"; const startMainApplicationInjectable = getInjectable({ id: "start-main-application", @@ -16,21 +26,52 @@ const startMainApplicationInjectable = getInjectable({ instantiate: (di) => { const runMany = runManyFor(di); const runManySync = runManySyncFor(di); - const app = di.inject(electronAppInjectable); + const electronApp = di.inject(electronAppInjectable); + const applicationWindow = di.inject(applicationWindowInjectable); + const applicationIsLoadingWindow = di.inject(applicationIsLoadingWindowInjectable); + const shouldStartHidden = di.inject(shouldStartHiddenInjectable); + const openDeepLink = di.inject(openDeepLinkInjectable); + const commandLineArguments = di.inject(commandLineArgumentsInjectable); + + const beforeElectronIsReady = runManySync(beforeElectronIsReadyInjectionToken); + const beforeApplicationIsLoading = runMany(beforeApplicationIsLoadingInjectionToken); + const whenApplicationIsLoading = runMany(whenApplicationIsLoadingInjectionToken); + const afterApplicationIsLoaded = runMany(afterApplicationIsLoadedInjectionToken); return async () => { // Stuff happening before application is ready needs to be synchronous because of // https://github.com/electron/electron/issues/21370 - runManySync(beforeApplicationIsReadyInjectionToken)(); + beforeElectronIsReady(); - await app.whenReady(); + await electronApp.whenReady(); - await runMany(afterApplicationIsReadyInjectionToken)(); + await beforeApplicationIsLoading(); + + if (!shouldStartHidden) { + await applicationIsLoadingWindow.show(); + } + + await whenApplicationIsLoading(); + + if (!shouldStartHidden) { + const url = getDeepLinkUrl(commandLineArguments); + + if (url) { + await openDeepLink(url); + } else { + await applicationWindow.show(); + } + + applicationIsLoadingWindow.close(); + } + + await afterApplicationIsLoaded(); }; }, }); +const getDeepLinkUrl = (commandLineArguments: string[]) => + pipeline(commandLineArguments, map(toLower), find(startsWith("lens://"))); + + export default startMainApplicationInjectable; - - - diff --git a/src/main/start-main-application/after-application-is-ready/implementations/initialize-extensions.injectable.ts b/src/main/start-main-application/when-application-is-loading/implementations/initialize-extensions.injectable.ts similarity index 93% rename from src/main/start-main-application/after-application-is-ready/implementations/initialize-extensions.injectable.ts rename to src/main/start-main-application/when-application-is-loading/implementations/initialize-extensions.injectable.ts index a9594a9a72..3bb91d04bc 100644 --- a/src/main/start-main-application/after-application-is-ready/implementations/initialize-extensions.injectable.ts +++ b/src/main/start-main-application/when-application-is-loading/implementations/initialize-extensions.injectable.ts @@ -3,13 +3,13 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { afterApplicationIsReadyInjectionToken } from "../after-application-is-ready-injection-token"; import type { InstalledExtension } from "../../../../extensions/extension-discovery/extension-discovery"; import type { LensExtensionId } from "../../../../extensions/lens-extension"; import loggerInjectable from "../../../../common/logger.injectable"; import extensionDiscoveryInjectable from "../../../../extensions/extension-discovery/extension-discovery.injectable"; import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable"; import showErrorPopupInjectable from "../../../electron-app/features/show-error-popup.injectable"; +import { whenApplicationIsLoadingInjectionToken } from "../when-application-is-loading-injection-token"; const initializeExtensionsInjectable = getInjectable({ id: "initialize-extensions", @@ -61,7 +61,7 @@ const initializeExtensionsInjectable = getInjectable({ causesSideEffects: true, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: whenApplicationIsLoadingInjectionToken, }); export default initializeExtensionsInjectable; diff --git a/src/main/start-main-application/after-application-is-ready/implementations/setup-detector-registry.injectable.ts b/src/main/start-main-application/when-application-is-loading/implementations/setup-detector-registry.injectable.ts similarity index 88% rename from src/main/start-main-application/after-application-is-ready/implementations/setup-detector-registry.injectable.ts rename to src/main/start-main-application/when-application-is-loading/implementations/setup-detector-registry.injectable.ts index 05de0548d5..6f7193c409 100644 --- a/src/main/start-main-application/after-application-is-ready/implementations/setup-detector-registry.injectable.ts +++ b/src/main/start-main-application/when-application-is-loading/implementations/setup-detector-registry.injectable.ts @@ -3,13 +3,13 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { afterApplicationIsReadyInjectionToken } from "../after-application-is-ready-injection-token"; import { ClusterIdDetector } from "../../../cluster-detectors/cluster-id-detector"; import { LastSeenDetector } from "../../../cluster-detectors/last-seen-detector"; import { VersionDetector } from "../../../cluster-detectors/version-detector"; import { DistributionDetector } from "../../../cluster-detectors/distribution-detector"; import { NodesCountDetector } from "../../../cluster-detectors/nodes-count-detector"; import detectorRegistryInjectable from "../../../cluster-detectors/detector-registry.injectable"; +import { whenApplicationIsLoadingInjectionToken } from "../when-application-is-loading-injection-token"; const setupDetectorRegistryInjectable = getInjectable({ id: "setup-detector-registry", @@ -29,7 +29,7 @@ const setupDetectorRegistryInjectable = getInjectable({ }; }, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: whenApplicationIsLoadingInjectionToken, }); export default setupDetectorRegistryInjectable; diff --git a/src/main/start-main-application/after-application-is-ready/implementations/setup-hotbar-store.injectable.ts b/src/main/start-main-application/when-application-is-loading/implementations/setup-hotbar-store.injectable.ts similarity index 82% rename from src/main/start-main-application/after-application-is-ready/implementations/setup-hotbar-store.injectable.ts rename to src/main/start-main-application/when-application-is-loading/implementations/setup-hotbar-store.injectable.ts index 1bce3e7e2c..f97b582a8c 100644 --- a/src/main/start-main-application/after-application-is-ready/implementations/setup-hotbar-store.injectable.ts +++ b/src/main/start-main-application/when-application-is-loading/implementations/setup-hotbar-store.injectable.ts @@ -3,9 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { afterApplicationIsReadyInjectionToken } from "../after-application-is-ready-injection-token"; import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable"; import setupSyncingOfGeneralCatalogEntitiesInjectable from "./setup-syncing-of-general-catalog-entities.injectable"; +import { whenApplicationIsLoadingInjectionToken } from "../when-application-is-loading-injection-token"; const setupHotbarStoreInjectable = getInjectable({ id: "setup-hotbar-store", @@ -20,7 +20,7 @@ const setupHotbarStoreInjectable = getInjectable({ runAfter: di.inject(setupSyncingOfGeneralCatalogEntitiesInjectable), }), - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: whenApplicationIsLoadingInjectionToken, }); export default setupHotbarStoreInjectable; diff --git a/src/main/start-main-application/after-application-is-ready/implementations/setup-prometheus-registry.injectable.ts b/src/main/start-main-application/when-application-is-loading/implementations/setup-prometheus-registry.injectable.ts similarity index 87% rename from src/main/start-main-application/after-application-is-ready/implementations/setup-prometheus-registry.injectable.ts rename to src/main/start-main-application/when-application-is-loading/implementations/setup-prometheus-registry.injectable.ts index 4d3b7e6b7d..8f11eee869 100644 --- a/src/main/start-main-application/after-application-is-ready/implementations/setup-prometheus-registry.injectable.ts +++ b/src/main/start-main-application/when-application-is-loading/implementations/setup-prometheus-registry.injectable.ts @@ -3,12 +3,12 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { afterApplicationIsReadyInjectionToken } from "../after-application-is-ready-injection-token"; import { PrometheusLens } from "../../../prometheus/lens"; import { PrometheusHelm } from "../../../prometheus/helm"; import { PrometheusOperator } from "../../../prometheus/operator"; import { PrometheusStacklight } from "../../../prometheus/stacklight"; import prometheusProviderRegistryInjectable from "../../../prometheus/prometheus-provider-registry.injectable"; +import { whenApplicationIsLoadingInjectionToken } from "../when-application-is-loading-injection-token"; const setupPrometheusRegistryInjectable = getInjectable({ id: "setup-prometheus-registry", @@ -27,7 +27,7 @@ const setupPrometheusRegistryInjectable = getInjectable({ }; }, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: whenApplicationIsLoadingInjectionToken, }); export default setupPrometheusRegistryInjectable; diff --git a/src/main/start-main-application/after-application-is-ready/implementations/setup-reactions-in-user-store.injectable.ts b/src/main/start-main-application/when-application-is-loading/implementations/setup-reactions-in-user-store.injectable.ts similarity index 79% rename from src/main/start-main-application/after-application-is-ready/implementations/setup-reactions-in-user-store.injectable.ts rename to src/main/start-main-application/when-application-is-loading/implementations/setup-reactions-in-user-store.injectable.ts index bda8962b60..c1b91ea923 100644 --- a/src/main/start-main-application/after-application-is-ready/implementations/setup-reactions-in-user-store.injectable.ts +++ b/src/main/start-main-application/when-application-is-loading/implementations/setup-reactions-in-user-store.injectable.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { afterApplicationIsReadyInjectionToken } from "../after-application-is-ready-injection-token"; import userStoreInjectable from "../../../../common/user-store/user-store.injectable"; +import { whenApplicationIsLoadingInjectionToken } from "../when-application-is-loading-injection-token"; const setupReactionsInUserStoreInjectable = getInjectable({ id: "setup-reactions-in-user-store", @@ -19,7 +19,7 @@ const setupReactionsInUserStoreInjectable = getInjectable({ }; }, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: whenApplicationIsLoadingInjectionToken, }); export default setupReactionsInUserStoreInjectable; diff --git a/src/main/start-main-application/after-application-is-ready/implementations/setup-runnables-for-after-root-frame-is-ready.injectable.ts b/src/main/start-main-application/when-application-is-loading/implementations/setup-runnables-for-after-root-frame-is-ready.injectable.ts similarity index 87% rename from src/main/start-main-application/after-application-is-ready/implementations/setup-runnables-for-after-root-frame-is-ready.injectable.ts rename to src/main/start-main-application/when-application-is-loading/implementations/setup-runnables-for-after-root-frame-is-ready.injectable.ts index 4825f36676..4a05ab6bbe 100644 --- a/src/main/start-main-application/after-application-is-ready/implementations/setup-runnables-for-after-root-frame-is-ready.injectable.ts +++ b/src/main/start-main-application/when-application-is-loading/implementations/setup-runnables-for-after-root-frame-is-ready.injectable.ts @@ -6,8 +6,8 @@ import { getInjectable } from "@ogre-tools/injectable"; import { ipcMainOn } from "../../../../common/ipc"; import { IpcRendererNavigationEvents } from "../../../../renderer/navigation/events"; import { afterRootFrameIsReadyInjectionToken } from "../../after-root-frame-is-ready/after-root-frame-is-ready-injection-token"; -import { afterApplicationIsReadyInjectionToken } from "../after-application-is-ready-injection-token"; import { runManyFor } from "../../run-many-for"; +import { whenApplicationIsLoadingInjectionToken } from "../when-application-is-loading-injection-token"; const setupRunnablesForAfterRootFrameIsReadyInjectable = getInjectable({ id: "setup-runnables-for-after-root-frame-is-ready", @@ -31,7 +31,7 @@ const setupRunnablesForAfterRootFrameIsReadyInjectable = getInjectable({ // Direct usage of IPC causesSideEffects: true, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: whenApplicationIsLoadingInjectionToken, }); export default setupRunnablesForAfterRootFrameIsReadyInjectable; diff --git a/src/main/start-main-application/after-application-is-ready/implementations/setup-sentry.injectable.ts b/src/main/start-main-application/when-application-is-loading/implementations/setup-sentry.injectable.ts similarity index 77% rename from src/main/start-main-application/after-application-is-ready/implementations/setup-sentry.injectable.ts rename to src/main/start-main-application/when-application-is-loading/implementations/setup-sentry.injectable.ts index 6067665e60..40333dc156 100644 --- a/src/main/start-main-application/after-application-is-ready/implementations/setup-sentry.injectable.ts +++ b/src/main/start-main-application/when-application-is-loading/implementations/setup-sentry.injectable.ts @@ -3,9 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { afterApplicationIsReadyInjectionToken } from "../after-application-is-ready-injection-token"; import { initializeSentryReporting } from "../../../../common/sentry"; import { init } from "@sentry/electron/main"; +import { whenApplicationIsLoadingInjectionToken } from "../when-application-is-loading-injection-token"; const setupSentryInjectable = getInjectable({ id: "setup-sentry", @@ -18,7 +18,7 @@ const setupSentryInjectable = getInjectable({ causesSideEffects: true, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: whenApplicationIsLoadingInjectionToken, }); export default setupSentryInjectable; diff --git a/src/main/start-main-application/after-application-is-ready/implementations/setup-shell.injectable.ts b/src/main/start-main-application/when-application-is-loading/implementations/setup-shell.injectable.ts similarity index 80% rename from src/main/start-main-application/after-application-is-ready/implementations/setup-shell.injectable.ts rename to src/main/start-main-application/when-application-is-loading/implementations/setup-shell.injectable.ts index 362bafd859..383f3a8b03 100644 --- a/src/main/start-main-application/after-application-is-ready/implementations/setup-shell.injectable.ts +++ b/src/main/start-main-application/when-application-is-loading/implementations/setup-shell.injectable.ts @@ -3,9 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { afterApplicationIsReadyInjectionToken } from "../after-application-is-ready-injection-token"; import { shellSync } from "../../../shell-sync"; import loggerInjectable from "../../../../common/logger.injectable"; +import { whenApplicationIsLoadingInjectionToken } from "../when-application-is-loading-injection-token"; const setupShellInjectable = getInjectable({ id: "setup-shell", @@ -22,7 +22,7 @@ const setupShellInjectable = getInjectable({ }; }, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: whenApplicationIsLoadingInjectionToken, causesSideEffects: true, }); diff --git a/src/main/start-main-application/after-application-is-ready/implementations/setup-syncing-of-general-catalog-entities.injectable.ts b/src/main/start-main-application/when-application-is-loading/implementations/setup-syncing-of-general-catalog-entities.injectable.ts similarity index 81% rename from src/main/start-main-application/after-application-is-ready/implementations/setup-syncing-of-general-catalog-entities.injectable.ts rename to src/main/start-main-application/when-application-is-loading/implementations/setup-syncing-of-general-catalog-entities.injectable.ts index 9278c19f00..f3e83ae5b0 100644 --- a/src/main/start-main-application/after-application-is-ready/implementations/setup-syncing-of-general-catalog-entities.injectable.ts +++ b/src/main/start-main-application/when-application-is-loading/implementations/setup-syncing-of-general-catalog-entities.injectable.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { afterApplicationIsReadyInjectionToken } from "../after-application-is-ready-injection-token"; import syncGeneralCatalogEntitiesInjectable from "../../../catalog-sources/sync-general-catalog-entities.injectable"; +import { whenApplicationIsLoadingInjectionToken } from "../when-application-is-loading-injection-token"; const setupSyncingOfGeneralCatalogEntitiesInjectable = getInjectable({ id: "setup-syncing-of-general-catalog-entities", @@ -21,7 +21,7 @@ const setupSyncingOfGeneralCatalogEntitiesInjectable = getInjectable({ }; }, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: whenApplicationIsLoadingInjectionToken, }); export default setupSyncingOfGeneralCatalogEntitiesInjectable; diff --git a/src/main/start-main-application/after-application-is-ready/implementations/setup-syncing-of-weblinks.injectable.ts b/src/main/start-main-application/when-application-is-loading/implementations/setup-syncing-of-weblinks.injectable.ts similarity index 84% rename from src/main/start-main-application/after-application-is-ready/implementations/setup-syncing-of-weblinks.injectable.ts rename to src/main/start-main-application/when-application-is-loading/implementations/setup-syncing-of-weblinks.injectable.ts index 4f6a49191a..b6dccb2388 100644 --- a/src/main/start-main-application/after-application-is-ready/implementations/setup-syncing-of-weblinks.injectable.ts +++ b/src/main/start-main-application/when-application-is-loading/implementations/setup-syncing-of-weblinks.injectable.ts @@ -3,10 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { afterApplicationIsReadyInjectionToken } from "../after-application-is-ready-injection-token"; import { syncWeblinks } from "../../../catalog-sources"; import weblinkStoreInjectable from "../../../../common/weblink-store.injectable"; import catalogEntityRegistryInjectable from "../../../catalog/catalog-entity-registry.injectable"; +import { whenApplicationIsLoadingInjectionToken } from "../when-application-is-loading-injection-token"; const setupSyncingOfWeblinksInjectable = getInjectable({ id: "setup-syncing-of-weblinks", @@ -22,7 +22,7 @@ const setupSyncingOfWeblinksInjectable = getInjectable({ }; }, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: whenApplicationIsLoadingInjectionToken, }); export default setupSyncingOfWeblinksInjectable; diff --git a/src/main/start-main-application/when-application-is-loading/when-application-is-loading-injection-token.ts b/src/main/start-main-application/when-application-is-loading/when-application-is-loading-injection-token.ts new file mode 100644 index 0000000000..273949764a --- /dev/null +++ b/src/main/start-main-application/when-application-is-loading/when-application-is-loading-injection-token.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { Runnable } from "../run-many-for"; + +export const whenApplicationIsLoadingInjectionToken = getInjectionToken({ + id: "when-application-is-loading", +}); diff --git a/src/main/stop-services-and-exit-app.injectable.ts b/src/main/stop-services-and-exit-app.injectable.ts index 057bc79ca3..dc07f83304 100644 --- a/src/main/stop-services-and-exit-app.injectable.ts +++ b/src/main/stop-services-and-exit-app.injectable.ts @@ -5,23 +5,23 @@ import { getInjectable } from "@ogre-tools/injectable"; import exitAppInjectable from "./electron-app/features/exit-app.injectable"; import clusterManagerInjectable from "./cluster-manager.injectable"; -import windowManagerInjectable from "./window-manager.injectable"; import appEventBusInjectable from "../common/app-event-bus/app-event-bus.injectable"; import loggerInjectable from "../common/logger.injectable"; +import hideAllWindowsInjectable from "./start-main-application/lens-window/hide-all-windows/hide-all-windows.injectable"; const stopServicesAndExitAppInjectable = getInjectable({ id: "stop-services-and-exit-app", instantiate: (di) => { const exitApp = di.inject(exitAppInjectable); - const windowManager = di.inject(windowManagerInjectable); const clusterManager = di.inject(clusterManagerInjectable); const appEventBus = di.inject(appEventBusInjectable); const logger = di.inject(loggerInjectable); + const hideAllWindows = di.inject(hideAllWindowsInjectable); return () => { appEventBus.emit({ name: "service", action: "close" }); - windowManager.hide(); + hideAllWindows(); clusterManager.stop(); logger.info("SERVICE:QUIT"); setTimeout(exitApp, 1000); diff --git a/src/main/tray/start-tray.injectable.ts b/src/main/tray/start-tray.injectable.ts index 696d667786..f59cf2261f 100644 --- a/src/main/tray/start-tray.injectable.ts +++ b/src/main/tray/start-tray.injectable.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { afterApplicationIsReadyInjectionToken } from "../start-main-application/after-application-is-ready/after-application-is-ready-injection-token"; import trayInjectable from "./tray.injectable"; +import { whenApplicationIsLoadingInjectionToken } from "../start-main-application/when-application-is-loading/when-application-is-loading-injection-token"; const startTrayInjectable = getInjectable({ id: "start-tray", @@ -19,7 +19,7 @@ const startTrayInjectable = getInjectable({ }; }, - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: whenApplicationIsLoadingInjectionToken, }); export default startTrayInjectable; diff --git a/src/main/tray/tray.injectable.ts b/src/main/tray/tray.injectable.ts index 6e92437b24..787a37cdee 100644 --- a/src/main/tray/tray.injectable.ts +++ b/src/main/tray/tray.injectable.ts @@ -4,33 +4,36 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { initTray } from "./tray"; -import windowManagerInjectable from "../window-manager.injectable"; import trayMenuItemsInjectable from "./tray-menu-items.injectable"; import navigateToPreferencesInjectable from "../../common/front-end-routing/routes/preferences/navigate-to-preferences.injectable"; import stopServicesAndExitAppInjectable from "../stop-services-and-exit-app.injectable"; import { getStartableStoppable } from "../../common/utils/get-startable-stoppable"; import isAutoUpdateEnabledInjectable from "../is-auto-update-enabled.injectable"; import trayIconPathInjectable from "./tray-icon-path.injectable"; +import applicationWindowInjectable from "../start-main-application/lens-window/application-window/application-window.injectable"; +import showAboutInjectable from "../menu/show-about.injectable"; const trayInjectable = getInjectable({ id: "tray", instantiate: (di) => { - const windowManager = di.inject(windowManagerInjectable); const trayMenuItems = di.inject(trayMenuItemsInjectable); const navigateToPreferences = di.inject(navigateToPreferencesInjectable); const stopServicesAndExitApp = di.inject(stopServicesAndExitAppInjectable); const isAutoUpdateEnabled = di.inject(isAutoUpdateEnabledInjectable); const trayIconPath = di.inject(trayIconPathInjectable); + const applicationWindow = di.inject(applicationWindowInjectable); + const showAboutPopup = di.inject(showAboutInjectable); return getStartableStoppable("build-of-tray", () => initTray( - windowManager, trayMenuItems, navigateToPreferences, stopServicesAndExitApp, isAutoUpdateEnabled, trayIconPath, + applicationWindow, + showAboutPopup, ), ); }, diff --git a/src/main/tray/tray.ts b/src/main/tray/tray.ts index 631624e0fb..f9f1fceba8 100644 --- a/src/main/tray/tray.ts +++ b/src/main/tray/tray.ts @@ -7,15 +7,14 @@ import packageInfo from "../../../package.json"; import { Menu, Tray } from "electron"; import type { IComputedValue } from "mobx"; import { autorun } from "mobx"; -import { showAbout } from "../menu/menu"; import { checkForUpdates } from "../app-updater"; -import type { WindowManager } from "../window-manager"; import logger from "../logger"; import { isDevelopment, isWindows, productName, staticFilesDirectory } from "../../common/vars"; import type { Disposer } from "../../common/utils"; import { disposer, toJS } from "../../common/utils"; import type { TrayMenuRegistration } from "./tray-menu-registration"; import path from "path"; +import type { LensWindow } from "../start-main-application/lens-window/application-window/lens-window-injection-token"; const TRAY_LOG_PREFIX = "[TRAY]"; @@ -31,11 +30,12 @@ function getTrayIconPath(): string { } export function initTray( - windowManager: WindowManager, trayMenuItems: IComputedValue, navigateToPreferences: () => void, stopServicesAndExitApp: () => void, isAutoUpdateEnabled: () => boolean, + applicationWindow: LensWindow, + showAbout: () => void, ): Disposer { const icon = getTrayIconPath(); @@ -45,8 +45,7 @@ export function initTray( if (isWindows) { tray.on("click", () => { - windowManager - .ensureMainWindow() + applicationWindow.show() .catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to open lens`, { error })); }); } @@ -54,7 +53,7 @@ export function initTray( return disposer( autorun(() => { try { - const menu = createTrayMenu(windowManager, toJS(trayMenuItems.get()), navigateToPreferences, stopServicesAndExitApp, isAutoUpdateEnabled); + const menu = createTrayMenu(toJS(trayMenuItems.get()), navigateToPreferences, stopServicesAndExitApp, isAutoUpdateEnabled, applicationWindow, showAbout); tray.setContextMenu(menu); } catch (error) { @@ -79,19 +78,18 @@ function getMenuItemConstructorOptions(trayItem: TrayMenuRegistration): Electron } function createTrayMenu( - windowManager: WindowManager, extensionTrayItems: TrayMenuRegistration[], navigateToPreferences: () => void, stopServicesAndExitApp: () => void, isAutoUpdateEnabled: () => boolean, + applicationWindow: LensWindow, + showAbout: () => void, ): Menu { let template: Electron.MenuItemConstructorOptions[] = [ { label: `Open ${productName}`, click() { - windowManager - .ensureMainWindow() - .catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to open lens`, { error })); + applicationWindow.show().catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to open lens`, { error })); }, }, { @@ -107,7 +105,7 @@ function createTrayMenu( label: "Check for updates", click() { checkForUpdates() - .then(() => windowManager.ensureMainWindow()); + .then(() => applicationWindow.show()); }, }); } @@ -118,7 +116,7 @@ function createTrayMenu( { label: `About ${productName}`, click() { - windowManager.ensureMainWindow() + applicationWindow.show() .then(showAbout) .catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to show Lens About view`, { error })); }, diff --git a/src/main/window-manager.injectable.ts b/src/main/window-manager.injectable.ts deleted file mode 100644 index 28f8c13c10..0000000000 --- a/src/main/window-manager.injectable.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { WindowManager } from "./window-manager"; -import lensProxyPortNumberStateInjectable from "./lens-proxy-port-number-state.injectable"; - -const windowManagerInjectable = getInjectable({ - id: "window-manager", - - instantiate: (di) => - new WindowManager({ - lensProxyPortNumberState: di.inject(lensProxyPortNumberStateInjectable), - }), - - causesSideEffects: true, -}); - -export default windowManagerInjectable; diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts deleted file mode 100644 index 08a11d9c87..0000000000 --- a/src/main/window-manager.ts +++ /dev/null @@ -1,289 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { ClusterId } from "../common/cluster-types"; -import { makeObservable, observable } from "mobx"; -import { app, BrowserWindow, dialog, ipcMain, webContents } from "electron"; -import windowStateKeeper from "electron-window-state"; -import { appEventBus } from "../common/app-event-bus/event-bus"; -import { ipcMainOn } from "../common/ipc"; -import { delay, iter, openBrowser } from "../common/utils"; -import type { ClusterFrameInfo } from "../common/cluster-frames"; -import { clusterFrameMap } from "../common/cluster-frames"; -import { IpcRendererNavigationEvents } from "../renderer/navigation/events"; -import logger from "./logger"; -import { isMac, productName } from "../common/vars"; -import { bundledExtensionsLoaded } from "../common/ipc/extension-handling"; - -function isHideable(window: BrowserWindow | null): boolean { - return Boolean(window && !window.isDestroyed()); -} - -export interface SendToViewArgs { - channel: string; - frameInfo?: ClusterFrameInfo; - data?: any[]; -} - -interface Dependencies { - lensProxyPortNumberState: { get: () => number }; -} - -export class WindowManager { - protected mainWindow: BrowserWindow; - protected splashWindow: BrowserWindow; - protected windowState: windowStateKeeper.State; - protected disposers: Record = {}; - - @observable activeClusterId: ClusterId; - - constructor(private dependencies: Dependencies) { - makeObservable(this); - this.bindEvents(); - } - - get mainContentUrl() { - return `http://localhost:${this.dependencies.lensProxyPortNumberState.get()}`; - } - - private async initMainWindow(showSplash: boolean) { - // Manage main window size and position with state persistence - if (!this.windowState) { - this.windowState = windowStateKeeper({ - defaultHeight: 900, - defaultWidth: 1440, - }); - } - - if (!this.mainWindow) { - // show icon in dock (mac-os only) - app.dock?.show(); - - 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: isMac ? "hiddenInset" : "hidden", - frame: isMac, - backgroundColor: "#1e2124", - webPreferences: { - nodeIntegration: true, - nodeIntegrationInSubFrames: true, - webviewTag: true, - contextIsolation: false, - nativeWindowOpen: false, - }, - }); - 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("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"); - }) - .on("will-attach-webview", (event, webPreferences, params) => { - logger.debug("[WINDOW-MANAGER]: Attaching webview"); - // Following is security recommendations because we allow webview tag (webviewTag: true) - // suggested by https://www.electronjs.org/docs/tutorial/security#11-verify-webview-options-before-creation - // and https://www.electronjs.org/docs/tutorial/security#10-do-not-use-allowpopups - - if (webPreferences.preload) { - logger.warn("[WINDOW-MANAGER]: Strip away preload scripts of webview"); - delete webPreferences.preload; - } - - // @ts-expect-error some electron version uses webPreferences.preloadURL/webPreferences.preload - if (webPreferences.preloadURL) { - logger.warn("[WINDOW-MANAGER]: Strip away preload scripts of webview"); - delete webPreferences.preload; - } - - if (params.allowpopups) { - logger.warn("[WINDOW-MANAGER]: We do not allow allowpopups props, stop webview from renderer"); - - // event.preventDefault() will destroy the guest page. - event.preventDefault(); - - return; - } - - // Always disable Node.js integration for all webviews - webPreferences.nodeIntegration = false; - }) - .setWindowOpenHandler((details) => { - openBrowser(details.url).catch(error => { - logger.error("[WINDOW-MANAGER]: failed to open browser", { error }); - }); - - return { action: "deny" }; - }); - } - - try { - if (showSplash) await this.showSplash(); - logger.info(`[WINDOW-MANAGER]: Loading Main window from url: ${this.mainContentUrl} ...`); - await this.mainWindow.loadURL(this.mainContentUrl); - } catch (error) { - logger.error("Loading main window failed", { error }); - dialog.showErrorBox("ERROR!", error.toString()); - } - } - - protected bindEvents() { - // track visible cluster from ui - ipcMainOn(IpcRendererNavigationEvents.CLUSTER_VIEW_CURRENT_ID, (event, clusterId: ClusterId) => { - this.activeClusterId = clusterId; - }); - } - - async ensureMainWindow(showSplash = true): Promise { - // This needs to be ready to hear the IPC message before the window is loaded - let viewHasLoaded = Promise.resolve(); - - if (!this.mainWindow) { - viewHasLoaded = new Promise(resolve => { - ipcMain.once(bundledExtensionsLoaded, () => resolve()); - }); - await this.initMainWindow(showSplash); - } - - 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"); - - this.mainWindow.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}`); - dialog.showErrorBox("ERROR!", error.toString()); - } - - return this.mainWindow; - } - - sendToView({ channel, frameInfo, data = [] }: SendToViewArgs) { - if (frameInfo) { - this.mainWindow.webContents.sendToFrame([frameInfo.processId, frameInfo.frameId], channel, ...data); - } else { - this.mainWindow.webContents.send(channel, ...data); - } - } - - async navigateExtension(extId: string, pageId?: string, params?: Record, frameId?: number) { - await this.ensureMainWindow(); - - const frameInfo = iter.find(clusterFrameMap.values(), frameInfo => frameInfo.frameId === frameId); - - this.sendToView({ - channel: "extension:navigate", - frameInfo, - data: [extId, pageId, params], - }); - } - - async navigate(url: string, frameId?: number) { - await this.ensureMainWindow(); - - this.navigateSync(url, frameId); - } - - navigateSync(url: string, frameId?: number) { - const frameInfo = iter.find(clusterFrameMap.values(), frameInfo => frameInfo.frameId === frameId); - const channel = frameInfo - ? IpcRendererNavigationEvents.NAVIGATE_IN_CLUSTER - : IpcRendererNavigationEvents.NAVIGATE_IN_APP; - - this.sendToView({ - channel, - frameInfo, - data: [url], - }); - } - - reload() { - const frameInfo = clusterFrameMap.get(this.activeClusterId); - - if (frameInfo) { - this.sendToView({ channel: IpcRendererNavigationEvents.RELOAD_PAGE, frameInfo }); - } else { - webContents.getAllWebContents().filter(wc => wc.getType() === "window").forEach(wc => { - wc.reload(); - wc.clearHistory(); - }); - } - } - - async showSplash() { - if (!this.splashWindow) { - this.splashWindow = new BrowserWindow({ - width: 500, - height: 300, - backgroundColor: "#1e2124", - center: true, - frame: false, - resizable: false, - show: false, - webPreferences: { - nodeIntegration: true, - contextIsolation: false, - nodeIntegrationInSubFrames: true, - nativeWindowOpen: true, - }, - }); - await this.splashWindow.loadURL("static://splash.html"); - } - this.splashWindow.show(); - } - - hide() { - if (isHideable(this.mainWindow)) { - this.mainWindow.hide(); - } - - if (isHideable(this.splashWindow)) { - this.splashWindow.hide(); - } - } - - destroy() { - this.mainWindow.destroy(); - 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/getDiForUnitTesting.tsx b/src/renderer/getDiForUnitTesting.tsx index db8c2e830f..d61c3f5d7e 100644 --- a/src/renderer/getDiForUnitTesting.tsx +++ b/src/renderer/getDiForUnitTesting.tsx @@ -36,6 +36,7 @@ import hotbarStoreInjectable from "../common/hotbar-store.injectable"; import themeStoreInjectable from "./theme-store.injectable"; import apiManagerInjectable from "./components/kube-object-menu/dependencies/api-manager.injectable"; import { ApiManager } from "../common/k8s-api/api-manager"; +import lensResourcesDirInjectable from "../common/vars/lens-resources-dir.injectable"; export const getDiForUnitTesting = ( { doGeneralOverrides } = { doGeneralOverrides: false }, @@ -63,6 +64,8 @@ export const getDiForUnitTesting = ( di.override(getAbsolutePathInjectable, () => getAbsolutePathFake); di.override(joinPathsInjectable, () => joinPathsFake); + di.override(lensResourcesDirInjectable, () => "/irrelevant"); + // eslint-disable-next-line unused-imports/no-unused-vars-ts di.override(extensionsStoreInjectable, () => ({ isEnabled: ({ id, isBundled }) => false }) as ExtensionsStore); diff --git a/src/test-utils/override-ipc-bridge.ts b/src/test-utils/override-ipc-bridge.ts index a4425a8add..16fe979b47 100644 --- a/src/test-utils/override-ipc-bridge.ts +++ b/src/test-utils/override-ipc-bridge.ts @@ -8,9 +8,9 @@ import getValueFromRegisteredChannelInjectable from "../renderer/app-paths/get-v import registerChannelInjectable from "../main/app-paths/register-channel/register-channel.injectable"; import asyncFn from "@async-fn/jest"; import registerIpcChannelListenerInjectable from "../renderer/app-paths/get-value-from-registered-channel/register-ipc-channel-listener.injectable"; -import windowManagerInjectable from "../main/window-manager.injectable"; -import type { SendToViewArgs, WindowManager } from "../main/window-manager"; -import { appNavigationIpcChannel } from "../common/front-end-routing/navigation-ipc-channel"; +import type { SendToViewArgs } from "../main/start-main-application/lens-window/application-window/lens-window-injection-token"; +import sendToChannelInElectronBrowserWindowInjectable from "../main/start-main-application/lens-window/application-window/send-to-channel-in-electron-browser-window.injectable"; + export const overrideIpcBridge = ({ rendererDi, @@ -68,7 +68,10 @@ export const overrideIpcBridge = ({ mainIpcRegistrations.set(channel, callback); }); - const rendererIpcFakeHandles = new Map void)[]>(); + const rendererIpcFakeHandles = new Map< + string, + ((...args: any[]) => void)[] + >(); rendererDi.override( registerIpcChannelListenerInjectable, @@ -81,21 +84,14 @@ export const overrideIpcBridge = ({ ); mainDi.override( - windowManagerInjectable, + sendToChannelInElectronBrowserWindowInjectable, () => - ({ - ensureMainWindow: () => Promise.resolve(), + (browserWindow, { channel: channelName, data }: SendToViewArgs) => { + const handles = rendererIpcFakeHandles.get(channelName); - sendToView: ({ channel: channelName, data }: SendToViewArgs) => { - const handles = rendererIpcFakeHandles.get(channelName); + handles.forEach((handle) => handle(...data)); - handles.forEach(handle => handle(...data)); - }, - - navigateSync(url: string) { - this.sendToView({ channel: appNavigationIpcChannel.name, data: [url] }); - }, - - } as unknown as WindowManager), + return Promise.resolve(); + }, ); };