From fb154f0674a8d0628ef3e625fce9ce7913e4f3fc Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 12 Apr 2022 15:43:02 -0400 Subject: [PATCH] fix circular dependency on catalogCategoryRegistry Signed-off-by: Sebastian Malton --- src/common/catalog-entities/general.ts | 12 ---- .../catalog-entities/kubernetes-cluster.ts | 7 +-- src/common/catalog-entities/web-link.ts | 11 +--- src/common/catalog/catalog-entity.ts | 6 +- .../on-context-menu-open.injectable.ts | 23 ++++++++ .../+catalog/catalog-entity-drawer-menu.tsx | 24 ++++++-- src/renderer/components/+catalog/catalog.tsx | 55 ++++++++++-------- .../components/hotbar/hotbar-entity-icon.tsx | 56 +++++++++++-------- .../components/layout/sidebar-cluster.tsx | 28 ++++++---- .../components/render-delay/render-delay.tsx | 3 +- 10 files changed, 130 insertions(+), 95 deletions(-) create mode 100644 src/common/catalog/on-context-menu-open.injectable.ts diff --git a/src/common/catalog-entities/general.ts b/src/common/catalog-entities/general.ts index 0f851869e5..2d3b404ee0 100644 --- a/src/common/catalog-entities/general.ts +++ b/src/common/catalog-entities/general.ts @@ -22,18 +22,6 @@ export class GeneralEntity extends CatalogEntity(this) - ?.emit("contextMenuOpen", this, context); } } diff --git a/src/common/catalog-entities/web-link.ts b/src/common/catalog-entities/web-link.ts index 09e02661a4..35dc86be57 100644 --- a/src/common/catalog-entities/web-link.ts +++ b/src/common/catalog-entities/web-link.ts @@ -5,7 +5,6 @@ import type { CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog"; import { CatalogCategory, CatalogEntity, categoryVersion } from "../catalog/catalog-entity"; -import { catalogCategoryRegistry } from "../catalog/catalog-category-registry"; import { productName } from "../vars"; import { WeblinkStore } from "../weblink-store"; @@ -30,11 +29,7 @@ export class WebLink extends CatalogEntity; - public abstract onContextMenuOpen(context: CatalogEntityContextMenuContext): void | Promise; - public abstract onSettingsOpen(context: CatalogEntitySettingsContext): void | Promise; + public onRun?(context: CatalogEntityActionContext): void | Promise; + public onContextMenuOpen?(context: CatalogEntityContextMenuContext): void | Promise; + public onSettingsOpen?(context: CatalogEntitySettingsContext): void | Promise; } diff --git a/src/common/catalog/on-context-menu-open.injectable.ts b/src/common/catalog/on-context-menu-open.injectable.ts new file mode 100644 index 0000000000..501bd20842 --- /dev/null +++ b/src/common/catalog/on-context-menu-open.injectable.ts @@ -0,0 +1,23 @@ +/** + * 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 type { CatalogEntity, CatalogEntityContextMenuContext } from "./catalog-entity"; +import catalogCategoryRegistryInjectable from "./category-registry.injectable"; + +export type OnContextMenuOpen = (entity: CatalogEntity, context: CatalogEntityContextMenuContext) => void; + +const onContextMenuOpenInjectable = getInjectable({ + id: "on-context-menu-open", + instantiate: (di): OnContextMenuOpen => { + const categoryRegistry = di.inject(catalogCategoryRegistryInjectable); + + return (entity, context) => { + entity.onContextMenuOpen?.(context); + categoryRegistry.getCategoryForEntity(entity)?.emit("contextMenuOpen", entity, context); + }; + }, +}); + +export default onContextMenuOpenInjectable; diff --git a/src/renderer/components/+catalog/catalog-entity-drawer-menu.tsx b/src/renderer/components/+catalog/catalog-entity-drawer-menu.tsx index 0e7c4686ff..6b2ca4c89f 100644 --- a/src/renderer/components/+catalog/catalog-entity-drawer-menu.tsx +++ b/src/renderer/components/+catalog/catalog-entity-drawer-menu.tsx @@ -15,17 +15,24 @@ import { MenuItem } from "../menu"; import { ConfirmDialog } from "../confirm-dialog"; import { Icon } from "../icon"; import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item"; +import type { OnContextMenuOpen } from "../../../common/catalog/on-context-menu-open.injectable"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import onContextMenuOpenInjectable from "../../../common/catalog/on-context-menu-open.injectable"; -export interface CatalogEntityDrawerMenuProps extends MenuActionsProps { - entity: T; +export interface CatalogEntityDrawerMenuProps extends MenuActionsProps { + entity: Entity; +} + +interface Dependencies { + onContextMenuOpen: OnContextMenuOpen; } @observer -export class CatalogEntityDrawerMenu extends React.Component> { +class NonInjectedCatalogEntityDrawerMenu extends React.Component> { private readonly menuItems = observable.array(); componentDidMount() { - this.props.entity?.onContextMenuOpen({ + this.props.onContextMenuOpen(this.props.entity, { menuItems: this.menuItems, navigate: (url) => navigate(url), }); @@ -48,7 +55,7 @@ export class CatalogEntityDrawerMenu extends React.Comp } } - getMenuItems(entity: T): React.ReactChild[] { + getMenuItems(entity: Entity): React.ReactChild[] { if (!entity) { return []; } @@ -117,3 +124,10 @@ export class CatalogEntityDrawerMenu extends React.Comp ); } } + +export const CatalogEntityDrawerMenu = withInjectables>(NonInjectedCatalogEntityDrawerMenu, { + getProps: (di, props) => ({ + ...props, + onContextMenuOpen: di.inject(onContextMenuOpenInjectable), + }), +}) as (props: CatalogEntityDrawerMenuProps) => JSX.Element; diff --git a/src/renderer/components/+catalog/catalog.tsx b/src/renderer/components/+catalog/catalog.tsx index 8b3b7e8a82..ff47c484f3 100644 --- a/src/renderer/components/+catalog/catalog.tsx +++ b/src/renderer/components/+catalog/catalog.tsx @@ -15,8 +15,7 @@ import { navigate } from "../../navigation"; import { MenuItem, MenuActions } from "../menu"; import type { CatalogEntityContextMenu } from "../../api/catalog-entity"; import { ConfirmDialog } from "../confirm-dialog"; -import type { CatalogCategory, CatalogEntity } from "../../../common/catalog"; -import { catalogCategoryRegistry } from "../../../common/catalog"; +import type { CatalogCategory, CatalogCategoryRegistry, CatalogEntity } from "../../../common/catalog"; import { CatalogAddButton } from "./catalog-add-button"; import { Notifications } from "../notifications"; import { MainLayout } from "../layout/main-layout"; @@ -43,6 +42,9 @@ import type { AppEvent } from "../../../common/app-event-bus/event-bus"; import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable"; import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; import type { HotbarStore } from "../../../common/hotbars/store"; +import type { OnContextMenuOpen } from "../../../common/catalog/on-context-menu-open.injectable"; +import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable"; +import onContextMenuOpenInjectable from "../../../common/catalog/on-context-menu-open.injectable"; interface Dependencies { catalogPreviousActiveTabStorage: { set: (value: string ) => void; get: () => string }; @@ -50,14 +52,14 @@ interface Dependencies { getCategoryColumns: (params: GetCategoryColumnsParams) => CategoryColumns; customCategoryViews: IComputedValue>>; emitEvent: (event: AppEvent) => void; - routeParameters: { group: IComputedValue; kind: IComputedValue; }; - navigateToCatalog: NavigateToCatalog; hotbarStore: HotbarStore; + catalogCategoryRegistry: CatalogCategoryRegistry; + onContextMenuOpen: OnContextMenuOpen; } @observer @@ -91,16 +93,24 @@ class NonInjectedCatalog extends React.Component { } async componentDidMount() { + const { + catalogEntityStore, + catalogPreviousActiveTabStorage, + catalogCategoryRegistry, + } = this.props; + disposeOnUnmount(this, [ - this.props.catalogEntityStore.watch(), + catalogEntityStore.watch(), reaction(() => this.routeActiveTab, async (routeTab) => { + catalogPreviousActiveTabStorage.set(this.routeActiveTab); + try { await when(() => (routeTab === browseCatalogTab || !!catalogCategoryRegistry.filteredItems.find(i => i.getId() === routeTab)), { timeout: 5_000 }); // we need to wait because extensions might take a while to load const item = catalogCategoryRegistry.filteredItems.find(i => i.getId() === routeTab); runInAction(() => { this.activeTab = routeTab; - this.props.catalogEntityStore.activeCategory.set(item); + catalogEntityStore.activeCategory.set(item); }); } catch (error) { console.error(error); @@ -112,22 +122,21 @@ class NonInjectedCatalog extends React.Component { )); } }, { fireImmediately: true }), + // If active category is filtered out, automatically switch to the first category + reaction(() => catalogCategoryRegistry.filteredItems, () => { + if (!catalogCategoryRegistry.filteredItems.find(item => item.getId() === catalogEntityStore.activeCategory.get()?.getId())) { + const item = catalogCategoryRegistry.filteredItems[0]; + + runInAction(() => { + if (item) { + this.activeTab = item.getId(); + this.props.catalogEntityStore.activeCategory.set(item); + } + }); + } + }), ]); - // If active category is filtered out, automatically switch to the first category - disposeOnUnmount(this, reaction(() => catalogCategoryRegistry.filteredItems, () => { - if (!catalogCategoryRegistry.filteredItems.find(item => item.getId() === this.props.catalogEntityStore.activeCategory.get()?.getId())) { - const item = catalogCategoryRegistry.filteredItems[0]; - - runInAction(() => { - if (item) { - this.activeTab = item.getId(); - this.props.catalogEntityStore.activeCategory.set(item); - } - }); - } - })); - this.props.emitEvent({ name: "catalog", action: "open", @@ -168,7 +177,7 @@ class NonInjectedCatalog extends React.Component { } get categories() { - return catalogCategoryRegistry.items; + return this.props.catalogCategoryRegistry.items; } onTabChange = action((tabId: string | null) => { @@ -194,7 +203,7 @@ class NonInjectedCatalog extends React.Component { renderItemMenu = (entity: CatalogEntity) => { const onOpen = () => { this.menuItems.clear(); - entity.onContextMenuOpen({ + this.props.onContextMenuOpen(entity, { menuItems: this.menuItems, navigate: (url) => navigate(url), }); @@ -358,5 +367,7 @@ export const Catalog = withInjectables( NonInjectedCatalog, { navigateToCatalog: di.inject(navigateToCatalogInjectable), emitEvent: di.inject(appEventBusInjectable).emit, hotbarStore: di.inject(hotbarStoreInjectable), + catalogCategoryRegistry: di.inject(catalogCategoryRegistryInjectable), + onContextMenuOpen: di.inject(onContextMenuOpenInjectable), }), }); diff --git a/src/renderer/components/hotbar/hotbar-entity-icon.tsx b/src/renderer/components/hotbar/hotbar-entity-icon.tsx index e4c26e45f0..f0e957b50b 100644 --- a/src/renderer/components/hotbar/hotbar-entity-icon.tsx +++ b/src/renderer/components/hotbar/hotbar-entity-icon.tsx @@ -10,15 +10,18 @@ import React from "react"; import { makeObservable, observable } from "mobx"; import { observer } from "mobx-react"; -import type { CatalogEntity, CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../../common/catalog"; -import { catalogCategoryRegistry } from "../../api/catalog-category-registry"; +import type { CatalogCategoryRegistry, CatalogEntity, CatalogEntityContextMenu } from "../../../common/catalog"; import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; -import { navigate } from "../../navigation"; import type { IClassName } from "../../utils"; import { cssNames } from "../../utils"; import { Icon } from "../icon"; import { HotbarIcon } from "./hotbar-icon"; import { LensKubernetesClusterStatus } from "../../../common/catalog-entities/kubernetes-cluster"; +import type { OnContextMenuOpen } from "../../../common/catalog/on-context-menu-open.injectable"; +import { navigate } from "../../navigation"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable"; +import onContextMenuOpenInjectable from "../../../common/catalog/on-context-menu-open.injectable"; export interface HotbarEntityIconProps extends HTMLAttributes { entity: CatalogEntity; @@ -29,21 +32,23 @@ export interface HotbarEntityIconProps extends HTMLAttributes { size?: number; } -@observer -export class HotbarEntityIcon extends React.Component { - @observable private contextMenu: CatalogEntityContextMenuContext = { - menuItems: [], - navigate: (url: string) => navigate(url), - }; +interface Dependencies { + onContextMenuOpen: OnContextMenuOpen; + catalogCategoryRegistry: CatalogCategoryRegistry; +} - constructor(props: HotbarEntityIconProps) { +@observer +class NonInjectedHotbarEntityIcon extends React.Component { + private readonly menuItems = observable.array(); + + constructor(props: HotbarEntityIconProps & Dependencies) { super(props); makeObservable(this); } get kindIcon() { const className = styles.badge; - const category = catalogCategoryRegistry.getCategoryForEntity(this.props.entity); + const category = this.props.catalogCategoryRegistry.getCategoryForEntity(this.props.entity); if (!category) { return ; @@ -70,24 +75,19 @@ export class HotbarEntityIcon extends React.Component { return catalogEntityRegistry.activeEntity?.metadata?.uid == item.getId(); } - async onMenuOpen() { - const menuItems: CatalogEntityContextMenu[] = []; - - menuItems.unshift({ + onMenuOpen() { + this.menuItems.replace([{ title: "Remove from Hotbar", onClick: () => this.props.remove(this.props.entity.getId()), + }]); + + this.props.onContextMenuOpen(this.props.entity, { + menuItems: this.menuItems, + navigate, }); - - this.contextMenu.menuItems = menuItems; - - await this.props.entity.onContextMenuOpen(this.contextMenu); } render() { - if (!this.contextMenu) { - return null; - } - const { entity, errorClass, add, remove, index, children, ...elemProps } = this.props; return ( @@ -102,7 +102,7 @@ export class HotbarEntityIcon extends React.Component { active={this.isActive(entity)} onMenuOpen={() => this.onMenuOpen()} disabled={!entity} - menuItems={this.contextMenu.menuItems} + menuItems={this.menuItems} tooltip={( entity.metadata.source ? `${entity.getName()} (${entity.metadata.source})` @@ -116,3 +116,11 @@ export class HotbarEntityIcon extends React.Component { ); } } + +export const HotbarEntityIcon = withInjectables(NonInjectedHotbarEntityIcon, { + getProps: (di, props) => ({ + ...props, + catalogCategoryRegistry: di.inject(catalogCategoryRegistryInjectable), + onContextMenuOpen: di.inject(onContextMenuOpenInjectable), + }), +}); diff --git a/src/renderer/components/layout/sidebar-cluster.tsx b/src/renderer/components/layout/sidebar-cluster.tsx index 99ed35aa7c..6c9030c49b 100644 --- a/src/renderer/components/layout/sidebar-cluster.tsx +++ b/src/renderer/components/layout/sidebar-cluster.tsx @@ -19,6 +19,8 @@ import { withInjectables } from "@ogre-tools/injectable-react"; import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; import type { HotbarStore } from "../../../common/hotbars/store"; import { observer } from "mobx-react"; +import type { OnContextMenuOpen } from "../../../common/catalog/on-context-menu-open.injectable"; +import onContextMenuOpenInjectable from "../../../common/catalog/on-context-menu-open.injectable"; const contextMenu: CatalogEntityContextMenuContext = observable({ menuItems: [], @@ -64,13 +66,18 @@ function renderLoadingSidebarCluster() { interface Dependencies { hotbarStore: HotbarStore; + onContextMenuOpen: OnContextMenuOpen; } interface SidebarClusterProps { clusterEntity: CatalogEntity | undefined; } -const NonInjectedSidebarCluster = observer(({ clusterEntity, hotbarStore }: Dependencies & SidebarClusterProps) => { +const NonInjectedSidebarCluster = observer(({ + clusterEntity, + hotbarStore, + onContextMenuOpen, +}: Dependencies & SidebarClusterProps) => { const [opened, setOpened] = useState(false); if (!clusterEntity) { @@ -87,7 +94,7 @@ const NonInjectedSidebarCluster = observer(({ clusterEntity, hotbarStore }: Depe : () => hotbarStore.addToHotbar(clusterEntity); contextMenu.menuItems = [{ title, onClick }]; - clusterEntity.onContextMenuOpen(contextMenu); + onContextMenuOpen(clusterEntity, contextMenu); toggle(); }; @@ -150,13 +157,10 @@ const NonInjectedSidebarCluster = observer(({ clusterEntity, hotbarStore }: Depe ); }); -export const SidebarCluster = withInjectables( - NonInjectedSidebarCluster, - - { - getProps: (di, props) => ({ - hotbarStore: di.inject(hotbarStoreInjectable), - ...props, - }), - }, -); +export const SidebarCluster = withInjectables(NonInjectedSidebarCluster, { + getProps: (di, props) => ({ + ...props, + hotbarStore: di.inject(hotbarStoreInjectable), + onContextMenuOpen: di.inject(onContextMenuOpenInjectable), + }), +}); diff --git a/src/renderer/components/render-delay/render-delay.tsx b/src/renderer/components/render-delay/render-delay.tsx index 2639bb9693..2ccdaff693 100644 --- a/src/renderer/components/render-delay/render-delay.tsx +++ b/src/renderer/components/render-delay/render-delay.tsx @@ -6,10 +6,11 @@ import React from "react"; import { makeObservable, observable } from "mobx"; import { observer } from "mobx-react"; +import type { SingleOrMany } from "../../utils"; export interface RenderDelayProps { placeholder?: React.ReactNode; - children: unknown; + children: SingleOrMany; } @observer