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 type { ApplicationMenuItemTypes } from "./menu-items/application-menu-item-injection-token";
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({
id: "application-menu-item-composite",

View File

@ -5,8 +5,9 @@
import { getInjectable } from "@ogre-tools/injectable";
import type { MenuItemConstructorOptions } from "electron";
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 { isShown } from "../../../common/utils/composable-responsibilities/showable/showable";
export interface MenuItemOpts extends MenuItemConstructorOptions {
submenu?: MenuItemConstructorOptions[];

View File

@ -5,17 +5,16 @@
import { getInjectionToken } from "@ogre-tools/injectable";
import type { BrowserWindow, KeyboardEvent, MenuItemConstructorOptions, MenuItem as ElectronMenuItem } from "electron";
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 {
keyboardShortcut?: string;
}
export interface Showable {
isShown?: boolean;
}
export const isShown = (showable: Showable) => showable.isShown !== false;
export interface Clickable {
export interface ElectronClickable {
// TODO: This leaky abstraction is exposed in Extension API, therefore cannot be updated
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 CanBeChildOfParent {
parentId: string;
}
export interface Orderable {
orderNumber: number;
}
export interface Identifiable {
id: string;
}
type ApplicationMenuItemType<T extends string> =
// 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
& Kind<T>
& Identifiable
& CanBeChildOfParent
& Discriminable<T>
& ParentOfChildComposite
& ChildOfParentComposite
& Showable
& Orderable;
interface Kind<T extends string> { kind: T }
export type TopLevelMenu =
& ApplicationMenuItemType<"top-level-menu">
& { parentId: "root" }
@ -64,13 +49,13 @@ type ElectronRoles = Exclude<MenuItemConstructorOptions["role"], undefined>;
export type SubMenu =
& ApplicationMenuItemType<"sub-menu">
& Labeled
& CanBeChildOfParent;
& ChildOfParentComposite;
export type ClickableMenuItem =
& ApplicationMenuItemType<"clickable-menu-item">
& MenuItem
& Labeled
& Clickable;
& ElectronClickable;
export type OsActionMenuItem =
& ApplicationMenuItemType<"os-action-menu-item">
@ -79,7 +64,7 @@ export type OsActionMenuItem =
& TriggersElectronAction;
type MenuItem =
& CanBeChildOfParent
& ChildOfParentComposite
& MayHaveKeyboardShortcut;
interface TriggersElectronAction {
@ -89,7 +74,7 @@ interface TriggersElectronAction {
// Todo: SeparatorMenuItem
export type Separator =
& ApplicationMenuItemType<"separator">
& CanBeChildOfParent;
& ChildOfParentComposite;
export type ApplicationMenuItemTypes =
| TopLevelMenu

View File

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

View File

@ -6,14 +6,15 @@ import type { IComputedValue } from "mobx";
import { computed } from "mobx";
import React from "react";
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 {
kind: "preference-tabs-root";
id: string;
parentId: undefined;
isShown: IComputedValue<true>;
childSeparator: () => React.ReactElement;
}
export type PreferenceTabsRoot =
& Discriminable<"preference-tabs-root">
& RootComposite
& Showable<IComputedValue<true>>
& { childSeparator: () => React.ReactElement };
export const preferenceTabsRoot: PreferenceTabsRoot = {
kind: "preference-tabs-root" as const,

View File

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