diff --git a/src/common/__tests__/catalog-category-registry.test.ts b/src/common/__tests__/catalog-category-registry.test.ts index 90615d552c..22d5527101 100644 --- a/src/common/__tests__/catalog-category-registry.test.ts +++ b/src/common/__tests__/catalog-category-registry.test.ts @@ -72,4 +72,34 @@ describe("CatalogCategoryRegistry", () => { d2(); expect(registry.items.length).toBe(0); }); + + it("doesn't return items that are filtered out", () => { + const registry = new TestCatalogCategoryRegistry(); + + registry.add(new TestCatalogCategory()); + registry.add(new TestCatalogCategory2()); + + expect(registry.items.length).toBe(2); + expect(registry.filteredItems.length).toBe(2); + + const disposer = registry.addCatalogCategoryFilter(category => category.metadata.name === "Test Category"); + + expect(registry.items.length).toBe(2); + expect(registry.filteredItems.length).toBe(1); + + const disposer2 = registry.addCatalogCategoryFilter(category => category.metadata.name === "foo"); + + expect(registry.items.length).toBe(2); + expect(registry.filteredItems.length).toBe(0); + + disposer(); + + expect(registry.items.length).toBe(2); + expect(registry.filteredItems.length).toBe(0); + + disposer2(); + + expect(registry.items.length).toBe(2); + expect(registry.filteredItems.length).toBe(2); + }); }); diff --git a/src/common/catalog/catalog-category-registry.ts b/src/common/catalog/catalog-category-registry.ts index bad1570c9c..e6da48a815 100644 --- a/src/common/catalog/catalog-category-registry.ts +++ b/src/common/catalog/catalog-category-registry.ts @@ -20,12 +20,18 @@ */ import { action, computed, observable, makeObservable } from "mobx"; -import { Disposer, ExtendedMap } from "../utils"; +import { Disposer, ExtendedMap, iter } from "../utils"; import { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity"; +import { once } from "lodash"; + +export type CategoryFilter = (category: CatalogCategory) => any; export class CatalogCategoryRegistry { protected categories = observable.set(); protected groupKinds = new ExtendedMap>(); + protected filters = observable.set([], { + deep: false, + }); constructor() { makeObservable(this); @@ -47,6 +53,17 @@ export class CatalogCategoryRegistry { return Array.from(this.categories); } + @computed get filteredItems() { + return Array.from( + iter.reduce( + this.filters, + iter.filter, + this.items, + ) + ); + } + + getForGroupKind(group: string, kind: string): T | undefined { return this.groupKinds.get(group)?.get(kind) as T; } @@ -80,6 +97,17 @@ export class CatalogCategoryRegistry { getByName(name: string) { return this.items.find(category => category.metadata?.name == name); } + + /** + * Add a new filter to the set of category filters + * @param fn The function that should return a truthy value if that category should be displayed + * @returns A function to remove that filter + */ + addCatalogCategoryFilter(fn: CategoryFilter): Disposer { + this.filters.add(fn); + + return once(() => void this.filters.delete(fn)); + } } export const catalogCategoryRegistry = new CatalogCategoryRegistry(); diff --git a/src/extensions/lens-renderer-extension.ts b/src/extensions/lens-renderer-extension.ts index b56ffb3d7e..abaac22308 100644 --- a/src/extensions/lens-renderer-extension.ts +++ b/src/extensions/lens-renderer-extension.ts @@ -26,6 +26,7 @@ import { getExtensionPageUrl } from "./registries/page-registry"; import type { CatalogEntity } from "../common/catalog"; import type { Disposer } from "../common/utils"; import { catalogEntityRegistry, EntityFilter } from "../renderer/api/catalog-entity-registry"; +import { catalogCategoryRegistry, CategoryFilter } from "../renderer/api/catalog-category-registry"; export class LensRendererExtension extends LensExtension { globalPages: registries.PageRegistration[] = []; @@ -63,8 +64,8 @@ export class LensRendererExtension extends LensExtension { } /** - * Add a filtering function for the catalog. This will be removed if the extension is disabled. - * @param fn The function which should return a truthy value for those entities which should be kepted + * Add a filtering function for the catalog entities. This will be removed if the extension is disabled. + * @param fn The function which should return a truthy value for those entities which should be kept. * @returns A function to clean up the filter */ addCatalogFilter(fn: EntityFilter): Disposer { @@ -74,4 +75,17 @@ export class LensRendererExtension extends LensExtension { return dispose; } + + /** + * Add a filtering function for the catalog catogries. This will be removed if the extension is disabled. + * @param fn The function which should return a truthy value for those categories which should be kept. + * @returns A function to clean up the filter + */ + addCatalogCategoryFilter(fn: CategoryFilter): Disposer { + const dispose = catalogCategoryRegistry.addCatalogCategoryFilter(fn); + + this[Disposers].push(dispose); + + return dispose; + } } diff --git a/src/renderer/api/catalog-category-registry.ts b/src/renderer/api/catalog-category-registry.ts index a345de5250..91226661a8 100644 --- a/src/renderer/api/catalog-category-registry.ts +++ b/src/renderer/api/catalog-category-registry.ts @@ -20,3 +20,4 @@ */ export { catalogCategoryRegistry } from "../../common/catalog"; +export type { CategoryFilter } from "../../common/catalog"; diff --git a/src/renderer/components/+catalog/catalog-menu.tsx b/src/renderer/components/+catalog/catalog-menu.tsx index 0e3d54094e..6634b2399d 100644 --- a/src/renderer/components/+catalog/catalog-menu.tsx +++ b/src/renderer/components/+catalog/catalog-menu.tsx @@ -29,6 +29,7 @@ import { Icon } from "../icon"; import { StylesProvider } from "@material-ui/core"; import { cssNames } from "../../utils"; import type { CatalogCategory } from "../../api/catalog-entity"; +import { observer } from "mobx-react"; type Props = { activeItem: string; @@ -36,7 +37,7 @@ type Props = { }; function getCategories() { - return catalogCategoryRegistry.items; + return catalogCategoryRegistry.filteredItems; } function getCategoryIcon(category: CatalogCategory) { @@ -53,7 +54,7 @@ function Item(props: TreeItemProps) { ); } -export function CatalogMenu(props: Props) { +export const CatalogMenu = observer((props: Props) => { return ( // Overwrite Material UI styles with injectFirst https://material-ui.com/guides/interoperability/#controlling-priority-4 @@ -88,4 +89,4 @@ export function CatalogMenu(props: Props) { ); -} +}); diff --git a/src/renderer/components/+catalog/catalog.tsx b/src/renderer/components/+catalog/catalog.tsx index 7f6347f5e3..8259e8f077 100644 --- a/src/renderer/components/+catalog/catalog.tsx +++ b/src/renderer/components/+catalog/catalog.tsx @@ -90,8 +90,8 @@ export class Catalog extends React.Component { previousActiveTab.set(this.routeActiveTab); try { - await when(() => (routeTab === "" || !!catalogCategoryRegistry.items.find(i => i.getId() === routeTab)), { timeout: 5_000 }); // we need to wait because extensions might take a while to load - const item = catalogCategoryRegistry.items.find(i => i.getId() === routeTab); + await when(() => (routeTab === "" || !!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; @@ -103,6 +103,20 @@ export class Catalog extends React.Component { } }, {fireImmediately: true}), ]); + + // 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.catalogEntityStore.activeCategory.getId())) { + const item = catalogCategoryRegistry.filteredItems[0]; + + runInAction(() => { + if (item) { + this.activeTab = item.getId(); + this.catalogEntityStore.activeCategory = item; + } + }); + } + })); } addToHotbar(item: CatalogEntityItem): void { HotbarStore.getInstance().addToHotbar(item.entity);