mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Technical requirements for behavioural unit tests (#5084)
* Implement a lot of technical requirements for behavioural unit tests Note: the crux of this was to make routing env-agnostic, and not based on URLs as magic strings, but instead something type-enforced. Note: extension-based routes comply to same exact interface by "late-registering" their routes when installed. Routes are just injectables. Note: another chunk of global shared state is no more. Note: a lot of explicit side effects have been cornered to injectables. Note: a lot of stuff has become reactive as part if this. Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make a directory commonly available Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Require id for <Select /> to prevent non-deterministic renders This was caused by global state in a 3rd party lib: "react-select". Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Specify id for all <Select /> to satisfy previous commit Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Prevent explicit side effect in component by using existing dependency instead Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract instantiation of "conf" as injectables for causing side effects Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce a legacy-helper to make gradual refactoring of inheritors of Singleton easier Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make legacy unit tests for hotbar green and more simple by using the new legacy helper Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Temporarily kludge all unit tests green with a disclaimer about allowing side-effects Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove kludge in previous commit by explicitly permitting specific side effects where old unit tests require it Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Prevent old unit test with side effects from accessing file system Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Migrate to actual typing for di.permitSideEffects Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Prevent unit tests from failing because of non-standard method of HTML-element not present in js-dom Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Adapt integration tests to recent changes Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Fix code style Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Fix artifact from bad rebase Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Add a deprecation from a review comment Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove change that is not required Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant comment Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix code style Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant file Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix bad merge Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Improve variable name Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Tweak logger interface to be more descriptive Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make injecting legacy singleton always provide new instance Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove conditional typing when not needed Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Improve naming of variable Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove unnecessary code style changes Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove flag for causing side effects from too broad scope Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Override side-effects in unit test using injectable instead of monkey patching Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Flag some side-effects and add general overrides Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Fix unit tests in CI by removing explicit side-effect Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove explicit side-effect from getting default shell Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce abstraction for getting absolute paths Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using abstraction for getting absolute path to control explicit side effect Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce abstraction for joining paths Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using abstraction for joining paths to control explicit side effect Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix fake implementation for join paths Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix test after removing explicit side effect Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove explicit side effects from kubeconfig-syncs Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix arguments after removing explicit side effect Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make registrators not async for not being needed anymore Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make generalCatalogEntities non-observable, as there is no requirement Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove redundant code Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify logic for registering general catalog entity sources Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add TODO Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Replace function for getting application menu items with reactive solution Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix typo in interface name Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove global shared state usages of hot bar store Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant enum Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
1 parent
ab2cd27a27
commit
a277cfcf02
583 files changed
+13073
-6556
No files matched your search
@@ -52,6 +52,8 @@ import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-p
|
||||
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
||||
import path from "path";
|
||||
import spawnInjectable from "../child-process/spawn.injectable";
|
||||
import getConfigurationFileModelInjectable from "../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||
import appVersionInjectable from "../../common/get-configuration-file-model/app-version/app-version.injectable";
|
||||
|
||||
console = new Console(stdout, stderr);
|
||||
|
||||
@@ -96,6 +98,9 @@ describe("kube auth proxy tests", () => {
|
||||
|
||||
di.override(spawnInjectable, () => mockSpawn);
|
||||
|
||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||
di.permitSideEffects(appVersionInjectable);
|
||||
|
||||
mockFs(mockMinikubeConfig);
|
||||
|
||||
await di.runSetups();
|
||||
@@ -134,7 +139,7 @@ describe("kube auth proxy tests", () => {
|
||||
beforeEach(async () => {
|
||||
mockedCP = mock<ChildProcess>();
|
||||
listeners = new EventEmitter();
|
||||
|
||||
|
||||
jest.spyOn(Kubectl.prototype, "checkBinary").mockReturnValueOnce(Promise.resolve(true));
|
||||
jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValueOnce(Promise.resolve(false));
|
||||
mockedCP.on.mockImplementation((event: string, listener: (message: any, sendHandle: any) => void): ChildProcess => {
|
||||
|
||||
@@ -16,9 +16,9 @@ import registerChannelInjectable from "./register-channel/register-channel.injec
|
||||
import { getAppPaths } from "./get-app-paths";
|
||||
import getElectronAppPathInjectable from "./get-electron-app-path/get-electron-app-path.injectable";
|
||||
import setElectronAppPathInjectable from "./set-electron-app-path/set-electron-app-path.injectable";
|
||||
import path from "path";
|
||||
import appNameInjectable from "./app-name/app-name.injectable";
|
||||
import directoryForIntegrationTestingInjectable from "./directory-for-integration-testing/directory-for-integration-testing.injectable";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
|
||||
const appPathsInjectable = getInjectable({
|
||||
id: "app-paths",
|
||||
@@ -55,10 +55,11 @@ const setupPathForUserData = async (di: DiContainerForSetup) => {
|
||||
const setElectronAppPath = await di.inject(setElectronAppPathInjectable);
|
||||
const appName = await di.inject(appNameInjectable);
|
||||
const getAppPath = await di.inject(getElectronAppPathInjectable);
|
||||
const joinPaths = await di.inject(joinPathsInjectable);
|
||||
|
||||
const appDataPath = getAppPath("appData");
|
||||
|
||||
setElectronAppPath("userData", path.join(appDataPath, appName));
|
||||
setElectronAppPath("userData", joinPaths(appDataPath, appName));
|
||||
};
|
||||
|
||||
// Todo: this kludge is here only until we have a proper place to setup integration testing.
|
||||
|
||||
@@ -7,6 +7,8 @@ import getElectronAppPathInjectable from "./get-electron-app-path.injectable";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import type { App } from "electron";
|
||||
import registerChannelInjectable from "../register-channel/register-channel.injectable";
|
||||
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
|
||||
import { joinPathsFake } from "../../../common/test-utils/join-paths-fake";
|
||||
|
||||
describe("get-electron-app-path", () => {
|
||||
let getElectronAppPath: (name: string) => string | null;
|
||||
@@ -31,6 +33,7 @@ describe("get-electron-app-path", () => {
|
||||
|
||||
di.override(electronAppInjectable, () => appStub);
|
||||
di.override(registerChannelInjectable, () => () => undefined);
|
||||
di.override(joinPathsInjectable, () => joinPathsFake);
|
||||
|
||||
await di.runSetups();
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@ import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
|
||||
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||
import { ClusterStore } from "../../../common/cluster-store/cluster-store";
|
||||
import getConfigurationFileModelInjectable
|
||||
from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||
import appVersionInjectable
|
||||
from "../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
@@ -48,6 +52,13 @@ describe("kubeconfig-sync.source tests", () => {
|
||||
createCluster: di.inject(createClusterInjectionToken),
|
||||
});
|
||||
|
||||
di.override(clusterStoreInjectable, () =>
|
||||
ClusterStore.createInstance({ createCluster: () => null }),
|
||||
);
|
||||
|
||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||
di.permitSideEffects(appVersionInjectable);
|
||||
|
||||
di.inject(clusterStoreInjectable);
|
||||
|
||||
ClusterManager.createInstance();
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { observable } from "mobx";
|
||||
import { GeneralEntity } from "../../common/catalog-entities/general";
|
||||
import { catalogURL, preferencesURL, welcomeURL } from "../../common/routes";
|
||||
import { catalogEntityRegistry } from "../catalog";
|
||||
|
||||
export const catalogEntity = new GeneralEntity({
|
||||
metadata: {
|
||||
uid: "catalog-entity",
|
||||
name: "Catalog",
|
||||
source: "app",
|
||||
labels: {},
|
||||
},
|
||||
spec: {
|
||||
path: catalogURL(),
|
||||
icon: {
|
||||
material: "view_list",
|
||||
background: "#3d90ce",
|
||||
},
|
||||
},
|
||||
status: {
|
||||
phase: "active",
|
||||
},
|
||||
});
|
||||
|
||||
const preferencesEntity = new GeneralEntity({
|
||||
metadata: {
|
||||
uid: "preferences-entity",
|
||||
name: "Preferences",
|
||||
source: "app",
|
||||
labels: {},
|
||||
},
|
||||
spec: {
|
||||
path: preferencesURL(),
|
||||
icon: {
|
||||
material: "settings",
|
||||
background: "#3d90ce",
|
||||
},
|
||||
},
|
||||
status: {
|
||||
phase: "active",
|
||||
},
|
||||
});
|
||||
|
||||
const welcomePageEntity = new GeneralEntity({
|
||||
metadata: {
|
||||
uid: "welcome-page-entity",
|
||||
name: "Welcome Page",
|
||||
source: "app",
|
||||
labels: {},
|
||||
},
|
||||
spec: {
|
||||
path: welcomeURL(),
|
||||
icon: {
|
||||
material: "meeting_room",
|
||||
background: "#3d90ce",
|
||||
},
|
||||
},
|
||||
status: {
|
||||
phase: "active",
|
||||
},
|
||||
});
|
||||
|
||||
const generalEntities = observable([
|
||||
catalogEntity,
|
||||
preferencesEntity,
|
||||
welcomePageEntity,
|
||||
]);
|
||||
|
||||
export function syncGeneralEntities() {
|
||||
catalogEntityRegistry.addObservableSource("lens:general", generalEntities);
|
||||
}
|
||||
@@ -4,4 +4,3 @@
|
||||
*/
|
||||
|
||||
export { syncWeblinks } from "./weblinks";
|
||||
export { syncGeneralEntities } from "./general";
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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 catalogEntityRegistryInjectable from "../catalog/catalog-entity-registry.injectable";
|
||||
import { generalCatalogEntityInjectionToken } from "../../common/catalog-entities/general-catalog-entities/general-catalog-entity-injection-token";
|
||||
import { computed } from "mobx";
|
||||
|
||||
const syncGeneralCatalogEntitiesInjectable = getInjectable({
|
||||
id: "sync-general-catalog-entities",
|
||||
|
||||
instantiate: (di) => {
|
||||
const generalCatalogEntities = di.injectMany(generalCatalogEntityInjectionToken);
|
||||
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
||||
|
||||
// TODO: This shouldn't be reactive at all but catalogEntityRegistry accepts only reactive sources
|
||||
const reactiveGeneralCatalogEntities = computed(() => generalCatalogEntities);
|
||||
|
||||
return () => {
|
||||
catalogEntityRegistry.addComputedSource(
|
||||
"lens:general",
|
||||
reactiveGeneralCatalogEntities,
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default syncGeneralCatalogEntitiesInjectable;
|
||||
@@ -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 { catalogEntityRegistry } from "./catalog-entity-registry";
|
||||
|
||||
const catalogEntityRegistryInjectable = getInjectable({
|
||||
id: "catalog-entity-registry",
|
||||
instantiate: () => catalogEntityRegistry,
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default catalogEntityRegistryInjectable;
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { createContainer } from "@ogre-tools/injectable";
|
||||
import { setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
|
||||
export const getDi = () => {
|
||||
const di = createContainer(
|
||||
@@ -13,7 +13,7 @@ export const getDi = () => {
|
||||
getRequireContextForCommonCode,
|
||||
);
|
||||
|
||||
setLegacyGlobalDiForExtensionApi(di);
|
||||
setLegacyGlobalDiForExtensionApi(di, Environments.main);
|
||||
|
||||
return di;
|
||||
};
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
*/
|
||||
|
||||
import glob from "glob";
|
||||
import { memoize, kebabCase } from "lodash/fp";
|
||||
import { kebabCase, memoize, noop } from "lodash/fp";
|
||||
import { createContainer } from "@ogre-tools/injectable";
|
||||
|
||||
import { setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
import getElectronAppPathInjectable from "./app-paths/get-electron-app-path/get-electron-app-path.injectable";
|
||||
import setElectronAppPathInjectable from "./app-paths/set-electron-app-path/set-electron-app-path.injectable";
|
||||
import appNameInjectable from "./app-paths/app-name/app-name.injectable";
|
||||
@@ -16,14 +16,32 @@ import writeJsonFileInjectable from "../common/fs/write-json-file.injectable";
|
||||
import readJsonFileInjectable from "../common/fs/read-json-file.injectable";
|
||||
import readFileInjectable from "../common/fs/read-file.injectable";
|
||||
import directoryForBundledBinariesInjectable from "../common/app-paths/directory-for-bundled-binaries/directory-for-bundled-binaries.injectable";
|
||||
import loggerInjectable from "../common/logger.injectable";
|
||||
import spawnInjectable from "./child-process/spawn.injectable";
|
||||
import extensionsStoreInjectable from "../extensions/extensions-store/extensions-store.injectable";
|
||||
import type { ExtensionsStore } from "../extensions/extensions-store/extensions-store";
|
||||
import fileSystemProvisionerStoreInjectable from "../extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store.injectable";
|
||||
import type { FileSystemProvisionerStore } from "../extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store";
|
||||
import clusterStoreInjectable from "../common/cluster-store/cluster-store.injectable";
|
||||
import type { ClusterStore } from "../common/cluster-store/cluster-store";
|
||||
import type { Cluster } from "../common/cluster/cluster";
|
||||
import userStoreInjectable from "../common/user-store/user-store.injectable";
|
||||
import type { UserStore } from "../common/user-store";
|
||||
import isMacInjectable from "../common/vars/is-mac.injectable";
|
||||
import isWindowsInjectable from "../common/vars/is-windows.injectable";
|
||||
import isLinuxInjectable from "../common/vars/is-linux.injectable";
|
||||
import getAbsolutePathInjectable from "../common/path/get-absolute-path.injectable";
|
||||
import { getAbsolutePathFake } from "../common/test-utils/get-absolute-path-fake";
|
||||
import joinPathsInjectable from "../common/path/join-paths.injectable";
|
||||
import { joinPathsFake } from "../common/test-utils/join-paths-fake";
|
||||
import hotbarStoreInjectable from "../common/hotbar-store.injectable";
|
||||
|
||||
export const getDiForUnitTesting = (
|
||||
{ doGeneralOverrides } = { doGeneralOverrides: false },
|
||||
) => {
|
||||
const di = createContainer();
|
||||
|
||||
setLegacyGlobalDiForExtensionApi(di);
|
||||
setLegacyGlobalDiForExtensionApi(di, Environments.main);
|
||||
|
||||
for (const filePath of getInjectableFilePaths()) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
@@ -38,6 +56,24 @@ export const getDiForUnitTesting = (
|
||||
di.preventSideEffects();
|
||||
|
||||
if (doGeneralOverrides) {
|
||||
di.override(isMacInjectable, () => true);
|
||||
di.override(isWindowsInjectable, () => false);
|
||||
di.override(isLinuxInjectable, () => false);
|
||||
|
||||
di.override(getAbsolutePathInjectable, () => getAbsolutePathFake);
|
||||
di.override(joinPathsInjectable, () => joinPathsFake);
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
||||
di.override(extensionsStoreInjectable, () => ({ isEnabled: ({ id, isBundled }) => false }) as ExtensionsStore);
|
||||
|
||||
di.override(hotbarStoreInjectable, () => ({}));
|
||||
|
||||
di.override(fileSystemProvisionerStoreInjectable, () => ({}) as FileSystemProvisionerStore);
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
||||
di.override(clusterStoreInjectable, () => ({ getById: (id): Cluster => ({}) as Cluster }) as ClusterStore);
|
||||
di.override(userStoreInjectable, () => ({}) as UserStore);
|
||||
|
||||
di.override(
|
||||
getElectronAppPathInjectable,
|
||||
() => (name: string) => `some-electron-app-path-for-${kebabCase(name)}`,
|
||||
@@ -47,8 +83,9 @@ export const getDiForUnitTesting = (
|
||||
di.override(appNameInjectable, () => "some-electron-app-name");
|
||||
di.override(registerChannelInjectable, () => () => undefined);
|
||||
di.override(directoryForBundledBinariesInjectable, () => "some-bin-directory");
|
||||
di.override(spawnInjectable, () => () => {
|
||||
return {
|
||||
|
||||
di.override(spawnInjectable, () => () => {
|
||||
return {
|
||||
stderr: { on: jest.fn(), removeAllListeners: jest.fn() },
|
||||
stdout: { on: jest.fn(), removeAllListeners: jest.fn() },
|
||||
on: jest.fn(),
|
||||
@@ -66,6 +103,13 @@ export const getDiForUnitTesting = (
|
||||
di.override(readFileInjectable, () => () => {
|
||||
throw new Error("Tried to read file from file system without specifying explicit override.");
|
||||
});
|
||||
|
||||
di.override(loggerInjectable, () => ({
|
||||
warn: noop,
|
||||
debug: noop,
|
||||
error: (message: string, ...args: any) => console.error(message, ...args),
|
||||
info: noop,
|
||||
}));
|
||||
}
|
||||
|
||||
return di;
|
||||
|
||||
+18
-11
@@ -30,11 +30,10 @@ import { IpcRendererNavigationEvents } from "../renderer/navigation/events";
|
||||
import { startCatalogSyncToRenderer } from "./catalog-pusher";
|
||||
import { catalogEntityRegistry } from "./catalog";
|
||||
import { HelmRepoManager } from "./helm/helm-repo-manager";
|
||||
import { syncGeneralEntities, syncWeblinks } from "./catalog-sources";
|
||||
import { syncWeblinks } from "./catalog-sources";
|
||||
import configurePackages from "../common/configure-packages";
|
||||
import { PrometheusProviderRegistry } from "./prometheus";
|
||||
import * as initializers from "./initializers";
|
||||
import { HotbarStore } from "../common/hotbar-store";
|
||||
import { WeblinkStore } from "../common/weblink-store";
|
||||
import { SentryInit } from "../common/sentry";
|
||||
import { ensureDir } from "fs-extra";
|
||||
@@ -48,7 +47,6 @@ import lensProtocolRouterMainInjectable from "./protocol-handler/lens-protocol-r
|
||||
import extensionDiscoveryInjectable from "../extensions/extension-discovery/extension-discovery.injectable";
|
||||
import directoryForExesInjectable from "../common/app-paths/directory-for-exes/directory-for-exes.injectable";
|
||||
import initIpcMainHandlersInjectable from "./initializers/init-ipc-main-handlers/init-ipc-main-handlers.injectable";
|
||||
import electronMenuItemsInjectable from "./menu/electron-menu-items.injectable";
|
||||
import directoryForKubeConfigsInjectable from "../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||
import kubeconfigSyncManagerInjectable from "./catalog-sources/kubeconfig-sync-manager/kubeconfig-sync-manager.injectable";
|
||||
import clusterStoreInjectable from "../common/cluster-store/cluster-store.injectable";
|
||||
@@ -57,6 +55,11 @@ import shellApiRequestInjectable from "./proxy-functions/shell-api-request/shell
|
||||
import userStoreInjectable from "../common/user-store/user-store.injectable";
|
||||
import trayMenuItemsInjectable from "./tray/tray-menu-items.injectable";
|
||||
import { broadcastNativeThemeOnUpdate } from "./native-theme";
|
||||
import windowManagerInjectable from "./window-manager.injectable";
|
||||
import navigateToPreferencesInjectable from "../common/front-end-routing/routes/preferences/navigate-to-preferences.injectable";
|
||||
import syncGeneralCatalogEntitiesInjectable from "./catalog-sources/sync-general-catalog-entities.injectable";
|
||||
import hotbarStoreInjectable from "../common/hotbar-store.injectable";
|
||||
import applicationMenuItemsInjectable from "./menu/application-menu-items.injectable";
|
||||
|
||||
const di = getDi();
|
||||
|
||||
@@ -66,7 +69,7 @@ app.on("ready", async () => {
|
||||
await di.runSetups();
|
||||
|
||||
injectSystemCAs();
|
||||
|
||||
|
||||
const onCloseCleanup = disposer();
|
||||
const onQuitCleanup = disposer();
|
||||
|
||||
@@ -184,7 +187,7 @@ app.on("ready", async () => {
|
||||
lensProtocolRouterMain.route(rawUrl);
|
||||
});
|
||||
|
||||
logger.debug("[APP-MAIN] waiting for 'ready' and other messages");
|
||||
logger.debug("[APP-MAIN] waiting for 'ready' and other messages");
|
||||
|
||||
const directoryForExes = di.inject(directoryForExesInjectable);
|
||||
|
||||
@@ -204,7 +207,9 @@ app.on("ready", async () => {
|
||||
* store has migrations that will remove items that previous migrations add
|
||||
* if this is not present
|
||||
*/
|
||||
syncGeneralEntities();
|
||||
const syncGeneralCatalogEntities = di.inject(syncGeneralCatalogEntitiesInjectable);
|
||||
|
||||
syncGeneralCatalogEntities();
|
||||
|
||||
logger.info("💾 Loading stores");
|
||||
|
||||
@@ -218,7 +223,7 @@ app.on("ready", async () => {
|
||||
clusterStore.provideInitialFromMain();
|
||||
|
||||
// HotbarStore depends on: ClusterStore
|
||||
HotbarStore.createInstance();
|
||||
di.inject(hotbarStoreInjectable);
|
||||
|
||||
WeblinkStore.createInstance();
|
||||
|
||||
@@ -291,13 +296,15 @@ app.on("ready", async () => {
|
||||
const startHidden = process.argv.includes("--hidden") || (isMac && app.getLoginItemSettings().wasOpenedAsHidden);
|
||||
|
||||
logger.info("🖥️ Starting WindowManager");
|
||||
const windowManager = WindowManager.createInstance();
|
||||
const menuItems = di.inject(electronMenuItemsInjectable);
|
||||
const windowManager = di.inject(windowManagerInjectable);
|
||||
|
||||
const applicationMenuItems = di.inject(applicationMenuItemsInjectable);
|
||||
const trayMenuItems = di.inject(trayMenuItemsInjectable);
|
||||
const navigateToPreferences = di.inject(navigateToPreferencesInjectable);
|
||||
|
||||
onQuitCleanup.push(
|
||||
initMenu(windowManager, menuItems),
|
||||
initTray(windowManager, trayMenuItems),
|
||||
initMenu(applicationMenuItems),
|
||||
initTray(windowManager, trayMenuItems, navigateToPreferences),
|
||||
() => ShellSession.cleanup(),
|
||||
);
|
||||
|
||||
|
||||
@@ -3,17 +3,18 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import electronMenuItemsInjectable from "../../menu/electron-menu-items.injectable";
|
||||
import directoryForLensLocalStorageInjectable
|
||||
from "../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
|
||||
import directoryForLensLocalStorageInjectable from "../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
|
||||
import { initIpcMainHandlers } from "./init-ipc-main-handlers";
|
||||
import getAbsolutePathInjectable from "../../../common/path/get-absolute-path.injectable";
|
||||
import applicationMenuItemsInjectable from "../../menu/application-menu-items.injectable";
|
||||
|
||||
const initIpcMainHandlersInjectable = getInjectable({
|
||||
id: "init-ipc-main-handlers",
|
||||
|
||||
instantiate: (di) => initIpcMainHandlers({
|
||||
electronMenuItems: di.inject(electronMenuItemsInjectable),
|
||||
applicationMenuItems: di.inject(applicationMenuItemsInjectable),
|
||||
directoryForLensLocalStorage: di.inject(directoryForLensLocalStorageInjectable),
|
||||
getAbsolutePath: di.inject(getAbsolutePathInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -14,25 +14,24 @@ import { catalogEntityRegistry } from "../../catalog";
|
||||
import { pushCatalogToRenderer } from "../../catalog-pusher";
|
||||
import { ClusterManager } from "../../cluster-manager";
|
||||
import { ResourceApplier } from "../../resource-applier";
|
||||
import { WindowManager } from "../../window-manager";
|
||||
import path from "path";
|
||||
import { remove } from "fs-extra";
|
||||
import { getAppMenu } from "../../menu/menu";
|
||||
import type { MenuRegistration } from "../../menu/menu-registration";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { onLocationChange, handleWindowAction } from "../../ipc/window";
|
||||
import { openFilePickingDialogChannel } from "../../../common/ipc/dialog";
|
||||
import { showOpenDialog } from "../../ipc/dialog";
|
||||
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../common/ipc/window";
|
||||
import { getNativeColorTheme } from "../../native-theme";
|
||||
import { getNativeThemeChannel } from "../../../common/ipc/native-theme";
|
||||
import type { GetAbsolutePath } from "../../../common/path/get-absolute-path.injectable";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import type { MenuItemOpts } from "../../menu/application-menu-items.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
electronMenuItems: IComputedValue<MenuRegistration[]>;
|
||||
directoryForLensLocalStorage: string;
|
||||
getAbsolutePath: GetAbsolutePath;
|
||||
applicationMenuItems: IComputedValue<MenuItemOpts[]>;
|
||||
}
|
||||
|
||||
export const initIpcMainHandlers = ({ electronMenuItems, directoryForLensLocalStorage }: Dependencies) => () => {
|
||||
export const initIpcMainHandlers = ({ applicationMenuItems, directoryForLensLocalStorage, getAbsolutePath }: Dependencies) => () => {
|
||||
ipcMainHandle(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
|
||||
return ClusterStore.getInstance()
|
||||
.getById(clusterId)
|
||||
@@ -88,7 +87,7 @@ export const initIpcMainHandlers = ({ electronMenuItems, directoryForLensLocalSt
|
||||
|
||||
try {
|
||||
// remove the local storage file
|
||||
const localStorageFilePath = path.resolve(directoryForLensLocalStorage, `${cluster.id}.json`);
|
||||
const localStorageFilePath = getAbsolutePath(directoryForLensLocalStorage, `${cluster.id}.json`);
|
||||
|
||||
await remove(localStorageFilePath);
|
||||
} catch {
|
||||
@@ -151,7 +150,9 @@ export const initIpcMainHandlers = ({ electronMenuItems, directoryForLensLocalSt
|
||||
ipcMainHandle(broadcastMainChannel, (event, channel, ...args) => broadcastMessage(channel, ...args));
|
||||
|
||||
ipcMainOn(windowOpenAppMenuAsContextMenuChannel, async (event) => {
|
||||
const menu = Menu.buildFromTemplate(getAppMenu(WindowManager.getInstance(), electronMenuItems.get()));
|
||||
const appMenu = applicationMenuItems.get();
|
||||
|
||||
const menu = Menu.buildFromTemplate(appMenu);
|
||||
|
||||
menu.popup({
|
||||
...BrowserWindow.fromWebContents(event.sender),
|
||||
|
||||
@@ -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 { isAutoUpdateEnabled } from "./app-updater";
|
||||
|
||||
const isAutoUpdateEnabledInjectable = getInjectable({
|
||||
id: "is-auto-update-enabled",
|
||||
instantiate: () => isAutoUpdateEnabled,
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default isAutoUpdateEnabledInjectable;
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { Kubectl } from "./kubectl";
|
||||
import directoryForKubectlBinariesInjectable from "./directory-for-kubectl-binaries/directory-for-kubectl-binaries.injectable";
|
||||
import directoryForKubectlBinariesInjectable from "../../common/app-paths/directory-for-kubectl-binaries/directory-for-kubectl-binaries.injectable";
|
||||
import userStoreInjectable from "../../common/user-store/user-store.injectable";
|
||||
|
||||
const createKubectlInjectable = getInjectable({
|
||||
|
||||
-16
@@ -1,16 +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 directoryForBinariesInjectable from "../../../common/app-paths/directory-for-binaries/directory-for-binaries.injectable";
|
||||
import path from "path";
|
||||
|
||||
const directoryForKubectlBinariesInjectable = getInjectable({
|
||||
id: "directory-for-kubectl-binaries",
|
||||
|
||||
instantiate: (di) =>
|
||||
path.join(di.inject(directoryForBinariesInjectable), "kubectl"),
|
||||
});
|
||||
|
||||
export default directoryForKubectlBinariesInjectable;
|
||||
@@ -0,0 +1,337 @@
|
||||
/**
|
||||
* 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 { checkForUpdates } from "../app-updater";
|
||||
import { docsUrl, productName, supportUrl } from "../../common/vars";
|
||||
import { exitApp } from "../exit-app";
|
||||
import { broadcastMessage } from "../../common/ipc";
|
||||
import { openBrowser } from "../../common/utils";
|
||||
import { showAbout } from "./menu";
|
||||
import windowManagerInjectable from "../window-manager.injectable";
|
||||
import {
|
||||
BrowserWindow,
|
||||
MenuItem,
|
||||
MenuItemConstructorOptions,
|
||||
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";
|
||||
import isAutoUpdateEnabledInjectable from "../is-auto-update-enabled.injectable";
|
||||
import navigateToPreferencesInjectable from "../../common/front-end-routing/routes/preferences/navigate-to-preferences.injectable";
|
||||
import navigateToExtensionsInjectable from "../../common/front-end-routing/routes/extensions/navigate-to-extensions.injectable";
|
||||
import navigateToCatalogInjectable from "../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
||||
import navigateToWelcomeInjectable from "../../common/front-end-routing/routes/welcome/navigate-to-welcome.injectable";
|
||||
import navigateToAddClusterInjectable from "../../common/front-end-routing/routes/add-cluster/navigate-to-add-cluster.injectable";
|
||||
import isMacInjectable from "../../common/vars/is-mac.injectable";
|
||||
import { computed } from "mobx";
|
||||
|
||||
function ignoreIf(check: boolean, menuItems: MenuItemConstructorOptions[]) {
|
||||
return check ? [] : menuItems;
|
||||
}
|
||||
|
||||
export interface MenuItemOpts extends MenuItemConstructorOptions {
|
||||
submenu?: MenuItemConstructorOptions[];
|
||||
}
|
||||
|
||||
const applicationMenuItemsInjectable = getInjectable({
|
||||
id: "application-menu-items",
|
||||
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const appName = di.inject(appNameInjectable);
|
||||
const isMac = di.inject(isMacInjectable);
|
||||
const isAutoUpdateEnabled = di.inject(isAutoUpdateEnabledInjectable);
|
||||
const electronMenuItems = di.inject(electronMenuItemsInjectable);
|
||||
|
||||
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 navigateToPreferences = di.inject(navigateToPreferencesInjectable);
|
||||
const navigateToExtensions = di.inject(navigateToExtensionsInjectable);
|
||||
const navigateToCatalog = di.inject(navigateToCatalogInjectable);
|
||||
const navigateToWelcome = di.inject(navigateToWelcomeInjectable);
|
||||
const navigateToAddCluster = di.inject(navigateToAddClusterInjectable);
|
||||
|
||||
const autoUpdateDisabled = !isAutoUpdateEnabled();
|
||||
|
||||
logger.info(`[MENU]: autoUpdateDisabled=${autoUpdateDisabled}`);
|
||||
|
||||
const macAppMenu: MenuItemOpts = {
|
||||
label: appName,
|
||||
id: "root",
|
||||
submenu: [
|
||||
{
|
||||
label: `About ${productName}`,
|
||||
id: "about",
|
||||
click(menuItem: MenuItem, browserWindow: BrowserWindow) {
|
||||
showAbout(browserWindow);
|
||||
},
|
||||
},
|
||||
...ignoreIf(autoUpdateDisabled, [
|
||||
{
|
||||
label: "Check for updates",
|
||||
click() {
|
||||
checkForUpdates().then(() => windowManager.ensureMainWindow());
|
||||
},
|
||||
},
|
||||
]),
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: "Preferences",
|
||||
accelerator: "CmdOrCtrl+,",
|
||||
id: "preferences",
|
||||
click() {
|
||||
navigateToPreferences();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Extensions",
|
||||
accelerator: "CmdOrCtrl+Shift+E",
|
||||
id: "extensions",
|
||||
click() {
|
||||
navigateToExtensions();
|
||||
},
|
||||
},
|
||||
{ type: "separator" },
|
||||
{ role: "services" },
|
||||
{ type: "separator" },
|
||||
{ role: "hide" },
|
||||
{ role: "hideOthers" },
|
||||
{ role: "unhide" },
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: "Quit",
|
||||
accelerator: "Cmd+Q",
|
||||
id: "quit",
|
||||
click() {
|
||||
exitApp();
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const fileMenu: MenuItemOpts = {
|
||||
label: "File",
|
||||
id: "file",
|
||||
submenu: [
|
||||
{
|
||||
label: "Add Cluster",
|
||||
accelerator: "CmdOrCtrl+Shift+A",
|
||||
id: "add-cluster",
|
||||
click() {
|
||||
navigateToAddCluster();
|
||||
},
|
||||
},
|
||||
...ignoreIf(isMac, [
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: "Preferences",
|
||||
id: "preferences",
|
||||
accelerator: "Ctrl+,",
|
||||
click() {
|
||||
navigateToPreferences();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Extensions",
|
||||
accelerator: "Ctrl+Shift+E",
|
||||
click() {
|
||||
navigateToExtensions();
|
||||
},
|
||||
},
|
||||
]),
|
||||
|
||||
{ type: "separator" },
|
||||
|
||||
...(isMac
|
||||
? ([
|
||||
{
|
||||
role: "close",
|
||||
label: "Close Window",
|
||||
accelerator: "Shift+Cmd+W",
|
||||
},
|
||||
] as MenuItemConstructorOptions[])
|
||||
: []),
|
||||
|
||||
...ignoreIf(isMac, [
|
||||
{
|
||||
label: "Exit",
|
||||
accelerator: "Alt+F4",
|
||||
id: "quit",
|
||||
click() {
|
||||
exitApp();
|
||||
},
|
||||
},
|
||||
]),
|
||||
],
|
||||
};
|
||||
const editMenu: MenuItemOpts = {
|
||||
label: "Edit",
|
||||
id: "edit",
|
||||
submenu: [
|
||||
{ role: "undo" },
|
||||
{ role: "redo" },
|
||||
{ type: "separator" },
|
||||
{ role: "cut" },
|
||||
{ role: "copy" },
|
||||
{ role: "paste" },
|
||||
{ role: "delete" },
|
||||
{ type: "separator" },
|
||||
{ role: "selectAll" },
|
||||
],
|
||||
};
|
||||
const viewMenu: MenuItemOpts = {
|
||||
label: "View",
|
||||
id: "view",
|
||||
submenu: [
|
||||
{
|
||||
label: "Catalog",
|
||||
accelerator: "Shift+CmdOrCtrl+C",
|
||||
id: "catalog",
|
||||
click() {
|
||||
navigateToCatalog();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Command Palette...",
|
||||
accelerator: "Shift+CmdOrCtrl+P",
|
||||
id: "command-palette",
|
||||
click(_m, _b, event) {
|
||||
/**
|
||||
* Don't broadcast unless it was triggered by menu iteration so that
|
||||
* there aren't double events in renderer
|
||||
*
|
||||
* NOTE: this `?` is required because of a bug in playwright. https://github.com/microsoft/playwright/issues/10554
|
||||
*/
|
||||
if (!event?.triggeredByAccelerator) {
|
||||
broadcastMessage("command-palette:open");
|
||||
}
|
||||
},
|
||||
},
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: "Back",
|
||||
accelerator: "CmdOrCtrl+[",
|
||||
id: "go-back",
|
||||
click() {
|
||||
webContents
|
||||
.getAllWebContents()
|
||||
.filter((wc) => wc.getType() === "window")
|
||||
.forEach((wc) => wc.goBack());
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Forward",
|
||||
accelerator: "CmdOrCtrl+]",
|
||||
id: "go-forward",
|
||||
click() {
|
||||
webContents
|
||||
.getAllWebContents()
|
||||
.filter((wc) => wc.getType() === "window")
|
||||
.forEach((wc) => wc.goForward());
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Reload",
|
||||
accelerator: "CmdOrCtrl+R",
|
||||
id: "reload",
|
||||
click() {
|
||||
windowManager.reload();
|
||||
},
|
||||
},
|
||||
{ role: "toggleDevTools" },
|
||||
{ type: "separator" },
|
||||
{ role: "resetZoom" },
|
||||
{ role: "zoomIn" },
|
||||
{ role: "zoomOut" },
|
||||
{ type: "separator" },
|
||||
{ role: "togglefullscreen" },
|
||||
],
|
||||
};
|
||||
const helpMenu: MenuItemOpts = {
|
||||
role: "help",
|
||||
id: "help",
|
||||
submenu: [
|
||||
{
|
||||
label: "Welcome",
|
||||
id: "welcome",
|
||||
click() {
|
||||
navigateToWelcome();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Documentation",
|
||||
id: "documentation",
|
||||
click: async () => {
|
||||
openBrowser(docsUrl).catch((error) => {
|
||||
logger.error("[MENU]: failed to open browser", { error });
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Support",
|
||||
id: "support",
|
||||
click: async () => {
|
||||
openBrowser(supportUrl).catch((error) => {
|
||||
logger.error("[MENU]: failed to open browser", { error });
|
||||
});
|
||||
},
|
||||
},
|
||||
...ignoreIf(isMac, [
|
||||
{
|
||||
label: `About ${productName}`,
|
||||
id: "about",
|
||||
click(menuItem: MenuItem, browserWindow: BrowserWindow) {
|
||||
showAbout(browserWindow);
|
||||
},
|
||||
},
|
||||
...ignoreIf(autoUpdateDisabled, [
|
||||
{
|
||||
label: "Check for updates",
|
||||
click() {
|
||||
checkForUpdates().then(() =>
|
||||
windowManager.ensureMainWindow(),
|
||||
);
|
||||
},
|
||||
},
|
||||
]),
|
||||
]),
|
||||
],
|
||||
};
|
||||
// Prepare menu items order
|
||||
const appMenu = new Map([
|
||||
["mac", macAppMenu],
|
||||
["file", fileMenu],
|
||||
["edit", editMenu],
|
||||
["view", viewMenu],
|
||||
["help", helpMenu],
|
||||
]);
|
||||
|
||||
// Modify menu from extensions-api
|
||||
for (const menuItem of electronMenuItems.get()) {
|
||||
if (!appMenu.has(menuItem.parentId)) {
|
||||
logger.error(
|
||||
`[MENU]: cannot register menu item for parentId=${menuItem.parentId}, parent item doesn't exist`,
|
||||
{ menuItem },
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
appMenu.get(menuItem.parentId).submenu.push(menuItem);
|
||||
}
|
||||
|
||||
if (!isMac) {
|
||||
appMenu.delete("mac");
|
||||
}
|
||||
|
||||
return [...appMenu.values()];
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default applicationMenuItemsInjectable;
|
||||
+7
-297
@@ -2,30 +2,18 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { app, BrowserWindow, dialog, Menu, MenuItem, MenuItemConstructorOptions, webContents } from "electron";
|
||||
import { app, BrowserWindow, dialog, Menu } from "electron";
|
||||
import { autorun, IComputedValue } from "mobx";
|
||||
import type { WindowManager } from "../window-manager";
|
||||
import { appName, isMac, isWindows, docsUrl, supportUrl, productName } from "../../common/vars";
|
||||
import logger from "../logger";
|
||||
import { exitApp } from "../exit-app";
|
||||
import { broadcastMessage } from "../../common/ipc";
|
||||
import { openBrowser } from "../../common/utils";
|
||||
import { appName, isWindows, productName } from "../../common/vars";
|
||||
import packageJson from "../../../package.json";
|
||||
import { preferencesURL, extensionsURL, addClusterURL, catalogURL, welcomeURL } from "../../common/routes";
|
||||
import { checkForUpdates, isAutoUpdateEnabled } from "../app-updater";
|
||||
import type { MenuRegistration } from "./menu-registration";
|
||||
import type { MenuItemOpts } from "./application-menu-items.injectable";
|
||||
|
||||
export type MenuTopId = "mac" | "file" | "edit" | "view" | "help";
|
||||
|
||||
interface MenuItemsOpts extends MenuItemConstructorOptions {
|
||||
submenu?: MenuItemConstructorOptions[];
|
||||
}
|
||||
|
||||
export function initMenu(
|
||||
windowManager: WindowManager,
|
||||
electronMenuItems: IComputedValue<MenuRegistration[]>,
|
||||
applicationMenuItems: IComputedValue<MenuItemOpts[]>,
|
||||
) {
|
||||
return autorun(() => buildMenu(windowManager, electronMenuItems.get()), {
|
||||
return autorun(() => buildMenu(applicationMenuItems.get()), {
|
||||
delay: 100,
|
||||
});
|
||||
}
|
||||
@@ -48,288 +36,10 @@ export function showAbout(browserWindow: BrowserWindow) {
|
||||
});
|
||||
}
|
||||
|
||||
export function getAppMenu(
|
||||
windowManager: WindowManager,
|
||||
electronMenuItems: MenuRegistration[],
|
||||
) {
|
||||
function ignoreIf(check: boolean, menuItems: MenuItemConstructorOptions[]) {
|
||||
return check ? [] : menuItems;
|
||||
}
|
||||
|
||||
async function navigate(url: string) {
|
||||
logger.info(`[MENU]: navigating to ${url}`);
|
||||
await windowManager.navigate(url);
|
||||
}
|
||||
|
||||
const autoUpdateDisabled = !isAutoUpdateEnabled();
|
||||
|
||||
logger.info(`[MENU]: autoUpdateDisabled=${autoUpdateDisabled}`);
|
||||
|
||||
const macAppMenu: MenuItemsOpts = {
|
||||
label: app.getName(),
|
||||
id: "root",
|
||||
submenu: [
|
||||
{
|
||||
label: `About ${productName}`,
|
||||
id: "about",
|
||||
click(menuItem: MenuItem, browserWindow: BrowserWindow) {
|
||||
showAbout(browserWindow);
|
||||
},
|
||||
},
|
||||
...ignoreIf(autoUpdateDisabled, [{
|
||||
label: "Check for updates",
|
||||
click() {
|
||||
checkForUpdates()
|
||||
.then(() => windowManager.ensureMainWindow());
|
||||
},
|
||||
}]),
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: "Preferences",
|
||||
accelerator: "CmdOrCtrl+,",
|
||||
id: "preferences",
|
||||
click() {
|
||||
navigate(preferencesURL());
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Extensions",
|
||||
accelerator: "CmdOrCtrl+Shift+E",
|
||||
id: "extensions",
|
||||
click() {
|
||||
navigate(extensionsURL());
|
||||
},
|
||||
},
|
||||
{ type: "separator" },
|
||||
{ role: "services" },
|
||||
{ type: "separator" },
|
||||
{ role: "hide" },
|
||||
{ role: "hideOthers" },
|
||||
{ role: "unhide" },
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: "Quit",
|
||||
accelerator: "Cmd+Q",
|
||||
id: "quit",
|
||||
click() {
|
||||
exitApp();
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const fileMenu: MenuItemsOpts = {
|
||||
label: "File",
|
||||
id: "file",
|
||||
submenu: [
|
||||
{
|
||||
label: "Add Cluster",
|
||||
accelerator: "CmdOrCtrl+Shift+A",
|
||||
id: "add-cluster",
|
||||
click() {
|
||||
navigate(addClusterURL());
|
||||
},
|
||||
},
|
||||
...ignoreIf(isMac, [
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: "Preferences",
|
||||
id: "preferences",
|
||||
accelerator: "Ctrl+,",
|
||||
click() {
|
||||
navigate(preferencesURL());
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Extensions",
|
||||
accelerator: "Ctrl+Shift+E",
|
||||
click() {
|
||||
navigate(extensionsURL());
|
||||
},
|
||||
},
|
||||
]),
|
||||
|
||||
{ type: "separator" },
|
||||
|
||||
...(isMac ? [
|
||||
{
|
||||
role: "close",
|
||||
label: "Close Window",
|
||||
accelerator: "Shift+Cmd+W",
|
||||
},
|
||||
] as MenuItemConstructorOptions[] : []),
|
||||
|
||||
...ignoreIf(isMac, [
|
||||
{
|
||||
label: "Exit",
|
||||
accelerator: "Alt+F4",
|
||||
id: "quit",
|
||||
click() {
|
||||
exitApp();
|
||||
},
|
||||
},
|
||||
]),
|
||||
],
|
||||
};
|
||||
const editMenu: MenuItemsOpts = {
|
||||
label: "Edit",
|
||||
id: "edit",
|
||||
submenu: [
|
||||
{ role: "undo" },
|
||||
{ role: "redo" },
|
||||
{ type: "separator" },
|
||||
{ role: "cut" },
|
||||
{ role: "copy" },
|
||||
{ role: "paste" },
|
||||
{ role: "delete" },
|
||||
{ type: "separator" },
|
||||
{ role: "selectAll" },
|
||||
],
|
||||
};
|
||||
const viewMenu: MenuItemsOpts = {
|
||||
label: "View",
|
||||
id: "view",
|
||||
submenu: [
|
||||
{
|
||||
label: "Catalog",
|
||||
accelerator: "Shift+CmdOrCtrl+C",
|
||||
id: "catalog",
|
||||
click() {
|
||||
navigate(catalogURL());
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Command Palette...",
|
||||
accelerator: "Shift+CmdOrCtrl+P",
|
||||
id: "command-palette",
|
||||
click(_m, _b, event) {
|
||||
/**
|
||||
* Don't broadcast unless it was triggered by menu iteration so that
|
||||
* there aren't double events in renderer
|
||||
*
|
||||
* NOTE: this `?` is required because of a bug in playwright. https://github.com/microsoft/playwright/issues/10554
|
||||
*/
|
||||
if (!event?.triggeredByAccelerator) {
|
||||
broadcastMessage("command-palette:open");
|
||||
}
|
||||
},
|
||||
},
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: "Back",
|
||||
accelerator: "CmdOrCtrl+[",
|
||||
id: "go-back",
|
||||
click() {
|
||||
webContents.getAllWebContents().filter(wc => wc.getType() === "window").forEach(wc => wc.goBack());
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Forward",
|
||||
accelerator: "CmdOrCtrl+]",
|
||||
id: "go-forward",
|
||||
click() {
|
||||
webContents.getAllWebContents().filter(wc => wc.getType() === "window").forEach(wc => wc.goForward());
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Reload",
|
||||
accelerator: "CmdOrCtrl+R",
|
||||
id: "reload",
|
||||
click() {
|
||||
windowManager.reload();
|
||||
},
|
||||
},
|
||||
{ role: "toggleDevTools" },
|
||||
{ type: "separator" },
|
||||
{ role: "resetZoom" },
|
||||
{ role: "zoomIn" },
|
||||
{ role: "zoomOut" },
|
||||
{ type: "separator" },
|
||||
{ role: "togglefullscreen" },
|
||||
],
|
||||
};
|
||||
const helpMenu: MenuItemsOpts = {
|
||||
role: "help",
|
||||
id: "help",
|
||||
submenu: [
|
||||
{
|
||||
label: "Welcome",
|
||||
id: "welcome",
|
||||
click() {
|
||||
navigate(welcomeURL());
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Documentation",
|
||||
id: "documentation",
|
||||
click: async () => {
|
||||
openBrowser(docsUrl).catch(error => {
|
||||
logger.error("[MENU]: failed to open browser", { error });
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Support",
|
||||
id: "support",
|
||||
click: async () => {
|
||||
openBrowser(supportUrl).catch(error => {
|
||||
logger.error("[MENU]: failed to open browser", { error });
|
||||
});
|
||||
},
|
||||
},
|
||||
...ignoreIf(isMac, [
|
||||
{
|
||||
label: `About ${productName}`,
|
||||
id: "about",
|
||||
click(menuItem: MenuItem, browserWindow: BrowserWindow) {
|
||||
showAbout(browserWindow);
|
||||
},
|
||||
},
|
||||
...ignoreIf(autoUpdateDisabled, [{
|
||||
label: "Check for updates",
|
||||
click() {
|
||||
checkForUpdates()
|
||||
.then(() => windowManager.ensureMainWindow());
|
||||
},
|
||||
}]),
|
||||
]),
|
||||
],
|
||||
};
|
||||
// Prepare menu items order
|
||||
const appMenu = new Map([
|
||||
["mac", macAppMenu],
|
||||
["file", fileMenu],
|
||||
["edit", editMenu],
|
||||
["view", viewMenu],
|
||||
["help", helpMenu],
|
||||
]);
|
||||
|
||||
// Modify menu from extensions-api
|
||||
for (const menuItem of electronMenuItems) {
|
||||
if (!appMenu.has(menuItem.parentId)) {
|
||||
logger.error(
|
||||
`[MENU]: cannot register menu item for parentId=${menuItem.parentId}, parent item doesn't exist`,
|
||||
{ menuItem },
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
appMenu.get(menuItem.parentId).submenu.push(menuItem);
|
||||
}
|
||||
|
||||
if (!isMac) {
|
||||
appMenu.delete("mac");
|
||||
}
|
||||
|
||||
return [...appMenu.values()];
|
||||
|
||||
}
|
||||
|
||||
export function buildMenu(
|
||||
windowManager: WindowManager,
|
||||
electronMenuItems: MenuRegistration[],
|
||||
applicationMenuItems: MenuItemOpts[],
|
||||
) {
|
||||
Menu.setApplicationMenu(
|
||||
Menu.buildFromTemplate(getAppMenu(windowManager, electronMenuItems)),
|
||||
Menu.buildFromTemplate(applicationMenuItems),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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 { navigateToUrlInjectionToken } from "../../common/front-end-routing/navigate-to-url-injection-token";
|
||||
import { navigateToRouteInjectionToken } from "../../common/front-end-routing/navigate-to-route-injection-token";
|
||||
import { buildURL } from "../../common/utils/buildUrl";
|
||||
|
||||
const navigateToRouteInjectable = getInjectable({
|
||||
id: "navigate-to-route",
|
||||
|
||||
instantiate: (di) => {
|
||||
const navigateToUrl = di.inject(navigateToUrlInjectionToken);
|
||||
|
||||
return (route, options) => {
|
||||
const url = buildURL(route.path, {
|
||||
// TODO: enhance typing
|
||||
params: options?.parameters as any,
|
||||
query: options?.query,
|
||||
fragment: options?.fragment,
|
||||
});
|
||||
|
||||
navigateToUrl(url, options);
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: navigateToRouteInjectionToken,
|
||||
});
|
||||
|
||||
export default navigateToRouteInjectable;
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 { navigateToUrlInjectionToken } from "../../common/front-end-routing/navigate-to-url-injection-token";
|
||||
|
||||
const navigateToUrlInjectable = getInjectable({
|
||||
id: "navigate-to-url",
|
||||
|
||||
instantiate: (di) => {
|
||||
const windowManager = di.inject(windowManagerInjectable);
|
||||
|
||||
return (url) => {
|
||||
windowManager.navigateSync(url);
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: navigateToUrlInjectionToken,
|
||||
});
|
||||
|
||||
export default navigateToUrlInjectable;
|
||||
@@ -13,12 +13,11 @@ import { ExtensionsStore } from "../../../extensions/extensions-store/extensions
|
||||
import type { LensProtocolRouterMain } from "../lens-protocol-router-main/lens-protocol-router-main";
|
||||
import mockFs from "mock-fs";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import extensionLoaderInjectable
|
||||
from "../../../extensions/extension-loader/extension-loader.injectable";
|
||||
import lensProtocolRouterMainInjectable
|
||||
from "../lens-protocol-router-main/lens-protocol-router-main.injectable";
|
||||
import extensionsStoreInjectable
|
||||
from "../../../extensions/extensions-store/extensions-store.injectable";
|
||||
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
|
||||
import lensProtocolRouterMainInjectable from "../lens-protocol-router-main/lens-protocol-router-main.injectable";
|
||||
import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
|
||||
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||
import appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
||||
|
||||
jest.mock("../../../common/ipc");
|
||||
|
||||
@@ -42,11 +41,16 @@ describe("protocol router tests", () => {
|
||||
"tmp": {},
|
||||
});
|
||||
|
||||
di.override(extensionsStoreInjectable, () => ExtensionsStore.createInstance());
|
||||
|
||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||
di.permitSideEffects(appVersionInjectable);
|
||||
|
||||
await di.runSetups();
|
||||
|
||||
extensionLoader = di.inject(extensionLoaderInjectable);
|
||||
extensionsStore = di.inject(extensionsStoreInjectable);
|
||||
|
||||
extensionsStore = di.inject(extensionsStoreInjectable);
|
||||
|
||||
lpr = di.inject(lensProtocolRouterMainInjectable);
|
||||
|
||||
@@ -56,9 +60,6 @@ describe("protocol router tests", () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
// TODO: Remove Singleton from BaseStore to achieve independent unit testing
|
||||
ExtensionsStore.resetInstance();
|
||||
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import Call from "@hapi/call";
|
||||
import type http from "http";
|
||||
import type httpProxy from "http-proxy";
|
||||
import { toPairs } from "lodash/fp";
|
||||
import path from "path";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import type { LensApiResultContentType } from "./router-content-types";
|
||||
import { contentTypes } from "./router-content-types";
|
||||
@@ -51,7 +50,6 @@ interface Dependencies {
|
||||
|
||||
export class Router {
|
||||
protected router = new Call.Router();
|
||||
protected static rootPath = path.resolve(__static);
|
||||
|
||||
constructor(routes: Route<unknown>[], private dependencies: Dependencies) {
|
||||
routes.forEach(route => {
|
||||
|
||||
@@ -9,23 +9,28 @@ import logger from "../logger";
|
||||
import { routeInjectionToken } from "../router/router.injectable";
|
||||
import { appName, publicPath } from "../../common/vars";
|
||||
import path from "path";
|
||||
import readFileInjectable from "../../common/fs/read-file.injectable";
|
||||
import isDevelopmentInjectable from "../../common/vars/is-development.injectable";
|
||||
import httpProxy from "http-proxy";
|
||||
import readFileBufferInjectable from "../../common/fs/read-file-buffer.injectable";
|
||||
import getAbsolutePathInjectable, { GetAbsolutePath } from "../../common/path/get-absolute-path.injectable";
|
||||
import type { JoinPaths } from "../../common/path/join-paths.injectable";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
|
||||
interface ProductionDependencies {
|
||||
readFile: (path: string) => Promise<Buffer>;
|
||||
readFileBuffer: (path: string) => Promise<Buffer>;
|
||||
getAbsolutePath: GetAbsolutePath;
|
||||
joinPaths: JoinPaths;
|
||||
}
|
||||
|
||||
const handleStaticFileInProduction =
|
||||
({ readFile }: ProductionDependencies) =>
|
||||
({ readFileBuffer, getAbsolutePath, joinPaths }: ProductionDependencies) =>
|
||||
async ({ params }: LensApiRequest) => {
|
||||
const staticPath = path.resolve(__static);
|
||||
const staticPath = getAbsolutePath(__static);
|
||||
let filePath = params.path;
|
||||
|
||||
for (let retryCount = 0; retryCount < 5; retryCount += 1) {
|
||||
const asset = path.join(staticPath, filePath);
|
||||
const normalizedFilePath = path.resolve(asset);
|
||||
const asset = joinPaths(staticPath, filePath);
|
||||
const normalizedFilePath = getAbsolutePath(asset);
|
||||
|
||||
if (!normalizedFilePath.startsWith(staticPath)) {
|
||||
return { statusCode: 404 };
|
||||
@@ -38,7 +43,7 @@ const handleStaticFileInProduction =
|
||||
|
||||
const contentType = contentTypes[fileExtension] || contentTypes.txt;
|
||||
|
||||
return { response: await readFile(asset), contentType };
|
||||
return { response: await readFileBuffer(asset), contentType };
|
||||
} catch (err) {
|
||||
if (retryCount > 5) {
|
||||
logger.error("handleStaticFile:", err.toString());
|
||||
@@ -78,13 +83,16 @@ const staticFileRouteInjectable = getInjectable({
|
||||
|
||||
instantiate: (di): Route<Buffer> => {
|
||||
const isDevelopment = di.inject(isDevelopmentInjectable);
|
||||
const readFileBuffer = di.inject(readFileBufferInjectable);
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
|
||||
return {
|
||||
method: "get",
|
||||
path: `/{path*}`,
|
||||
handler: isDevelopment
|
||||
? handleStaticFileInDevelopment({ proxy: httpProxy.createProxy() })
|
||||
: handleStaticFileInProduction({ readFile: di.inject(readFileInjectable) }),
|
||||
: handleStaticFileInProduction({ readFileBuffer, getAbsolutePath, joinPaths }),
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -13,10 +13,10 @@ import type { WindowManager } from "../window-manager";
|
||||
import logger from "../logger";
|
||||
import { isDevelopment, isWindows, productName } from "../../common/vars";
|
||||
import { exitApp } from "../exit-app";
|
||||
import { preferencesURL } from "../../common/routes";
|
||||
import { toJS } from "../../common/utils";
|
||||
import type { TrayMenuRegistration } from "./tray-menu-registration";
|
||||
|
||||
|
||||
const TRAY_LOG_PREFIX = "[TRAY]";
|
||||
|
||||
// note: instance of Tray should be saved somewhere, otherwise it disappears
|
||||
@@ -33,6 +33,7 @@ export function getTrayIcon(): string {
|
||||
export function initTray(
|
||||
windowManager: WindowManager,
|
||||
trayMenuItems: IComputedValue<TrayMenuRegistration[]>,
|
||||
navigateToPreferences: () => void,
|
||||
) {
|
||||
const icon = getTrayIcon();
|
||||
|
||||
@@ -51,7 +52,7 @@ export function initTray(
|
||||
const disposers = [
|
||||
autorun(() => {
|
||||
try {
|
||||
const menu = createTrayMenu(windowManager, toJS(trayMenuItems.get()));
|
||||
const menu = createTrayMenu(windowManager, toJS(trayMenuItems.get()), navigateToPreferences);
|
||||
|
||||
tray.setContextMenu(menu);
|
||||
} catch (error) {
|
||||
@@ -80,6 +81,7 @@ function getMenuItemConstructorOptions(trayItem: TrayMenuRegistration): Electron
|
||||
function createTrayMenu(
|
||||
windowManager: WindowManager,
|
||||
extensionTrayItems: TrayMenuRegistration[],
|
||||
navigateToPreferences: () => void,
|
||||
): Menu {
|
||||
let template: Electron.MenuItemConstructorOptions[] = [
|
||||
{
|
||||
@@ -93,9 +95,7 @@ function createTrayMenu(
|
||||
{
|
||||
label: "Preferences",
|
||||
click() {
|
||||
windowManager
|
||||
.navigate(preferencesURL())
|
||||
.catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to navigate to Preferences`, { error }));
|
||||
navigateToPreferences();
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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 { WindowManager } from "./window-manager";
|
||||
|
||||
const windowManagerInjectable = getInjectable({
|
||||
id: "window-manager",
|
||||
|
||||
instantiate: () => {
|
||||
WindowManager.resetInstance();
|
||||
|
||||
return WindowManager.createInstance();
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default windowManagerInjectable;
|
||||
@@ -187,7 +187,7 @@ export class WindowManager extends Singleton {
|
||||
return this.mainWindow;
|
||||
}
|
||||
|
||||
private sendToView({ channel, frameInfo, data = [] }: SendToViewArgs) {
|
||||
sendToView({ channel, frameInfo, data = [] }: SendToViewArgs) {
|
||||
if (frameInfo) {
|
||||
this.mainWindow.webContents.sendToFrame([frameInfo.processId, frameInfo.frameId], channel, ...data);
|
||||
} else {
|
||||
@@ -210,6 +210,10 @@ export class WindowManager extends Singleton {
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user