From f68264a933163a3b7d3fe7ad9a03501d7e58a0c3 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 24 May 2021 12:30:36 +0300 Subject: [PATCH] fix: catalog entity context menu stale/unobservable Signed-off-by: Roman --- .../+catalog/catalog-entity.store.ts | 37 ++++-- src/renderer/components/+catalog/catalog.tsx | 111 +++++++++--------- 2 files changed, 82 insertions(+), 66 deletions(-) diff --git a/src/renderer/components/+catalog/catalog-entity.store.ts b/src/renderer/components/+catalog/catalog-entity.store.ts index 3c40a77d84..c681338bf9 100644 --- a/src/renderer/components/+catalog/catalog-entity.store.ts +++ b/src/renderer/components/+catalog/catalog-entity.store.ts @@ -19,16 +19,15 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { action, computed, makeObservable } from "mobx"; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from "mobx"; import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import type { CatalogEntity, CatalogEntityActionContext } from "../../api/catalog-entity"; import { ItemObject, ItemStore } from "../../item.store"; -import type { CatalogCategory } from "../../../common/catalog"; +import { CatalogCategory } from "../../../common/catalog"; +import { autoBind } from "../../../common/utils"; export class CatalogEntityItem implements ItemObject { - constructor(public entity: CatalogEntity) { - makeObservable(this); - } + constructor(public entity: CatalogEntity) {} get name() { return this.entity.metadata.name; @@ -86,20 +85,32 @@ export class CatalogEntityItem implements ItemObject { } export class CatalogEntityStore extends ItemStore { - getEntities(category?: CatalogCategory): CatalogEntityItem[] { - if (!category) { + constructor() { + super(); + makeObservable(this); + autoBind(this); + } + + @observable activeCategory?: CatalogCategory; + + @computed get entities() { + if (!this.activeCategory) { return catalogEntityRegistry.items.map(entity => new CatalogEntityItem(entity)); } - return catalogEntityRegistry.getItemsForCategory(category) - .map(entity => new CatalogEntityItem(entity)); + return catalogEntityRegistry.getItemsForCategory(this.activeCategory).map(entity => new CatalogEntityItem(entity)); } - async loadItems(entities: CatalogEntityItem[]) { - return super.loadItems(() => entities); + watch() { + const disposers: IReactionDisposer[] = [ + reaction(() => this.entities, () => this.loadAll()), + reaction(() => this.activeCategory, () => this.loadAll(), { delay: 100}) + ]; + + return () => disposers.forEach((dispose) => dispose()); } - async loadAll() { - return this.loadItems(this.getEntities()); + loadAll() { + return this.loadItems(() => this.entities); } } diff --git a/src/renderer/components/+catalog/catalog.tsx b/src/renderer/components/+catalog/catalog.tsx index 520010af8e..2dc8b59862 100644 --- a/src/renderer/components/+catalog/catalog.tsx +++ b/src/renderer/components/+catalog/catalog.tsx @@ -23,23 +23,22 @@ import "./catalog.scss"; import React from "react"; import { disposeOnUnmount, observer } from "mobx-react"; import { ItemListLayout } from "../item-object-list"; -import { computed, makeObservable, reaction } from "mobx"; +import { action, makeObservable, observable, reaction, when } from "mobx"; import { CatalogEntityItem, CatalogEntityStore } from "./catalog-entity.store"; import { navigate } from "../../navigation"; import { kebabCase } from "lodash"; import { PageLayout } from "../layout/page-layout"; -import { MenuActions, MenuItem } from "../menu"; -import { CatalogCategory, CatalogEntityContextMenu, CatalogEntityContextMenuContext, catalogEntityRunContext } from "../../api/catalog-entity"; +import { MenuItem, MenuActions } from "../menu"; +import { CatalogEntityContextMenu, CatalogEntityContextMenuContext, catalogEntityRunContext } from "../../api/catalog-entity"; import { Badge } from "../badge"; import { HotbarStore } from "../../../common/hotbar-store"; -import { boundMethod } from "../../utils"; import { ConfirmDialog } from "../confirm-dialog"; import { Tab, Tabs } from "../tabs"; import { catalogCategoryRegistry } from "../../../common/catalog"; import { CatalogAddButton } from "./catalog-add-button"; import type { RouteComponentProps } from "react-router"; import type { ICatalogViewRouteParam } from "./catalog.route"; -import { catalogURL } from "./catalog.route"; +import { Notifications } from "../notifications"; enum sortBy { name = "name", @@ -47,46 +46,52 @@ enum sortBy { status = "status" } -interface Props extends RouteComponentProps { -} +interface Props extends RouteComponentProps {} @observer export class Catalog extends React.Component { + @observable private catalogEntityStore?: CatalogEntityStore; + @observable private contextMenu: CatalogEntityContextMenuContext; + @observable activeTab?: string; + constructor(props: Props) { super(props); makeObservable(this); } - private catalogEntityStore = new CatalogEntityStore(); + get routeActiveTab(): string | undefined { + const { group, kind } = this.props.match.params ?? {}; - private contextMenu: CatalogEntityContextMenuContext = { - menuItems: [], - navigate: (url: string) => navigate(url), - }; + if (group && kind) { + return `${group}/${kind}`; + } - get routeParams(): ICatalogViewRouteParam { - return this.props.match.params ?? {}; + return undefined; } - get activeCategory(): CatalogCategory | undefined { - const { group, kind } = this.routeParams; - - return catalogCategoryRegistry.getForGroupKind(group, kind); - } - - @computed get categories(): CatalogCategory[] { - return catalogCategoryRegistry.items; - } - - @computed get entities(): CatalogEntityItem[] { - return this.catalogEntityStore.getEntities(this.activeCategory); - } - - componentDidMount() { + async componentDidMount() { + this.contextMenu = { + menuItems: [], + navigate: (url: string) => navigate(url) + }; + this.catalogEntityStore = new CatalogEntityStore(); disposeOnUnmount(this, [ - // autofill catalog entities into store - reaction(() => this.entities, items => this.catalogEntityStore.loadItems(items), { - fireImmediately: true, + this.catalogEntityStore.watch(), + when(() => catalogCategoryRegistry.items.length > 0, () => { + const item = catalogCategoryRegistry.items.find(i => i.getId() === this.routeActiveTab); + + if (item || this.routeActiveTab === undefined) { + this.activeTab = this.routeActiveTab; + this.catalogEntityStore.activeCategory = item; + } else { + Notifications.error(

Unknown category: {this.routeActiveTab}

); + } + }), + reaction(() => catalogCategoryRegistry.items, (items) => { + if (!this.activeTab && items.length > 0) { + this.activeTab = items[0].getId(); + this.catalogEntityStore.activeCategory = items[0]; + } }), ]); } @@ -116,24 +121,21 @@ export class Catalog extends React.Component { } } - onTabChange = (categoryId: string) => { - const { group, kind } = CatalogCategory.parseId(categoryId); + get categories() { + return catalogCategoryRegistry.items; + } - if (group && kind) { - navigate(catalogURL({ params: { group, kind } })); - } else { - navigate(catalogURL()); - } + @action + onTabChange = (tabId: string | null) => { + const activeCategory = this.categories.find(category => category.getId() === tabId); + + this.catalogEntityStore.activeCategory = activeCategory; + this.activeTab = activeCategory?.getId(); }; renderNavigation() { return ( - +
Catalog
{ ); } - @boundMethod - renderItemMenu(item: CatalogEntityItem) { + renderItemMenu = (item: CatalogEntityItem) => { const menuItems = this.contextMenu.menuItems.filter((menuItem) => !menuItem.onlyVisibleForSource || menuItem.onlyVisibleForSource === item.entity.metadata.source); return ( @@ -170,14 +171,18 @@ export class Catalog extends React.Component { )) } - this.addToHotbar(item)}> + this.addToHotbar(item) }> Pin to Hotbar ); - } + }; render() { + if (!this.catalogEntityStore) { + return null; + } + return ( { provideBackButtonNavigation={false} contentGaps={false}> { renderTableContents={(item: CatalogEntityItem) => [ item.name, item.source, - item.labels.map((label) => ), + item.labels.map((label) => ), { title: item.phase, className: kebabCase(item.phase) } ]} - onDetails={(item: CatalogEntityItem) => this.onDetails(item)} + onDetails={(item: CatalogEntityItem) => this.onDetails(item) } renderItemMenu={this.renderItemMenu} /> - + ); }