diff --git a/src/features/helm-charts/__snapshots__/add-custom-helm-repository-in-preferences.test.ts.snap b/src/features/helm-charts/__snapshots__/add-custom-helm-repository-in-preferences.test.ts.snap
index dfca026c18..39c4451cfb 100644
--- a/src/features/helm-charts/__snapshots__/add-custom-helm-repository-in-preferences.test.ts.snap
+++ b/src/features/helm-charts/__snapshots__/add-custom-helm-repository-in-preferences.test.ts.snap
@@ -93,7 +93,7 @@ exports[`add custom helm repository in preferences when navigating to preference
@@ -690,7 +690,7 @@ exports[`add custom helm repository in preferences when navigating to preference
@@ -1298,7 +1298,7 @@ exports[`add custom helm repository in preferences when navigating to preference
@@ -2017,7 +2017,7 @@ exports[`add custom helm repository in preferences when navigating to preference
@@ -2633,7 +2633,7 @@ exports[`add custom helm repository in preferences when navigating to preference
@@ -3352,7 +3352,7 @@ exports[`add custom helm repository in preferences when navigating to preference
@@ -4253,7 +4253,7 @@ exports[`add custom helm repository in preferences when navigating to preference
@@ -4972,7 +4972,7 @@ exports[`add custom helm repository in preferences when navigating to preference
@@ -5873,7 +5873,7 @@ exports[`add custom helm repository in preferences when navigating to preference
@@ -6593,7 +6593,7 @@ exports[`add custom helm repository in preferences when navigating to preference
@@ -7209,7 +7209,7 @@ exports[`add custom helm repository in preferences when navigating to preference
@@ -7814,7 +7814,7 @@ exports[`add custom helm repository in preferences when navigating to preference
diff --git a/src/features/preferences/closing-preferences.test.tsx b/src/features/preferences/closing-preferences.test.tsx
index e46853a6e1..4d192200a0 100644
--- a/src/features/preferences/closing-preferences.test.tsx
+++ b/src/features/preferences/closing-preferences.test.tsx
@@ -219,7 +219,7 @@ const testPreferenceTabInjectable = getInjectable({
kind: "tab" as const,
id: "test-tab",
pathId: "test-tab",
- parentId: "preference-tabs" as const,
+ parentId: "general-tab-group" as const,
testId: "some-test-id-for-test-tab",
label: "Test",
orderNumber: 90,
diff --git a/src/features/preferences/renderer/preference-items/application/application-preference-tab.injectable.ts b/src/features/preferences/renderer/preference-items/application/application-preference-tab.injectable.ts
index c89b35e894..623fe084cf 100644
--- a/src/features/preferences/renderer/preference-items/application/application-preference-tab.injectable.ts
+++ b/src/features/preferences/renderer/preference-items/application/application-preference-tab.injectable.ts
@@ -11,10 +11,10 @@ const applicationPreferenceTabInjectable = getInjectable({
instantiate: () => ({
kind: "tab" as const,
id: "application-tab",
- parentId: "preference-tabs" as const,
+ parentId: "general-tab-group" as const,
pathId: "app",
testId: "application-preferences-page",
- label: "Application",
+ label: "App",
orderNumber: 10,
}),
diff --git a/src/features/preferences/renderer/preference-items/current-preference-tab-composite.injectable.ts b/src/features/preferences/renderer/preference-items/current-preference-tab-composite.injectable.ts
index cb2e8b95f9..de82d8b193 100644
--- a/src/features/preferences/renderer/preference-items/current-preference-tab-composite.injectable.ts
+++ b/src/features/preferences/renderer/preference-items/current-preference-tab-composite.injectable.ts
@@ -3,65 +3,39 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
-import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
import { computed } from "mobx";
import type { PreferenceTab, PreferenceTypes } from "./preference-item-injection-token";
-import { preferenceItemInjectionToken } from "./preference-item-injection-token";
import type { Composite } from "../../../application-menu/main/menu-items/get-composite/get-composite";
-import getComposite from "../../../application-menu/main/menu-items/get-composite/get-composite";
import routePathParametersInjectable from "../../../../renderer/routes/route-path-parameters.injectable";
import preferencesRouteInjectable from "../../common/preferences-route.injectable";
-import { filter, find } from "lodash/fp";
+import { filter, map } from "lodash/fp";
import { pipeline } from "@ogre-tools/fp";
-
-interface PreferenceTabsRoot {
- kind: "preference-tabs-root";
- id: string;
- parentId: undefined;
- isShown: true;
-}
-
-const preferenceTabRoot: PreferenceTabsRoot = {
- kind: "preference-tabs-root" as const,
- id: "preference-tabs",
- parentId: undefined,
- isShown: true,
-};
+import { normalizeComposite } from "../../../application-menu/main/menu-items/get-composite/normalize-composite/normalize-composite";
+import { findExactlyOne } from "../../../../common/utils/find-exactly-one/find-exactly-one";
+import type { PreferenceTabsRoot } from "./preferences-composite.injectable";
+import preferencesCompositeInjectable from "./preferences-composite.injectable";
const currentPreferenceTabCompositeInjectable = getInjectable({
id: "current-preference-page-composite",
instantiate: (di) => {
- const computedInjectMany = di.inject(computedInjectManyInjectable);
- const preferenceItems = computedInjectMany(preferenceItemInjectionToken);
const preferencesRoute = di.inject(preferencesRouteInjectable);
const routePathParameters = di.inject(routePathParametersInjectable, preferencesRoute);
+ const preferencesComposite = di.inject(preferencesCompositeInjectable);
return computed(() => {
const { preferenceTabId } = routePathParameters.get();
- const tabComposite = pipeline(
- [preferenceTabRoot, ...preferenceItems.get()],
- filter(isShown),
- (items) => getComposite({ source: items }),
- (rootComposite) => rootComposite.children,
+ return pipeline(
+ normalizeComposite(preferencesComposite.get()),
+ map(([, composite]) => composite),
filter(isPreferenceTab),
- find(hasMatchingPathId(preferenceTabId)),
+ findExactlyOne(hasMatchingPathId(preferenceTabId)),
);
-
- if (!tabComposite) {
- throw new Error(
- `Tried to open preferences but no tab exists for ID "${preferenceTabId}"`,
- );
- }
-
- return tabComposite;
});
},
});
-const isShown = (item: PreferenceTypes) => item.isShown ?? true;
-
const isPreferenceTab = (composite: Composite
): composite is Composite =>
composite.value.kind === "tab";
diff --git a/src/features/preferences/renderer/preference-items/editor/editor-preference-tab.injectable.ts b/src/features/preferences/renderer/preference-items/editor/editor-preference-tab.injectable.ts
index 26d2d3c7b9..1c07a27442 100644
--- a/src/features/preferences/renderer/preference-items/editor/editor-preference-tab.injectable.ts
+++ b/src/features/preferences/renderer/preference-items/editor/editor-preference-tab.injectable.ts
@@ -11,11 +11,11 @@ const editorPreferenceTabInjectable = getInjectable({
instantiate: () => ({
kind: "tab" as const,
id: "editor-tab",
- parentId: "preference-tabs" as const,
+ parentId: "general-tab-group" as const,
pathId: "editor",
testId: "editor-preferences-page",
label: "Editor",
- orderNumber: 30,
+ orderNumber: 40,
}),
injectionToken: preferenceItemInjectionToken,
diff --git a/src/features/preferences/renderer/preference-items/general-preference-tab-group.injectable.ts b/src/features/preferences/renderer/preference-items/general-preference-tab-group.injectable.ts
new file mode 100644
index 0000000000..fe46026aa8
--- /dev/null
+++ b/src/features/preferences/renderer/preference-items/general-preference-tab-group.injectable.ts
@@ -0,0 +1,22 @@
+/**
+ * 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 { preferenceItemInjectionToken } from "./preference-item-injection-token";
+
+const generalPreferenceTabGroupInjectable = getInjectable({
+ id: "general-preference-tab-group",
+
+ instantiate: () => ({
+ kind: "tab-group" as const,
+ id: "general-tab-group",
+ parentId: "preference-tabs" as const,
+ label: "Preferences",
+ orderNumber: 10,
+ }),
+
+ injectionToken: preferenceItemInjectionToken,
+});
+
+export default generalPreferenceTabGroupInjectable;
diff --git a/src/features/preferences/renderer/preference-items/kubernetes/kubernetes-preference-tab.injectable.ts b/src/features/preferences/renderer/preference-items/kubernetes/kubernetes-preference-tab.injectable.ts
index e2b240c6fa..e24d0b2061 100644
--- a/src/features/preferences/renderer/preference-items/kubernetes/kubernetes-preference-tab.injectable.ts
+++ b/src/features/preferences/renderer/preference-items/kubernetes/kubernetes-preference-tab.injectable.ts
@@ -11,11 +11,11 @@ const kubernetesPreferenceTabInjectable = getInjectable({
instantiate: () => ({
kind: "tab" as const,
id: "kubernetes-tab",
- parentId: "preference-tabs" as const,
+ parentId: "general-tab-group" as const,
pathId: "kubernetes",
testId: "kubernetes-preferences-page",
label: "Kubernetes",
- orderNumber: 10,
+ orderNumber: 30,
}),
injectionToken: preferenceItemInjectionToken,
diff --git a/src/features/preferences/renderer/preference-items/preference-item-injection-token.ts b/src/features/preferences/renderer/preference-items/preference-item-injection-token.ts
index d36ebc48ed..0a2e78a92f 100644
--- a/src/features/preferences/renderer/preference-items/preference-item-injection-token.ts
+++ b/src/features/preferences/renderer/preference-items/preference-item-injection-token.ts
@@ -10,7 +10,7 @@ export type PreferenceItemComponent = React.ComponentType<{ children: React.Reac
export interface PreferenceTab {
kind: "tab";
id: string;
- parentId: "preference-tabs";
+ parentId: string;
pathId: string;
testId: string;
label: string;
@@ -18,6 +18,15 @@ export interface PreferenceTab {
isShown?: boolean;
}
+export interface PreferenceTabGroup {
+ kind: "tab-group";
+ id: string;
+ parentId: "preference-tabs";
+ label: string;
+ orderNumber: number;
+ isShown?: boolean;
+}
+
export interface PreferencePage {
kind: "page";
id: string;
@@ -45,7 +54,7 @@ export interface PreferenceItem {
childrenSeparator?: () => React.ReactElement;
}
-export type PreferenceTypes = PreferenceTab | PreferenceItem | PreferencePage | PreferenceGroup;
+export type PreferenceTypes = PreferenceTabGroup | PreferenceTab | PreferenceItem | PreferencePage | PreferenceGroup;
export const preferenceItemInjectionToken = getInjectionToken({
id: "preference-item-injection-token",
diff --git a/src/features/preferences/renderer/preference-items/preferences-composite.injectable.ts b/src/features/preferences/renderer/preference-items/preferences-composite.injectable.ts
new file mode 100644
index 0000000000..ba23c28f5c
--- /dev/null
+++ b/src/features/preferences/renderer/preference-items/preferences-composite.injectable.ts
@@ -0,0 +1,47 @@
+/**
+ * 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 { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
+import { computed } from "mobx";
+import type { PreferenceTypes } from "./preference-item-injection-token";
+import { preferenceItemInjectionToken } from "./preference-item-injection-token";
+import getComposite from "../../../application-menu/main/menu-items/get-composite/get-composite";
+import { filter } from "lodash/fp";
+import { pipeline } from "@ogre-tools/fp";
+
+export interface PreferenceTabsRoot {
+ kind: "preference-tabs-root";
+ id: string;
+ parentId: undefined;
+ isShown: true;
+}
+
+const preferenceTabsRoot: PreferenceTabsRoot = {
+ kind: "preference-tabs-root" as const,
+ id: "preference-tabs",
+ parentId: undefined,
+ isShown: true,
+};
+
+const preferencesCompositeInjectable = getInjectable({
+ id: "preferences-composite",
+
+ instantiate: (di) => {
+ const computedInjectMany = di.inject(computedInjectManyInjectable);
+ const preferenceItems = computedInjectMany(preferenceItemInjectionToken);
+
+ return computed(() =>
+ pipeline(
+ [preferenceTabsRoot, ...preferenceItems.get()],
+ filter(isShown),
+ (items) => getComposite({ source: items }),
+ ),
+ );
+ },
+});
+
+const isShown = (item: PreferenceTypes) => item.isShown ?? true;
+
+export default preferencesCompositeInjectable;
diff --git a/src/features/preferences/renderer/preference-items/proxy/proxy-preference-tab.injectable.ts b/src/features/preferences/renderer/preference-items/proxy/proxy-preference-tab.injectable.ts
index 6a68608e9c..7d22847314 100644
--- a/src/features/preferences/renderer/preference-items/proxy/proxy-preference-tab.injectable.ts
+++ b/src/features/preferences/renderer/preference-items/proxy/proxy-preference-tab.injectable.ts
@@ -11,7 +11,7 @@ const proxyPreferenceTabInjectable = getInjectable({
instantiate: () => ({
kind: "tab" as const,
id: "proxy-tab",
- parentId: "preference-tabs" as const,
+ parentId: "general-tab-group" as const,
pathId: "proxy",
testId: "proxy-preferences-page",
label: "Proxy",
diff --git a/src/features/preferences/renderer/preference-items/telemetry/telemetry-preference-page.injectable.tsx b/src/features/preferences/renderer/preference-items/telemetry/telemetry-preference-page.injectable.tsx
index 3cb2775896..8c78c0d008 100644
--- a/src/features/preferences/renderer/preference-items/telemetry/telemetry-preference-page.injectable.tsx
+++ b/src/features/preferences/renderer/preference-items/telemetry/telemetry-preference-page.injectable.tsx
@@ -6,18 +6,24 @@ import { getInjectable } from "@ogre-tools/injectable";
import { preferenceItemInjectionToken } from "../preference-item-injection-token";
import { TelemetryPage } from "./telemetry-page";
import React from "react";
+import sentryDataSourceNameInjectable from "../../../../../common/vars/sentry-dsn-url.injectable";
const telemetryPreferencePageInjectable = getInjectable({
id: "telemetry-preference-page",
- instantiate: () => ({
- kind: "page" as const,
- id: "telemetry-page",
- parentId: "telemetry-tab",
- orderNumber: 0,
- Component: TelemetryPage,
- childrenSeparator: () =>
,
- }),
+ instantiate: (di) => {
+ const sentryDnsUrl = di.inject(sentryDataSourceNameInjectable);
+
+ return {
+ kind: "page" as const,
+ id: "telemetry-page",
+ parentId: "telemetry-tab",
+ orderNumber: 0,
+ Component: TelemetryPage,
+ childrenSeparator: () =>
,
+ isShown: !!sentryDnsUrl,
+ };
+ },
injectionToken: preferenceItemInjectionToken,
});
diff --git a/src/features/preferences/renderer/preference-items/telemetry/telemetry-preference-tab.injectable.ts b/src/features/preferences/renderer/preference-items/telemetry/telemetry-preference-tab.injectable.ts
index 5d96258bb8..e6333698d8 100644
--- a/src/features/preferences/renderer/preference-items/telemetry/telemetry-preference-tab.injectable.ts
+++ b/src/features/preferences/renderer/preference-items/telemetry/telemetry-preference-tab.injectable.ts
@@ -4,19 +4,25 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import { preferenceItemInjectionToken } from "../preference-item-injection-token";
+import sentryDataSourceNameInjectable from "../../../../../common/vars/sentry-dsn-url.injectable";
const telemetryPreferenceTabInjectable = getInjectable({
id: "telemetry-preference-tab",
- instantiate: () => ({
- kind: "tab" as const,
- id: "telemetry-tab",
- parentId: "preference-tabs" as const,
- pathId: "telemetry",
- testId: "terminal-preferences-page",
- label: "Telemetry",
- orderNumber: 20,
- }),
+ instantiate: (di) => {
+ const sentryDnsUrl = di.inject(sentryDataSourceNameInjectable);
+
+ return {
+ kind: "tab" as const,
+ id: "telemetry-tab",
+ parentId: "general-tab-group" as const,
+ pathId: "telemetry",
+ testId: "terminal-preferences-page",
+ label: "Telemetry",
+ orderNumber: 60,
+ isShown: !!sentryDnsUrl,
+ };
+ },
injectionToken: preferenceItemInjectionToken,
});
diff --git a/src/features/preferences/renderer/preference-items/terminal/terminal-preference-tab.injectable.ts b/src/features/preferences/renderer/preference-items/terminal/terminal-preference-tab.injectable.ts
index 4654e54c90..39e3814c3a 100644
--- a/src/features/preferences/renderer/preference-items/terminal/terminal-preference-tab.injectable.ts
+++ b/src/features/preferences/renderer/preference-items/terminal/terminal-preference-tab.injectable.ts
@@ -12,10 +12,10 @@ const terminalPreferenceTabInjectable = getInjectable({
kind: "tab" as const,
id: "terminal-tab",
pathId: "terminal",
- parentId: "preference-tabs" as const,
+ parentId: "general-tab-group" as const,
testId: "terminal-preferences-page",
label: "Terminal",
- orderNumber: 20,
+ orderNumber: 50,
}),
injectionToken: preferenceItemInjectionToken,
diff --git a/src/features/preferences/renderer/preference-navigation/preferences-navigation-tab.tsx b/src/features/preferences/renderer/preference-navigation/preferences-navigation-tab.tsx
new file mode 100644
index 0000000000..09e033f55d
--- /dev/null
+++ b/src/features/preferences/renderer/preference-navigation/preferences-navigation-tab.tsx
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { Tab } from "../../../../renderer/components/tabs";
+import navigateToPreferenceTabInjectable from "../../../../renderer/components/+preferences/preferences-navigation/navigate-to-preference-tab/navigate-to-preference-tab.injectable";
+import { withInjectables } from "@ogre-tools/injectable-react";
+import { observer } from "mobx-react";
+import type { PreferenceTab } from "../preference-items/preference-item-injection-token";
+import type { IComputedValue } from "mobx";
+import preferenceTabIsActiveInjectable from "../../../../renderer/components/+preferences/preferences-navigation/navigate-to-preference-tab/preference-tab-is-active.injectable";
+import React from "react";
+
+interface Dependencies {
+ navigateToTab: (tabId: string) => void;
+ tabIsActive: IComputedValue;
+}
+
+interface PreferenceNavigationTabProps {
+ tab: PreferenceTab;
+}
+
+const NonInjectedPreferencesNavigationTab = observer(({ navigateToTab, tabIsActive, tab } : Dependencies & PreferenceNavigationTabProps) => (
+ navigateToTab(tab.pathId)}
+ data-testid={`tab-link-for-${tab.pathId}`}
+ active={tabIsActive.get()}
+ label={tab.label}
+ />
+));
+
+export const PreferencesNavigationTab = withInjectables(
+ NonInjectedPreferencesNavigationTab,
+
+ {
+ getProps: (di, props) => ({
+ navigateToTab: di.inject(navigateToPreferenceTabInjectable),
+ tabIsActive: di.inject(preferenceTabIsActiveInjectable, props.tab.pathId),
+ ...props,
+ }),
+ },
+);
diff --git a/src/features/preferences/renderer/preference-navigation/preferences-navigation.tsx b/src/features/preferences/renderer/preference-navigation/preferences-navigation.tsx
new file mode 100644
index 0000000000..73f2e39596
--- /dev/null
+++ b/src/features/preferences/renderer/preference-navigation/preferences-navigation.tsx
@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { Tabs } from "../../../../renderer/components/tabs";
+import React from "react";
+import type { Composite } from "../../../application-menu/main/menu-items/get-composite/get-composite";
+import type { PreferenceTypes } from "../preference-items/preference-item-injection-token";
+import { Map } from "../../../../renderer/components/map/map";
+import { withInjectables } from "@ogre-tools/injectable-react";
+import type { IComputedValue } from "mobx";
+import preferencesCompositeInjectable from "../preference-items/preferences-composite.injectable";
+import { observer } from "mobx-react";
+import { PreferencesNavigationTab } from "./preferences-navigation-tab";
+
+interface Dependencies {
+ composite: IComputedValue>;
+}
+
+const NonInjectedPreferencesNavigation = observer(({ composite }: Dependencies) => (
+
+
+
+));
+
+export const PreferencesNavigation = withInjectables(
+ NonInjectedPreferencesNavigation,
+
+ {
+ getProps: (di) => ({
+ composite: di.inject(preferencesCompositeInjectable),
+ }),
+ },
+);
+
+
+const toNavigationHierarchy = (composite: Composite) => {
+ const value = composite.value;
+
+ switch (value.kind) {
+ case "page":
+ case "item":
+
+ // eslint-disable-next-line no-fallthrough
+ case "group": {
+ throw new Error("Should never come here");
+ }
+
+ case "tab-group": {
+ return (
+ <>
+ {value.label}
+
+
+ >
+ );
+ }
+
+ case "tab": {
+ return (
+
+ );
+ }
+
+ default: {
+ // Note: this will fail at transpilation time, if all kinds
+ // are not handled in switch/case.
+ const _exhaustiveCheck: never = value;
+
+ // Note: this code is unreachable, it is here to make ts not complain about
+ // _exhaustiveCheck not being used.
+ // See: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking
+ throw new Error(
+ `Tried to create preference navigation, but foreign item was encountered: ${_exhaustiveCheck} ${composite.value}`,
+ );
+ }
+ }
+};
diff --git a/src/features/preferences/renderer/preferences.tsx b/src/features/preferences/renderer/preferences.tsx
index e41100609f..136e1f1bac 100644
--- a/src/features/preferences/renderer/preferences.tsx
+++ b/src/features/preferences/renderer/preferences.tsx
@@ -6,7 +6,6 @@ import "../../../renderer/components/+preferences/preferences.scss";
import React from "react";
import { SettingLayout } from "../../../renderer/components/layout/setting-layout";
-import { PreferencesNavigation } from "../../../renderer/components/+preferences/preferences-navigation/preferences-navigation";
import { withInjectables } from "@ogre-tools/injectable-react";
import closePreferencesInjectable from "../../../renderer/components/+preferences/close-preferences.injectable";
import currentPreferenceTabCompositeInjectable from "./preference-items/current-preference-tab-composite.injectable";
@@ -15,6 +14,7 @@ import type { PreferenceTypes, PreferenceTab } from "./preference-items/preferen
import type { IComputedValue } from "mobx";
import { Map } from "../../../renderer/components/map/map";
import { observer } from "mobx-react";
+import { PreferencesNavigation } from "./preference-navigation/preferences-navigation";
interface Dependencies {
closePreferences: () => void;
@@ -69,6 +69,9 @@ const toPreferenceItemHierarchy = (composite: Composite) => {
);
}
+ case "tab-group":
+
+ // eslint-disable-next-line no-fallthrough
case "tab": {
return (