1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Introduce competition for preferences navigation

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen 2022-10-14 11:54:03 +03:00
parent 6d56771404
commit 515ec34c87
No known key found for this signature in database
GPG Key ID: 8C6CFB2FFFE8F68A
16 changed files with 266 additions and 79 deletions

View File

@ -93,7 +93,7 @@ exports[`add custom helm repository in preferences when navigating to preference
</div>
<div
class="Tab flex gaps align-center"
data-testid="tab-link-for-application"
data-testid="tab-link-for-app"
role="tab"
tabindex="0"
>
@ -690,7 +690,7 @@ exports[`add custom helm repository in preferences when navigating to preference
</div>
<div
class="Tab flex gaps align-center"
data-testid="tab-link-for-application"
data-testid="tab-link-for-app"
role="tab"
tabindex="0"
>
@ -1298,7 +1298,7 @@ exports[`add custom helm repository in preferences when navigating to preference
</div>
<div
class="Tab flex gaps align-center"
data-testid="tab-link-for-application"
data-testid="tab-link-for-app"
role="tab"
tabindex="0"
>
@ -2017,7 +2017,7 @@ exports[`add custom helm repository in preferences when navigating to preference
</div>
<div
class="Tab flex gaps align-center"
data-testid="tab-link-for-application"
data-testid="tab-link-for-app"
role="tab"
tabindex="0"
>
@ -2633,7 +2633,7 @@ exports[`add custom helm repository in preferences when navigating to preference
</div>
<div
class="Tab flex gaps align-center"
data-testid="tab-link-for-application"
data-testid="tab-link-for-app"
role="tab"
tabindex="0"
>
@ -3352,7 +3352,7 @@ exports[`add custom helm repository in preferences when navigating to preference
</div>
<div
class="Tab flex gaps align-center"
data-testid="tab-link-for-application"
data-testid="tab-link-for-app"
role="tab"
tabindex="0"
>
@ -4253,7 +4253,7 @@ exports[`add custom helm repository in preferences when navigating to preference
</div>
<div
class="Tab flex gaps align-center"
data-testid="tab-link-for-application"
data-testid="tab-link-for-app"
role="tab"
tabindex="0"
>
@ -4972,7 +4972,7 @@ exports[`add custom helm repository in preferences when navigating to preference
</div>
<div
class="Tab flex gaps align-center"
data-testid="tab-link-for-application"
data-testid="tab-link-for-app"
role="tab"
tabindex="0"
>
@ -5873,7 +5873,7 @@ exports[`add custom helm repository in preferences when navigating to preference
</div>
<div
class="Tab flex gaps align-center"
data-testid="tab-link-for-application"
data-testid="tab-link-for-app"
role="tab"
tabindex="0"
>
@ -6593,7 +6593,7 @@ exports[`add custom helm repository in preferences when navigating to preference
</div>
<div
class="Tab flex gaps align-center"
data-testid="tab-link-for-application"
data-testid="tab-link-for-app"
role="tab"
tabindex="0"
>
@ -7209,7 +7209,7 @@ exports[`add custom helm repository in preferences when navigating to preference
</div>
<div
class="Tab flex gaps align-center"
data-testid="tab-link-for-application"
data-testid="tab-link-for-app"
role="tab"
tabindex="0"
>
@ -7814,7 +7814,7 @@ exports[`add custom helm repository in preferences when navigating to preference
</div>
<div
class="Tab flex gaps align-center"
data-testid="tab-link-for-application"
data-testid="tab-link-for-app"
role="tab"
tabindex="0"
>

View File

@ -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,

View File

@ -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,
}),

View File

@ -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<PreferenceTypes | PreferenceTabsRoot>): composite is Composite<PreferenceTab> =>
composite.value.kind === "tab";

View File

@ -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,

View File

@ -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;

View File

@ -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,

View File

@ -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<PreferenceTypes>({
id: "preference-item-injection-token",

View File

@ -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;

View File

@ -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",

View File

@ -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: () => <hr className="small" />,
}),
instantiate: (di) => {
const sentryDnsUrl = di.inject(sentryDataSourceNameInjectable);
return {
kind: "page" as const,
id: "telemetry-page",
parentId: "telemetry-tab",
orderNumber: 0,
Component: TelemetryPage,
childrenSeparator: () => <hr className="small" />,
isShown: !!sentryDnsUrl,
};
},
injectionToken: preferenceItemInjectionToken,
});

View File

@ -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,
});

View File

@ -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,

View File

@ -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<boolean>;
}
interface PreferenceNavigationTabProps {
tab: PreferenceTab;
}
const NonInjectedPreferencesNavigationTab = observer(({ navigateToTab, tabIsActive, tab } : Dependencies & PreferenceNavigationTabProps) => (
<Tab
onClick={() => navigateToTab(tab.pathId)}
data-testid={`tab-link-for-${tab.pathId}`}
active={tabIsActive.get()}
label={tab.label}
/>
));
export const PreferencesNavigationTab = withInjectables<Dependencies, PreferenceNavigationTabProps>(
NonInjectedPreferencesNavigationTab,
{
getProps: (di, props) => ({
navigateToTab: di.inject(navigateToPreferenceTabInjectable),
tabIsActive: di.inject(preferenceTabIsActiveInjectable, props.tab.pathId),
...props,
}),
},
);

View File

@ -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<Composite<PreferenceTypes>>;
}
const NonInjectedPreferencesNavigation = observer(({ composite }: Dependencies) => (
<Tabs className="flex column" scrollable={false}>
<Map items={composite.get().children}>{toNavigationHierarchy}</Map>
</Tabs>
));
export const PreferencesNavigation = withInjectables<Dependencies>(
NonInjectedPreferencesNavigation,
{
getProps: (di) => ({
composite: di.inject(preferencesCompositeInjectable),
}),
},
);
const toNavigationHierarchy = (composite: Composite<PreferenceTypes>) => {
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 (
<>
<div className="header">{value.label}</div>
<Map items={composite.children}>{toNavigationHierarchy}</Map>
</>
);
}
case "tab": {
return (
<PreferencesNavigationTab tab={value} />
);
}
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}`,
);
}
}
};

View File

@ -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<PreferenceTypes>) => {
);
}
case "tab-group":
// eslint-disable-next-line no-fallthrough
case "tab": {
return (
<Map items={composite.children}>
@ -78,7 +81,7 @@ const toPreferenceItemHierarchy = (composite: Composite<PreferenceTypes>) => {
}
default: {
// Note: this will fail at transpilation time, if all ApplicationMenuItemTypes
// Note: this will fail at transpilation time, if all kinds
// are not handled in switch/case.
const _exhaustiveCheck: never = composite.value;