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

Introduce competition for preferences as a Feature

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen 2022-10-13 14:07:55 +03:00
parent dfaf90a39f
commit aa90e8e7d2
No known key found for this signature in database
GPG Key ID: 8C6CFB2FFFE8F68A
5 changed files with 271 additions and 0 deletions

View File

@ -0,0 +1,73 @@
/**
* 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 { 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 "../preferences-route.injectable";
import { filter, find } 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,
};
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);
return computed(() => {
const { preferenceTabId } = routePathParameters.get();
const tabComposite = pipeline(
[preferenceTabRoot, ...preferenceItems.get()],
filter(isShown),
(items) => getComposite({ source: items }),
(rootComposite) => rootComposite.children,
filter(isPreferenceTab),
find(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";
const hasMatchingPathId =
(preferenceTabId: string) =>
({ value: { pathId }}: Composite<PreferenceTab>) =>
pathId === preferenceTabId;
export default currentPreferenceTabCompositeInjectable;

View File

@ -0,0 +1,53 @@
/**
* 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 React from "react";
export type PreferenceItemComponent = React.ComponentType<{ children: React.ReactElement }>;
export interface PreferenceTab {
kind: "tab";
id: string;
parentId: "preference-tabs";
pathId: string;
testId: string;
label: string;
orderNumber: number;
isShown?: boolean;
}
export interface PreferencePage {
kind: "page";
id: string;
parentId: string;
isShown?: boolean;
childrenSeparator?: () => React.ReactElement;
Component: PreferenceItemComponent;
}
export interface PreferenceGroup {
kind: "group";
id: string;
parentId: string;
isShown?: boolean;
childrenSeparator?: () => React.ReactElement;
}
export interface PreferenceItem {
kind: "item";
Component: PreferenceItemComponent;
id: string;
parentId: string;
orderNumber: number;
isShown?: boolean;
childrenSeparator?: () => React.ReactElement;
}
export type PreferenceTypes = PreferenceTab | PreferenceItem | PreferencePage | PreferenceGroup;
export const preferenceItemInjectionToken = getInjectionToken<PreferenceTypes>({
id: "preference-item-injection-token",
});

View File

@ -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 { routeSpecificComponentInjectionToken } from "../../../renderer/routes/route-specific-component-injection-token";
import { Preferences } from "./preferences";
import preferencesRouteInjectable from "./preferences-route.injectable";
const preferencesRouteComponentInjectable = getInjectable({
id: "preferences-route-component",
instantiate: (di) => ({
route: di.inject(preferencesRouteInjectable),
Component: Preferences,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default preferencesRouteComponentInjectable;

View File

@ -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 { computed } from "mobx";
import { frontEndRouteInjectionToken } from "../../../common/front-end-routing/front-end-route-injection-token";
const preferencesRouteInjectable = getInjectable({
id: "preferences-route",
instantiate: () => ({
path: "/preferences2/:preferenceTabId",
clusterFrame: false,
isEnabled: computed(() => true),
}),
injectionToken: frontEndRouteInjectionToken,
});
export default preferencesRouteInjectable;

View File

@ -0,0 +1,103 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
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";
import type { Composite } from "../../application-menu/main/menu-items/get-composite/get-composite";
import type { PreferenceTypes, PreferenceTab } from "./preference-items/preference-item-injection-token";
import type { IComputedValue } from "mobx";
import { Map } from "../../../renderer/components/map/map";
interface Dependencies {
closePreferences: () => void;
pageComposite: IComputedValue<Composite<PreferenceTab>>;
}
const NonInjectedPreferences = ({
closePreferences,
pageComposite,
}: Dependencies) => {
const composite = pageComposite.get();
return (
<SettingLayout
navigation={<PreferencesNavigation />}
className="Preferences"
contentGaps={false}
closeButtonProps={{ "data-testid": "close-preferences" }}
back={closePreferences}
data-testid={composite.value.testId}
>
{toPreferenceItemHierarchy(composite)}
</SettingLayout>
);
};
const toPreferenceItemHierarchy = (composite: Composite<PreferenceTypes>) => {
switch (composite.value.kind) {
case "group": {
return (
<section id={composite.value.id}>
<Map items={composite.children} getSeparator={composite.value.childrenSeparator}>
{toPreferenceItemHierarchy}
</Map>
</section>
);
}
case "item":
// eslint-disable-next-line no-fallthrough
case "page": {
const Component = composite.value.Component;
return (
<Component>
<Map items={composite.children} getSeparator={composite.value.childrenSeparator}>
{toPreferenceItemHierarchy}
</Map>
</Component>
);
}
case "tab": {
return (
<Map items={composite.children}>
{toPreferenceItemHierarchy}
</Map>
);
}
default: {
// Note: this will fail at transpilation time, if all ApplicationMenuItemTypes
// are not handled in switch/case.
const _exhaustiveCheck: never = composite.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 preferences, but foreign item was encountered: ${_exhaustiveCheck} ${composite.value}`);
}
}
};
export const Preferences = withInjectables<Dependencies>(
NonInjectedPreferences,
{
getProps: (di, props) => ({
closePreferences: di.inject(closePreferencesInjectable),
pageComposite: di.inject(currentPreferenceTabCompositeInjectable),
...props,
}),
},
);