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

Extract "composable-responsibilities" for Discriminable, Labelable, Orderable, and Showable

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>
This commit is contained in:
Iku-turso 2022-10-19 17:17:45 +03:00 committed by Janne Savolainen
parent 5503b938b7
commit f88de99511
No known key found for this signature in database
GPG Key ID: 8C6CFB2FFFE8F68A
11 changed files with 133 additions and 83 deletions

View File

@ -0,0 +1,7 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
// See: https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions
export interface Discriminable<T extends string> { kind: T }

View File

@ -0,0 +1,7 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface Labelable {
label: string;
}

View File

@ -0,0 +1,8 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface Orderable {
orderNumber: number;
}

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { IComputedValue } from "mobx";
import { isBoolean } from "../../type-narrowing";
export interface Showable<
T extends IComputedValue<boolean> | boolean =
| IComputedValue<boolean>
| boolean,
> {
isShown?: T;
}
export const isShown = (showable: Showable) => {
if (showable.isShown === undefined) {
return true;
}
if (isBoolean(showable.isShown)) {
return showable.isShown;
}
return showable.isShown.get();
};

View File

@ -0,0 +1,17 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export interface ParentOfChildComposite<Id extends string = string> {
id: Id;
}
export interface ChildOfParentComposite<Id extends string = string> {
parentId: Id;
}
export type RootComposite<Id extends string = string> =
& { parentId: undefined }
& ParentOfChildComposite<Id>;

View File

@ -10,8 +10,14 @@ import { computed } from "mobx";
import { pipeline } from "@ogre-tools/fp"; import { pipeline } from "@ogre-tools/fp";
import type { ApplicationMenuItemTypes } from "./menu-items/application-menu-item-injection-token"; import type { ApplicationMenuItemTypes } from "./menu-items/application-menu-item-injection-token";
import loggerInjectable from "../../../common/logger.injectable"; import loggerInjectable from "../../../common/logger.injectable";
import type { RootComposite } from "../../../common/utils/composite/interfaces";
import type { Discriminable } from "../../../common/utils/composable-responsibilities/discriminable/discriminable";
import type { Orderable } from "../../../common/utils/composable-responsibilities/orderable/orderable";
export interface MenuItemRoot { id: "root"; parentId: undefined; kind: "root"; orderNumber: 0 } export type MenuItemRoot =
& Discriminable<"root">
& RootComposite<"root">
& Orderable;
const applicationMenuItemCompositeInjectable = getInjectable({ const applicationMenuItemCompositeInjectable = getInjectable({
id: "application-menu-item-composite", id: "application-menu-item-composite",

View File

@ -5,8 +5,9 @@
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import type { MenuItemConstructorOptions } from "electron"; import type { MenuItemConstructorOptions } from "electron";
import { computed } from "mobx"; import { computed } from "mobx";
import applicationMenuItemInjectionToken, { isShown } from "./menu-items/application-menu-item-injection-token"; import applicationMenuItemInjectionToken from "./menu-items/application-menu-item-injection-token";
import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx"; import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
import { isShown } from "../../../common/utils/composable-responsibilities/showable/showable";
export interface MenuItemOpts extends MenuItemConstructorOptions { export interface MenuItemOpts extends MenuItemConstructorOptions {
submenu?: MenuItemConstructorOptions[]; submenu?: MenuItemConstructorOptions[];

View File

@ -5,17 +5,16 @@
import { getInjectionToken } from "@ogre-tools/injectable"; import { getInjectionToken } from "@ogre-tools/injectable";
import type { BrowserWindow, KeyboardEvent, MenuItemConstructorOptions, MenuItem as ElectronMenuItem } from "electron"; import type { BrowserWindow, KeyboardEvent, MenuItemConstructorOptions, MenuItem as ElectronMenuItem } from "electron";
import type { SetOptional } from "type-fest"; import type { SetOptional } from "type-fest";
import type { ChildOfParentComposite, ParentOfChildComposite } from "../../../../common/utils/composite/interfaces";
import type { Showable } from "../../../../common/utils/composable-responsibilities/showable/showable";
import type { Discriminable } from "../../../../common/utils/composable-responsibilities/discriminable/discriminable";
import type { Orderable } from "../../../../common/utils/composable-responsibilities/orderable/orderable";
export interface MayHaveKeyboardShortcut { export interface MayHaveKeyboardShortcut {
keyboardShortcut?: string; keyboardShortcut?: string;
} }
export interface Showable { export interface ElectronClickable {
isShown?: boolean;
}
export const isShown = (showable: Showable) => showable.isShown !== false;
export interface Clickable {
// TODO: This leaky abstraction is exposed in Extension API, therefore cannot be updated // TODO: This leaky abstraction is exposed in Extension API, therefore cannot be updated
onClick: (menuItem: ElectronMenuItem, browserWindow: (BrowserWindow) | (undefined), event: KeyboardEvent) => void; onClick: (menuItem: ElectronMenuItem, browserWindow: (BrowserWindow) | (undefined), event: KeyboardEvent) => void;
} }
@ -26,29 +25,15 @@ export interface Labeled {
export interface MaybeLabeled extends SetOptional<Labeled, "label"> {} export interface MaybeLabeled extends SetOptional<Labeled, "label"> {}
export interface CanBeChildOfParent {
parentId: string;
}
export interface Orderable {
orderNumber: number;
}
export interface Identifiable {
id: string;
}
type ApplicationMenuItemType<T extends string> = type ApplicationMenuItemType<T extends string> =
// Note: "kind" is being used for Discriminated unions of TypeScript to achieve type narrowing. // Note: "kind" is being used for Discriminated unions of TypeScript to achieve type narrowing.
// See: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions // See: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions
& Kind<T> & Discriminable<T>
& Identifiable & ParentOfChildComposite
& CanBeChildOfParent & ChildOfParentComposite
& Showable & Showable
& Orderable; & Orderable;
interface Kind<T extends string> { kind: T }
export type TopLevelMenu = export type TopLevelMenu =
& ApplicationMenuItemType<"top-level-menu"> & ApplicationMenuItemType<"top-level-menu">
& { parentId: "root" } & { parentId: "root" }
@ -64,13 +49,13 @@ type ElectronRoles = Exclude<MenuItemConstructorOptions["role"], undefined>;
export type SubMenu = export type SubMenu =
& ApplicationMenuItemType<"sub-menu"> & ApplicationMenuItemType<"sub-menu">
& Labeled & Labeled
& CanBeChildOfParent; & ChildOfParentComposite;
export type ClickableMenuItem = export type ClickableMenuItem =
& ApplicationMenuItemType<"clickable-menu-item"> & ApplicationMenuItemType<"clickable-menu-item">
& MenuItem & MenuItem
& Labeled & Labeled
& Clickable; & ElectronClickable;
export type OsActionMenuItem = export type OsActionMenuItem =
& ApplicationMenuItemType<"os-action-menu-item"> & ApplicationMenuItemType<"os-action-menu-item">
@ -79,7 +64,7 @@ export type OsActionMenuItem =
& TriggersElectronAction; & TriggersElectronAction;
type MenuItem = type MenuItem =
& CanBeChildOfParent & ChildOfParentComposite
& MayHaveKeyboardShortcut; & MayHaveKeyboardShortcut;
interface TriggersElectronAction { interface TriggersElectronAction {
@ -89,7 +74,7 @@ interface TriggersElectronAction {
// Todo: SeparatorMenuItem // Todo: SeparatorMenuItem
export type Separator = export type Separator =
& ApplicationMenuItemType<"separator"> & ApplicationMenuItemType<"separator">
& CanBeChildOfParent; & ChildOfParentComposite;
export type ApplicationMenuItemTypes = export type ApplicationMenuItemTypes =
| TopLevelMenu | TopLevelMenu

View File

@ -3,52 +3,54 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectionToken } from "@ogre-tools/injectable"; import { getInjectionToken } from "@ogre-tools/injectable";
import type { IComputedValue } from "mobx";
import type React from "react"; import type React from "react";
import type { ChildOfParentComposite, ParentOfChildComposite } from "../../../../common/utils/composite/interfaces";
import type { Discriminable } from "../../../../common/utils/composable-responsibilities/discriminable/discriminable";
import type { Labelable } from "../../../../common/utils/composable-responsibilities/labelable/labelable";
import type { Showable } from "../../../../common/utils/composable-responsibilities/showable/showable";
import type { Orderable } from "../../../../common/utils/composable-responsibilities/orderable/orderable";
export type PreferenceItemComponent<T> = React.ComponentType<{ export type PreferenceItemComponent<T> = React.ComponentType<{
children: React.ReactElement; children: React.ReactElement;
item: T; item: T;
}>; }>;
export interface PreferenceTab { export type PreferenceTab =
kind: "tab"; & Discriminable<"tab">
id: string; & ParentOfChildComposite
parentId: string; & ChildOfParentComposite
pathId: string; & Showable
label: string; & Labelable
orderNumber: number; & Orderable
isShown?: IComputedValue<boolean> | boolean; & { pathId: string };
}
export interface PreferenceTabGroup { export type PreferenceTabGroup =
kind: "tab-group"; & Discriminable<"tab-group">
id: string; & ParentOfChildComposite
parentId: "preference-tabs"; & ChildOfParentComposite<"preference-tabs">
label: string; & Showable
orderNumber: number; & Labelable
isShown?: IComputedValue<boolean> | boolean; & Orderable
iconName?: string; & { iconName? : string };
}
export interface PreferencePage { interface RenderableWithSiblings<T extends PreferenceTypes> {
kind: "page";
id: string;
parentId: string;
isShown?: IComputedValue<boolean> | boolean;
childSeparator?: () => React.ReactElement; childSeparator?: () => React.ReactElement;
Component: PreferenceItemComponent<PreferencePage>; Component: PreferenceItemComponent<T>;
} }
export interface PreferenceBlock { export type PreferencePage =
kind: "block"; & Discriminable<"page">
id: string; & ParentOfChildComposite
parentId: string; & ChildOfParentComposite
orderNumber: number; & Showable
isShown?: IComputedValue<boolean> | boolean; & RenderableWithSiblings<PreferencePage>;
childSeparator?: () => React.ReactElement;
Component: PreferenceItemComponent<PreferenceBlock>; export type PreferenceBlock =
} & Discriminable<"block">
& ParentOfChildComposite
& ChildOfParentComposite
& Showable
& RenderableWithSiblings<PreferenceBlock>;
export type PreferenceTypes = PreferenceTabGroup | PreferenceTab | PreferenceBlock | PreferencePage; export type PreferenceTypes = PreferenceTabGroup | PreferenceTab | PreferenceBlock | PreferencePage;

View File

@ -6,14 +6,15 @@ import type { IComputedValue } from "mobx";
import { computed } from "mobx"; import { computed } from "mobx";
import React from "react"; import React from "react";
import { HorizontalLine } from "../../../../renderer/components/horizontal-line/horizontal-line"; import { HorizontalLine } from "../../../../renderer/components/horizontal-line/horizontal-line";
import type { RootComposite } from "../../../../common/utils/composite/interfaces";
import type { Discriminable } from "../../../../common/utils/composable-responsibilities/discriminable/discriminable";
import type { Showable } from "../../../../common/utils/composable-responsibilities/showable/showable";
export interface PreferenceTabsRoot { export type PreferenceTabsRoot =
kind: "preference-tabs-root"; & Discriminable<"preference-tabs-root">
id: string; & RootComposite
parentId: undefined; & Showable<IComputedValue<true>>
isShown: IComputedValue<true>; & { childSeparator: () => React.ReactElement };
childSeparator: () => React.ReactElement;
}
export const preferenceTabsRoot: PreferenceTabsRoot = { export const preferenceTabsRoot: PreferenceTabsRoot = {
kind: "preference-tabs-root" as const, kind: "preference-tabs-root" as const,

View File

@ -12,7 +12,7 @@ import { filter } from "lodash/fp";
import { pipeline } from "@ogre-tools/fp"; import { pipeline } from "@ogre-tools/fp";
import { preferenceTabsRoot } from "./preference-tab-root"; import { preferenceTabsRoot } from "./preference-tab-root";
import logErrorInjectable from "../../../../common/log-error.injectable"; import logErrorInjectable from "../../../../common/log-error.injectable";
import { isBoolean } from "../../../../common/utils"; import { isShown } from "../../../../common/utils/composable-responsibilities/showable/showable";
const preferencesCompositeInjectable = getInjectable({ const preferencesCompositeInjectable = getInjectable({
id: "preferences-composite", id: "preferences-composite",
@ -25,18 +25,7 @@ const preferencesCompositeInjectable = getInjectable({
return computed(() => return computed(() =>
pipeline( pipeline(
[preferenceTabsRoot, ...preferenceItems.get()], [preferenceTabsRoot, ...preferenceItems.get()],
filter((item: PreferenceTypes) => isShown(item)),
filter((item: PreferenceTypes) => {
if (item.isShown === undefined) {
return true;
}
if (isBoolean(item.isShown)) {
return item.isShown;
}
return item.isShown.get();
}),
(items) => (items) =>
getComposite({ getComposite({