Remove BaseStore<T> and replace with composition of dependencies (#7002)

* Rename ExtensionStore -> BaseExtensionStore
- The name was too close to ExtensionsStore

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

* Move ExtensionsStore to new format

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

* Move ClusterStore to new format

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

* Move UserStore to new format

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

* Cleanup types to remove multiple cast locations

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

* Move HotbarStore to new format

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

* Move WeblinkStore to new format

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

* Move FileSystemProvisionerStore to new format

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

* Update snapshots

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

* Clean up impl and rename to better describe intent

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

* Fix remaining type errors

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

* Fully split apart the enabled extensions storage

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

* Fully split apart the clusters storage

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

* Fully split apart the hotbar storage

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

* Fully split apart the weblinks storage

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

* Fully split apart the user preferences storage

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

* Fix crashing

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

* Fix tests and snapshots

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

* Fix integration test failures

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

* Improve typing to prevent errors in the future.

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

* Cleanup @k8slens/messaging and friends

- To fix type errors

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

* Fix lint issue

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

* Fix type errors

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

* Fix global override not being complete enough causing tests to fail

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

* Bump memory for unit tests on CI

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

* Attempt to fix memory issue on CI again

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

* Fixup test because of new injectables

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

* Upgrade Jest

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

* Fix unit tests falling over

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

* Back out jest config change

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

* Remove console log

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

* Update snapshot

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

* Fix tests by matching equality instead of snapshots

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

* Fix tests by forcing specific snapshot style

- Ubuntu CI seems to format arrays in snapshots differently than macOS locally

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 2023-03-28 17:54:25 +03:00
1 parent ba4a283af9
commit 49f0a1af9c
365 files changed
+6733 -3972

No files matched your search

@@ -230,7 +230,7 @@ exports[`extension special characters in page registrations renders 1`] = `
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -434,7 +434,7 @@ exports[`extension special characters in page registrations when navigating to r
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -230,7 +230,7 @@ exports[`navigate to extension page renders 1`] = `
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -434,7 +434,7 @@ exports[`navigate to extension page when extension navigates to child route rend
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -654,7 +654,7 @@ exports[`navigate to extension page when extension navigates to route with param
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -874,7 +874,7 @@ exports[`navigate to extension page when extension navigates to route without pa
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1094,7 +1094,7 @@ exports[`navigate to extension page when extension navigates to route without pa
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -154,7 +154,7 @@ exports[`navigating between routes given route with optional path parameters whe
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -358,7 +358,7 @@ exports[`navigating between routes given route without path parameters when navi
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -230,7 +230,7 @@ exports[`add-cluster - navigation using application menu renders 1`] = `
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -518,7 +518,7 @@ exports[`add-cluster - navigation using application menu when navigating to add
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1,132 +1,132 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`application-menu, given platform is 'darwin' given enough time passes populates application menu 1`] = `
Array [
"root",
"root -> mac",
"root -> mac -> about",
"root -> mac -> separator-1",
"root -> mac -> navigate-to-preferences",
"root -> mac -> navigate-to-extensions",
"root -> mac -> separator-2",
"root -> mac -> services",
"root -> mac -> separator-3",
"root -> mac -> hide",
"root -> mac -> hide-others",
"root -> mac -> unhide",
"root -> mac -> separator-4",
"root -> mac -> quit",
"root -> file",
"root -> file -> add-cluster",
"root -> file -> separator-1-for-file",
"root -> file -> close-window",
"root -> edit",
"root -> edit -> undo",
"root -> edit -> redo",
"root -> edit -> separator-1-in-edit",
"root -> edit -> cut",
"root -> edit -> copy",
"root -> edit -> paste",
"root -> edit -> delete",
"root -> edit -> separator-2-in-edit",
"root -> edit -> selectAll",
"root -> view",
"root -> view -> navigate-to-catalog",
"root -> view -> open-command-palette",
"root -> view -> separator-1-for-view",
"root -> view -> go-back",
"root -> view -> go-forward",
"root -> view -> reload",
"root -> view -> toggle-dev-tools",
"root -> view -> separator-2-for-view",
"root -> view -> reset-zoom",
"root -> view -> zoom-in",
"root -> view -> zoom-out",
"root -> view -> separator-3-for-view",
"root -> view -> toggle-full-screen",
"root -> help",
"root -> help -> navigate-to-welcome",
"root -> help -> open-documentation",
"root -> help -> open-support",
]
"[
'root',
'root -> mac',
'root -> mac -> about',
'root -> mac -> separator-1',
'root -> mac -> navigate-to-preferences',
'root -> mac -> navigate-to-extensions',
'root -> mac -> separator-2',
'root -> mac -> services',
'root -> mac -> separator-3',
'root -> mac -> hide',
'root -> mac -> hide-others',
'root -> mac -> unhide',
'root -> mac -> separator-4',
'root -> mac -> quit',
'root -> file',
'root -> file -> add-cluster',
'root -> file -> separator-1-for-file',
'root -> file -> close-window',
'root -> edit',
'root -> edit -> undo',
'root -> edit -> redo',
'root -> edit -> separator-1-in-edit',
'root -> edit -> cut',
'root -> edit -> copy',
'root -> edit -> paste',
'root -> edit -> delete',
'root -> edit -> separator-2-in-edit',
'root -> edit -> selectAll',
'root -> view',
'root -> view -> navigate-to-catalog',
'root -> view -> open-command-palette',
'root -> view -> separator-1-for-view',
'root -> view -> go-back',
'root -> view -> go-forward',
'root -> view -> reload',
'root -> view -> toggle-dev-tools',
'root -> view -> separator-2-for-view',
'root -> view -> reset-zoom',
'root -> view -> zoom-in',
'root -> view -> zoom-out',
'root -> view -> separator-3-for-view',
'root -> view -> toggle-full-screen',
'root -> help',
'root -> help -> navigate-to-welcome',
'root -> help -> open-documentation',
'root -> help -> open-support'
]"
`;
exports[`application-menu, given platform is 'linux' given enough time passes populates application menu 1`] = `
Array [
"root",
"root -> file",
"root -> file -> add-cluster",
"root -> file -> navigate-to-preferences",
"root -> file -> navigate-to-extensions",
"root -> file -> quit",
"root -> edit",
"root -> edit -> undo",
"root -> edit -> redo",
"root -> edit -> separator-1-in-edit",
"root -> edit -> cut",
"root -> edit -> copy",
"root -> edit -> paste",
"root -> edit -> delete",
"root -> edit -> separator-2-in-edit",
"root -> edit -> selectAll",
"root -> view",
"root -> view -> navigate-to-catalog",
"root -> view -> open-command-palette",
"root -> view -> separator-1-for-view",
"root -> view -> go-back",
"root -> view -> go-forward",
"root -> view -> reload",
"root -> view -> toggle-dev-tools",
"root -> view -> separator-2-for-view",
"root -> view -> reset-zoom",
"root -> view -> zoom-in",
"root -> view -> zoom-out",
"root -> view -> separator-3-for-view",
"root -> view -> toggle-full-screen",
"root -> help",
"root -> help -> navigate-to-welcome",
"root -> help -> open-documentation",
"root -> help -> open-support",
"root -> help -> about",
]
"[
'root',
'root -> file',
'root -> file -> add-cluster',
'root -> file -> navigate-to-preferences',
'root -> file -> navigate-to-extensions',
'root -> file -> quit',
'root -> edit',
'root -> edit -> undo',
'root -> edit -> redo',
'root -> edit -> separator-1-in-edit',
'root -> edit -> cut',
'root -> edit -> copy',
'root -> edit -> paste',
'root -> edit -> delete',
'root -> edit -> separator-2-in-edit',
'root -> edit -> selectAll',
'root -> view',
'root -> view -> navigate-to-catalog',
'root -> view -> open-command-palette',
'root -> view -> separator-1-for-view',
'root -> view -> go-back',
'root -> view -> go-forward',
'root -> view -> reload',
'root -> view -> toggle-dev-tools',
'root -> view -> separator-2-for-view',
'root -> view -> reset-zoom',
'root -> view -> zoom-in',
'root -> view -> zoom-out',
'root -> view -> separator-3-for-view',
'root -> view -> toggle-full-screen',
'root -> help',
'root -> help -> navigate-to-welcome',
'root -> help -> open-documentation',
'root -> help -> open-support',
'root -> help -> about'
]"
`;
exports[`application-menu, given platform is 'win32' given enough time passes populates application menu 1`] = `
Array [
"root",
"root -> file",
"root -> file -> add-cluster",
"root -> file -> navigate-to-preferences",
"root -> file -> navigate-to-extensions",
"root -> file -> quit",
"root -> edit",
"root -> edit -> undo",
"root -> edit -> redo",
"root -> edit -> separator-1-in-edit",
"root -> edit -> cut",
"root -> edit -> copy",
"root -> edit -> paste",
"root -> edit -> delete",
"root -> edit -> separator-2-in-edit",
"root -> edit -> selectAll",
"root -> view",
"root -> view -> navigate-to-catalog",
"root -> view -> open-command-palette",
"root -> view -> separator-1-for-view",
"root -> view -> go-back",
"root -> view -> go-forward",
"root -> view -> reload",
"root -> view -> toggle-dev-tools",
"root -> view -> separator-2-for-view",
"root -> view -> reset-zoom",
"root -> view -> zoom-in",
"root -> view -> zoom-out",
"root -> view -> separator-3-for-view",
"root -> view -> toggle-full-screen",
"root -> help",
"root -> help -> navigate-to-welcome",
"root -> help -> open-documentation",
"root -> help -> open-support",
"root -> help -> about",
]
"[
'root',
'root -> file',
'root -> file -> add-cluster',
'root -> file -> navigate-to-preferences',
'root -> file -> navigate-to-extensions',
'root -> file -> quit',
'root -> edit',
'root -> edit -> undo',
'root -> edit -> redo',
'root -> edit -> separator-1-in-edit',
'root -> edit -> cut',
'root -> edit -> copy',
'root -> edit -> paste',
'root -> edit -> delete',
'root -> edit -> separator-2-in-edit',
'root -> edit -> selectAll',
'root -> view',
'root -> view -> navigate-to-catalog',
'root -> view -> open-command-palette',
'root -> view -> separator-1-for-view',
'root -> view -> go-back',
'root -> view -> go-forward',
'root -> view -> reload',
'root -> view -> toggle-dev-tools',
'root -> view -> separator-2-for-view',
'root -> view -> reset-zoom',
'root -> view -> zoom-in',
'root -> view -> zoom-out',
'root -> view -> separator-3-for-view',
'root -> view -> toggle-full-screen',
'root -> help',
'root -> help -> navigate-to-welcome',
'root -> help -> open-documentation',
'root -> help -> open-support',
'root -> help -> about'
]"
`;
@@ -8,6 +8,7 @@ import populateApplicationMenuInjectable from "./main/populate-application-menu.
import { advanceFakeTime, testUsingFakeTime } from "../../test-utils/use-fake-time";
import { getCompositePaths } from "../../common/utils/composite/get-composite-paths/get-composite-paths";
import platformInjectable, { allPlatforms } from "../../common/vars/platform.injectable";
import { inspect } from "util";
describe.each(allPlatforms)("application-menu, given platform is '%s'", (platform) => {
let builder: ApplicationBuilder;
@@ -53,7 +54,14 @@ describe.each(allPlatforms)("application-menu, given platform is '%s'", (platfor
});
it("populates application menu", () => {
expect(applicationMenuPaths.map(x => x.join(" -> "))).toMatchSnapshot();
expect(inspect(applicationMenuPaths.map(x => x.join(" -> ")), {
compact: false,
breakLength: Infinity,
colors: false,
depth: Infinity,
maxArrayLength: Infinity,
maxStringLength: Infinity,
})).toMatchSnapshot();
});
});
});
@@ -231,7 +231,7 @@ exports[`installing update when started renders 1`] = `
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -516,7 +516,7 @@ exports[`installing update when started when user checks for updates renders 1`]
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -801,7 +801,7 @@ exports[`installing update when started when user checks for updates when new up
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1086,7 +1086,7 @@ exports[`installing update when started when user checks for updates when new up
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1396,7 +1396,7 @@ exports[`installing update when started when user checks for updates when new up
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1706,7 +1706,7 @@ exports[`installing update when started when user checks for updates when new up
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2016,7 +2016,7 @@ exports[`installing update when started when user checks for updates when new up
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2301,7 +2301,7 @@ exports[`installing update when started when user checks for updates when no new
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -256,7 +256,7 @@ exports[`encourage user to update when sufficient time passed since update was d
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -541,7 +541,7 @@ exports[`encourage user to update when sufficient time passed since update was d
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -231,7 +231,7 @@ exports[`installing update using tray when started renders 1`] = `
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -516,7 +516,7 @@ exports[`installing update using tray when started when user checks for updates
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -801,7 +801,7 @@ exports[`installing update using tray when started when user checks for updates
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1086,7 +1086,7 @@ exports[`installing update using tray when started when user checks for updates
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1396,7 +1396,7 @@ exports[`installing update using tray when started when user checks for updates
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1681,7 +1681,7 @@ exports[`installing update using tray when started when user checks for updates
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -256,7 +256,7 @@ exports[`force user to update when too long since update was downloaded when app
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -566,7 +566,7 @@ exports[`force user to update when too long since update was downloaded when app
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -923,7 +923,7 @@ exports[`force user to update when too long since update was downloaded when app
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -231,7 +231,7 @@ exports[`periodical checking of updates given updater is enabled and configurati
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -231,7 +231,7 @@ exports[`selection of update stability when started renders 1`] = `
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -662,7 +662,7 @@ exports[`entity running technical tests when navigated to catalog renders 1`] =
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1378,7 +1378,7 @@ exports[`entity running technical tests when navigated to catalog when details p
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -231,7 +231,7 @@ exports[`opening catalog entity details panel renders 1`] = `
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1052,7 +1052,7 @@ exports[`opening catalog entity details panel when navigated to the catalog rend
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1873,7 +1873,7 @@ exports[`opening catalog entity details panel when navigated to the catalog when
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2726,7 +2726,7 @@ exports[`opening catalog entity details panel when navigated to the catalog when
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -3579,7 +3579,7 @@ exports[`opening catalog entity details panel when navigated to the catalog when
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -4683,7 +4683,7 @@ exports[`opening catalog entity details panel when navigated to the catalog when
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -5524,7 +5524,7 @@ exports[`opening catalog entity details panel when navigated to the catalog when
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -6365,7 +6365,7 @@ exports[`opening catalog entity details panel when navigated to the catalog when
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -6841,7 +6841,7 @@ exports[`opening catalog entity details panel when not navigated to the catalog
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -6,19 +6,18 @@
import type { DiContainer } from "@ogre-tools/injectable";
import type { RenderResult } from "@testing-library/react";
import { KubernetesCluster, WebLink } from "../../common/catalog-entities";
import getClusterByIdInjectable from "../../common/cluster-store/get-by-id.injectable";
import { Cluster } from "../../common/cluster/cluster";
import navigateToCatalogInjectable from "../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
import { advanceFakeTime, testUsingFakeTime } from "../../test-utils/use-fake-time";
import { advanceFakeTime } from "../../test-utils/use-fake-time";
import catalogEntityRegistryInjectable from "../../renderer/api/catalog/entity/registry.injectable";
import showEntityDetailsInjectable from "../../renderer/components/+catalog/entity-details/show.injectable";
import { type ApplicationBuilder, getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import writeJsonFileInjectable from "../../common/fs/write-json-file.injectable";
import addClusterInjectable from "../cluster/storage/common/add.injectable";
describe("opening catalog entity details panel", () => {
let builder: ApplicationBuilder;
let rendered: RenderResult;
let windowDi: DiContainer;
let cluster: Cluster;
let clusterEntity: KubernetesCluster;
let localClusterEntity: KubernetesCluster;
let otherEntity: WebLink;
@@ -26,20 +25,7 @@ describe("opening catalog entity details panel", () => {
beforeEach(async () => {
builder = getApplicationBuilder();
builder.beforeWindowStart(({ windowDi }) => {
// TODO: remove once ClusterStore can be used without overriding it
windowDi.override(getClusterByIdInjectable, () => (clusterId) => {
if (clusterId === cluster?.id) {
return cluster;
}
return undefined;
});
});
testUsingFakeTime();
builder.afterWindowStart(({ windowDi }) => {
builder.afterWindowStart(async ({ windowDi }) => {
clusterEntity = new KubernetesCluster({
metadata: {
labels: {},
@@ -82,12 +68,33 @@ describe("opening catalog entity details panel", () => {
phase: "available",
},
});
cluster = new Cluster({
contextName: clusterEntity.spec.kubeconfigContext,
const writeJsonFile = windowDi.inject(writeJsonFileInjectable);
const addCluster = windowDi.inject(addClusterInjectable);
await writeJsonFile(clusterEntity.spec.kubeconfigPath, {
contexts: [{
name: clusterEntity.spec.kubeconfigContext,
context: {
cluster: "some-cluster",
user: "some-user",
},
}],
clusters: [{
name: "some-cluster",
cluster: {
server: "https://localhost:9999",
},
}],
users: [{
name: "some-user",
}],
});
addCluster({
id: clusterEntity.getId(),
kubeConfigPath: clusterEntity.spec.kubeconfigPath,
}, {
clusterServerUrl: "https://localhost:9999",
contextName: clusterEntity.spec.kubeconfigContext,
});
// TODO: replace with proper entity source once syncing entities between main and windows is injectable
@@ -3,8 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable";
import clusterConnectionInjectable from "../../../../main/cluster/cluster-connection.injectable";
import getClusterByIdInjectable from "../../storage/common/get-by-id.injectable";
import { requestClusterActivationInjectionToken } from "../common/request-token";
const requestClusterActivationInjectable = getInjectable({
@@ -5,8 +5,8 @@
import { getInjectable } from "@ogre-tools/injectable";
import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable";
import clusterFramesInjectable from "../../../../common/cluster-frames.injectable";
import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable";
import clusterConnectionInjectable from "../../../../main/cluster/cluster-connection.injectable";
import getClusterByIdInjectable from "../../storage/common/get-by-id.injectable";
import { requestClusterDeactivationInjectionToken } from "../common/request-token";
const requestClusterDeactivationInjectable = getInjectable({
@@ -542,7 +542,7 @@ exports[`Deleting a cluster when an internal kubeconfig cluster is used when the
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1194,7 +1194,7 @@ exports[`Deleting a cluster when the kubeconfig has multiple clusters when the d
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1879,7 +1879,7 @@ exports[`Deleting a cluster when the kubeconfig has multiple clusters when the d
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2643,7 +2643,7 @@ exports[`Deleting a cluster when the kubeconfig has multiple clusters when the d
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -3407,7 +3407,7 @@ exports[`Deleting a cluster when the kubeconfig has only one cluster when the di
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -4,7 +4,6 @@
*/
import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable";
import clusterFramesInjectable from "../../../../common/cluster-frames.injectable";
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
import directoryForLensLocalStorageInjectable from "../../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
import removePathInjectable from "../../../../common/fs/remove.injectable";
import joinPathsInjectable from "../../../../common/path/join-paths.injectable";
@@ -12,22 +11,23 @@ import clusterConnectionInjectable from "../../../../main/cluster/cluster-connec
import { noop } from "@k8slens/utilities";
import { getRequestChannelListenerInjectable } from "@k8slens/messaging";
import { deleteClusterChannel } from "../common/delete-channel";
import clustersStateInjectable from "../../storage/common/state.injectable";
const deleteClusterChannelListenerInjectable = getRequestChannelListenerInjectable({
id: "delete-cluster-channel-listener",
channel: deleteClusterChannel,
getHandler: (di) => {
const emitAppEvent = di.inject(emitAppEventInjectable);
const clusterStore = di.inject(clusterStoreInjectable);
const clusterFrames = di.inject(clusterFramesInjectable);
const joinPaths = di.inject(joinPathsInjectable);
const directoryForLensLocalStorage = di.inject(directoryForLensLocalStorageInjectable);
const deleteFile = di.inject(removePathInjectable);
const clustersState = di.inject(clustersStateInjectable);
return async (clusterId) => {
emitAppEvent({ name: "cluster", action: "remove" });
const cluster = clusterStore.getById(clusterId);
const cluster = clustersState.get(clusterId);
if (!cluster) {
return;
@@ -37,9 +37,7 @@ const deleteClusterChannelListenerInjectable = getRequestChannelListenerInjectab
clusterConnection.disconnect();
clusterFrames.delete(cluster.id);
// Remove from the cluster store as well, this should clear any old settings
clusterStore.clusters.delete(cluster.id);
clustersState.delete(cluster.id);
// remove the local storage file
const localStorageFilePath = joinPaths(directoryForLensLocalStorage, `${cluster.id}.json`);
@@ -6,7 +6,6 @@
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import type { AuthorizationV1Api, CoreV1Api, V1APIGroupList, V1APIVersions, V1NamespaceList, V1SelfSubjectAccessReview, V1SelfSubjectRulesReview } from "@kubernetes/client-node";
import clusterStoreInjectable from "../../common/cluster-store/cluster-store.injectable";
import type { Cluster } from "../../common/cluster/cluster";
import createAuthorizationApiInjectable from "../../common/cluster/create-authorization-api.injectable";
import writeJsonFileInjectable from "../../common/fs/write-json-file.injectable";
@@ -26,6 +25,7 @@ import type { KubeAuthProxy } from "../../main/kube-auth-proxy/create-kube-auth-
import createKubeAuthProxyInjectable from "../../main/kube-auth-proxy/create-kube-auth-proxy.injectable";
import type { Mocked } from "../../test-utils/mock-interface";
import { flushPromises } from "@k8slens/test-utils";
import addClusterInjectable from "./storage/common/add.injectable";
describe("Refresh Cluster Accessibility Technical Tests", () => {
let builder: ApplicationBuilder;
@@ -79,7 +79,7 @@ describe("Refresh Cluster Accessibility Technical Tests", () => {
beforeEach(async () => {
const mainDi = builder.mainDi;
const clusterStore = mainDi.inject(clusterStoreInjectable);
const addCluster = mainDi.inject(addClusterInjectable);
const writeJsonFile = mainDi.inject(writeJsonFileInjectable);
await writeJsonFile("/some-kube-config-path", {
@@ -103,13 +103,11 @@ describe("Refresh Cluster Accessibility Technical Tests", () => {
}],
});
clusterStore.addCluster({
cluster = addCluster({
contextName: "some-cluster-context",
id: "some-cluster-id",
kubeConfigPath: "/some-kube-config-path",
});
cluster = clusterStore.getById("some-cluster-id") ?? (() => { throw new Error("missing cluster"); })();
clusterConnection = mainDi.inject(clusterConnectionInjectable, cluster);
refreshPromise = clusterConnection.refreshAccessibilityAndMetadata();
});
@@ -0,0 +1,12 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { IComputedValue } from "mobx";
import type { KubeApiResourceDescriptor } from "../../../../common/rbac";
export const shouldShowResourceInjectionToken = getInjectionToken<IComputedValue<boolean>, KubeApiResourceDescriptor>({
id: "should-show-resource",
});
@@ -2,17 +2,17 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
import { getRequestChannelListenerInjectable } from "@k8slens/messaging";
import clustersInjectable from "../../storage/common/clusters.injectable";
import { initialClusterStatesChannel } from "../common/channels";
const handleInitialClusterStateSyncInjectable = getRequestChannelListenerInjectable({
id: "handle-initial-cluster-state-sync",
channel: initialClusterStatesChannel,
getHandler: (di) => {
const clusterStore = di.inject(clusterStoreInjectable);
const clusters = di.inject(clustersInjectable);
return () => clusterStore.clustersList.map(cluster => ({
return () => clusters.get().map(cluster => ({
clusterId: cluster.id,
state: cluster.getState(),
}));
@@ -5,22 +5,22 @@
import { getInjectable } from "@ogre-tools/injectable";
import { isEqual } from "lodash";
import { autorun } from "mobx";
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
import type { ClusterId, ClusterState } from "../../../../common/cluster-types";
import { beforeApplicationIsLoadingInjectionToken } from "@k8slens/application";
import initClusterStoreInjectable from "../../store/main/init.injectable";
import initClusterStoreInjectable from "../../storage/main/init.injectable";
import emitClusterStateUpdateInjectable from "./emit-update.injectable";
import clustersInjectable from "../../storage/common/clusters.injectable";
const setupClusterStateBroadcastingInjectable = getInjectable({
id: "setup-cluster-state-broadcasting",
instantiate: (di) => ({
run: () => {
const emitClusterStateUpdate = di.inject(emitClusterStateUpdateInjectable);
const clusterStore = di.inject(clusterStoreInjectable);
const clusters = di.inject(clustersInjectable);
const prevStates = new Map<ClusterId, ClusterState>();
autorun(() => {
for (const cluster of clusterStore.clusters.values()) {
for (const cluster of clusters.get()) {
const prevState = prevStates.get(cluster.id);
const curState = cluster.getState();
@@ -2,8 +2,8 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable";
import { getMessageChannelListenerInjectable } from "@k8slens/messaging";
import getClusterByIdInjectable from "../../storage/common/get-by-id.injectable";
import { clusterStateSyncChannel } from "../common/channels";
const clusterStateListenerInjectable = getMessageChannelListenerInjectable({
@@ -3,9 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable";
import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens";
import initClusterStoreInjectable from "../../store/renderer/init.injectable";
import getClusterByIdInjectable from "../../storage/common/get-by-id.injectable";
import initClusterStoreInjectable from "../../storage/renderer/init.injectable";
import requestInitialClusterStatesInjectable from "./request-initial.injectable";
const setupClusterStateSyncInjectable = getInjectable({
@@ -0,0 +1,375 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { GetCustomKubeConfigFilePath } from "../../../common/app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
import getCustomKubeConfigFilePathInjectable from "../../../common/app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
import type { DiContainer } from "@ogre-tools/injectable";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting";
import assert from "assert";
import directoryForTempInjectable from "../../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
import kubectlBinaryNameInjectable from "../../../main/kubectl/binary-name.injectable";
import kubectlDownloadingNormalizedArchInjectable from "../../../main/kubectl/normalized-arch.injectable";
import normalizedPlatformInjectable from "../../../common/vars/normalized-platform.injectable";
import storeMigrationVersionInjectable from "../../../common/vars/store-migration-version.injectable";
import type { WriteJsonSync } from "../../../common/fs/write-json-sync.injectable";
import writeJsonSyncInjectable from "../../../common/fs/write-json-sync.injectable";
import type { ReadFileSync } from "../../../common/fs/read-file-sync.injectable";
import readFileSyncInjectable from "../../../common/fs/read-file-sync.injectable";
import { readFileSync } from "fs";
import type { WriteFileSync } from "../../../common/fs/write-file-sync.injectable";
import writeFileSyncInjectable from "../../../common/fs/write-file-sync.injectable";
import type { WriteBufferSync } from "../../../common/fs/write-buffer-sync.injectable";
import writeBufferSyncInjectable from "../../../common/fs/write-buffer-sync.injectable";
import { Cluster } from "../../../common/cluster/cluster";
import clustersPersistentStorageInjectable from "./common/storage.injectable";
import type { PersistentStorage } from "../../../common/persistent-storage/create.injectable";
import type { AddCluster } from "./common/add.injectable";
import addClusterInjectable from "./common/add.injectable";
import type { GetClusterById } from "./common/get-by-id.injectable";
import getClusterByIdInjectable from "./common/get-by-id.injectable";
import type { IComputedValue } from "mobx";
import clustersInjectable from "./common/clusters.injectable";
// NOTE: this is intended to read the actual file system
const testDataIcon = readFileSync("test-data/cluster-store-migration-icon.png");
const clusterServerUrl = "https://localhost";
const kubeconfig = `
apiVersion: v1
clusters:
- cluster:
server: ${clusterServerUrl}
name: test
contexts:
- context:
cluster: test
user: test
name: foo
- context:
cluster: test
user: test
name: foo2
current-context: test
kind: Config
preferences: {}
users:
- name: test
user:
token: kubeconfig-user-q4lm4:xxxyyyy
`;
describe("cluster storage technical tests", () => {
let di: DiContainer;
let clustersPersistentStorage: PersistentStorage;
let writeJsonSync: WriteJsonSync;
let writeFileSync: WriteFileSync;
let writeBufferSync: WriteBufferSync;
let readFileSync: ReadFileSync;
let getCustomKubeConfigFilePath: GetCustomKubeConfigFilePath;
let writeFileSyncAndReturnPath: (filePath: string, contents: string) => string;
let addCluster: AddCluster;
let getClusterById: GetClusterById;
let clusters: IComputedValue<Cluster[]>;
beforeEach(async () => {
di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
di.override(directoryForTempInjectable, () => "/some-temp-directory");
di.override(kubectlBinaryNameInjectable, () => "kubectl");
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
di.override(normalizedPlatformInjectable, () => "darwin");
writeJsonSync = di.inject(writeJsonSyncInjectable);
writeFileSync = di.inject(writeFileSyncInjectable);
writeBufferSync = di.inject(writeBufferSyncInjectable);
readFileSync = di.inject(readFileSyncInjectable);
addCluster = di.inject(addClusterInjectable);
getClusterById = di.inject(getClusterByIdInjectable);
clusters = di.inject(clustersInjectable);
writeFileSyncAndReturnPath = (filePath, contents) => (writeFileSync(filePath, contents), filePath);
});
describe("empty config", () => {
beforeEach(async () => {
getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable);
writeJsonSync("/some-directory-for-user-data/lens-cluster-store.json", {});
clustersPersistentStorage = di.inject(clustersPersistentStorageInjectable);
clustersPersistentStorage.loadAndStartSyncing();
});
describe("with foo cluster added", () => {
beforeEach(() => {
const cluster = new Cluster({
id: "foo",
contextName: "foo",
preferences: {
terminalCWD: "/some-directory-for-user-data",
icon: "data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5",
clusterName: "minikube",
},
kubeConfigPath: writeFileSyncAndReturnPath(
getCustomKubeConfigFilePath("foo"),
kubeconfig,
),
}, {
clusterServerUrl,
});
addCluster(cluster);
});
it("adds new cluster to store", async () => {
const storedCluster = getClusterById("foo");
assert(storedCluster);
expect(storedCluster.id).toBe("foo");
expect(storedCluster.preferences.terminalCWD).toBe("/some-directory-for-user-data");
expect(storedCluster.preferences.icon).toBe(
"data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5",
);
});
});
describe("with prod and dev clusters added", () => {
beforeEach(() => {
addCluster({
id: "prod",
contextName: "foo",
preferences: {
clusterName: "prod",
},
kubeConfigPath: writeFileSyncAndReturnPath(
getCustomKubeConfigFilePath("prod"),
kubeconfig,
),
});
addCluster({
id: "dev",
contextName: "foo2",
preferences: {
clusterName: "dev",
},
kubeConfigPath: writeFileSyncAndReturnPath(
getCustomKubeConfigFilePath("dev"),
kubeconfig,
),
});
});
it("check if store can contain multiple clusters", () => {
expect(clusters.get().length).toBe(2);
});
it("check if cluster's kubeconfig file saved", () => {
const file = writeFileSyncAndReturnPath(getCustomKubeConfigFilePath("boo"), "kubeconfig");
expect(readFileSync(file)).toBe("kubeconfig");
});
});
});
describe("config with existing clusters", () => {
beforeEach(() => {
writeFileSync("/temp-kube-config", kubeconfig);
writeJsonSync("/some-directory-for-user-data/lens-cluster-store.json", {
__internal__: {
migrations: {
version: "99.99.99",
},
},
clusters: [
{
id: "cluster1",
kubeConfigPath: "/temp-kube-config",
contextName: "foo",
preferences: { terminalCWD: "/foo" },
workspace: "default",
},
{
id: "cluster2",
kubeConfigPath: "/temp-kube-config",
contextName: "foo2",
preferences: { terminalCWD: "/foo2" },
},
{
id: "cluster3",
kubeConfigPath: "/temp-kube-config",
contextName: "foo",
preferences: { terminalCWD: "/foo" },
workspace: "foo",
ownerRef: "foo",
},
],
});
getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable);
clustersPersistentStorage = di.inject(clustersPersistentStorageInjectable);
clustersPersistentStorage.loadAndStartSyncing();
});
it("allows to retrieve a cluster", () => {
const storedCluster = getClusterById("cluster1");
assert(storedCluster);
expect(storedCluster.id).toBe("cluster1");
expect(storedCluster.preferences.terminalCWD).toBe("/foo");
});
it("allows getting all of the clusters", async () => {
const storedClusters = clusters.get();
expect(storedClusters.length).toBe(3);
expect(storedClusters[0].id).toBe("cluster1");
expect(storedClusters[0].preferences.terminalCWD).toBe("/foo");
expect(storedClusters[1].id).toBe("cluster2");
expect(storedClusters[1].preferences.terminalCWD).toBe("/foo2");
expect(storedClusters[2].id).toBe("cluster3");
});
});
describe("config with invalid cluster kubeconfig", () => {
beforeEach(() => {
writeFileSync("/invalid-kube-config", invalidKubeconfig);
writeFileSync("/valid-kube-config", kubeconfig);
writeJsonSync("/some-directory-for-user-data/lens-cluster-store.json", {
__internal__: {
migrations: {
version: "99.99.99",
},
},
clusters: [
{
id: "cluster1",
kubeConfigPath: "/invalid-kube-config",
contextName: "test",
preferences: { terminalCWD: "/foo" },
workspace: "foo",
},
{
id: "cluster2",
kubeConfigPath: "/valid-kube-config",
contextName: "foo",
preferences: { terminalCWD: "/foo" },
workspace: "default",
},
],
});
getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable);
clustersPersistentStorage = di.inject(clustersPersistentStorageInjectable);
clustersPersistentStorage.loadAndStartSyncing();
});
it("does not enable clusters with invalid kubeconfig", () => {
const storedClusters = clusters.get();
expect(storedClusters.length).toBe(1);
});
});
describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
beforeEach(() => {
di.override(storeMigrationVersionInjectable, () => "3.6.0");
getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable);
writeJsonSync("/some-directory-for-user-data/lens-cluster-store.json", {
__internal__: {
migrations: {
version: "3.5.0",
},
},
clusters: [
{
id: "cluster1",
kubeConfig: minimalValidKubeConfig,
contextName: "cluster",
preferences: {
icon: "store://icon_path",
},
},
],
});
writeBufferSync("/some-directory-for-user-data/icon_path", testDataIcon);
clustersPersistentStorage = di.inject(clustersPersistentStorageInjectable);
clustersPersistentStorage.loadAndStartSyncing();
});
it("migrates to modern format with kubeconfig in a file", async () => {
const configPath = clusters.get()[0].kubeConfigPath.get();
expect(readFileSync(configPath)).toBe(minimalValidKubeConfig);
});
it("migrates to modern format with icon not in file", async () => {
expect(clusters.get()[0].preferences.icon).toMatch(/data:;base64,/);
});
});
});
const invalidKubeconfig = JSON.stringify({
apiVersion: "v1",
clusters: [{
cluster: {
server: "https://localhost",
},
name: "test2",
}],
contexts: [{
context: {
cluster: "test",
user: "test",
},
name: "test",
}],
"current-context": "test",
kind: "Config",
preferences: {},
users: [{
user: {
token: "kubeconfig-user-q4lm4:xxxyyyy",
},
name: "test",
}],
});
const minimalValidKubeConfig = JSON.stringify({
apiVersion: "v1",
clusters: [
{
name: "minikube",
cluster: {
server: "https://192.168.64.3:8443",
},
},
],
"current-context": "minikube",
contexts: [
{
context: {
cluster: "minikube",
user: "minikube",
},
name: "minikube",
},
],
users: [
{
name: "minikube",
user: {
"client-certificate": "/Users/foo/.minikube/client.crt",
"client-key": "/Users/foo/.minikube/client.key",
},
},
],
kind: "Config",
preferences: {},
});
@@ -0,0 +1,39 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { action } from "mobx";
import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable";
import readClusterConfigSyncInjectable from "./read-cluster-config.injectable";
import type { ClusterModel } from "../../../../common/cluster-types";
import { Cluster } from "../../../../common/cluster/cluster";
import clustersStateInjectable from "./state.injectable";
export type AddCluster = (clusterOrModel: ClusterModel | Cluster) => Cluster;
const addClusterInjectable = getInjectable({
id: "add-cluster",
instantiate: (di): AddCluster => {
const clustersState = di.inject(clustersStateInjectable);
const emitAppEvent = di.inject(emitAppEventInjectable);
const readClusterConfigSync = di.inject(readClusterConfigSyncInjectable);
return action((clusterOrModel) => {
emitAppEvent({ name: "cluster", action: "add" });
const cluster = clusterOrModel instanceof Cluster
? clusterOrModel
: new Cluster(
clusterOrModel,
readClusterConfigSync(clusterOrModel),
);
clustersState.set(cluster.id, cluster);
return cluster;
});
},
});
export default addClusterInjectable;
@@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import clustersStateInjectable from "./state.injectable";
const clustersInjectable = getInjectable({
id: "clusters",
instantiate: (di) => {
const clustersState = di.inject(clustersStateInjectable);
return computed(() => [...clustersState.values()]);
},
});
export default clustersInjectable;
@@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { ClusterId } from "../../../../common/cluster-types";
import type { Cluster } from "../../../../common/cluster/cluster";
import clustersStateInjectable from "./state.injectable";
export type GetClusterById = (id: ClusterId) => Cluster | undefined;
const getClusterByIdInjectable = getInjectable({
id: "get-cluster-by-id",
instantiate: (di): GetClusterById => {
const clustersState = di.inject(clustersStateInjectable);
return (id) => clustersState.get(id);
},
});
export default getClusterByIdInjectable;
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { MigrationDeclaration } from "../../../../common/persistent-storage/migrations.injectable";
export const clusterStoreMigrationInjectionToken = getInjectionToken<MigrationDeclaration>({
id: "cluster-store-migration",
});
@@ -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 type { ClusterConfigData, ClusterModel } from "../../../../common/cluster-types";
import readFileSyncInjectable from "../../../../common/fs/read-file-sync.injectable";
import { loadConfigFromString, validateKubeConfig } from "../../../../common/kube-helpers";
export type ReadClusterConfigSync = (model: ClusterModel) => ClusterConfigData;
const readClusterConfigSyncInjectable = getInjectable({
id: "read-cluster-config-sync",
instantiate: (di): ReadClusterConfigSync => {
const readFileSync = di.inject(readFileSyncInjectable);
return ({ kubeConfigPath, contextName }) => {
const kubeConfigData = readFileSync(kubeConfigPath);
const { config } = loadConfigFromString(kubeConfigData);
const result = validateKubeConfig(config, contextName);
if (result.error) {
throw result.error;
}
return { clusterServerUrl: result.cluster.server };
};
},
});
export default readClusterConfigSyncInjectable;
@@ -0,0 +1,15 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { observable } from "mobx";
import type { ClusterId } from "../../../../common/cluster-types";
import type { Cluster } from "../../../../common/cluster/cluster";
const clustersStateInjectable = getInjectable({
id: "clusters-state",
instantiate: () => observable.map<ClusterId, Cluster>(),
});
export default clustersStateInjectable;
@@ -0,0 +1,72 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { iter } from "@k8slens/utilities";
import { getInjectable } from "@ogre-tools/injectable";
import { comparer, action } from "mobx";
import { clusterStoreMigrationInjectionToken } from "./migration-token";
import readClusterConfigSyncInjectable from "./read-cluster-config.injectable";
import type { ClusterId, ClusterModel } from "../../../../common/cluster-types";
import { Cluster } from "../../../../common/cluster/cluster";
import loggerInjectable from "../../../../common/logger.injectable";
import createPersistentStorageInjectable from "../../../../common/persistent-storage/create.injectable";
import persistentStorageMigrationsInjectable from "../../../../common/persistent-storage/migrations.injectable";
import storeMigrationVersionInjectable from "../../../../common/vars/store-migration-version.injectable";
import clustersStateInjectable from "./state.injectable";
export interface ClusterStoreModel {
clusters?: ClusterModel[];
}
const clustersPersistentStorageInjectable = getInjectable({
id: "clusters-persistent-storage",
instantiate: (di) => {
const createPersistentStorage = di.inject(createPersistentStorageInjectable);
const readClusterConfigSync = di.inject(readClusterConfigSyncInjectable);
const clustersState = di.inject(clustersStateInjectable);
const logger = di.inject(loggerInjectable);
return createPersistentStorage<ClusterStoreModel>({
configName: "lens-cluster-store",
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
syncOptions: {
equals: comparer.structural,
},
projectVersion: di.inject(storeMigrationVersionInjectable),
migrations: di.inject(persistentStorageMigrationsInjectable, clusterStoreMigrationInjectionToken),
fromStore: action(({ clusters = [] }) => {
const currentClusters = new Map(clustersState);
const newClusters = new Map<ClusterId, Cluster>();
// update new clusters
for (const clusterModel of clusters) {
try {
let cluster = currentClusters.get(clusterModel.id);
if (cluster) {
cluster.updateModel(clusterModel);
} else {
cluster = new Cluster(
clusterModel,
readClusterConfigSync(clusterModel),
);
}
newClusters.set(clusterModel.id, cluster);
} catch (error) {
logger.warn(`[CLUSTER-STORE]: Failed to update/create a cluster: ${error}`);
}
}
clustersState.replace(newClusters);
}),
toJSON: () => ({
clusters: iter.chain(clustersState.values())
.map(cluster => cluster.toJSON())
.toArray(),
}),
});
},
});
export default clustersPersistentStorageInjectable;
@@ -3,19 +3,19 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
import { beforeApplicationIsLoadingInjectionToken } from "@k8slens/application";
import initUserStoreInjectable from "../../../../main/stores/init-user-store.injectable";
import clustersPersistentStorageInjectable from "../common/storage.injectable";
import loadUserPreferencesStorageInjectable from "../../../user-preferences/main/load-storage.injectable";
const initClusterStoreInjectable = getInjectable({
id: "init-cluster-store",
instantiate: (di) => ({
run: () => {
const clusterStore = di.inject(clusterStoreInjectable);
const storage = di.inject(clustersPersistentStorageInjectable);
clusterStore.load();
storage.loadAndStartSyncing();
},
runAfter: initUserStoreInjectable,
runAfter: loadUserPreferencesStorageInjectable,
}),
injectionToken: beforeApplicationIsLoadingInjectionToken,
});
@@ -3,17 +3,17 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens";
import initUserStoreInjectable from "../../../../renderer/stores/init-user-store.injectable";
import initUserStoreInjectable from "../../../user-preferences/renderer/load-storage.injectable";
import clustersPersistentStorageInjectable from "../common/storage.injectable";
const initClusterStoreInjectable = getInjectable({
id: "init-cluster-store",
instantiate: (di) => ({
run: () => {
const clusterStore = di.inject(clusterStoreInjectable);
const storage = di.inject(clustersPersistentStorageInjectable);
clusterStore.load();
storage.loadAndStartSyncing();
},
runAfter: initUserStoreInjectable,
}),
@@ -13,7 +13,7 @@ import { frontEndRouteInjectionToken } from "../../common/front-end-routing/fron
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { navigateToRouteInjectionToken } from "../../common/front-end-routing/navigate-to-route-injection-token";
import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token";
import { shouldShowResourceInjectionToken } from "./showing-kube-resources/common/allowed-resources-injection-token";
describe("cluster - visibility of sidebar items", () => {
let builder: ApplicationBuilder;
@@ -322,7 +322,7 @@ exports[`Command Pallet: keyboard shortcut tests when on linux renders 1`] = `
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -698,7 +698,7 @@ exports[`Command Pallet: keyboard shortcut tests when on linux when pressing ESC
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1074,7 +1074,7 @@ exports[`Command Pallet: keyboard shortcut tests when on linux when pressing SHI
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1462,7 +1462,7 @@ exports[`Command Pallet: keyboard shortcut tests when on linux when pressing SHI
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1747,7 +1747,7 @@ exports[`Command Pallet: keyboard shortcut tests when on macOS renders 1`] = `
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2032,7 +2032,7 @@ exports[`Command Pallet: keyboard shortcut tests when on macOS when pressing ESC
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2317,7 +2317,7 @@ exports[`Command Pallet: keyboard shortcut tests when on macOS when pressing SHI
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2614,7 +2614,7 @@ exports[`Command Pallet: keyboard shortcut tests when on macOS when pressing SHI
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -231,7 +231,7 @@ exports[`Showing correct entity settings renders 1`] = `
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -593,7 +593,7 @@ exports[`Showing correct entity settings when navigating to local cluster entity
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -976,7 +976,7 @@ exports[`Showing correct entity settings when navigating to non-local cluster en
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1261,7 +1261,7 @@ exports[`Showing correct entity settings when navigating to weblink entity setti
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -6,11 +6,11 @@
import type { DiContainer } from "@ogre-tools/injectable";
import type { RenderResult } from "@testing-library/react";
import { KubernetesCluster, WebLink } from "../../common/catalog-entities";
import getClusterByIdInjectable from "../../common/cluster-store/get-by-id.injectable";
import { Cluster } from "../../common/cluster/cluster";
import navigateToEntitySettingsInjectable from "../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable";
import writeJsonFileInjectable from "../../common/fs/write-json-file.injectable";
import catalogEntityRegistryInjectable from "../../renderer/api/catalog/entity/registry.injectable";
import { type ApplicationBuilder, getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import addClusterInjectable from "../cluster/storage/common/add.injectable";
describe("Showing correct entity settings", () => {
let builder: ApplicationBuilder;
@@ -19,23 +19,11 @@ describe("Showing correct entity settings", () => {
let clusterEntity: KubernetesCluster;
let localClusterEntity: KubernetesCluster;
let otherEntity: WebLink;
let cluster: Cluster;
beforeEach(async () => {
builder = getApplicationBuilder();
builder.beforeWindowStart(({ windowDi }) => {
// TODO: remove once ClusterStore can be used without overriding it
windowDi.override(getClusterByIdInjectable, () => (clusterId) => {
if (clusterId === cluster.id) {
return cluster;
}
return undefined;
});
});
builder.afterWindowStart(({ windowDi }) => {
builder.afterWindowStart(async ({ windowDi }) => {
clusterEntity = new KubernetesCluster({
metadata: {
labels: {},
@@ -78,14 +66,34 @@ describe("Showing correct entity settings", () => {
phase: "available",
},
});
cluster = new Cluster({
contextName: clusterEntity.spec.kubeconfigContext,
id: clusterEntity.getId(),
kubeConfigPath: clusterEntity.spec.kubeconfigPath,
}, {
clusterServerUrl: "https://localhost:9999",
const writeJsonFile = windowDi.inject(writeJsonFileInjectable);
const addCluster = windowDi.inject(addClusterInjectable);
await writeJsonFile(clusterEntity.spec.kubeconfigPath, {
contexts: [{
name: clusterEntity.spec.kubeconfigContext,
context: {
cluster: "some-cluster",
user: "some-user",
},
}],
clusters: [{
name: "some-cluster",
cluster: {
server: "https://localhost:9999",
},
}],
users: [{
name: "some-user",
}],
});
addCluster({
id: clusterEntity.getId(),
kubeConfigPath: clusterEntity.spec.kubeconfigPath,
contextName: clusterEntity.spec.kubeconfigContext,
});
// TODO: replace with proper entity source once syncing entities between main and windows is injectable
const catalogEntityRegistry = windowDi.inject(catalogEntityRegistryInjectable);
@@ -230,7 +230,7 @@ exports[`extensions - navigation using application menu renders 1`] = `
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -543,7 +543,7 @@ exports[`extensions - navigation using application menu when navigating to exten
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { iter } from "@k8slens/utilities";
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import enabledExtensionsStateInjectable from "./state.injectable";
const enabledExtensionsInjectable = getInjectable({
id: "enabled-extensions",
instantiate: (di) => {
const state = di.inject(enabledExtensionsStateInjectable);
return computed(() => (
iter.chain(state.values())
.filter(({ enabled }) => enabled)
.map(({ name }) => name)
.toArray()
));
},
});
export default enabledExtensionsInjectable;
@@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import enabledExtensionsStateInjectable from "./state.injectable";
export interface IsEnabledExtensionDescriptor {
readonly id: string;
readonly isBundled: boolean;
}
export type IsExtensionEnabled = (desc: IsEnabledExtensionDescriptor) => boolean;
const isExtensionEnabledInjectable = getInjectable({
id: "is-extension-enabled",
instantiate: (di): IsExtensionEnabled => {
const state = di.inject(enabledExtensionsStateInjectable);
return ({ id, isBundled }) => isBundled || (state.get(id)?.enabled ?? false);
},
});
export default isExtensionEnabledInjectable;
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { MigrationDeclaration } from "../../../../common/persistent-storage/migrations.injectable";
export const enabledExtensionsMigrationDeclarationInjectionToken = getInjectionToken<MigrationDeclaration>({
id: "enabled-extensions-migration-declaration",
});
@@ -0,0 +1,19 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensExtensionId } from "@k8slens/legacy-extensions";
import { getInjectable } from "@ogre-tools/injectable";
import { observable } from "mobx";
export interface LensExtensionState {
enabled?: boolean;
name: string;
}
const enabledExtensionsStateInjectable = getInjectable({
id: "enabled-extensions-state",
instantiate: () => observable.map<LensExtensionId, LensExtensionState>(),
});
export default enabledExtensionsStateInjectable;
@@ -0,0 +1,39 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensExtensionId } from "@k8slens/legacy-extensions";
import { getInjectable } from "@ogre-tools/injectable";
import { action, toJS } from "mobx";
import createPersistentStorageInjectable from "../../../../common/persistent-storage/create.injectable";
import persistentStorageMigrationsInjectable from "../../../../common/persistent-storage/migrations.injectable";
import storageMigrationVersionInjectable from "../../../../common/persistent-storage/storage-migration-version.injectable";
import { enabledExtensionsMigrationDeclarationInjectionToken } from "./migrations";
import type { LensExtensionState } from "./state.injectable";
import enabledExtensionsStateInjectable from "./state.injectable";
interface EnabledExtensionsStorageModal {
extensions: [LensExtensionId, LensExtensionState][];
}
const enabledExtensionsPersistentStorageInjectable = getInjectable({
id: "enabled-extensions-persistent-storage",
instantiate: (di) => {
const createPersistentStorage = di.inject(createPersistentStorageInjectable);
const state = di.inject(enabledExtensionsStateInjectable);
return createPersistentStorage<EnabledExtensionsStorageModal>({
configName: "lens-extensions",
fromStore: action(({ extensions = [] }) => {
state.replace(extensions);
}),
toJSON: () => ({
extensions: [...toJS(state)],
}),
projectVersion: di.inject(storageMigrationVersionInjectable, enabledExtensionsMigrationDeclarationInjectionToken),
migrations: di.inject(persistentStorageMigrationsInjectable, enabledExtensionsMigrationDeclarationInjectionToken),
});
},
});
export default enabledExtensionsPersistentStorageInjectable;
@@ -0,0 +1,23 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensExtensionId } from "@k8slens/legacy-extensions";
import { getInjectable } from "@ogre-tools/injectable";
import type { IObservableMapInitialValues } from "mobx";
import { action } from "mobx";
import type { LensExtensionState } from "./state.injectable";
import enabledExtensionsStateInjectable from "./state.injectable";
export type UpdateExtensionsState = (state: IObservableMapInitialValues<LensExtensionId, LensExtensionState>) => void;
const updateExtensionsStateInjectable = getInjectable({
id: "update-extensions-state",
instantiate: (di): UpdateExtensionsState => {
const state = di.inject(enabledExtensionsStateInjectable);
return action((newState) => state.merge(newState));
},
});
export default updateExtensionsStateInjectable;
@@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { beforeApplicationIsLoadingInjectionToken } from "@k8slens/application";
import { getInjectable } from "@ogre-tools/injectable";
import enabledExtensionsPersistentStorageInjectable from "../common/storage.injectable";
const loadEnabledExtensionsStorageInjectable = getInjectable({
id: "load-enabled-extensions-storage",
instantiate: (di) => ({
run: () => {
const storage = di.inject(enabledExtensionsPersistentStorageInjectable);
storage.loadAndStartSyncing();
},
}),
injectionToken: beforeApplicationIsLoadingInjectionToken,
});
export default loadEnabledExtensionsStorageInjectable;
@@ -0,0 +1,26 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { isObject } from "@k8slens/utilities";
import { getInjectable } from "@ogre-tools/injectable";
import { enabledExtensionsMigrationDeclarationInjectionToken } from "../common/migrations";
const enabledExtensionsMigrationV650Injectable = getInjectable({
id: "enabled-extensions-migration-v650",
instantiate: () => ({
version: "6.5.0",
run: (store) => {
const extensions = store.get("extensions");
if (!isObject(extensions)) {
store.delete("extensions");
} else {
store.set("extensions", Object.entries(extensions));
}
},
}),
injectionToken: enabledExtensionsMigrationDeclarationInjectionToken,
});
export default enabledExtensionsMigrationV650Injectable;
@@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens";
import enabledExtensionsPersistentStorageInjectable from "../common/storage.injectable";
const loadEnabledExtensionsStorageInjectable = getInjectable({
id: "load-enabled-extensions-storage",
instantiate: (di) => ({
run: () => {
const storage = di.inject(enabledExtensionsPersistentStorageInjectable);
storage.loadAndStartSyncing();
},
}),
injectionToken: beforeFrameStartsSecondInjectionToken,
});
export default loadEnabledExtensionsStorageInjectable;
@@ -649,7 +649,7 @@ exports[`add custom helm repository in preferences when navigating to preference
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1391,7 +1391,7 @@ exports[`add custom helm repository in preferences when navigating to preference
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2133,7 +2133,7 @@ exports[`add custom helm repository in preferences when navigating to preference
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2986,7 +2986,7 @@ exports[`add custom helm repository in preferences when navigating to preference
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -3736,7 +3736,7 @@ exports[`add custom helm repository in preferences when navigating to preference
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -4589,7 +4589,7 @@ exports[`add custom helm repository in preferences when navigating to preference
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -5624,7 +5624,7 @@ exports[`add custom helm repository in preferences when navigating to preference
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -6477,7 +6477,7 @@ exports[`add custom helm repository in preferences when navigating to preference
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -7512,7 +7512,7 @@ exports[`add custom helm repository in preferences when navigating to preference
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -8366,7 +8366,7 @@ exports[`add custom helm repository in preferences when navigating to preference
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -9105,7 +9105,7 @@ exports[`add custom helm repository in preferences when navigating to preference
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -9844,7 +9844,7 @@ exports[`add custom helm repository in preferences when navigating to preference
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -649,7 +649,7 @@ exports[`add helm repository from list in preferences when navigating to prefere
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1391,7 +1391,7 @@ exports[`add helm repository from list in preferences when navigating to prefere
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2135,7 +2135,7 @@ exports[`add helm repository from list in preferences when navigating to prefere
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2928,7 +2928,7 @@ exports[`add helm repository from list in preferences when navigating to prefere
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -3670,7 +3670,7 @@ exports[`add helm repository from list in preferences when navigating to prefere
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -4401,7 +4401,7 @@ exports[`add helm repository from list in preferences when navigating to prefere
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -5177,7 +5177,7 @@ exports[`add helm repository from list in preferences when navigating to prefere
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -5955,7 +5955,7 @@ exports[`add helm repository from list in preferences when navigating to prefere
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -6792,7 +6792,7 @@ exports[`add helm repository from list in preferences when navigating to prefere
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -7523,7 +7523,7 @@ exports[`add helm repository from list in preferences when navigating to prefere
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -649,7 +649,7 @@ exports[`listing active helm repositories in preferences when navigating to pref
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1380,7 +1380,7 @@ exports[`listing active helm repositories in preferences when navigating to pref
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2003,7 +2003,7 @@ exports[`listing active helm repositories in preferences when navigating to pref
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2734,7 +2734,7 @@ exports[`listing active helm repositories in preferences when navigating to pref
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -3357,7 +3357,7 @@ exports[`listing active helm repositories in preferences when navigating to pref
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -4088,7 +4088,7 @@ exports[`listing active helm repositories in preferences when navigating to pref
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -4864,7 +4864,7 @@ exports[`listing active helm repositories in preferences when navigating to pref
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -5487,7 +5487,7 @@ exports[`listing active helm repositories in preferences when navigating to pref
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -6110,7 +6110,7 @@ exports[`listing active helm repositories in preferences when navigating to pref
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -6733,7 +6733,7 @@ exports[`listing active helm repositories in preferences when navigating to pref
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -649,7 +649,7 @@ exports[`remove helm repository from list of active repositories in preferences
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1363,7 +1363,7 @@ exports[`remove helm repository from list of active repositories in preferences
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2077,7 +2077,7 @@ exports[`remove helm repository from list of active repositories in preferences
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2808,7 +2808,7 @@ exports[`remove helm repository from list of active repositories in preferences
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import activeHotbarIdInjectable from "./active-id.injectable";
import computeHotbarIndexInjectable from "./compute-hotbar-index.injectable";
const activeHotbarIndexInjectable = getInjectable({
id: "active-hotbar-index",
instantiate: (di) => {
const computeHotbarIndex = di.inject(computeHotbarIndexInjectable);
const activeHotbarId = di.inject(activeHotbarIdInjectable);
return computed(() => {
const activeId = activeHotbarId.get();
return (activeId && computeHotbarIndex(activeId)) || 0;
});
},
});
export default activeHotbarIndexInjectable;
@@ -0,0 +1,13 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { observable } from "mobx";
const activeHotbarIdInjectable = getInjectable({
id: "active-hotbar-id",
instantiate: () => observable.box<string>(),
});
export default activeHotbarIdInjectable;
@@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import activeHotbarIdInjectable from "./active-id.injectable";
import hotbarsStateInjectable from "./state.injectable";
const activeHotbarInjectable = getInjectable({
id: "active-hotbar",
instantiate: (di) => {
const state = di.inject(hotbarsStateInjectable);
const activeHotbarId = di.inject(activeHotbarIdInjectable);
return computed(() => {
const id = activeHotbarId.get();
return (id && state.get(id)) || undefined;
});
},
});
export default activeHotbarInjectable;
@@ -0,0 +1,33 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { action } from "mobx";
import type { CreateHotbarData, CreateHotbarOptions } from "./types";
import activeHotbarIdInjectable from "./active-id.injectable";
import hotbarsStateInjectable from "./state.injectable";
import createHotbarInjectable from "./create-hotbar.injectable";
export type AddHotbar = (data: CreateHotbarData, { setActive }?: CreateHotbarOptions) => void;
const addHotbarInjectable = getInjectable({
id: "add-hotbar",
instantiate: (di): AddHotbar => {
const state = di.inject(hotbarsStateInjectable);
const activeHotbarId = di.inject(activeHotbarIdInjectable);
const createHotbar = di.inject(createHotbarInjectable);
return action((data, { setActive = false } = {}) => {
const hotbar = createHotbar(data);
state.set(hotbar.id, hotbar);
if (setActive) {
activeHotbarId.set(hotbar.id);
}
});
},
});
export default addHotbarInjectable;
@@ -0,0 +1,19 @@
/**
* 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 computeHotbarIndexInjectable from "./compute-hotbar-index.injectable";
export type ComputeDisplayIndex = (hotbarId: string) => string;
const computeDisplayIndexInjectable = getInjectable({
id: "compute-display-index",
instantiate: (di): ComputeDisplayIndex => {
const computeHotbarIndex = di.inject(computeHotbarIndexInjectable);
return (hotbarId) => `${computeHotbarIndex(hotbarId) + 1}`;
},
});
export default computeDisplayIndexInjectable;
@@ -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 type { Hotbar } from "./hotbar";
import computeDisplayIndexInjectable from "./compute-display-index.injectable";
export type ComputeHotbarDisplayLabel = (hotbar: Hotbar) => string;
const computeHotbarDisplayLabelInjectable = getInjectable({
id: "compute-hotbar-display-label",
instantiate: (di): ComputeHotbarDisplayLabel => {
const computeDisplayIndex = di.inject(computeDisplayIndexInjectable);
return (hotbar) => `${computeDisplayIndex(hotbar.id)}: ${hotbar.name}`;
},
});
export default computeHotbarDisplayLabelInjectable;
@@ -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 hotbarsStateInjectable from "./state.injectable";
export type ComputeHotbarIndex = (hotbarId: string) => number;
const computeHotbarIndexInjectable = getInjectable({
id: "compute-hotbar-index",
instantiate: (di): ComputeHotbarIndex => {
const state = di.inject(hotbarsStateInjectable);
return (hotbarId) => {
let i = 0;
for (const hotbar of state.values()) {
if (hotbar.id === hotbarId) {
return i;
}
i += 1;
}
return 0;
};
},
});
export default computeHotbarIndexInjectable;
@@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { CreateHotbarData } from "./types";
import prefixedLoggerInjectable from "../../../../common/logger/prefixed-logger.injectable";
import type { HotbarDependencies } from "./hotbar";
import { Hotbar } from "./hotbar";
export type CreateHotbar = (data: CreateHotbarData) => Hotbar;
const createHotbarInjectable = getInjectable({
id: "create-hotbar",
instantiate: (di): CreateHotbar => {
const deps: HotbarDependencies = {
logger: di.inject(prefixedLoggerInjectable, "HOTBAR"),
};
return (data) => new Hotbar(deps, data);
},
});
export default createHotbarInjectable;
@@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { iter } from "@k8slens/utilities";
import { getInjectable } from "@ogre-tools/injectable";
import type { Hotbar } from "./hotbar";
import hotbarsStateInjectable from "./state.injectable";
export type FindHotbarByName = (name: string) => Hotbar | undefined;
const findHotbarByNameInjectable = getInjectable({
id: "find-hotbar-by-name",
instantiate: (di): FindHotbarByName => {
const state = di.inject(hotbarsStateInjectable);
return (name) => iter.find(state.values(), hotbar => hotbar.name.get() === name);
},
});
export default findHotbarByNameInjectable;
@@ -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 type { Hotbar } from "./hotbar";
import hotbarsStateInjectable from "./state.injectable";
export type GetHotbarById = (id: string) => Hotbar | undefined;
const getHotbarByIdInjectable = getInjectable({
id: "get-hotbar-by-id",
instantiate: (di): GetHotbarById => {
const state = di.inject(hotbarsStateInjectable);
return (id) => state.get(id);
},
});
export default getHotbarByIdInjectable;
@@ -0,0 +1,175 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { type IObservableValue, type IObservableArray, observable, runInAction, toJS } from "mobx";
import type { CatalogEntity } from "../../../../common/catalog";
import { getShortName } from "../../../../common/catalog/helpers";
import type { HotbarItem, CreateHotbarData } from "./types";
import { defaultHotbarCells } from "./types";
import { broadcastMessage } from "../../../../common/ipc";
import { hotbarTooManyItemsChannel } from "../../../../common/ipc/hotbar";
import * as uuid from "uuid";
import type { Logger } from "../../../../common/logger";
import { tuple } from "@k8slens/utilities";
export interface HotbarDependencies {
readonly logger: Logger;
}
export interface HotbarData {
readonly id: string;
readonly name: string;
readonly items: (HotbarItem | null)[];
}
export class Hotbar {
readonly id: string;
readonly name: IObservableValue<string>;
readonly items: IObservableArray<HotbarItem | null>;
constructor(private readonly dependencies: HotbarDependencies, data: CreateHotbarData) {
this.id = data.id ?? uuid.v4();
this.name = observable.box(data.name);
this.items = observable.array(data.items ?? tuple.filled(defaultHotbarCells, null));
}
isFull() {
for (const item of this.items) {
if (!item) {
return false;
}
}
return true;
}
hasEntity(entityId: string) {
return this.items.findIndex(item => item?.entity.uid === entityId) >= 0;
}
private findClosestEmptyIndex(from: number, direction = 1) {
let index = from;
while (this.items[index] != null) {
index += direction;
}
return index;
}
restack(from: number, to: number) {
runInAction(() => {
const source = this.items[from];
const moveDown = from < to;
if (
from < 0 ||
to < 0 ||
from >= this.items.length ||
to >= this.items.length ||
isNaN(from) ||
isNaN(to)
) {
throw new Error("Invalid 'from' or 'to' arguments");
}
if (from == to) {
return;
}
this.items.splice(from, 1, null);
if (this.items[to] == null) {
this.items.splice(to, 1, source);
} else {
// Move cells up or down to closes empty cell
this.items.splice(this.findClosestEmptyIndex(to, moveDown ? -1 : 1), 1);
this.items.splice(to, 0, source);
}
});
}
toggleEntity(item: CatalogEntity) {
runInAction(() => {
if (this.hasEntity(item.getId())) {
this.removeEntity(item.getId());
} else {
this.addEntity(item);
}
});
}
removeEntity(uid: string) {
runInAction(() => {
const index = this.items.findIndex((item) => item?.entity.uid === uid);
if (index < 0) {
return;
}
this.items[index] = null;
});
}
addEntity(item: CatalogEntity, cellIndex?: number) {
const uid = item.getId();
const name = item.getName();
const shortName = getShortName(item);
if (typeof uid !== "string") {
throw new TypeError("CatalogEntity's ID must be a string");
}
if (typeof name !== "string") {
throw new TypeError("CatalogEntity's NAME must be a string");
}
if (typeof shortName !== "string") {
throw new TypeError("CatalogEntity's SHORT_NAME must be a string");
}
if (this.hasEntity(item.getId())) {
return;
}
const entity = {
uid,
name,
source: item.metadata.source,
shortName,
};
const newItem = { entity };
if (cellIndex === undefined) {
// Add item to empty cell
const emptyCellIndex = this.items.indexOf(null);
if (emptyCellIndex >= 0) {
runInAction(() => {
this.items[emptyCellIndex] = newItem;
});
} else {
broadcastMessage(hotbarTooManyItemsChannel);
}
} else if (0 <= cellIndex && cellIndex < this.items.length) {
runInAction(() => {
this.items[cellIndex] = newItem;
});
} else {
this.dependencies.logger.error(
"cannot pin entity to hotbar outside of index range",
{ entityId: uid, hotbarId: this.id, cellIndex },
);
}
}
toJSON(): HotbarData {
return {
id: this.id,
items: toJS(this.items),
name: this.name.get(),
};
}
}
@@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import hotbarsStateInjectable from "./state.injectable";
const hotbarsInjectable = getInjectable({
id: "hotbars",
instantiate: (di) => {
const state = di.inject(hotbarsStateInjectable);
return computed(() => [...state.values()]);
},
});
export default hotbarsInjectable;
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { MigrationDeclaration } from "../../../../common/persistent-storage/migrations.injectable";
export const hotbarStoreMigrationInjectionToken = getInjectionToken<MigrationDeclaration>({
id: "hotbar-store-migration-token",
});
@@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { action } from "mobx";
import hotbarsInjectable from "./hotbars.injectable";
export type RemoveEntityFromAllHotbars = (entityId: string) => void;
const removeEntityFromAllHotbarsInjectable = getInjectable({
id: "remove-entity-from-all-hotbars",
instantiate: (di): RemoveEntityFromAllHotbars => {
const hotbars = di.inject(hotbarsInjectable);
return action((entityId) => {
for (const hotbar of hotbars.get()) {
hotbar.removeEntity(entityId);
}
});
},
});
export default removeEntityFromAllHotbarsInjectable;
@@ -0,0 +1,33 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { iter } from "@k8slens/utilities";
import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { action } from "mobx";
import activeHotbarIdInjectable from "./active-id.injectable";
import type { Hotbar } from "./hotbar";
import hotbarsStateInjectable from "./state.injectable";
export type RemoveHotbar = (hotbar: Hotbar) => void;
const removeHotbarInjectable = getInjectable({
id: "remove-hotbar",
instantiate: (di): RemoveHotbar => {
const state = di.inject(hotbarsStateInjectable);
const activeHotbarId = di.inject(activeHotbarIdInjectable);
return action((hotbar) => {
assert(state.size >= 2, "Cannot remove the last hotbar");
state.delete(hotbar.id);
if (activeHotbarId.get() === hotbar.id) {
activeHotbarId.set(iter.first(state.values())?.id);
}
});
},
});
export default removeHotbarInjectable;
@@ -0,0 +1,40 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { iter } from "@k8slens/utilities";
import { getInjectable } from "@ogre-tools/injectable";
import { action } from "mobx";
import activeHotbarIdInjectable from "./active-id.injectable";
import type { Hotbar } from "./hotbar";
import hotbarsStateInjectable from "./state.injectable";
export type SetAsActiveHotbar = (desc: Hotbar | number | string) => void;
const setAsActiveHotbarInjectable = getInjectable({
id: "set-as-active-hotbar",
instantiate: (di): SetAsActiveHotbar => {
const hotbarsState = di.inject(hotbarsStateInjectable);
const activeHotbarId = di.inject(activeHotbarIdInjectable);
return action((desc) => {
if (typeof desc === "number") {
const hotbar = iter.nth(hotbarsState.values(), desc);
if (hotbar) {
activeHotbarId.set(hotbar.id);
}
} else if (typeof desc === "string") {
if (hotbarsState.has(desc)) {
activeHotbarId.set(desc);
}
} else {
if (hotbarsState.has(desc.id)) {
activeHotbarId.set(desc.id);
}
}
});
},
});
export default setAsActiveHotbarInjectable;
@@ -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 { observable } from "mobx";
import type { Hotbar } from "./hotbar";
const hotbarsStateInjectable = getInjectable({
id: "hotbars-state",
instantiate: () => observable.map<string, Hotbar>(),
});
export default hotbarsStateInjectable;
@@ -0,0 +1,116 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { iter } from "@k8slens/utilities";
import { getInjectable } from "@ogre-tools/injectable";
import { action, comparer } from "mobx";
import catalogCatalogEntityInjectable from "../../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import { hotbarStoreMigrationInjectionToken } from "./migrations-token";
import { defaultHotbarCells } from "./types";
import createPersistentStorageInjectable from "../../../../common/persistent-storage/create.injectable";
import persistentStorageMigrationsInjectable from "../../../../common/persistent-storage/migrations.injectable";
import storeMigrationVersionInjectable from "../../../../common/vars/store-migration-version.injectable";
import activeHotbarIdInjectable from "./active-id.injectable";
import createHotbarInjectable from "./create-hotbar.injectable";
import type { Hotbar, HotbarData } from "./hotbar";
import hotbarsStateInjectable from "./state.injectable";
export interface HotbarStoreModel {
hotbars: HotbarData[];
activeHotbarId: string | undefined;
}
const hotbarsPersistentStorageInjectable = getInjectable({
id: "hotbars-persistent-storage",
instantiate: (di) => {
const state = di.inject(hotbarsStateInjectable);
const createPersistentStorage = di.inject(createPersistentStorageInjectable);
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
const activeHotbarId = di.inject(activeHotbarIdInjectable);
const createHotbar = di.inject(createHotbarInjectable);
return createPersistentStorage<HotbarStoreModel>({
configName: "lens-hotbar-store",
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
syncOptions: {
equals: comparer.structural,
},
projectVersion: di.inject(storeMigrationVersionInjectable),
migrations: di.inject(persistentStorageMigrationsInjectable, hotbarStoreMigrationInjectionToken),
fromStore: action((data) => {
if (!data.hotbars || !data.hotbars.length) {
const hotbar = createHotbar({
name: "Default",
});
const {
metadata: {
uid,
name,
source,
},
} = catalogCatalogEntity;
hotbar.items[0] = {
entity: {
uid,
name,
source,
},
};
state.replace([[hotbar.id, hotbar]]);
} else {
state.replace(data.hotbars.map((hotbar) => [hotbar.id, createHotbar(hotbar)]));
}
for (const hotbar of state.values()) {
ensureExactHotbarItemLength(hotbar);
}
if (data.activeHotbarId) {
activeHotbarId.set(data.activeHotbarId);
}
const firstHotbarId = iter.first(state.values())?.id;
if (!activeHotbarId.get()) {
activeHotbarId.set(firstHotbarId);
} else if (!iter.find(state.values(), hotbar => hotbar.id === activeHotbarId.get())) {
activeHotbarId.set(firstHotbarId);
}
}),
toJSON: () => ({
hotbars: iter.chain(state.values())
.map(hotbar => hotbar.toJSON())
.toArray(),
activeHotbarId: activeHotbarId.get(),
}),
});
},
});
export default hotbarsPersistentStorageInjectable;
/**
* This function ensures that there are always exactly `defaultHotbarCells`
* worth of items in the hotbar.
* @param hotbar The hotbar to modify
*/
function ensureExactHotbarItemLength(hotbar: Hotbar) {
// if there are not enough items
while (hotbar.items.length < defaultHotbarCells) {
hotbar.items.push(null);
}
// if for some reason the hotbar was overfilled before, remove as many entries
// as needed, but prefer empty slots and items at the end first.
while (hotbar.items.length > defaultHotbarCells) {
const lastNull = hotbar.items.lastIndexOf(null);
if (lastNull >= 0) {
hotbar.items.splice(lastNull, 1);
} else {
hotbar.items.length = defaultHotbarCells;
}
}
}
@@ -0,0 +1,32 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { action } from "mobx";
import activeHotbarIndexInjectable from "./active-hotbar-index.injectable";
import setAsActiveHotbarInjectable from "./set-as-active.injectable";
import hotbarsStateInjectable from "./state.injectable";
export type SwitchToNextHotbar = () => void;
const switchToNextHotbarInjectable = getInjectable({
id: "switch-to-next-hotbar",
instantiate: (di): SwitchToNextHotbar => {
const setAsActiveHotbar = di.inject(setAsActiveHotbarInjectable);
const activeHotbarIndex = di.inject(activeHotbarIndexInjectable);
const state = di.inject(hotbarsStateInjectable);
return action(() => {
const index = activeHotbarIndex.get() + 1;
if (index >= state.size) {
setAsActiveHotbar(0);
} else {
setAsActiveHotbar(index);
}
});
},
});
export default switchToNextHotbarInjectable;
@@ -0,0 +1,32 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { action } from "mobx";
import activeHotbarIndexInjectable from "./active-hotbar-index.injectable";
import setAsActiveHotbarInjectable from "./set-as-active.injectable";
import hotbarsStateInjectable from "./state.injectable";
export type SwitchToPreviousHotbar = () => void;
const switchToPreviousHotbarInjectable = getInjectable({
id: "switch-to-previous-hotbar",
instantiate: (di): SwitchToPreviousHotbar => {
const setAsActiveHotbar = di.inject(setAsActiveHotbarInjectable);
const activeHotbarIndex = di.inject(activeHotbarIndexInjectable);
const state = di.inject(hotbarsStateInjectable);
return action(() => {
const index = activeHotbarIndex.get() - 1;
if (index < 0) {
setAsActiveHotbar(state.size - 1);
} else {
setAsActiveHotbar(index);
}
});
},
});
export default switchToPreviousHotbarInjectable;
@@ -0,0 +1,25 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import activeHotbarInjectable from "./active.injectable";
import type { Hotbar } from "./hotbar";
export type ActiveHotbarModel = Pick<Hotbar, "hasEntity" | "addEntity" | "removeEntity" | "toggleEntity">;
const activeHotbarModelInjectable = getInjectable({
id: "active-hotbar-model",
instantiate: (di): ActiveHotbarModel => {
const activeHotbar = di.inject(activeHotbarInjectable);
return {
hasEntity: (entityId) => activeHotbar.get()?.hasEntity(entityId) ?? false,
toggleEntity: (entity) => activeHotbar.get()?.toggleEntity(entity),
addEntity: (entity) => activeHotbar.get()?.addEntity(entity),
removeEntity: (entityId) => activeHotbar.get()?.removeEntity(entityId),
};
},
});
export default activeHotbarModelInjectable;
@@ -0,0 +1,28 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface HotbarItem {
entity: {
uid: string;
name: string;
source?: string;
};
params?: {
[key: string]: string;
};
}
export interface CreateHotbarData {
id?: string;
name: string;
items?: (HotbarItem | null)[];
}
export interface CreateHotbarOptions {
setActive?: boolean;
}
export const defaultHotbarCells = 12; // Number is chosen to easy hit any item with keyboard
@@ -0,0 +1,30 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
// Cleans up a store that had the state related data stored
import catalogCatalogEntityInjectable from "../../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import { getInjectable } from "@ogre-tools/injectable";
import { hotbarStoreMigrationInjectionToken } from "../common/migrations-token";
import createHotbarInjectable from "../common/create-hotbar.injectable";
const v500Alpha0HotbarStoreMigrationInjectable = getInjectable({
id: "v5.0.0-alpha.0-hotbar-store-migration",
instantiate: (di) => ({
version: "5.0.0-alpha.0",
run(store) {
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
const createHotbar = di.inject(createHotbarInjectable);
const hotbar = createHotbar({ name: "default" });
hotbar.addEntity(catalogCatalogEntity);
store.set("hotbars", [hotbar.toJSON()]);
},
}),
injectionToken: hotbarStoreMigrationInjectionToken,
});
export default v500Alpha0HotbarStoreMigrationInjectable;
@@ -0,0 +1,30 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
// Cleans up a store that had the state related data stored
import * as uuid from "uuid";
import { getInjectable } from "@ogre-tools/injectable";
import { hotbarStoreMigrationInjectionToken } from "../common/migrations-token";
import type { HotbarData } from "../common/hotbar";
const v500Alpha2HotbarStoreMigrationInjectable = getInjectable({
id: "v5.0.0-alpha.2-hotbar-store-migration",
instantiate: () => ({
version: "5.0.0-alpha.2",
run(store) {
const rawHotbars = store.get("hotbars");
const hotbars: HotbarData[] = Array.isArray(rawHotbars) ? rawHotbars : [];
store.set("hotbars", hotbars.map(({ id, ...rest }) => ({
id: id || uuid.v4(),
...rest,
})));
},
}),
injectionToken: hotbarStoreMigrationInjectionToken,
});
export default v500Alpha2HotbarStoreMigrationInjectable;
@@ -0,0 +1,168 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import * as uuid from "uuid";
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import catalogCatalogEntityInjectable from "../../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import { isDefined, isErrnoException } from "@k8slens/utilities";
import joinPathsInjectable from "../../../../common/path/join-paths.injectable";
import { getInjectable } from "@ogre-tools/injectable";
import { hotbarStoreMigrationInjectionToken } from "../common/migrations-token";
import readJsonSyncInjectable from "../../../../common/fs/read-json-sync.injectable";
import loggerInjectable from "../../../../common/logger.injectable";
import { generateNewIdFor } from "../../../../common/utils/generate-new-id-for";
import type { ClusterModel } from "../../../../common/cluster-types";
import { defaultHotbarCells } from "../common/types";
import type { HotbarData } from "../common/hotbar";
import createHotbarInjectable from "../common/create-hotbar.injectable";
interface Pre500WorkspaceStoreModel {
workspaces: {
id: string;
name: string;
}[];
}
interface Pre500ClusterModel extends ClusterModel {
workspace?: string;
workspaces?: string[];
}
interface Pre500ClusterStoreModel {
clusters?: Pre500ClusterModel[];
}
const v500Beta10HotbarStoreMigrationInjectable = getInjectable({
id: "v5.0.0-beta.10-hotbar-store-migration",
instantiate: (di) => ({
version: "5.0.0-beta.10",
run(store) {
const userDataPath = di.inject(directoryForUserDataInjectable);
const joinPaths = di.inject(joinPathsInjectable);
const readJsonSync = di.inject(readJsonSyncInjectable);
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
const logger = di.inject(loggerInjectable);
const createHotbar = di.inject(createHotbarInjectable);
const rawHotbars = store.get("hotbars");
const hotbars: HotbarData[] = Array.isArray(rawHotbars) ? rawHotbars.filter(h => h && typeof h === "object") : [];
// Hotbars might be empty, if some of the previous migrations weren't run
if (hotbars.length === 0) {
const hotbar = createHotbar({ name: "default" });
hotbar.addEntity(catalogCatalogEntity);
hotbars.push(hotbar.toJSON());
}
try {
const workspaceStoreData: Pre500WorkspaceStoreModel = readJsonSync(joinPaths(userDataPath, "lens-workspace-store.json"));
const { clusters = [] }: Pre500ClusterStoreModel = readJsonSync(joinPaths(userDataPath, "lens-cluster-store.json"));
const workspaceHotbars = new Map<string, HotbarData>(); // mapping from WorkspaceId to HotBar
for (const { id, name } of workspaceStoreData.workspaces) {
logger.info(`Creating new hotbar for ${name}`);
workspaceHotbars.set(id, {
id: uuid.v4(),
items: [],
name: `Workspace: ${name}`,
});
}
{
// grab the default named hotbar or the first.
const defaultHotbarIndex = Math.max(0, hotbars.findIndex(hotbar => hotbar.name === "default"));
const [{ name, id, items }] = hotbars.splice(defaultHotbarIndex, 1);
workspaceHotbars.set("default", {
name,
id,
items: items.filter(isDefined),
});
}
for (const cluster of clusters) {
const uid = generateNewIdFor(cluster);
for (const workspaceId of cluster.workspaces ?? [cluster.workspace].filter(isDefined)) {
const workspaceHotbar = workspaceHotbars.get(workspaceId);
if (!workspaceHotbar) {
logger.info(`Cluster ${uid} has unknown workspace ID, skipping`);
continue;
}
logger.info(`Adding cluster ${uid} to ${workspaceHotbar.name}`);
if (workspaceHotbar?.items.length < defaultHotbarCells) {
workspaceHotbar.items.push({
entity: {
uid: generateNewIdFor(cluster),
name: cluster.preferences?.clusterName || cluster.contextName,
},
});
}
}
}
for (const hotbar of workspaceHotbars.values()) {
if (hotbar.items.length === 0) {
logger.info(`Skipping ${hotbar.name} due to it being empty`);
continue;
}
while (hotbar.items.length < defaultHotbarCells) {
hotbar.items.push(null);
}
hotbars.push(hotbar);
}
/**
* Finally, make sure that the catalog entity hotbar item is in place.
* Just in case something else removed it.
*
* if every hotbar has elements that all not the `catalog-entity` item
*/
if (hotbars.every(hotbar => hotbar.items.every(item => item?.entity?.uid !== "catalog-entity"))) {
// note, we will add a new whole hotbar here called "default" if that was previously removed
const defaultHotbarIndex = hotbars.findIndex(hotbar => hotbar.name === "default");
if (defaultHotbarIndex >= 0) {
const defaultHotbar = createHotbar(hotbars[defaultHotbarIndex]);
if (defaultHotbar.isFull()) {
// making a new hotbar is less destructive if the first hotbar
// called "default" is full than overriding a hotbar item
const hotbar = createHotbar({ name: "initial" });
hotbar.addEntity(catalogCatalogEntity);
hotbars.unshift(hotbar.toJSON());
} else {
defaultHotbar.addEntity(catalogCatalogEntity);
hotbars[defaultHotbarIndex] = defaultHotbar.toJSON();
}
} else {
const hotbar = createHotbar({ name: "default" });
hotbar.addEntity(catalogCatalogEntity);
hotbars.unshift(hotbar.toJSON());
}
}
} catch (error) {
// ignore files being missing
if (isErrnoException(error) && error.code !== "ENOENT") {
throw error;
}
}
store.set("hotbars", hotbars);
},
}),
injectionToken: hotbarStoreMigrationInjectionToken,
});
export default v500Beta10HotbarStoreMigrationInjectable;
@@ -0,0 +1,51 @@
/**
* 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 "../../../../main/catalog/entity-registry.injectable";
import type { HotbarData } from "../common/hotbar";
import { hotbarStoreMigrationInjectionToken } from "../common/migrations-token";
const v500Beta5HotbarStoreMigrationInjectable = getInjectable({
id: "v500-beta5-hotbar-store-migration",
instantiate: (di) => ({
version: "5.0.0-beta.5",
run(store) {
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
const rawHotbars = store.get("hotbars");
const hotbars: HotbarData[] = Array.isArray(rawHotbars) ? rawHotbars : [];
for (const hotbar of hotbars) {
for (let i = 0; i < hotbar.items.length; i += 1) {
const item = hotbar.items[i];
if (!item) {
continue;
}
const entity = catalogEntityRegistry.findById(item.entity.uid);
if (!entity) {
// Clear disabled item
hotbar.items[i] = null;
} else {
// Save additional data
item.entity = {
...item.entity,
name: entity.metadata.name,
source: entity.metadata.source,
};
}
}
}
store.set("hotbars", hotbars);
},
}),
injectionToken: hotbarStoreMigrationInjectionToken,
});
export default v500Beta5HotbarStoreMigrationInjectable;
@@ -3,21 +3,21 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
import setupSyncingOfGeneralCatalogEntitiesInjectable from "../../../../main/start-main-application/runnables/setup-syncing-of-general-catalog-entities.injectable";
import hotbarsPersistentStorageInjectable from "../common/storage.injectable";
const initHotbarStoreInjectable = getInjectable({
id: "init-hotbar-store",
const loadHotbarStorageInjectable = getInjectable({
id: "load-hotbar-storage",
instantiate: (di) => ({
run: () => {
const hotbarStore = di.inject(hotbarStoreInjectable);
const storage = di.inject(hotbarsPersistentStorageInjectable);
hotbarStore.load();
storage.loadAndStartSyncing();
},
runAfter: setupSyncingOfGeneralCatalogEntitiesInjectable,
}),
injectionToken: onLoadOfApplicationInjectionToken,
});
export default initHotbarStoreInjectable;
export default loadHotbarStorageInjectable;
@@ -3,21 +3,21 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens";
import initClusterStoreInjectable from "../../../cluster/store/renderer/init.injectable";
import initClusterStoreInjectable from "../../../cluster/storage/renderer/init.injectable";
import hotbarsPersistentStorageInjectable from "../common/storage.injectable";
const initHotbarStoreInjectable = getInjectable({
id: "init-hotbar-store",
const loadHotbarStorageInjectable = getInjectable({
id: "load-hotbar-storage",
instantiate: (di) => ({
run: () => {
const hotbarStore = di.inject(hotbarStoreInjectable);
const storage = di.inject(hotbarsPersistentStorageInjectable);
hotbarStore.load();
storage.loadAndStartSyncing();
},
runAfter: initClusterStoreInjectable,
}),
injectionToken: beforeFrameStartsSecondInjectionToken,
});
export default initHotbarStoreInjectable;
export default loadHotbarStorageInjectable;
@@ -0,0 +1,373 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { anyObject } from "jest-mock-extended";
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../../../common/catalog";
import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting";
import type { DiContainer } from "@ogre-tools/injectable";
import catalogEntityRegistryInjectable from "../../../main/catalog/entity-registry.injectable";
import type { IComputedValue } from "mobx";
import { computed } from "mobx";
import hasCategoryForEntityInjectable from "../../../common/catalog/has-category-for-entity.injectable";
import catalogCatalogEntityInjectable from "../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import loggerInjectable from "../../../common/logger.injectable";
import type { Logger } from "../../../common/logger";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import storeMigrationVersionInjectable from "../../../common/vars/store-migration-version.injectable";
import writeJsonSyncInjectable from "../../../common/fs/write-json-sync.injectable";
import type { SetAsActiveHotbar } from "./common/set-as-active.injectable";
import setAsActiveHotbarInjectable from "./common/set-as-active.injectable";
import hotbarsPersistentStorageInjectable from "./common/storage.injectable";
import type { Hotbar } from "./common/hotbar";
import hotbarsInjectable from "./common/hotbars.injectable";
import activeHotbarInjectable from "./common/active.injectable";
import type { AddHotbar } from "./common/add.injectable";
import type { GetHotbarById } from "./common/get-by-id.injectable";
import getHotbarByIdInjectable from "./common/get-by-id.injectable";
import addHotbarInjectable from "./common/add.injectable";
import { defaultHotbarCells } from "./common/types";
function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
return {
getName: jest.fn(() => data.metadata?.name),
getId: jest.fn(() => data.metadata?.uid),
getSource: jest.fn(() => data.metadata?.source ?? "unknown"),
isEnabled: jest.fn(() => data.status?.enabled ?? true),
onContextMenuOpen: jest.fn(),
onSettingsOpen: jest.fn(),
metadata: {},
spec: {},
status: {},
...data,
} as CatalogEntity;
}
describe("Hotbars technical tests", () => {
let di: DiContainer;
let testCluster: CatalogEntity;
let minikubeCluster: CatalogEntity;
let awsCluster: CatalogEntity;
let loggerMock: jest.Mocked<Logger>;
let setAsActiveHotbar: SetAsActiveHotbar;
let hotbars: IComputedValue<Hotbar[]>;
let activeHotbar: IComputedValue<Hotbar | undefined>;
let addHotbar: AddHotbar;
let getHotbarById: GetHotbarById;
beforeEach(async () => {
di = getDiForUnitTesting();
testCluster = getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "some-test-id",
name: "my-test-cluster",
source: "local",
labels: {},
},
});
minikubeCluster = getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "some-minikube-id",
name: "my-minikube-cluster",
source: "local",
labels: {},
},
});
awsCluster = getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "some-aws-id",
name: "my-aws-cluster",
source: "local",
labels: {},
},
});
di.override(hasCategoryForEntityInjectable, () => () => true);
loggerMock = {
warn: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
info: jest.fn(),
silly: jest.fn(),
};
di.override(loggerInjectable, () => loggerMock);
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
catalogEntityRegistry.addComputedSource("some-id", computed(() => [
testCluster,
minikubeCluster,
awsCluster,
catalogCatalogEntity,
]));
setAsActiveHotbar = di.inject(setAsActiveHotbarInjectable);
hotbars = di.inject(hotbarsInjectable);
activeHotbar = di.inject(activeHotbarInjectable);
addHotbar = di.inject(addHotbarInjectable);
getHotbarById = di.inject(getHotbarByIdInjectable);
});
describe("given no previous data in store, running all migrations", () => {
beforeEach(() => {
di.override(storeMigrationVersionInjectable, () => "9999.0.0");
di.inject(hotbarsPersistentStorageInjectable).loadAndStartSyncing();
});
describe("load", () => {
it("loads one hotbar by default", () => {
expect(hotbars.get().length).toEqual(1);
});
});
describe("add", () => {
it("adds a hotbar", () => {
addHotbar({ name: "hottest" });
expect(hotbars.get().length).toEqual(2);
});
});
describe("hotbar items", () => {
it("initially creates default number of empty cells", () => {
expect(activeHotbar.get()?.items?.length).toEqual(defaultHotbarCells);
});
it("initially adds catalog entity as first item", () => {
expect(activeHotbar.get()?.items[0]?.entity.name).toEqual("Catalog");
});
it("adds items", () => {
activeHotbar.get()?.addEntity(testCluster);
const items = activeHotbar.get()?.items.filter(Boolean);
expect(items?.length).toEqual(2);
});
it("removes items", () => {
activeHotbar.get()?.addEntity(testCluster);
activeHotbar.get()?.removeEntity("some-test-id");
activeHotbar.get()?.removeEntity("catalog-entity");
const items = activeHotbar.get()?.items.filter(Boolean);
expect(items).toStrictEqual([]);
});
it("does nothing if removing with invalid uid", () => {
activeHotbar.get()?.addEntity(testCluster);
activeHotbar.get()?.removeEntity("invalid uid");
const items = activeHotbar.get()?.items.filter(Boolean);
expect(items?.length).toEqual(2);
});
it("moves item to empty cell", () => {
activeHotbar.get()?.addEntity(testCluster);
activeHotbar.get()?.addEntity(minikubeCluster);
activeHotbar.get()?.addEntity(awsCluster);
expect(activeHotbar.get()?.items[6]).toBeNull();
activeHotbar.get()?.restack(1, 5);
expect(activeHotbar.get()?.items[5]).toBeTruthy();
expect(activeHotbar.get()?.items[5]?.entity.uid).toEqual("some-test-id");
});
it("moves items down", () => {
activeHotbar.get()?.addEntity(testCluster);
activeHotbar.get()?.addEntity(minikubeCluster);
activeHotbar.get()?.addEntity(awsCluster);
// aws -> catalog
activeHotbar.get()?.restack(3, 0);
const items = activeHotbar.get()?.items.map(item => item?.entity.uid || null);
expect(items?.slice(0, 4)).toEqual(["some-aws-id", "catalog-entity", "some-test-id", "some-minikube-id"]);
});
it("moves items up", () => {
activeHotbar.get()?.addEntity(testCluster);
activeHotbar.get()?.addEntity(minikubeCluster);
activeHotbar.get()?.addEntity(awsCluster);
// test -> aws
activeHotbar.get()?.restack(1, 3);
const items = activeHotbar.get()?.items.map(item => item?.entity.uid || null);
expect(items?.slice(0, 4)).toEqual(["catalog-entity", "some-minikube-id", "some-aws-id", "some-test-id"]);
});
it("logs an error if cellIndex is out of bounds", () => {
addHotbar({ name: "hottest", id: "hottest" });
setAsActiveHotbar("hottest");
activeHotbar.get()?.addEntity(testCluster, -1);
expect(loggerMock.error).toBeCalledWith("[HOTBAR]: cannot pin entity to hotbar outside of index range", anyObject());
activeHotbar.get()?.addEntity(testCluster, 12);
expect(loggerMock.error).toBeCalledWith("[HOTBAR]: cannot pin entity to hotbar outside of index range", anyObject());
activeHotbar.get()?.addEntity(testCluster, 13);
expect(loggerMock.error).toBeCalledWith("[HOTBAR]: cannot pin entity to hotbar outside of index range", anyObject());
});
it("throws an error if getId is invalid or returns not a string", () => {
expect(() => activeHotbar.get()?.addEntity({} as any)).toThrowError(TypeError);
expect(() => activeHotbar.get()?.addEntity({ getId: () => true } as any)).toThrowError(TypeError);
});
it("throws an error if getName is invalid or returns not a string", () => {
expect(() => activeHotbar.get()?.addEntity({ getId: () => "" } as any)).toThrowError(TypeError);
expect(() => activeHotbar.get()?.addEntity({ getId: () => "", getName: () => 4 } as any)).toThrowError(TypeError);
});
it("does nothing when item moved to same cell", () => {
activeHotbar.get()?.addEntity(testCluster);
activeHotbar.get()?.restack(1, 1);
expect(activeHotbar.get()?.items[1]?.entity.uid).toEqual("some-test-id");
});
it("new items takes first empty cell", () => {
activeHotbar.get()?.addEntity(testCluster);
activeHotbar.get()?.addEntity(awsCluster);
activeHotbar.get()?.restack(0, 3);
activeHotbar.get()?.addEntity(minikubeCluster);
expect(activeHotbar.get()?.items[0]?.entity.uid).toEqual("some-minikube-id");
});
it("throws if invalid arguments provided", () => {
activeHotbar.get()?.addEntity(testCluster);
expect(() => activeHotbar.get()?.restack(-5, 0)).toThrow();
expect(() => activeHotbar.get()?.restack(2, -1)).toThrow();
expect(() => activeHotbar.get()?.restack(14, 1)).toThrow();
expect(() => activeHotbar.get()?.restack(11, 112)).toThrow();
});
it("checks if entity already pinned to hotbar", () => {
activeHotbar.get()?.addEntity(testCluster);
expect(activeHotbar.get()?.hasEntity(testCluster.getId())).toBeTruthy();
expect(activeHotbar.get()?.hasEntity(awsCluster.getId())).toBeFalsy();
});
});
});
describe("given data from 5.0.0-beta.3 and version being 5.0.0-beta.10", () => {
beforeEach(() => {
const writeJsonSync = di.inject(writeJsonSyncInjectable);
writeJsonSync("/some-directory-for-user-data/lens-hotbar-store.json", {
__internal__: {
migrations: {
version: "5.0.0-beta.3",
},
},
hotbars: [
{
id: "3caac17f-aec2-4723-9694-ad204465d935",
name: "myhotbar",
items: [
{
entity: {
uid: "some-aws-id",
},
},
{
entity: {
uid: "55b42c3c7ba3b04193416cda405269a5",
},
},
{
entity: {
uid: "176fd331968660832f62283219d7eb6e",
},
},
{
entity: {
uid: "61c4fb45528840ebad1badc25da41d14",
name: "user1-context",
source: "local",
},
},
{
entity: {
uid: "27d6f99fe9e7548a6e306760bfe19969",
name: "foo2",
source: "local",
},
},
null,
{
entity: {
uid: "c0b20040646849bb4dcf773e43a0bf27",
name: "multinode-demo",
source: "local",
},
},
null,
null,
null,
null,
null,
],
},
],
});
di.override(storeMigrationVersionInjectable, () => "5.0.0-beta.10");
di.inject(hotbarsPersistentStorageInjectable).loadAndStartSyncing();
});
it("allows to retrieve a hotbar", () => {
const hotbar = getHotbarById("3caac17f-aec2-4723-9694-ad204465d935");
expect(hotbar?.id).toBe("3caac17f-aec2-4723-9694-ad204465d935");
});
it("clears cells without entity", () => {
const items = hotbars.get()[0].items;
expect(items[2]).toBeNull();
});
it("adds extra data to cells with according entity", () => {
const items = hotbars.get()[0].items;
expect(items[0]).toEqual({
entity: {
name: "my-aws-cluster",
source: "local",
uid: "some-aws-id",
},
});
});
});
});
@@ -709,7 +709,7 @@ exports[`preferences - closing-preferences given accessing preferences directly
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1057,7 +1057,7 @@ exports[`preferences - closing-preferences given accessing preferences directly
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1263,7 +1263,7 @@ exports[`preferences - closing-preferences given accessing preferences directly
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1469,7 +1469,7 @@ exports[`preferences - closing-preferences given accessing preferences directly
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2231,7 +2231,7 @@ exports[`preferences - closing-preferences given already in a page and then navi
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2579,7 +2579,7 @@ exports[`preferences - closing-preferences given already in a page and then navi
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2785,7 +2785,7 @@ exports[`preferences - closing-preferences given already in a page and then navi
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2991,7 +2991,7 @@ exports[`preferences - closing-preferences given already in a page and then navi
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -722,7 +722,7 @@ exports[`preferences: extension adding preference tabs given in preferences, whe
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -697,7 +697,7 @@ exports[`preferences - hiding-of-empty-branches, given in preferences page given
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1475,7 +1475,7 @@ exports[`preferences - hiding-of-empty-branches, given in preferences page given
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2265,7 +2265,7 @@ exports[`preferences - hiding-of-empty-branches, given in preferences page given
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -697,7 +697,7 @@ exports[`preferences - navigation to application preferences given in preference
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1478,7 +1478,7 @@ exports[`preferences - navigation to application preferences given in preference
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1884,7 +1884,7 @@ exports[`preferences - navigation to application preferences given in some child
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2634,7 +2634,7 @@ exports[`preferences - navigation to application preferences given in some child
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -3385,7 +3385,7 @@ exports[`preferences - navigation to application preferences given in some child
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -697,7 +697,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1336,7 +1336,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -710,7 +710,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1090,7 +1090,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1481,7 +1481,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1872,7 +1872,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2289,7 +2289,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -3039,7 +3039,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -3827,7 +3827,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -4232,7 +4232,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -4568,7 +4568,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -697,7 +697,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1374,7 +1374,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -697,7 +697,7 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1103,7 +1103,7 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -337,7 +337,7 @@ exports[`preferences - navigation to telemetry preferences given URL for Sentry
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1087,7 +1087,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1849,7 +1849,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2228,7 +2228,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -2567,7 +2567,7 @@ exports[`preferences - navigation to telemetry preferences given no URL for Sent
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -697,7 +697,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1310,7 +1310,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -230,7 +230,7 @@ exports[`preferences - navigation using application menu renders 1`] = `
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -980,7 +980,7 @@ exports[`preferences - navigation using application menu when navigating to pref
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -231,7 +231,7 @@ exports[`show-about-using-tray renders 1`] = `
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -983,7 +983,7 @@ exports[`show-about-using-tray when navigating using tray renders 1`] = `
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -375,7 +375,7 @@ exports[`preferences: URLs of legacy extensions given extension with both custom
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -804,7 +804,7 @@ exports[`preferences: URLs of legacy extensions given extension with both custom
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1195,7 +1195,7 @@ exports[`preferences: URLs of legacy extensions given extension with custom pref
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1560,7 +1560,7 @@ exports[`preferences: URLs of legacy extensions given extension with custom pref
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -1965,7 +1965,7 @@ exports[`preferences: URLs of legacy extensions given extension with custom pref
class="HotbarSelector"
>
<i
class="Icon Icon previous material interactive focusable"
class="Icon material interactive focusable"
tabindex="0"
>
<span
@@ -6,16 +6,16 @@ import React from "react";
import { SubTitle } from "../../../../../../renderer/components/layout/sub-title";
import { Select } from "../../../../../../renderer/components/select";
import { withInjectables } from "@ogre-tools/injectable-react";
import { defaultExtensionRegistryUrl, defaultExtensionRegistryUrlLocation } from "../../../../../../common/user-store/preferences-helpers";
import { Input } from "../../../../../../renderer/components/input";
import { isUrl } from "../../../../../../renderer/components/input/input_validators";
import type { UserStore } from "../../../../../../common/user-store";
import { runInAction } from "mobx";
import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable";
import { observer } from "mobx-react";
import type { UserPreferencesState } from "../../../../../user-preferences/common/state.injectable";
import userPreferencesStateInjectable from "../../../../../user-preferences/common/state.injectable";
import { defaultExtensionRegistryUrlLocation, defaultExtensionRegistryUrl } from "../../../../../user-preferences/common/preferences-helpers";
interface Dependencies {
userStore: UserStore;
state: UserPreferencesState;
}
const extensionInstallRegistryOptions = [
@@ -33,8 +33,8 @@ const extensionInstallRegistryOptions = [
},
] as const;
const NonInjectedExtensionInstallRegistry = observer(({ userStore }: Dependencies) => {
const [customUrl, setCustomUrl] = React.useState(userStore.extensionRegistryUrl.customUrl || "");
const NonInjectedExtensionInstallRegistry = observer(({ state }: Dependencies) => {
const [customUrl, setCustomUrl] = React.useState(state.extensionRegistryUrl.customUrl || "");
return (
<section id="extensionRegistryUrl">
@@ -42,14 +42,14 @@ const NonInjectedExtensionInstallRegistry = observer(({ userStore }: Dependencie
<Select
id="extension-install-registry-input"
options={extensionInstallRegistryOptions}
value={userStore.extensionRegistryUrl.location}
value={state.extensionRegistryUrl.location}
onChange={(value) =>
runInAction(() => {
userStore.extensionRegistryUrl.location =
state.extensionRegistryUrl.location =
value?.value ?? defaultExtensionRegistryUrlLocation;
if (userStore.extensionRegistryUrl.location === "custom") {
userStore.extensionRegistryUrl.customUrl = "";
if (state.extensionRegistryUrl.location === "custom") {
state.extensionRegistryUrl.customUrl = "";
}
})
}
@@ -69,20 +69,16 @@ const NonInjectedExtensionInstallRegistry = observer(({ userStore }: Dependencie
validators={isUrl}
value={customUrl}
onChange={setCustomUrl}
onBlur={() => (userStore.extensionRegistryUrl.customUrl = customUrl)}
onBlur={() => (state.extensionRegistryUrl.customUrl = customUrl)}
placeholder="Custom Extension Registry URL..."
disabled={userStore.extensionRegistryUrl.location !== "custom"}
disabled={state.extensionRegistryUrl.location !== "custom"}
/>
</section>
);
});
export const ExtensionInstallRegistry = withInjectables<Dependencies>(
NonInjectedExtensionInstallRegistry,
{
getProps: (di) => ({
userStore: di.inject(userStoreInjectable),
}),
},
);
export const ExtensionInstallRegistry = withInjectables<Dependencies>(NonInjectedExtensionInstallRegistry, {
getProps: (di) => ({
state: di.inject(userPreferencesStateInjectable),
}),
});
@@ -5,33 +5,29 @@
import React from "react";
import { SubTitle } from "../../../../../../renderer/components/layout/sub-title";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { UserStore } from "../../../../../../common/user-store";
import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable";
import { Switch } from "../../../../../../renderer/components/switch";
import { observer } from "mobx-react";
import type { UserPreferencesState } from "../../../../../user-preferences/common/state.injectable";
import userPreferencesStateInjectable from "../../../../../user-preferences/common/state.injectable";
interface Dependencies {
userStore: UserStore;
state: UserPreferencesState;
}
const NonInjectedStartUp = observer(({ userStore }: Dependencies) => (
const NonInjectedStartUp = observer(({ state }: Dependencies) => (
<section id="other">
<SubTitle title="Start-up" />
<Switch
checked={userStore.openAtLogin}
onChange={() => (userStore.openAtLogin = !userStore.openAtLogin)}
checked={state.openAtLogin}
onChange={() => (state.openAtLogin = !state.openAtLogin)}
>
Automatically start Lens on login
</Switch>
</section>
));
export const StartUp = withInjectables<Dependencies>(
NonInjectedStartUp,
{
getProps: (di) => ({
userStore: di.inject(userStoreInjectable),
}),
},
);
export const StartUp = withInjectables<Dependencies>(NonInjectedStartUp, {
getProps: (di) => ({
state: di.inject(userPreferencesStateInjectable),
}),
});
@@ -7,26 +7,26 @@ import { SubTitle } from "../../../../../../renderer/components/layout/sub-title
import { Select } from "../../../../../../renderer/components/select";
import { withInjectables } from "@ogre-tools/injectable-react";
import { observer } from "mobx-react";
import type { UserStore } from "../../../../../../common/user-store";
import type { LensTheme } from "../../../../../../renderer/themes/lens-theme";
import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable";
import defaultLensThemeInjectable from "../../../../../../renderer/themes/default-theme.injectable";
import { lensThemeDeclarationInjectionToken } from "../../../../../../renderer/themes/declaration";
import type { UserPreferencesState } from "../../../../../user-preferences/common/state.injectable";
import userPreferencesStateInjectable from "../../../../../user-preferences/common/state.injectable";
interface Dependencies {
userStore: UserStore;
state: UserPreferencesState;
defaultTheme: LensTheme;
themes: LensTheme[];
}
const NonInjectedTheme = observer(({
userStore,
state,
themes,
defaultTheme,
}: Dependencies) => {
const themeOptions = [
{
value: "system", // TODO: replace with a sentinal value that isn't string (and serialize it differently)
value: "system", // TODO: replace with a sentinel value that isn't string (and serialize it differently)
label: "Sync with computer",
},
...themes.map(theme => ({
@@ -41,9 +41,9 @@ const NonInjectedTheme = observer(({
<Select
id="theme-input"
options={themeOptions}
value={userStore.colorTheme}
value={state.colorTheme}
onChange={(value) =>
(userStore.colorTheme = value?.value ?? defaultTheme.name)
(state.colorTheme = value?.value ?? defaultTheme.name)
}
themeName="lens"
/>
@@ -53,7 +53,7 @@ const NonInjectedTheme = observer(({
export const Theme = withInjectables<Dependencies>(NonInjectedTheme, {
getProps: (di) => ({
userStore: di.inject(userStoreInjectable),
state: di.inject(userPreferencesStateInjectable),
defaultTheme: di.inject(defaultLensThemeInjectable),
themes: di.injectMany(lensThemeDeclarationInjectionToken),
}),
@@ -5,15 +5,15 @@
import React from "react";
import { SubTitle } from "../../../../../../renderer/components/layout/sub-title";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { UserStore } from "../../../../../../common/user-store";
import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable";
import { Select } from "../../../../../../renderer/components/select";
import moment from "moment-timezone";
import { observer } from "mobx-react";
import currentTimezoneInjectable from "../../../../../../common/user-store/current-timezone.injectable";
import type { UserPreferencesState } from "../../../../../user-preferences/common/state.injectable";
import currentTimezoneInjectable from "../../../../../../common/vars/current-timezone.injectable";
import userPreferencesStateInjectable from "../../../../../user-preferences/common/state.injectable";
interface Dependencies {
userStore: UserStore;
state: UserPreferencesState;
currentTimezone: string;
}
@@ -23,9 +23,8 @@ const timezoneOptions = moment.tz.names()
label: timezone.replace("_", " "),
}));
const NonInjectedTimezone = observer(({
userStore,
state,
currentTimezone,
}: Dependencies) => (
<section id="locale">
@@ -33,17 +32,16 @@ const NonInjectedTimezone = observer(({
<Select
id="timezone-input"
options={timezoneOptions}
value={userStore.localeTimezone}
onChange={value => userStore.localeTimezone = value?.value ?? currentTimezone}
value={state.localeTimezone}
onChange={value => state.localeTimezone = value?.value ?? currentTimezone}
themeName="lens"
/>
</section>
));
export const Timezone = withInjectables<Dependencies>(NonInjectedTimezone, {
getProps: (di) => ({
userStore: di.inject(userStoreInjectable),
state: di.inject(userPreferencesStateInjectable),
currentTimezone: di.inject(currentTimezoneInjectable),
}),
});
@@ -5,16 +5,16 @@
import React from "react";
import { SubTitle } from "../../../../../../renderer/components/layout/sub-title";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { UserStore } from "../../../../../../common/user-store";
import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable";
import { Input } from "../../../../../../renderer/components/input";
import { observer } from "mobx-react";
import type { UserPreferencesState } from "../../../../../user-preferences/common/state.injectable";
import userPreferencesStateInjectable from "../../../../../user-preferences/common/state.injectable";
interface Dependencies {
userStore: UserStore;
state: UserPreferencesState;
}
const NonInjectedEditorFontFamily = observer(({ userStore: { editorConfiguration }}: Dependencies) => (
const NonInjectedEditorFontFamily = observer(({ state: { editorConfiguration }}: Dependencies) => (
<section>
<SubTitle title="Font family" />
<Input
@@ -26,12 +26,8 @@ const NonInjectedEditorFontFamily = observer(({ userStore: { editorConfiguration
</section>
));
export const EditorFontFamily = withInjectables<Dependencies>(
NonInjectedEditorFontFamily,
{
getProps: (di) => ({
userStore: di.inject(userStoreInjectable),
}),
},
);
export const EditorFontFamily = withInjectables<Dependencies>(NonInjectedEditorFontFamily, {
getProps: (di) => ({
state: di.inject(userPreferencesStateInjectable),
}),
});
Loaded 100 of 365 files, more files were not shown because too many files have changed in this diff. Show more