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:
parent
dfaf90a39f
commit
aa90e8e7d2
@ -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;
|
||||
@ -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",
|
||||
});
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
103
src/features/preferences/renderer/preferences.tsx
Normal file
103
src/features/preferences/renderer/preferences.tsx
Normal 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,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user