Make base store non Singleton (#6690)

* Remove Singleton from BaseStore to remove global shared state

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove more usages of Singleton

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Replace use of legacy global execHelm with injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove last use of legacy global execHelm

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Extract BaseStore deps into constructor argument

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Introduce method to make store migrations injectable
- Use it for ClusterStore

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Switch HotbarStore to injectable migrations

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Switch UserStore to injectable migrations

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move migration utils into common/utils/

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Switch WeblinkStore to injectable migrations

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove dead code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix type error in base-store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove tests that reference lastSeenVersion
- That value is not used anywhere in code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usage of legacy global .getInstance

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usage of legacy global ClusterStore.getInstance

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add simple migrations dependency for stores without any preexisting migrations

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix messed up import

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add typing to transient injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Cleanup formatting

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix typing in tests to satisfy requirement to have cacheFile

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* More consistent use of BaseStore.displayName

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add catching of error while starting main application

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move initializing sentry to runnable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove unneeded appPathsInjectionToken
- Only had once impl, which was in common anyway

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add support for multiple "runAfter" runnables
- Needed so that several dependencies can be declared

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Use multiple runAfter support to fix crash on renderer

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove traces

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add global override to fix tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix base store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix runManyFor tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix hotbar store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix user store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add global override for getConfigurationFileModel to fix tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove overrides for configuration stores

- Now that there is an override for getConfiguration

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Overhaul FS fakes with full in-memory filesystem
- This increases our confidence in fs related logic

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove use of global shared Electron.App

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add fake access support

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Handle copy as part of fake FS

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add ensureDir/Sync support to fake FS

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix type error

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Use pathExistsSync instead of fsInjectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add createReadStream to fake FS

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add stat to fake FS

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove dead code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix test failures due to incomplete overrides

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fully injectable-ize BaseStore so that ApplicationBuilder tests work

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Consolidate more bootstrapping into startFrame

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move initializing CatalogCategories to runnable in bootstrap

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Convert contextMenuOpen initializers into runnables

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Convert navigateForExtension init to runnable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make cluster state sync fully injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move init hotbar store into runnables

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make LensTheme fully injectable and runnable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Cleanup old code from missed from previous commit

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Manually split out terminal color names and fully type LensTheme

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix old imports

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove unnecessart awaits

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove dead code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fully cherry pick injectablizing custom monaco themes

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix duplicate mock warning

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix incorrectly fully cherry picking new runnable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Complete cherry-pick of current cluster injcetablization

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix override file name

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix injecting before app paths are set up

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix injecting before app paths are set up

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix ordering of runnable and order of injection

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Convert all renderer runnables to late-inject style
- To help fix issues around injection time

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix react-beautiful-dnd mocks

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update and fix WriteJson(Sync) to fix error in tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix HotbarStore.load being called twice is being buggy

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update listing-active-helm-repositories-in-preferences snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix sidebar-and-tab-navigation-tests
- Move enabling extensions in tests to a proper location
- Fix flushing promises

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove props from dnd mock to make snapshot diffs smaller

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix import

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests by overriding things that are no longer overriden by default
- NOTE: They are overridden when using ApplicationBuilder

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix hotbar store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix cluster store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix extension-loader tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix extension-discovery tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix cluster-role-dialog tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix user store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix kubeconfig sync tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix sidebar and tab tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove unused code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix pick paths import type error and simplify signature

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix type error in legacy ipc registration

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove another use of legacy requestOpenPathPicker

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Replace use of legacy global PathPicker.Pick

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix usage in light of changed prop names

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix catalog tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix more type errors

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix test flakiness by removing side effects from userStore preferences

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix loading

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix type error

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix crash

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Cherry pick updated startFrameInjectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add tests to verify runMany behaviour in new possible incorrect configuration

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix init ordering during start frame

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix cluster state sync

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots after removing side-effects

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add override for technical test

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Correctly mark currentlyInClusterFrame as causedSideEffects

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Better formatting

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix behaviour regression

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add better logging

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix BaseStore sync

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update last snapshot

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add global override for randomBytes

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make startMainApplication not an injection time side effect

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Choose better names for start-frame runnable tokens

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove duplication of code in RunManyFor

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add unit tests and fix handling empty runAfter array

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Replace use of mobx from runManyFor with custom barrier

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add missing test

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton authored and GitHub committed 2022-12-15 17:07:19 +02:00
1 parent 7c5faaaf1d
commit 25f37ac1d1
364 files changed
+10035 -5246

No files matched your search

@@ -13,6 +13,7 @@ import type { CategoryColumns, GetCategoryColumnsParams } from "../columns/get.i
import getCategoryColumnsInjectable from "../columns/get.injectable";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
import extensionInjectable from "../../../../extensions/extension-loader/extension/extension.injectable";
import currentlyInClusterFrameInjectable from "../../../routes/currently-in-cluster-frame.injectable";
class TestCategory extends CatalogCategory {
apiVersion = "catalog.k8slens.dev/v1alpha1";
@@ -46,6 +47,8 @@ describe("Custom Category Columns", () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(hotbarStoreInjectable, () => ({}));
di.override(currentlyInClusterFrameInjectable, () => false);
getCategoryColumns = di.inject(getCategoryColumnsInjectable);
});
@@ -26,6 +26,10 @@ import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.in
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import { flushPromises } from "../../../common/test-utils/flush-promises";
import userStoreInjectable from "../../../common/user-store/user-store.injectable";
import releaseChannelInjectable from "../../../common/vars/release-channel.injectable";
import defaultUpdateChannelInjectable from "../../../features/application-update/common/selected-update-channel/default-update-channel.injectable";
import currentlyInClusterFrameInjectable from "../../routes/currently-in-cluster-frame.injectable";
class MockCatalogEntity extends CatalogEntity {
public apiVersion = "api";
@@ -61,11 +65,11 @@ describe("<Catalog />", () => {
let catalogEntityItem: MockCatalogEntity;
let render: DiRender;
beforeEach(() => {
beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.override(currentlyInClusterFrameInjectable, () => false);
di.override(broadcastMessageInjectable, () => async () => {});
di.permitSideEffects(getConfigurationFileModelInjectable);
@@ -82,6 +86,13 @@ describe("<Catalog />", () => {
Object.assign(catalogEntityStore, {
selectedItem: computed(() => catalogEntityItem),
});
di.override(releaseChannelInjectable, () => ({
get: () => "latest" as const,
init: async () => {},
}));
await di.inject(defaultUpdateChannelInjectable).init();
di.inject(userStoreInjectable).load();
});
describe("can use catalogEntityRegistry.addOnBeforeRun to add hooks for catalog entities", () => {
@@ -27,7 +27,7 @@ import type { PageParam } from "../../navigation";
import type { ToggleKubeDetailsPane } from "../kube-detail-params/toggle-details.injectable";
import kubeSelectedUrlParamInjectable from "../kube-detail-params/kube-selected-url.injectable";
import toggleKubeDetailsPaneInjectable from "../kube-detail-params/toggle-details.injectable";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import activeThemeInjectable from "../../themes/active.injectable";
export interface ClusterIssuesProps {
@@ -16,7 +16,7 @@ import type { PieChartData } from "../chart";
import { PieChart } from "../chart";
import { ClusterNoMetrics } from "./cluster-no-metrics";
import { bytesToUnits, cssNames } from "../../utils";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { getMetricLastPoints } from "../../../common/k8s-api/endpoints/metrics.api";
import { withInjectables } from "@ogre-tools/injectable-react";
import clusterOverviewStoreInjectable from "./cluster-overview-store/cluster-overview-store.injectable";
@@ -23,12 +23,13 @@ import installExtensionFromInputInjectable from "../install-extension-from-input
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import { observable, when } from "mobx";
import type { DeleteFile } from "../../../../common/fs/delete-file.injectable";
import deleteFileInjectable from "../../../../common/fs/delete-file.injectable";
import type { RemovePath } from "../../../../common/fs/remove.injectable";
import removePathInjectable from "../../../../common/fs/remove.injectable";
import type { DownloadJson } from "../../../../common/fetch/download-json.injectable";
import type { DownloadBinary } from "../../../../common/fetch/download-binary.injectable";
import downloadJsonInjectable from "../../../../common/fetch/download-json.injectable";
import downloadBinaryInjectable from "../../../../common/fetch/download-binary.injectable";
import currentlyInClusterFrameInjectable from "../../../routes/currently-in-cluster-frame.injectable";
describe("Extensions", () => {
let extensionLoader: ExtensionLoader;
@@ -36,7 +37,7 @@ describe("Extensions", () => {
let installExtensionFromInput: jest.MockedFunction<InstallExtensionFromInput>;
let extensionInstallationStateStore: ExtensionInstallationStateStore;
let render: DiRender;
let deleteFileMock: jest.MockedFunction<DeleteFile>;
let deleteFileMock: jest.MockedFunction<RemovePath>;
let downloadJson: jest.MockedFunction<DownloadJson>;
let downloadBinary: jest.MockedFunction<DownloadBinary>;
@@ -45,6 +46,7 @@ describe("Extensions", () => {
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.override(directoryForDownloadsInjectable, () => "some-directory-for-downloads");
di.override(currentlyInClusterFrameInjectable, () => false);
render = renderFor(di);
@@ -52,7 +54,7 @@ describe("Extensions", () => {
di.override(installExtensionFromInputInjectable, () => installExtensionFromInput);
deleteFileMock = jest.fn();
di.override(deleteFileInjectable, () => deleteFileMock);
di.override(removePathInjectable, () => deleteFileMock);
downloadJson = jest.fn().mockImplementation((url) => { throw new Error(`Unexpected call to downloadJson for url=${url}`); });
di.override(downloadJsonInjectable, () => downloadJson);
@@ -1,21 +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 { UserStore } from "../../../../common/user-store";
import { getBaseRegistryUrl } from "./get-base-registry-url";
const getBaseRegistryUrlInjectable = getInjectable({
id: "get-base-registry-url",
instantiate: () => getBaseRegistryUrl({
// TODO: use injection
getRegistryUrlPreference: () => UserStore.getInstance().extensionRegistryUrl,
}),
});
export default getBaseRegistryUrlInjectable;
@@ -0,0 +1,58 @@
/**
* 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 React from "react";
import execFileInjectable from "../../../../common/fs/exec-file.injectable";
import loggerInjectable from "../../../../common/logger.injectable";
import { defaultExtensionRegistryUrl } from "../../../../common/user-store/preferences-helpers";
import userStoreInjectable from "../../../../common/user-store/user-store.injectable";
import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
const getBaseRegistryUrlInjectable = getInjectable({
id: "get-base-registry-url",
instantiate: (di) => {
const { extensionRegistryUrl } = di.inject(userStoreInjectable);
const showErrorNotification = di.inject(showErrorNotificationInjectable);
const logger = di.inject(loggerInjectable);
const execFile = di.inject(execFileInjectable);
return async () => {
switch (extensionRegistryUrl.location) {
case "custom":
return extensionRegistryUrl.customUrl;
case "npmrc": {
const filteredEnv = Object.fromEntries(
Object.entries(process.env)
.filter(([key]) => !key.startsWith("npm")),
);
const result = await execFile("npm", ["config", "get", "registry"], { env: filteredEnv });
if (result.callWasSuccessful) {
return result.response.trim();
}
showErrorNotification((
<p>
Failed to get configured registry from
<code>.npmrc</code>
. Falling back to default registry.
</p>
));
logger.warn("[EXTENSIONS]: failed to get configured registry from .npmrc", result.error);
}
// fallthrough
default:
case "default":
return defaultExtensionRegistryUrl;
}
};
},
});
export default getBaseRegistryUrlInjectable;
@@ -1,49 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import type { ExtensionRegistry } from "../../../../common/user-store/preferences-helpers";
import { defaultExtensionRegistryUrl } from "../../../../common/user-store/preferences-helpers";
import { promiseExecFile } from "../../../utils";
import { Notifications } from "../../notifications";
interface Dependencies {
getRegistryUrlPreference: () => ExtensionRegistry;
}
export const getBaseRegistryUrl = ({ getRegistryUrlPreference }: Dependencies) => async () => {
const extensionRegistryUrl = getRegistryUrlPreference();
switch (extensionRegistryUrl.location) {
case "custom":
return extensionRegistryUrl.customUrl;
case "npmrc": {
try {
const filteredEnv = Object.fromEntries(
Object.entries(process.env)
.filter(([key]) => !key.startsWith("npm")),
);
const { stdout } = await promiseExecFile("npm", ["config", "get", "registry"], { env: filteredEnv });
return stdout.trim();
} catch (error) {
Notifications.error((
<p>
Failed to get configured registry from
<code>.npmrc</code>
. Falling back to default registry.
</p>
));
console.warn("[EXTENSIONS]: failed to get configured registry from .npmrc", error);
}
}
// fallthrough
default:
case "default":
return defaultExtensionRegistryUrl;
}
};
@@ -3,37 +3,30 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { requestOpenFilePickingDialog } from "../../ipc";
import { supportedExtensionFormats } from "./supported-extension-formats";
import attemptInstallsInjectable from "./attempt-installs.injectable";
import directoryForDownloadsInjectable from "../../../common/app-paths/directory-for-downloads/directory-for-downloads.injectable";
import openPathPickingDialogInjectable from "../../../features/path-picking-dialog/renderer/pick-paths.injectable";
interface Dependencies {
attemptInstalls: (filePaths: string[]) => Promise<void>;
directoryForDownloads: string;
}
const installFromSelectFileDialog = ({ attemptInstalls, directoryForDownloads }: Dependencies) => async () => {
const { canceled, filePaths } = await requestOpenFilePickingDialog({
defaultPath: directoryForDownloads,
properties: ["openFile", "multiSelections"],
message: `Select extensions to install (formats: ${supportedExtensionFormats.join(", ")}), `,
buttonLabel: "Use configuration",
filters: [{ name: "tarball", extensions: supportedExtensionFormats }],
});
if (!canceled) {
await attemptInstalls(filePaths);
}
};
export type InstallFromSelectFileDialog = () => Promise<void>;
const installFromSelectFileDialogInjectable = getInjectable({
id: "install-from-select-file-dialog",
instantiate: (di) => installFromSelectFileDialog({
attemptInstalls: di.inject(attemptInstallsInjectable),
directoryForDownloads: di.inject(directoryForDownloadsInjectable),
}),
instantiate: (di): InstallFromSelectFileDialog => {
const attemptInstalls = di.inject(attemptInstallsInjectable);
const directoryForDownloads = di.inject(directoryForDownloadsInjectable);
const openPathPickingDialog = di.inject(openPathPickingDialogInjectable);
return () => openPathPickingDialog({
defaultPath: directoryForDownloads,
properties: ["openFile", "multiSelections"],
message: `Select extensions to install (formats: ${supportedExtensionFormats.join(", ")}), `,
buttonLabel: "Use configuration",
filters: [{ name: "tarball", extensions: supportedExtensionFormats }],
onPick: attemptInstalls,
});
},
});
export default installFromSelectFileDialogInjectable;
@@ -8,7 +8,7 @@ import { runInAction, action, observable, computed } from "mobx";
import type { TargetHelmRelease } from "../target-helm-release.injectable";
import type { RequestDetailedHelmRelease, DetailedHelmRelease } from "./request-detailed-helm-release.injectable";
import requestDetailedHelmReleaseInjectable from "./request-detailed-helm-release.injectable";
import type { LensTheme } from "../../../../themes/store";
import type { LensTheme } from "../../../../themes/lens-theme";
import type { RequestHelmReleaseConfiguration } from "../../../../../common/k8s-api/endpoints/helm-releases.api/request-configuration.injectable";
import requestHelmReleaseConfigurationInjectable from "../../../../../common/k8s-api/endpoints/helm-releases.api/request-configuration.injectable";
import { pipeline } from "@ogre-tools/fp";
@@ -12,7 +12,7 @@ import { ResourceMetricsContext } from "../resource-metrics";
import { observer } from "mobx-react";
import { mapValues } from "lodash";
import { type MetricsTab, metricTabOptions } from "../chart/options";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import activeThemeInjectable from "../../themes/active.injectable";
@@ -10,7 +10,7 @@ import { BarChart, memoryOptions } from "../chart";
import { isMetricsEmpty, normalizeMetrics } from "../../../common/k8s-api/endpoints/metrics.api";
import { NoMetrics } from "../resource-metrics/no-metrics";
import { ResourceMetricsContext } from "../resource-metrics";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import activeThemeInjectable from "../../themes/active.injectable";
@@ -12,6 +12,7 @@ import type { DiRender } from "../../../test-utils/renderFor";
import { renderFor } from "../../../test-utils/renderFor";
import clusterRoleStoreInjectable from "../../+cluster-roles/store.injectable";
import storesAndApisCanBeCreatedInjectable from "../../../../stores-apis-can-be-created.injectable";
import directoryForUserDataInjectable from "../../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
describe("ClusterRoleBindingDialog tests", () => {
let render: DiRender;
@@ -20,6 +21,7 @@ describe("ClusterRoleBindingDialog tests", () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(storesAndApisCanBeCreatedInjectable, () => true);
di.override(directoryForUserDataInjectable, () => "/some-path-for-user-data");
render = renderFor(di);
@@ -15,6 +15,7 @@ import type { DiContainer } from "@ogre-tools/injectable";
import rendererExtensionsInjectable from "../../../../extensions/renderer-extensions.injectable";
import { LensRendererExtension } from "../../../../extensions/lens-renderer-extension";
import type { WelcomeBannerRegistration } from "../welcome-banner-items/welcome-banner-registration";
import currentlyInClusterFrameInjectable from "../../../routes/currently-in-cluster-frame.injectable";
jest.mock("electron", () => ({
ipcRenderer: {
@@ -32,6 +33,9 @@ describe("<Welcome/>", () => {
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(currentlyInClusterFrameInjectable, () => false);
render = renderFor(di);
welcomeBannersStub = [];
@@ -11,7 +11,7 @@ import { observer } from "mobx-react";
import type { PieChartData } from "../chart";
import { PieChart } from "../chart";
import { object } from "../../utils";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { PascalCase } from "type-fest";
import type { IComputedValue } from "mobx";
@@ -10,7 +10,7 @@ import { BarChart } from "../chart";
import { isMetricsEmpty, normalizeMetrics } from "../../../common/k8s-api/endpoints/metrics.api";
import { NoMetrics } from "../resource-metrics/no-metrics";
import { ResourceMetricsContext } from "../resource-metrics";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { mapValues } from "lodash";
import { type MetricsTab, metricTabOptions } from "../chart/options";
import { withInjectables } from "@ogre-tools/injectable-react";
-1
View File
@@ -8,7 +8,6 @@
@tailwind utilities;
@import "./flexbox";
@import "../themes/theme-vars";
@import "./fonts";
:root {
@@ -7,13 +7,15 @@ import React from "react";
import { observer } from "mobx-react";
import { Input } from "../input";
import { isUrl } from "../input/input_validators";
import { WeblinkStore } from "../../../common/weblink-store";
import type { WeblinkStore } from "../../../common/weblinks-store/weblink-store";
import { computed, makeObservable, observable } from "mobx";
import { withInjectables } from "@ogre-tools/injectable-react";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
import weblinkStoreInjectable from "../../../common/weblinks-store/weblink-store.injectable";
interface Dependencies {
closeCommandOverlay: () => void;
weblinkStore: WeblinkStore;
}
@@ -40,7 +42,7 @@ class NonInjectedWeblinkAddCommand extends React.Component<Dependencies> {
}
onSubmit(name: string) {
WeblinkStore.getInstance().add({
this.props.weblinkStore.add({
name: name || this.url,
url: this.url,
});
@@ -64,7 +66,7 @@ class NonInjectedWeblinkAddCommand extends React.Component<Dependencies> {
value={this.url}
onChange={(v) => this.onChangeUrl(v)}
onSubmit={(v) => this.onSubmitUrl(v)}
showValidationLine={true}
showValidationLine={true}
/>
{ this.nameHidden && (
<small className="hint">
@@ -93,7 +95,8 @@ class NonInjectedWeblinkAddCommand extends React.Component<Dependencies> {
export const WeblinkAddCommand = withInjectables<Dependencies>(NonInjectedWeblinkAddCommand, {
getProps: (di, props) => ({
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
...props,
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
weblinkStore: di.inject(weblinkStoreInjectable),
}),
});
+1 -1
View File
@@ -13,7 +13,7 @@ import type { ChartProps } from "./chart";
import { Chart, ChartKind } from "./chart";
import { bytesToUnits, cssNames, isObject } from "../../utils";
import { ZebraStripesPlugin } from "./zebra-stripes.plugin";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { NoMetrics } from "../resource-metrics/no-metrics";
import assert from "assert";
import { withInjectables } from "@ogre-tools/injectable-react";
+1 -1
View File
@@ -11,7 +11,7 @@ import ChartJS from "chart.js";
import type { ChartProps } from "./chart";
import { Chart } from "./chart";
import { cssNames } from "../../utils";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import activeThemeInjectable from "../../themes/active.injectable";
@@ -37,11 +37,18 @@ export class ClusterFrameHandler {
@action
public initView(clusterId: ClusterId) {
const cluster = this.dependencies.getClusterById(clusterId);
if (!cluster) {
this.dependencies.logger.warn(`[LENS-VIEW]: not initializing view; unknown clusterId="${clusterId}"`);
return;
}
const parentElem = document.getElementById("lens-views");
assert(parentElem, "DOM with #lens-views must be present");
if (!cluster || this.views.has(clusterId)) {
if (this.views.has(clusterId)) {
return;
}
@@ -11,7 +11,6 @@ import { disposeOnUnmount, observer } from "mobx-react";
import { ClusterStatus } from "./cluster-status";
import type { ClusterFrameHandler } from "./cluster-frame-handler";
import type { Cluster } from "../../../common/cluster/cluster";
import { ClusterStore } from "../../../common/cluster-store/cluster-store";
import { requestClusterActivation } from "../../ipc";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { NavigateToCatalog } from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
@@ -20,18 +19,19 @@ import clusterViewRouteParametersInjectable from "./cluster-view-route-parameter
import clusterFrameHandlerInjectable from "./cluster-frame-handler.injectable";
import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
import type { GetClusterById } from "../../../common/cluster-store/get-by-id.injectable";
import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable";
interface Dependencies {
clusterId: IComputedValue<string>;
clusterFrames: ClusterFrameHandler;
navigateToCatalog: NavigateToCatalog;
entityRegistry: CatalogEntityRegistry;
getClusterById: GetClusterById;
}
@observer
class NonInjectedClusterView extends React.Component<Dependencies> {
private readonly store = ClusterStore.getInstance();
constructor(props: Dependencies) {
super(props);
makeObservable(this);
@@ -42,7 +42,7 @@ class NonInjectedClusterView extends React.Component<Dependencies> {
}
@computed get cluster(): Cluster | undefined {
return this.store.getById(this.clusterId);
return this.props.getClusterById(this.clusterId);
}
private readonly isViewLoaded = computed(() => this.props.clusterFrames.hasLoadedView(this.clusterId), {
@@ -110,10 +110,11 @@ class NonInjectedClusterView extends React.Component<Dependencies> {
export const ClusterView = withInjectables<Dependencies>(NonInjectedClusterView, {
getProps: (di) => ({
clusterId: di.inject(clusterViewRouteParametersInjectable).clusterId,
...di.inject(clusterViewRouteParametersInjectable),
navigateToCatalog: di.inject(navigateToCatalogInjectable),
clusterFrames: di.inject(clusterFrameHandlerInjectable),
entityRegistry: di.inject(catalogEntityRegistryInjectable),
getClusterById: di.inject(getClusterByIdInjectable),
}),
});
@@ -13,7 +13,7 @@ import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import type { DiRender } from "../../test-utils/renderFor";
import { renderFor } from "../../test-utils/renderFor";
import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
import statInjectable from "../../../../common/fs/stat/stat.injectable";
import statInjectable from "../../../../common/fs/stat.injectable";
describe("ClusterLocalTerminalSettings", () => {
let render: DiRender;
@@ -10,7 +10,6 @@ import { Input } from "../input";
import { SubTitle } from "../layout/sub-title";
import type { ShowNotification } from "../notifications";
import { Icon } from "../icon";
import { PathPicker } from "../path-picker";
import { withInjectables } from "@ogre-tools/injectable-react";
import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable";
import type { ValidateDirectory } from "../../../common/fs/validate-directory.injectable";
@@ -19,6 +18,8 @@ import type { ResolveTilde } from "../../../common/path/resolve-tilde.injectable
import resolveTildeInjectable from "../../../common/path/resolve-tilde.injectable";
import Gutter from "../gutter/gutter";
import isWindowsInjectable from "../../../common/vars/is-windows.injectable";
import type { OpenPathPickingDialog } from "../../../features/path-picking-dialog/renderer/pick-paths.injectable";
import openPathPickingDialogInjectable from "../../../features/path-picking-dialog/renderer/pick-paths.injectable";
export interface ClusterLocalTerminalSettingProps {
cluster: Cluster;
@@ -27,6 +28,7 @@ interface Dependencies {
showErrorNotification: ShowNotification;
validateDirectory: ValidateDirectory;
resolveTilde: ResolveTilde;
openPathPickingDialog: OpenPathPickingDialog;
isWindows: boolean;
}
@@ -36,6 +38,7 @@ const NonInjectedClusterLocalTerminalSetting = observer(({
validateDirectory,
resolveTilde,
isWindows,
openPathPickingDialog,
}: Dependencies & ClusterLocalTerminalSettingProps) => {
if (!cluster) {
return null;
@@ -95,8 +98,8 @@ const NonInjectedClusterLocalTerminalSetting = observer(({
};
const openFilePicker = () => {
PathPicker.pick({
label: "Choose Working Directory",
openPathPickingDialog({
message: "Choose Working Directory",
buttonLabel: "Pick",
properties: ["openDirectory", "showHiddenFiles"],
onPick: ([directory]) => setAndCommitDirectory(directory),
@@ -168,5 +171,6 @@ export const ClusterLocalTerminalSetting = withInjectables<Dependencies, Cluster
validateDirectory: di.inject(validateDirectoryInjectable),
resolveTilde: di.inject(resolveTildeInjectable),
isWindows: di.inject(isWindowsInjectable),
openPathPickingDialog: di.inject(openPathPickingDialogInjectable),
}),
});
+17 -4
View File
@@ -15,13 +15,15 @@ import { disposeOnUnmount, observer } from "mobx-react";
import moment from "moment-timezone";
import type { Align, ListOnScrollProps } from "react-window";
import { SearchStore } from "../../../search-store/search-store";
import { UserStore } from "../../../../common/user-store";
import type { UserStore } from "../../../../common/user-store";
import { array, autoBind, cssNames } from "../../../utils";
import type { VirtualListRef } from "../../virtual-list";
import { VirtualList } from "../../virtual-list";
import { ToBottom } from "./to-bottom";
import type { LogTabViewModel } from "../logs/logs-view-model";
import { Spinner } from "../../spinner";
import { withInjectables } from "@ogre-tools/injectable-react";
import userStoreInjectable from "../../../../common/user-store/user-store.injectable";
export interface LogListProps {
model: LogTabViewModel;
@@ -33,8 +35,12 @@ export interface LogListRef {
scrollToItem: (index: number, align: Align) => void;
}
interface Dependencies {
userStore: UserStore;
}
@observer
class NonForwardedLogList extends React.Component<LogListProps & { innerRef: ForwardedRef<LogListRef> }> {
class NonForwardedLogList extends React.Component<Dependencies & LogListProps & { innerRef: ForwardedRef<LogListRef> }> {
@observable isJumpButtonVisible = false;
@observable isLastLineVisible = true;
@@ -122,7 +128,7 @@ class NonForwardedLogList extends React.Component<LogListProps & { innerRef: For
return this.props.model.timestampSplitLogs
.get()
.map(([logTimestamp, log]) => (`${logTimestamp && moment.tz(logTimestamp, UserStore.getInstance().localeTimezone).format()}${log}`));
.map(([logTimestamp, log]) => (`${logTimestamp && moment.tz(logTimestamp, this.props.userStore.localeTimezone).format()}${log}`));
}
/**
@@ -273,6 +279,13 @@ class NonForwardedLogList extends React.Component<LogListProps & { innerRef: For
}
}
const InjectedNonForwardedLogList = withInjectables<Dependencies, LogListProps & { innerRef: ForwardedRef<LogListRef> }>(NonForwardedLogList, {
getProps: (di, props) => ({
...props,
userStore: di.inject(userStoreInjectable),
}),
});
export const LogList = React.forwardRef<LogListRef, LogListProps>((props, ref) => (
<NonForwardedLogList {...props} innerRef={ref} />
<InjectedNonForwardedLogList {...props} innerRef={ref} />
));
@@ -10,9 +10,9 @@ import type { TerminalApi } from "../../../api/terminal-api";
import terminalSpawningPoolInjectable from "./terminal-spawning-pool.injectable";
import terminalConfigInjectable from "../../../../common/user-store/terminal-config.injectable";
import terminalCopyOnSelectInjectable from "../../../../common/user-store/terminal-copy-on-select.injectable";
import themeStoreInjectable from "../../../themes/store.injectable";
import isMacInjectable from "../../../../common/vars/is-mac.injectable";
import openLinkInBrowserInjectable from "../../../../common/utils/open-link-in-browser.injectable";
import xtermColorThemeInjectable from "../../../themes/terminal-colors.injectable";
export type CreateTerminal = (tabId: TabId, api: TerminalApi) => Terminal;
@@ -23,9 +23,9 @@ const createTerminalInjectable = getInjectable({
spawningPool: di.inject(terminalSpawningPoolInjectable),
terminalConfig: di.inject(terminalConfigInjectable),
terminalCopyOnSelect: di.inject(terminalCopyOnSelectInjectable),
themeStore: di.inject(themeStoreInjectable),
isMac: di.inject(isMacInjectable),
openLinkInBrowser: di.inject(openLinkInBrowserInjectable),
xtermColorTheme: di.inject(xtermColorThemeInjectable),
};
return (tabId, api) => new Terminal(dependencies, { tabId, api });
@@ -10,7 +10,6 @@ import { Terminal as XTerm } from "xterm";
import { FitAddon } from "xterm-addon-fit";
import type { TabId } from "../dock/store";
import type { TerminalApi } from "../../../api/terminal-api";
import type { ThemeStore } from "../../../themes/store";
import { disposer } from "../../../utils";
import { once } from "lodash";
import { clipboard } from "electron";
@@ -25,8 +24,8 @@ export interface TerminalDependencies {
readonly spawningPool: HTMLElement;
readonly terminalConfig: IComputedValue<TerminalConfig>;
readonly terminalCopyOnSelect: IComputedValue<boolean>;
readonly themeStore: ThemeStore;
readonly isMac: boolean;
readonly xtermColorTheme: IComputedValue<Record<string, string>>;
openLinkInBrowser: OpenLinkInBrowser;
}
@@ -75,10 +74,6 @@ export class Terminal {
return this.dependencies.terminalConfig.get().fontSize;
}
get theme(): Record<string/*paramName*/, string/*color*/> {
return this.dependencies.themeStore.xtermColors;
}
constructor(protected readonly dependencies: TerminalDependencies, {
tabId,
api,
@@ -120,9 +115,12 @@ export class Terminal {
this.disposer.push(
this.xterm.registerLinkProvider(linkProvider),
reaction(() => this.theme, colors => this.xterm.setOption("theme", colors), {
fireImmediately: true,
}),
reaction(() => this.dependencies.xtermColorTheme.get(),
colors => this.xterm.options.theme = colors,
{
fireImmediately: true,
},
),
reaction(() => this.fontSize, this.setFontSize, { fireImmediately: true }),
reaction(() => this.fontFamily, this.setFontFamily, { fireImmediately: true }),
() => onDataHandler.dispose(),
@@ -10,7 +10,7 @@ import { disposeOnUnmount, observer } from "mobx-react";
import { cssNames } from "../../../utils";
import type { Terminal } from "./terminal";
import type { TerminalStore } from "./store";
import type { LensTheme } from "../../../themes/store";
import type { LensTheme } from "../../../themes/lens-theme";
import type { DockTab, DockStore } from "../dock/store";
import { withInjectables } from "@ogre-tools/injectable-react";
import dockStoreInjectable from "../dock/store.injectable";
@@ -21,7 +21,7 @@ import { NoItems } from "../no-items";
import { Spinner } from "../spinner";
import type { ItemObject } from "../../../common/item.store";
import type { Filter, PageFiltersStore } from "./page-filters/store";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { MenuActions } from "../menu/menu-actions";
import { MenuItem } from "../menu";
import { Checkbox } from "../checkbox";
@@ -4,30 +4,28 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import { action } from "mobx";
import { beforeFrameStartsInjectionToken } from "../../../before-frame-starts/before-frame-starts-injection-token";
import { beforeFrameStartsInjectionToken } from "../../../before-frame-starts/tokens";
import ipcRendererInjectable from "../../../utils/channel/ipc-renderer.injectable";
import topBarStateInjectable from "./state.injectable";
// TODO: replace with a SyncBox
const startTopbarStateSyncInjectable = getInjectable({
id: "start-topbar-state-sync",
instantiate: (di) => {
const state = di.inject(topBarStateInjectable);
const ipcRenderer = di.inject(ipcRendererInjectable);
instantiate: (di) => ({
id: "start-topbar-state-sync",
run: () => {
const state = di.inject(topBarStateInjectable);
const ipcRenderer = di.inject(ipcRendererInjectable);
return {
id: "start-topbar-state-sync",
run: () => {
ipcRenderer.on("history:can-go-back", action((event, canGoBack: boolean) => {
state.prevEnabled = canGoBack;
}));
ipcRenderer.on("history:can-go-back", action((event, canGoBack: boolean) => {
state.prevEnabled = canGoBack;
}));
ipcRenderer.on("history:can-go-forward", action((event, canGoForward: boolean) => {
state.nextEnabled = canGoForward;
}));
},
};
},
ipcRenderer.on("history:can-go-forward", action((event, canGoForward: boolean) => {
state.nextEnabled = canGoForward;
}));
},
}),
injectionToken: beforeFrameStartsInjectionToken,
causesSideEffects: true,
});
@@ -21,6 +21,7 @@ import toggleMaximizeWindowInjectable from "./toggle-maximize-window/toggle-maxi
import topBarStateInjectable from "./state.injectable";
import platformInjectable from "../../../../common/vars/platform.injectable";
import goForwardInjectable from "./top-bar-items/navigation-to-forward/go-forward/go-forward.injectable";
import currentlyInClusterFrameInjectable from "../../../routes/currently-in-cluster-frame.injectable";
describe("<TopBar/>", () => {
let di: DiContainer;
@@ -42,6 +43,7 @@ describe("<TopBar/>", () => {
di.override(closeWindowInjectable, () => closeWindow = jest.fn());
di.override(maximizeWindowInjectable, () => maximizeWindow = jest.fn());
di.override(toggleMaximizeWindowInjectable, () => toggleMaximizeWindow = jest.fn());
di.override(currentlyInClusterFrameInjectable, () => false);
render = renderFor(di);
});
@@ -6,17 +6,27 @@
import React from "react";
import { observer } from "mobx-react";
import moment from "moment-timezone";
import { UserStore } from "../../../common/user-store";
import type { UserStore } from "../../../common/user-store";
import { withInjectables } from "@ogre-tools/injectable-react";
import userStoreInjectable from "../../../common/user-store/user-store.injectable";
export interface LocaleDateProps {
date: string;
}
@observer
export class LocaleDate extends React.Component<LocaleDateProps> {
render() {
const { date } = this.props;
return moment.tz(date, UserStore.getInstance().localeTimezone).format();
}
interface Dependencies {
userStore: UserStore;
}
const NonInjectedLocaleDate = observer(({ date, userStore }: LocaleDateProps & Dependencies) => (
<>
{`${moment.tz(date, userStore.localeTimezone).format()}`}
</>
));
export const LocaleDate = withInjectables<Dependencies, LocaleDateProps>(NonInjectedLocaleDate, {
getProps: (di, props) => ({
...props,
userStore: di.inject(userStoreInjectable),
}),
});
@@ -14,7 +14,7 @@ import { type MonacoValidator, monacoValidators } from "./monaco-validators";
import { debounce, merge } from "lodash";
import { autoBind, cssNames, disposer } from "../../utils";
import type { UserStore } from "../../../common/user-store";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { withInjectables } from "@ogre-tools/injectable-react";
import userStoreInjectable from "../../../common/user-store/user-store.injectable";
import activeThemeInjectable from "../../themes/active.injectable";
@@ -4,27 +4,16 @@
*/
// Monaco editor themes customization
import { editor } from "monaco-editor";
import cloudsMidnight from "./monaco-themes/clouds-midnight.json";
import { getInjectionToken } from "@ogre-tools/injectable";
import type { editor } from "monaco-editor";
export type MonacoTheme = "vs" | "vs-dark" | "hc-black" | MonacoCustomTheme;
export type MonacoCustomTheme = "clouds-midnight";
export interface MonacoThemeData extends editor.IStandaloneThemeData {
name?: string;
name: string;
}
// Registered names could be then used in "themes/*.json" in "{monacoTheme: [name]}"
export const customThemes: Partial<Record<MonacoCustomTheme, MonacoThemeData>> = {
"clouds-midnight": cloudsMidnight as MonacoThemeData,
};
export function registerCustomThemes(): void {
Object.entries(customThemes).forEach(([name, theme]) => {
editor.defineTheme(name, theme);
});
}
export async function loadCustomTheme(name: string): Promise<MonacoThemeData> {
return import(`./monaco-themes/${name}.json`);
}
export const customMonacoThemeInjectionToken = getInjectionToken<MonacoThemeData>({
id: "custom-monaco-theme-token",
});
@@ -0,0 +1,143 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { customMonacoThemeInjectionToken } from "../monaco-themes";
import { getInjectable } from "@ogre-tools/injectable";
const cloudsMidnightThemeInjectable = getInjectable({
id: "clouds-midnight-theme",
instantiate: () => ({
"name": "clouds-midnight",
"base": "vs-dark" as const,
"inherit": true,
"rules": [
{
"background": "191919",
"token": "",
},
{
"foreground": "3c403b",
"token": "comment",
},
{
"foreground": "5d90cd",
"token": "string",
},
{
"foreground": "46a609",
"token": "constant.numeric",
},
{
"foreground": "39946a",
"token": "constant.language",
},
{
"foreground": "927c5d",
"token": "keyword",
},
{
"foreground": "927c5d",
"token": "support.constant.property-value",
},
{
"foreground": "927c5d",
"token": "constant.other.color",
},
{
"foreground": "366f1a",
"token": "keyword.other.unit",
},
{
"foreground": "a46763",
"token": "entity.other.attribute-name.html",
},
{
"foreground": "4b4b4b",
"token": "keyword.operator",
},
{
"foreground": "e92e2e",
"token": "storage",
},
{
"foreground": "858585",
"token": "entity.other.inherited-class",
},
{
"foreground": "606060",
"token": "entity.name.tag",
},
{
"foreground": "a165ac",
"token": "constant.character.entity",
},
{
"foreground": "a165ac",
"token": "support.class.js",
},
{
"foreground": "606060",
"token": "entity.other.attribute-name",
},
{
"foreground": "e92e2e",
"token": "meta.selector.css",
},
{
"foreground": "e92e2e",
"token": "entity.name.tag.css",
},
{
"foreground": "e92e2e",
"token": "entity.other.attribute-name.id.css",
},
{
"foreground": "e92e2e",
"token": "entity.other.attribute-name.class.css",
},
{
"foreground": "616161",
"token": "meta.property-name.css",
},
{
"foreground": "e92e2e",
"token": "support.function",
},
{
"foreground": "ffffff",
"background": "e92e2e",
"token": "invalid",
},
{
"foreground": "e92e2e",
"token": "punctuation.section.embedded",
},
{
"foreground": "606060",
"token": "punctuation.definition.tag",
},
{
"foreground": "a165ac",
"token": "constant.other.color.rgb-value.css",
},
{
"foreground": "a165ac",
"token": "support.constant.property-value.css",
},
],
"colors": {
"editor.foreground": "#929292",
"editor.background": "#191919",
"editor.selectionBackground": "#000000",
"editor.lineHighlightBackground": "#D7D7D708",
"editorCursor.foreground": "#7DA5DC",
"editorWhitespace.foreground": "#BFBFBF",
},
}),
injectionToken: customMonacoThemeInjectionToken,
});
export default cloudsMidnightThemeInjectable;
@@ -3,17 +3,19 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { withInjectables } from "@ogre-tools/injectable-react";
import type { FileFilter, OpenDialogOptions } from "electron";
import { observer } from "mobx-react";
import React from "react";
import type { OpenPathPickingDialog } from "../../../features/path-picking-dialog/renderer/pick-paths.injectable";
import openPathPickingDialogInjectable from "../../../features/path-picking-dialog/renderer/pick-paths.injectable";
import { cssNames } from "../../utils";
import { Button } from "../button";
import { requestOpenFilePickingDialog } from "../../ipc";
export interface PathPickOpts {
label: string;
onPick?: (paths: string[]) => any;
onCancel?: () => any;
message: string;
onPick?: (paths: string[]) => void | Promise<void>;
onCancel?: () => void | Promise<void>;
defaultPath?: string;
buttonLabel?: string;
filters?: FileFilter[];
@@ -26,40 +28,32 @@ export interface PathPickerProps extends PathPickOpts {
disabled?: boolean;
}
@observer
export class PathPicker extends React.Component<PathPickerProps> {
static async pick(opts: PathPickOpts) {
const { onPick, onCancel, label, ...dialogOptions } = opts;
const { canceled, filePaths } = await requestOpenFilePickingDialog({
message: label,
...dialogOptions,
});
if (canceled) {
await onCancel?.();
} else {
await onPick?.(filePaths);
}
}
async onClick() {
const { className, disabled, ...pickOpts } = this.props;
return PathPicker.pick(pickOpts);
}
render() {
const { className, label, disabled } = this.props;
return (
<Button
primary
label={label}
disabled={disabled}
className={cssNames("PathPicker", className)}
onClick={() => void this.onClick()}
/>
);
}
interface Dependencies {
openPathPickingDialog: OpenPathPickingDialog;
}
const NonInjectedPathPicker = observer((props: PathPickerProps & Dependencies) => {
const {
className,
disabled,
openPathPickingDialog,
...pickOpts
} = props;
return (
<Button
primary
label={pickOpts.message}
disabled={disabled}
className={cssNames("PathPicker", className)}
onClick={() => void openPathPickingDialog(pickOpts)}
/>
);
});
export const PathPicker = withInjectables<Dependencies, PathPickerProps>(NonInjectedPathPicker, {
getProps: (di, props) => ({
...props,
openPathPickingDialog: di.inject(openPathPickingDialogInjectable),
}),
});
+1 -1
View File
@@ -13,7 +13,7 @@ import { action, computed, makeObservable } from "mobx";
import { observer } from "mobx-react";
import ReactSelect, { components, createFilter } from "react-select";
import type { Props as ReactSelectProps, GroupBase, MultiValue, OptionsOrGroups, PropsValue, SingleValue } from "react-select";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { autoBind, cssNames } from "../../utils";
import { withInjectables } from "@ogre-tools/injectable-react";
import activeThemeInjectable from "../../themes/active.injectable";
@@ -63,12 +63,15 @@ import type { FakeExtensionOptions } from "./get-extension-fake";
import { getExtensionFakeForMain, getExtensionFakeForRenderer } from "./get-extension-fake";
import namespaceApiInjectable from "../../../common/k8s-api/endpoints/namespace.api.injectable";
import { Namespace } from "../../../common/k8s-api/endpoints";
import { overrideFsWithFakes } from "../../../test-utils/override-fs-with-fakes";
import { getOverrideFsWithFakes } from "../../../test-utils/override-fs-with-fakes";
import applicationMenuItemCompositeInjectable from "../../../features/application-menu/main/application-menu-item-composite.injectable";
import { getCompositePaths } from "../../../common/utils/composite/get-composite-paths/get-composite-paths";
import { discoverFor } from "./discovery-of-html-elements";
import { findComposite } from "../../../common/utils/composite/find-composite/find-composite";
import shouldStartHiddenInjectable from "../../../main/electron-app/features/should-start-hidden.injectable";
import fsInjectable from "../../../common/fs/fs.injectable";
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
import homeDirectoryPathInjectable from "../../../common/os/home-directory-path.injectable";
type Callback = (di: DiContainer) => void | Promise<void>;
@@ -114,6 +117,7 @@ export interface ApplicationBuilder {
allowKubeResource: (resourceName: KubeResource) => ApplicationBuilder;
beforeApplicationStart: (callback: Callback) => ApplicationBuilder;
afterApplicationStart: (callback: Callback) => ApplicationBuilder;
beforeWindowStart: (callback: Callback) => ApplicationBuilder;
afterWindowStart: (callback: Callback) => ApplicationBuilder;
@@ -170,12 +174,22 @@ export const getApplicationBuilder = () => {
const { overrideForWindow, sendToWindow } = overrideChannels(mainDi);
const beforeApplicationStartCallbacks: Callback[] = [];
const afterApplicationStartCallbacks: Callback[] = [];
const beforeWindowStartCallbacks: Callback[] = [];
const afterWindowStartCallbacks: Callback[] = [];
const fsState = new Map();
const overrideFsWithFakes = getOverrideFsWithFakes();
overrideFsWithFakes(mainDi, fsState);
overrideFsWithFakes(mainDi);
// Set up ~/.kube as existing as a folder
{
const { ensureDirSync } = mainDi.inject(fsInjectable);
const joinPaths = mainDi.inject(joinPathsInjectable);
const homeDirectoryPath = mainDi.inject(homeDirectoryPathInjectable);
ensureDirSync(joinPaths(homeDirectoryPath, ".kube"));
}
let environment = environments.application;
@@ -210,7 +224,7 @@ export const getApplicationBuilder = () => {
const windowDi = getRendererDi({ doGeneralOverrides: true });
overrideForWindow(windowDi, windowId);
overrideFsWithFakes(windowDi, fsState);
overrideFsWithFakes(windowDi);
runInAction(() => {
windowDi.register(rendererExtensionsStateInjectable);
@@ -276,6 +290,24 @@ export const getApplicationBuilder = () => {
const namespaces = observable.set<string>();
const namespaceItems = observable.array<Namespace>();
const selectedNamespaces = observable.set<string>();
const startMainApplication = mainDi.inject(startMainApplicationInjectable);
const startApplication = async ({ shouldStartHidden }: { shouldStartHidden: boolean }) => {
mainDi.inject(lensProxyPortInjectable).set(42);
for (const callback of beforeApplicationStartCallbacks) {
await callback(mainDi);
}
mainDi.override(shouldStartHiddenInjectable, () => shouldStartHidden);
await startMainApplication();
for (const callback of afterApplicationStartCallbacks) {
await callback(mainDi);
}
applicationHasStarted = true;
};
const builder: ApplicationBuilder = {
mainDi,
@@ -556,7 +588,7 @@ export const getApplicationBuilder = () => {
},
enable: (...extensions) => {
builder.beforeWindowStart((windowDi) => {
builder.afterWindowStart((windowDi) => {
const rendererExtensionInstances = extensions.map((options) =>
getExtensionFakeForRenderer(
windowDi,
@@ -571,7 +603,7 @@ export const getApplicationBuilder = () => {
);
});
builder.beforeApplicationStart((mainDi) => {
builder.afterApplicationStart((mainDi) => {
const mainExtensionInstances = extensions.map((extension) =>
getExtensionFakeForMain(mainDi, extension.id, extension.name, extension.mainOptions || {}),
);
@@ -585,7 +617,7 @@ export const getApplicationBuilder = () => {
},
disable: (...extensions) => {
builder.beforeWindowStart(windowDi => {
builder.afterWindowStart(windowDi => {
extensions
.map((ext) => ext.id)
.forEach(
@@ -593,7 +625,7 @@ export const getApplicationBuilder = () => {
);
});
builder.beforeApplicationStart(mainDi => {
builder.afterApplicationStart(mainDi => {
extensions
.map((ext) => ext.id)
.forEach(
@@ -623,6 +655,16 @@ export const getApplicationBuilder = () => {
return builder;
},
afterApplicationStart(callback) {
if (applicationHasStarted) {
callback(mainDi);
}
afterApplicationStartCallbacks.push(callback);
return builder;
},
beforeWindowStart(callback) {
const alreadyRenderedWindows = builder.applicationWindow.getAll();
@@ -648,29 +690,11 @@ export const getApplicationBuilder = () => {
},
startHidden: async () => {
mainDi.inject(lensProxyPortInjectable).set(42);
for (const callback of beforeApplicationStartCallbacks) {
await callback(mainDi);
}
mainDi.override(shouldStartHiddenInjectable, () => true);
await mainDi.inject(startMainApplicationInjectable);
applicationHasStarted = true;
await startApplication({ shouldStartHidden: true });
},
async render() {
mainDi.inject(lensProxyPortInjectable).set(42);
for (const callback of beforeApplicationStartCallbacks) {
await callback(mainDi);
}
mainDi.override(shouldStartHiddenInjectable, () => false);
await mainDi.inject(startMainApplicationInjectable);
applicationHasStarted = true;
await startApplication({ shouldStartHidden: false });
return builder
.applicationWindow