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

fix: catalog entity context menu stale/unobservable

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2021-05-24 12:30:36 +03:00
parent c05f3b1f61
commit f68264a933
2 changed files with 82 additions and 66 deletions

View File

@ -19,16 +19,15 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 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 { catalogEntityRegistry } from "../../api/catalog-entity-registry";
import type { CatalogEntity, CatalogEntityActionContext } from "../../api/catalog-entity"; import type { CatalogEntity, CatalogEntityActionContext } from "../../api/catalog-entity";
import { ItemObject, ItemStore } from "../../item.store"; 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 { export class CatalogEntityItem implements ItemObject {
constructor(public entity: CatalogEntity) { constructor(public entity: CatalogEntity) {}
makeObservable(this);
}
get name() { get name() {
return this.entity.metadata.name; return this.entity.metadata.name;
@ -86,20 +85,32 @@ export class CatalogEntityItem implements ItemObject {
} }
export class CatalogEntityStore extends ItemStore<CatalogEntityItem> { export class CatalogEntityStore extends ItemStore<CatalogEntityItem> {
getEntities(category?: CatalogCategory): CatalogEntityItem[] { constructor() {
if (!category) { super();
makeObservable(this);
autoBind(this);
}
@observable activeCategory?: CatalogCategory;
@computed get entities() {
if (!this.activeCategory) {
return catalogEntityRegistry.items.map(entity => new CatalogEntityItem(entity)); return catalogEntityRegistry.items.map(entity => new CatalogEntityItem(entity));
} }
return catalogEntityRegistry.getItemsForCategory(category) return catalogEntityRegistry.getItemsForCategory(this.activeCategory).map(entity => new CatalogEntityItem(entity));
.map(entity => new CatalogEntityItem(entity));
} }
async loadItems(entities: CatalogEntityItem[]) { watch() {
return super.loadItems(() => entities); const disposers: IReactionDisposer[] = [
reaction(() => this.entities, () => this.loadAll()),
reaction(() => this.activeCategory, () => this.loadAll(), { delay: 100})
];
return () => disposers.forEach((dispose) => dispose());
} }
async loadAll() { loadAll() {
return this.loadItems(this.getEntities()); return this.loadItems(() => this.entities);
} }
} }

View File

@ -23,23 +23,22 @@ import "./catalog.scss";
import React from "react"; import React from "react";
import { disposeOnUnmount, observer } from "mobx-react"; import { disposeOnUnmount, observer } from "mobx-react";
import { ItemListLayout } from "../item-object-list"; 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 { CatalogEntityItem, CatalogEntityStore } from "./catalog-entity.store";
import { navigate } from "../../navigation"; import { navigate } from "../../navigation";
import { kebabCase } from "lodash"; import { kebabCase } from "lodash";
import { PageLayout } from "../layout/page-layout"; import { PageLayout } from "../layout/page-layout";
import { MenuActions, MenuItem } from "../menu"; import { MenuItem, MenuActions } from "../menu";
import { CatalogCategory, CatalogEntityContextMenu, CatalogEntityContextMenuContext, catalogEntityRunContext } from "../../api/catalog-entity"; import { CatalogEntityContextMenu, CatalogEntityContextMenuContext, catalogEntityRunContext } from "../../api/catalog-entity";
import { Badge } from "../badge"; import { Badge } from "../badge";
import { HotbarStore } from "../../../common/hotbar-store"; import { HotbarStore } from "../../../common/hotbar-store";
import { boundMethod } from "../../utils";
import { ConfirmDialog } from "../confirm-dialog"; import { ConfirmDialog } from "../confirm-dialog";
import { Tab, Tabs } from "../tabs"; import { Tab, Tabs } from "../tabs";
import { catalogCategoryRegistry } from "../../../common/catalog"; import { catalogCategoryRegistry } from "../../../common/catalog";
import { CatalogAddButton } from "./catalog-add-button"; import { CatalogAddButton } from "./catalog-add-button";
import type { RouteComponentProps } from "react-router"; import type { RouteComponentProps } from "react-router";
import type { ICatalogViewRouteParam } from "./catalog.route"; import type { ICatalogViewRouteParam } from "./catalog.route";
import { catalogURL } from "./catalog.route"; import { Notifications } from "../notifications";
enum sortBy { enum sortBy {
name = "name", name = "name",
@ -47,46 +46,52 @@ enum sortBy {
status = "status" status = "status"
} }
interface Props extends RouteComponentProps<ICatalogViewRouteParam> { interface Props extends RouteComponentProps<ICatalogViewRouteParam> {}
}
@observer @observer
export class Catalog extends React.Component<Props> { export class Catalog extends React.Component<Props> {
@observable private catalogEntityStore?: CatalogEntityStore;
@observable private contextMenu: CatalogEntityContextMenuContext;
@observable activeTab?: string;
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
makeObservable(this); makeObservable(this);
} }
private catalogEntityStore = new CatalogEntityStore(); get routeActiveTab(): string | undefined {
const { group, kind } = this.props.match.params ?? {};
private contextMenu: CatalogEntityContextMenuContext = { if (group && kind) {
menuItems: [], return `${group}/${kind}`;
navigate: (url: string) => navigate(url), }
};
get routeParams(): ICatalogViewRouteParam { return undefined;
return this.props.match.params ?? {};
} }
get activeCategory(): CatalogCategory | undefined { async componentDidMount() {
const { group, kind } = this.routeParams; this.contextMenu = {
menuItems: [],
return catalogCategoryRegistry.getForGroupKind(group, kind); navigate: (url: string) => navigate(url)
} };
this.catalogEntityStore = new CatalogEntityStore();
@computed get categories(): CatalogCategory[] {
return catalogCategoryRegistry.items;
}
@computed get entities(): CatalogEntityItem[] {
return this.catalogEntityStore.getEntities(this.activeCategory);
}
componentDidMount() {
disposeOnUnmount(this, [ disposeOnUnmount(this, [
// autofill catalog entities into store this.catalogEntityStore.watch(),
reaction(() => this.entities, items => this.catalogEntityStore.loadItems(items), { when(() => catalogCategoryRegistry.items.length > 0, () => {
fireImmediately: true, 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(<p>Unknown category: {this.routeActiveTab}</p>);
}
}),
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<Props> {
} }
} }
onTabChange = (categoryId: string) => { get categories() {
const { group, kind } = CatalogCategory.parseId(categoryId); return catalogCategoryRegistry.items;
}
if (group && kind) { @action
navigate(catalogURL({ params: { group, kind } })); onTabChange = (tabId: string | null) => {
} else { const activeCategory = this.categories.find(category => category.getId() === tabId);
navigate(catalogURL());
} this.catalogEntityStore.activeCategory = activeCategory;
this.activeTab = activeCategory?.getId();
}; };
renderNavigation() { renderNavigation() {
return ( return (
<Tabs <Tabs className="flex column" scrollable={false} onChange={this.onTabChange} value={this.activeTab}>
className="flex column"
scrollable={false}
value={this.activeCategory?.getId()}
onChange={this.onTabChange}
>
<div className="sidebarHeader">Catalog</div> <div className="sidebarHeader">Catalog</div>
<div className="sidebarTabs"> <div className="sidebarTabs">
<Tab <Tab
@ -157,8 +159,7 @@ export class Catalog extends React.Component<Props> {
); );
} }
@boundMethod renderItemMenu = (item: CatalogEntityItem) => {
renderItemMenu(item: CatalogEntityItem) {
const menuItems = this.contextMenu.menuItems.filter((menuItem) => !menuItem.onlyVisibleForSource || menuItem.onlyVisibleForSource === item.entity.metadata.source); const menuItems = this.contextMenu.menuItems.filter((menuItem) => !menuItem.onlyVisibleForSource || menuItem.onlyVisibleForSource === item.entity.metadata.source);
return ( return (
@ -170,14 +171,18 @@ export class Catalog extends React.Component<Props> {
</MenuItem> </MenuItem>
)) ))
} }
<MenuItem key="add-to-hotbar" onClick={() => this.addToHotbar(item)}> <MenuItem key="add-to-hotbar" onClick={() => this.addToHotbar(item) }>
Pin to Hotbar Pin to Hotbar
</MenuItem> </MenuItem>
</MenuActions> </MenuActions>
); );
} };
render() { render() {
if (!this.catalogEntityStore) {
return null;
}
return ( return (
<PageLayout <PageLayout
className="CatalogPage" className="CatalogPage"
@ -185,7 +190,7 @@ export class Catalog extends React.Component<Props> {
provideBackButtonNavigation={false} provideBackButtonNavigation={false}
contentGaps={false}> contentGaps={false}>
<ItemListLayout <ItemListLayout
renderHeaderTitle={this.activeCategory?.metadata.name ?? "Browse All"} renderHeaderTitle={this.catalogEntityStore.activeCategory?.metadata.name ?? "Browse All"}
isClusterScoped isClusterScoped
isSearchable={true} isSearchable={true}
isSelectable={false} isSelectable={false}
@ -209,13 +214,13 @@ export class Catalog extends React.Component<Props> {
renderTableContents={(item: CatalogEntityItem) => [ renderTableContents={(item: CatalogEntityItem) => [
item.name, item.name,
item.source, item.source,
item.labels.map((label) => <Badge key={label} label={label} title={label}/>), item.labels.map((label) => <Badge key={label} label={label} title={label} />),
{ title: item.phase, className: kebabCase(item.phase) } { title: item.phase, className: kebabCase(item.phase) }
]} ]}
onDetails={(item: CatalogEntityItem) => this.onDetails(item)} onDetails={(item: CatalogEntityItem) => this.onDetails(item) }
renderItemMenu={this.renderItemMenu} renderItemMenu={this.renderItemMenu}
/> />
<CatalogAddButton category={this.activeCategory}/> <CatalogAddButton category={this.catalogEntityStore.activeCategory} />
</PageLayout> </PageLayout>
); );
} }