mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Turn on strict mode in tsconfig.json, some helpful lints, and required cleanup where strictness necessitates it (#5195)
* Turn on strict mode in tsconfig.json - Add route, clusterRoute, and payloadValidatedClusterRoute helper functions to improve types with backend routes - Turn on the following new lints: - react/jsx-first-prop-new-line - react/jsx-wrap-multilines - react/jsx-one-expression-per-line - react/jsx-max-props-per-line - react/jsx-indent - react/jsx-indent-props Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix build Signed-off-by: Sebastian Malton <sebastian@malton.name> * Replace KubeObject scope strings with enum Signed-off-by: Sebastian Malton <sebastian@malton.name> * Revert package.json version changes Signed-off-by: Sebastian Malton <sebastian@malton.name> * revert move hostedCluster(Id) Signed-off-by: Sebastian Malton <sebastian@malton.name> * change some type param names to be not single letters Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove copy-extension-themes Signed-off-by: Sebastian Malton <sebastian@malton.name> * add new make clean action Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix build to not use webpack for generating types Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix kube-object-menu.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix select.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix catalog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * revert move fileNameMigration to index Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix ref logic error Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix log-resource-selector.test.tsx tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix dock-store.test.ts test by overriding createStorage to not touch file system Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix cluster.test.ts tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix kube=api.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fixed hotbar-store.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix kubeconfig-manager.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix cluster-role-bindings/__tests__/dialog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix role-bindings/__tests__/dialog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix pods.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix delete-cluster-dialog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix daemonset.store.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix replicaset.store.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix statefulsets/dialog/dialog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix replicasets/scale-dialog/dialog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix deployments.store.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix deployments/scale/dialog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix cronjob.store.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix stateful-set.api.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix deployment.api.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix api-manager.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix statefulset.store.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix job.store.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix pods.store.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix scroll-spy.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix hotbar-remove-command.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix catalog-entity-registry.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix welcome.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix verify-that-all-routes-have-route-component.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix pod-tolerations.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * better fix for previous 3 fixes, plus also select.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix kube-object-menu.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix app-paths.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix dock-tabs.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix isReactNode typing Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix sub-title.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix drawer.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix list-layout.tsx and header.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix error-boundary.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix upgrade-chart/store.ts and dock-tab.store.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix install-chart/store.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix edit-resource/store.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix create-resource/store.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix namespace-select.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix namespace-select-filter.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix crd-list.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix wrong types for extensions Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix circular dependency Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix circular dependency on catalogCategoryRegistry Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix api-kube Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix type errors, most <Select /> errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * fixing more type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * some more fixing type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * convert all KubeApis to injectable with legacy global backups Signed-off-by: Sebastian Malton <sebastian@malton.name> * factor out into a common file all the exports Signed-off-by: Sebastian Malton <sebastian@malton.name> * convert all KubeObjectStores to injectable with legacy global backups Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix lint Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove unused legacy KubeApi globals Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix bad previous commit Signed-off-by: Sebastian Malton <sebastian@malton.name> * more crash fixing Signed-off-by: Sebastian Malton <sebastian@malton.name> * try and fix behavioural tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix sidebar-and-tab-navigation-for-core.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix sidebar-and-tab-navigation-for-extensions.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-using-application-menu.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix catalog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * Make ThemeStore non-singleton and fix navigation-to-terminal-preferences.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * extensions.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix catalog-entity-registry.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-using-application-menu.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix log-resource-selector.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix dock-tabs.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix delete-cluster-dialog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-to-kubernetes-preferences.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-to-editor-preferences.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-to-proxy-preferences.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-using-application-menu.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-to-application-preferences.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix dock-store.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix select.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix role-bindings/__tests__/dialog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix hotbar-remove-command.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix cluster-role-bindings/__tests__/dialog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-to-extension-specific-preferences.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-to-telemetry-preferences.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix closing-preferences.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-to-editor-preferences.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-to-proxy-preferences.test.ts - Fix other type errors too Signed-off-by: Sebastian Malton <sebastian@malton.name> * final tweaks Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add more tsconfig files, fix bug in <Catalog> - Make all of history, navigation injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * Convert all of kube-details-params/ and navigate/ to injectable - This fixes a runtime error that was encountered during testing Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix runtime errors on renderer - remove all static uses of `createPageParam` (and then removed the legacy global) - Made LensRendererExtension and LensMainExtension just used dependencies and not the getLegacyDi - Fixed circular dep in extension-loader Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move registerStore calls to after injectMany Signed-off-by: Sebastian Malton <sebastian@malton.name> * replace all the rest of the legacy uses of apiManager Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix stack overflow and cycles in DI Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix NamespaceSelectFilter not opening Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix WizardStep and AddNamespaceDialog Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix KubeApi's not being registered Signed-off-by: Sebastian Malton <sebastian@malton.name> * cleanup WindowManager Signed-off-by: Sebastian Malton <sebastian@malton.name> * Proper fix for Wizard, fix NamespaceStore.subscribe Signed-off-by: Sebastian Malton <sebastian@malton.name> * Rewrite withTooltip to be more type correct - Fixes mobx related "too many recursive actions" error - Change all the uses of withTooltips to be functional components Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add e2e test to cover kube api registration Signed-off-by: Sebastian Malton <sebastian@malton.name> * cleanup internal-commands Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove cast in <Animate> Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix command-palette e2e test Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix type error after rebase Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix test name Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix lint Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix code to help CodeQL scanner Signed-off-by: Sebastian Malton <sebastian@malton.name> * update intree extension lock files Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix build-extensions picking wrong @types/react Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix tests from rebase Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix type error Signed-off-by: Sebastian Malton <sebastian@malton.name> * Make KubeconfigSyncManager more injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix crash in test mode for Dialog Signed-off-by: Sebastian Malton <sebastian@malton.name> * make Select snapshots deterministic Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix new type error Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix kube-object.store.test.ts typing Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix merge build issues Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix snapshots after merge Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix lint after merge Signed-off-by: Sebastian Malton <sebastian@malton.name> * reexport BaseKubeJsonApiObjectMetadata Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix typo in terminalSpawningPool Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove duplicate license header Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix typo to waitUntilDefined Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove iter use from getLegacyGlobalDiForExtensionApi Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove complex createStorage override Signed-off-by: Sebastian Malton <sebastian@malton.name> * override logger with mocks only when needed for tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove specialized overrideStore flags for getDiForUnitTesting Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove unnecessary | undefined types from the exactOptionalFieldTypes experiment Signed-off-by: Sebastian Malton <sebastian@malton.name> * use more descriptive name for local test mocks Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove unnecessary addition to 'make clean' target Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove oddity of KubeObjectStore.getById(undefined) being allowed Signed-off-by: Sebastian Malton <sebastian@malton.name> * rename KubeObject.getDescriptor in favour of name without fundemental JS meaning Signed-off-by: Sebastian Malton <sebastian@malton.name> * Simplify legacyRegisterApi when working in behaviour unit tests - Don't emit within main environment as there should be no auto registering there Signed-off-by: Sebastian Malton <sebastian@malton.name> * change confusing variable name in ReactiveDuration Signed-off-by: Sebastian Malton <sebastian@malton.name> * make visitor pattern more explicit for Entity contextMenuOpen Signed-off-by: Sebastian Malton <sebastian@malton.name> * toggleDetails -> toggleKubeDetailsPane is more specific Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove outdated comment Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix bug where LensExtension dependencies are not set Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests from the revert of react 18 Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix more tests from merge Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix typings with new is-compatible-extension tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * more type fixing Signed-off-by: Sebastian Malton <sebastian@malton.name> * Revert in-tree extension versions Signed-off-by: Sebastian Malton <sebastian@malton.name> * Improve name of guarding injectable for stores and apis - New name better implies that it is just a guard state and does not do anything Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add helper for <Select>.isMulti for storing in a Set<Value> Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix is-compatible-extension.test.ts types Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
1 parent
381d77c633
commit
dfcb7c3330
1062 files changed
+26512
-28689
No files matched your search
@@ -11,16 +11,16 @@ import rendererExtensionsInjectable from "../../../../extensions/renderer-extens
|
||||
import { CatalogCategory } from "../../../api/catalog-entity";
|
||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||
import type { AdditionalCategoryColumnRegistration, CategoryColumnRegistration } from "../custom-category-columns";
|
||||
import type { CategoryColumns, GetCategoryColumnsParams } from "../get-category-columns.injectable";
|
||||
import getCategoryColumnsInjectable from "../get-category-columns.injectable";
|
||||
import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable";
|
||||
import type { CategoryColumns, GetCategoryColumnsParams } from "../columns/get.injectable";
|
||||
import getCategoryColumnsInjectable from "../columns/get.injectable";
|
||||
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
|
||||
|
||||
class TestCategory extends CatalogCategory {
|
||||
apiVersion = "catalog.k8slens.dev/v1alpha1";
|
||||
kind = "CatalogCategory";
|
||||
metadata: {
|
||||
name: "Test";
|
||||
icon: "question_mark";
|
||||
metadata = {
|
||||
name: "Test",
|
||||
icon: "question_mark",
|
||||
};
|
||||
spec: CatalogCategorySpec = {
|
||||
group: "foo.bar.bat",
|
||||
@@ -32,7 +32,10 @@ class TestCategory extends CatalogCategory {
|
||||
|
||||
constructor(columns?: CategoryColumnRegistration[]) {
|
||||
super();
|
||||
this.spec.displayColumns = columns;
|
||||
this.spec = {
|
||||
displayColumns: columns,
|
||||
...this.spec,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,19 +57,19 @@ describe("Custom Category Columns", () => {
|
||||
});
|
||||
|
||||
it("should contain a kind column if activeCategory is falsy", () => {
|
||||
expect(getCategoryColumns({ activeCategory: null }).renderTableHeader.find(elem => elem.title === "Kind")).toBeTruthy();
|
||||
expect(getCategoryColumns({ activeCategory: null }).renderTableHeader.find(elem => elem?.title === "Kind")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should not contain a kind column if activeCategory is truthy", () => {
|
||||
expect(getCategoryColumns({ activeCategory: new TestCategory() }).renderTableHeader.find(elem => elem.title === "Kind")).toBeFalsy();
|
||||
expect(getCategoryColumns({ activeCategory: new TestCategory() }).renderTableHeader.find(elem => elem?.title === "Kind")).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should include the default columns if the provided category doesn't provide any", () => {
|
||||
expect(getCategoryColumns({ activeCategory: new TestCategory() }).renderTableHeader.find(elem => elem.title === "Source")).toBeTruthy();
|
||||
expect(getCategoryColumns({ activeCategory: new TestCategory() }).renderTableHeader.find(elem => elem?.title === "Source")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should not include the default columns if the provided category provides any", () => {
|
||||
expect(getCategoryColumns({ activeCategory: new TestCategory([]) }).renderTableHeader.find(elem => elem.title === "Source")).toBeFalsy();
|
||||
expect(getCategoryColumns({ activeCategory: new TestCategory([]) }).renderTableHeader.find(elem => elem?.title === "Source")).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should include the displayColumns from the provided category", () => {
|
||||
@@ -80,7 +83,7 @@ describe("Custom Category Columns", () => {
|
||||
},
|
||||
];
|
||||
|
||||
expect(getCategoryColumns({ activeCategory: new TestCategory(columns) }).renderTableHeader.find(elem => elem.title === "Foo")).toBeTruthy();
|
||||
expect(getCategoryColumns({ activeCategory: new TestCategory(columns) }).renderTableHeader.find(elem => elem?.title === "Foo")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -117,11 +120,11 @@ describe("Custom Category Columns", () => {
|
||||
});
|
||||
|
||||
it("should include columns from extensions that match", () => {
|
||||
expect(getCategoryColumns({ activeCategory: new TestCategory() }).renderTableHeader.find(elem => elem.title === "High")).toBeTruthy();
|
||||
expect(getCategoryColumns({ activeCategory: new TestCategory() }).renderTableHeader.find(elem => elem?.title === "High")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should not include columns from extensions that don't match", () => {
|
||||
expect(getCategoryColumns({ activeCategory: new TestCategory() }).renderTableHeader.find(elem => elem.title === "High2")).toBeFalsy();
|
||||
expect(getCategoryColumns({ activeCategory: new TestCategory() }).renderTableHeader.find(elem => elem?.title === "High2")).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -20,8 +20,8 @@ describe("Custom Category Views", () => {
|
||||
});
|
||||
|
||||
it("should order items correctly over all extensions", () => {
|
||||
const component1 = (): React.ReactElement => null;
|
||||
const component2 = (): React.ReactElement => null;
|
||||
const component1 = (): React.ReactNode => null;
|
||||
const component2 = (): React.ReactNode => null;
|
||||
|
||||
di.override(rendererExtensionsInjectable, () => computed(() => [
|
||||
{
|
||||
@@ -51,15 +51,15 @@ describe("Custom Category Views", () => {
|
||||
] as LensRendererExtension[]));
|
||||
|
||||
const customCategoryViews = di.inject(customCategoryViewsInjectable);
|
||||
const { after } = customCategoryViews.get().get("foo").get("bar");
|
||||
const { after = [] } = customCategoryViews.get().get("foo")?.get("bar") ?? {};
|
||||
|
||||
expect(after[0].View).toBe(component2);
|
||||
expect(after[1].View).toBe(component1);
|
||||
});
|
||||
|
||||
it("should put put priority < 50 items in before", () => {
|
||||
const component1 = (): React.ReactElement => null;
|
||||
const component2 = (): React.ReactElement => null;
|
||||
const component1 = (): React.ReactNode => null;
|
||||
const component2 = (): React.ReactNode => null;
|
||||
|
||||
di.override(rendererExtensionsInjectable, () => computed(() => [
|
||||
{
|
||||
@@ -89,7 +89,7 @@ describe("Custom Category Views", () => {
|
||||
] as LensRendererExtension[]));
|
||||
|
||||
const customCategoryViews = di.inject(customCategoryViewsInjectable);
|
||||
const { before } = customCategoryViews.get().get("foo").get("bar");
|
||||
const { before = [] } = customCategoryViews.get().get("foo")?.get("bar") ?? {};
|
||||
|
||||
expect(before[0].View).toBe(component1);
|
||||
});
|
||||
|
||||
@@ -114,18 +114,20 @@ export class CatalogAddButton extends React.Component<CatalogAddButtonProps> {
|
||||
onClick={this.onButtonClick}
|
||||
>
|
||||
{this.items.map((menuItem, index) => {
|
||||
return <SpeedDialAction
|
||||
key={index}
|
||||
icon={<Icon material={menuItem.icon}/>}
|
||||
tooltipTitle={menuItem.title}
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation();
|
||||
menuItem.onClick();
|
||||
}}
|
||||
TooltipClasses={{
|
||||
popper: "catalogSpeedDialPopper",
|
||||
}}
|
||||
/>;
|
||||
return (
|
||||
<SpeedDialAction
|
||||
key={index}
|
||||
icon={<Icon material={menuItem.icon}/>}
|
||||
tooltipTitle={menuItem.title}
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation();
|
||||
menuItem.onClick();
|
||||
}}
|
||||
TooltipClasses={{
|
||||
popper: "catalogSpeedDialPopper",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</SpeedDial>
|
||||
);
|
||||
|
||||
@@ -14,16 +14,22 @@ import { CatalogEntityDetailRegistry } from "../../../extensions/registries";
|
||||
import { isDevelopment } from "../../../common/vars";
|
||||
import { cssNames } from "../../utils";
|
||||
import { Avatar } from "../avatar";
|
||||
import { getLabelBadges } from "./helpers";
|
||||
import type { GetLabelBadges } from "./get-label-badges.injectable";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import getLabelBadgesInjectable from "./get-label-badges.injectable";
|
||||
|
||||
export interface CatalogEntityDetailsProps<T extends CatalogEntity> {
|
||||
entity: T;
|
||||
export interface CatalogEntityDetailsProps<Entity extends CatalogEntity> {
|
||||
entity: Entity;
|
||||
hideDetails(): void;
|
||||
onRun: () => void;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
getLabelBadges: GetLabelBadges;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class CatalogEntityDetails<T extends CatalogEntity> extends Component<CatalogEntityDetailsProps<T>> {
|
||||
class NonInjectedCatalogEntityDetails<Entity extends CatalogEntity> extends Component<CatalogEntityDetailsProps<Entity> & Dependencies> {
|
||||
categoryIcon(category: CatalogCategory) {
|
||||
if (Icon.isSvg(category.metadata.icon)) {
|
||||
return <Icon svg={category.metadata.icon} smallest />;
|
||||
@@ -32,11 +38,11 @@ export class CatalogEntityDetails<T extends CatalogEntity> extends Component<Cat
|
||||
}
|
||||
}
|
||||
|
||||
renderContent(entity: T) {
|
||||
const { onRun, hideDetails } = this.props;
|
||||
renderContent(entity: Entity) {
|
||||
const { onRun, hideDetails, getLabelBadges } = this.props;
|
||||
const detailItems = CatalogEntityDetailRegistry.getInstance().getItemsForKind(entity.kind, entity.apiVersion);
|
||||
const details = detailItems.map(({ components }, index) => <components.Details entity={entity} key={index} />);
|
||||
const showDefaultDetails = detailItems.find((item) => item.priority > 999) === undefined;
|
||||
const showDefaultDetails = detailItems.find((item) => item.priority ?? 50 > 999) === undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -109,3 +115,10 @@ export class CatalogEntityDetails<T extends CatalogEntity> extends Component<Cat
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const CatalogEntityDetails = withInjectables<Dependencies, CatalogEntityDetailsProps<CatalogEntity>>(NonInjectedCatalogEntityDetails, {
|
||||
getProps: (di, props) => ({
|
||||
...props,
|
||||
getLabelBadges: di.inject(getLabelBadgesInjectable),
|
||||
}),
|
||||
}) as <Entity extends CatalogEntity>(props: CatalogEntityDetailsProps<Entity>) => React.ReactElement;
|
||||
@@ -4,45 +4,42 @@
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { cssNames } from "../../utils";
|
||||
import { cssNames, hasDefiniteField } from "../../utils";
|
||||
import type { MenuActionsProps } from "../menu/menu-actions";
|
||||
import { MenuActions } from "../menu/menu-actions";
|
||||
import type { CatalogEntity, CatalogEntityContextMenuContext } from "../../api/catalog-entity";
|
||||
import type { CatalogEntity, CatalogEntityContextMenu } from "../../api/catalog-entity";
|
||||
import { observer } from "mobx-react";
|
||||
import { makeObservable, observable } from "mobx";
|
||||
import { observable } from "mobx";
|
||||
import { MenuItem } from "../menu";
|
||||
import { Icon } from "../icon";
|
||||
import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item";
|
||||
import type { VisitEntityContextMenu } from "../../../common/catalog/visit-entity-context-menu.injectable";
|
||||
import visitEntityContextMenuInjectable from "../../../common/catalog/visit-entity-context-menu.injectable";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import type { Navigate } from "../../navigation/navigate.injectable";
|
||||
import navigateInjectable from "../../navigation/navigate.injectable";
|
||||
import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable";
|
||||
import normalizeCatalogEntityContextMenuInjectable from "../../catalog/normalize-menu-item.injectable";
|
||||
|
||||
export interface CatalogEntityDrawerMenuProps<T extends CatalogEntity> extends MenuActionsProps {
|
||||
entity: T;
|
||||
export interface CatalogEntityDrawerMenuProps<Entity extends CatalogEntity> extends MenuActionsProps {
|
||||
entity: Entity;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
normalizeMenuItem: NormalizeCatalogEntityContextMenu;
|
||||
navigate: Navigate;
|
||||
visitEntityContextMenu: VisitEntityContextMenu;
|
||||
}
|
||||
|
||||
@observer
|
||||
class NonInjectedCatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Component<CatalogEntityDrawerMenuProps<T> & Dependencies> {
|
||||
@observable private contextMenu: CatalogEntityContextMenuContext;
|
||||
|
||||
constructor(props: CatalogEntityDrawerMenuProps<T> & Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
private readonly menuItems = observable.array<CatalogEntityContextMenu>();
|
||||
|
||||
componentDidMount() {
|
||||
this.contextMenu = {
|
||||
menuItems: [],
|
||||
this.props.visitEntityContextMenu(this.props.entity, {
|
||||
menuItems: this.menuItems,
|
||||
navigate: this.props.navigate,
|
||||
};
|
||||
this.props.entity?.onContextMenuOpen(this.contextMenu);
|
||||
});
|
||||
}
|
||||
|
||||
getMenuItems(entity: T): React.ReactChild[] {
|
||||
@@ -50,9 +47,9 @@ class NonInjectedCatalogEntityDrawerMenu<T extends CatalogEntity> extends React.
|
||||
return [];
|
||||
}
|
||||
|
||||
const items = this.contextMenu.menuItems
|
||||
.filter(menuItem => menuItem.icon)
|
||||
const items = this.menuItems
|
||||
.map(this.props.normalizeMenuItem)
|
||||
.filter(hasDefiniteField("icon"))
|
||||
.map(menuItem => (
|
||||
<MenuItem key={menuItem.title} onClick={menuItem.onClick}>
|
||||
<Icon
|
||||
@@ -67,8 +64,22 @@ class NonInjectedCatalogEntityDrawerMenu<T extends CatalogEntity> extends React.
|
||||
<HotbarToggleMenuItem
|
||||
key="hotbar-toggle"
|
||||
entity={entity}
|
||||
addContent={<Icon material="push_pin" interactive small tooltip="Add to Hotbar"/>}
|
||||
removeContent={<Icon svg="push_off" interactive small tooltip="Remove from Hotbar"/>}
|
||||
addContent={(
|
||||
<Icon
|
||||
material="push_pin"
|
||||
interactive
|
||||
small
|
||||
tooltip="Add to Hotbar"
|
||||
/>
|
||||
)}
|
||||
removeContent={(
|
||||
<Icon
|
||||
svg="push_off"
|
||||
interactive
|
||||
small
|
||||
tooltip="Remove from Hotbar"
|
||||
/>
|
||||
)}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -78,7 +89,7 @@ class NonInjectedCatalogEntityDrawerMenu<T extends CatalogEntity> extends React.
|
||||
render() {
|
||||
const { className, entity, ...menuProps } = this.props;
|
||||
|
||||
if (!this.contextMenu || !entity.isEnabled()) {
|
||||
if (!this.menuItems.length || !entity.isEnabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -97,7 +108,8 @@ class NonInjectedCatalogEntityDrawerMenu<T extends CatalogEntity> extends React.
|
||||
export const CatalogEntityDrawerMenu = withInjectables<Dependencies, CatalogEntityDrawerMenuProps<CatalogEntity>>(NonInjectedCatalogEntityDrawerMenu, {
|
||||
getProps: (di, props) => ({
|
||||
...props,
|
||||
visitEntityContextMenu: di.inject(visitEntityContextMenuInjectable),
|
||||
normalizeMenuItem: di.inject(normalizeCatalogEntityContextMenuInjectable),
|
||||
navigate: di.inject(navigateInjectable),
|
||||
}),
|
||||
}) as <Entity extends CatalogEntity>(props: CatalogEntityDrawerMenuProps<Entity>) => React.ReactElement;
|
||||
}) as <Entity extends CatalogEntity>(props: CatalogEntityDrawerMenuProps<Entity>) => JSX.Element;
|
||||
+6
-4
@@ -3,14 +3,16 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { CatalogEntityStore } from "./catalog-entity.store";
|
||||
import catalogEntityRegistryInjectable from "../../../api/catalog-entity-registry/catalog-entity-registry.injectable";
|
||||
import { catalogEntityStore } from "./catalog-entity.store";
|
||||
import catalogEntityRegistryInjectable from "../../../api/catalog/entity/registry.injectable";
|
||||
import catalogCategoryRegistryInjectable from "../../../../common/catalog/category-registry.injectable";
|
||||
|
||||
const catalogEntityStoreInjectable = getInjectable({
|
||||
id: "catalog-entity-store",
|
||||
|
||||
instantiate: (di) => new CatalogEntityStore({
|
||||
registry: di.inject(catalogEntityRegistryInjectable),
|
||||
instantiate: (di) => catalogEntityStore({
|
||||
entityRegistry: di.inject(catalogEntityRegistryInjectable),
|
||||
catalogRegistry: di.inject(catalogCategoryRegistryInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -3,61 +3,81 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { computed, makeObservable, observable, reaction } from "mobx";
|
||||
import type { CatalogEntityRegistry } from "../../../api/catalog-entity-registry";
|
||||
import type { IComputedValue, IObservableValue } from "mobx";
|
||||
import { computed, observable, reaction } from "mobx";
|
||||
import type { CatalogEntityRegistry } from "../../../api/catalog/entity/registry";
|
||||
import type { CatalogEntity } from "../../../api/catalog-entity";
|
||||
import { ItemStore } from "../../../../common/item.store";
|
||||
import type { CatalogCategory } from "../../../../common/catalog";
|
||||
import { catalogCategoryRegistry } from "../../../../common/catalog";
|
||||
import { autoBind, disposer } from "../../../../common/utils";
|
||||
import type { CatalogCategory, CatalogCategoryRegistry } from "../../../../common/catalog";
|
||||
import type { Disposer } from "../../../../common/utils";
|
||||
import { disposer } from "../../../../common/utils";
|
||||
import type { ItemListStore } from "../../item-object-list";
|
||||
|
||||
interface Dependencies {
|
||||
registry: CatalogEntityRegistry;
|
||||
entityRegistry: CatalogEntityRegistry;
|
||||
catalogRegistry: CatalogCategoryRegistry;
|
||||
}
|
||||
|
||||
export class CatalogEntityStore extends ItemStore<CatalogEntity> {
|
||||
constructor(private dependencies: Dependencies) {
|
||||
super();
|
||||
makeObservable(this);
|
||||
autoBind(this);
|
||||
}
|
||||
export type CatalogEntityStore = ItemListStore<CatalogEntity, false> & {
|
||||
readonly entities: IComputedValue<CatalogEntity[]>;
|
||||
readonly activeCategory: IObservableValue<CatalogCategory | undefined>;
|
||||
readonly selectedItemId: IObservableValue<string | undefined>;
|
||||
readonly selectedItem: IComputedValue<CatalogEntity | undefined>;
|
||||
watch(): Disposer;
|
||||
onRun(entity: CatalogEntity): void;
|
||||
};
|
||||
|
||||
@observable activeCategory?: CatalogCategory;
|
||||
@observable selectedItemId?: string;
|
||||
export function catalogEntityStore({
|
||||
entityRegistry,
|
||||
catalogRegistry,
|
||||
}: Dependencies): CatalogEntityStore {
|
||||
const activeCategory = observable.box<CatalogCategory | undefined>(undefined);
|
||||
const selectedItemId = observable.box<string | undefined>(undefined);
|
||||
const entities = computed(() => {
|
||||
const category = activeCategory.get();
|
||||
|
||||
@computed get entities() {
|
||||
if (!this.activeCategory) {
|
||||
return this.dependencies.registry.filteredItems;
|
||||
return category
|
||||
? entityRegistry.getItemsForCategory(category, { filtered: true })
|
||||
: entityRegistry.filteredItems;
|
||||
});
|
||||
const selectedItem = computed(() => {
|
||||
const id = selectedItemId.get();
|
||||
|
||||
if (!id) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.dependencies.registry.getItemsForCategory(this.activeCategory, { filtered: true });
|
||||
}
|
||||
return entities.get().find(entity => entity.getId() === id);
|
||||
});
|
||||
const loadAll = () => {
|
||||
const category = activeCategory.get();
|
||||
|
||||
@computed get selectedItem() {
|
||||
return this.entities.find(e => e.getId() === this.selectedItemId);
|
||||
}
|
||||
|
||||
watch() {
|
||||
return disposer(
|
||||
reaction(() => this.entities, () => this.loadAll()),
|
||||
reaction(() => this.activeCategory, () => this.loadAll(), { delay: 100 }),
|
||||
);
|
||||
}
|
||||
|
||||
loadAll() {
|
||||
if (this.activeCategory) {
|
||||
this.activeCategory.emit("load");
|
||||
if (category) {
|
||||
category.emit("load");
|
||||
} else {
|
||||
for (const category of catalogCategoryRegistry.items) {
|
||||
for (const category of catalogRegistry.items) {
|
||||
category.emit("load");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// concurrency is true to fix bug if catalog filter is removed and added at the same time
|
||||
return this.loadItems(() => this.entities, undefined, true);
|
||||
}
|
||||
|
||||
onRun(entity: CatalogEntity): void {
|
||||
this.dependencies.registry.onRun(entity);
|
||||
}
|
||||
return {
|
||||
entities,
|
||||
selectedItem,
|
||||
activeCategory,
|
||||
selectedItemId,
|
||||
watch: () => disposer(
|
||||
reaction(() => entities.get(), loadAll),
|
||||
reaction(() => activeCategory.get(), loadAll, { delay: 100 }),
|
||||
),
|
||||
onRun: entity => entityRegistry.onRun(entity),
|
||||
failedLoading: false,
|
||||
getTotalCount: () => entityRegistry.filteredItems.length,
|
||||
isLoaded: true,
|
||||
isSelected: (item) => item.getId() === selectedItemId.get(),
|
||||
isSelectedAll: () => false,
|
||||
pickOnlySelected: () => [],
|
||||
toggleSelection: () => {},
|
||||
toggleSelectionAll: () => {},
|
||||
removeSelectedItems: async () => {},
|
||||
};
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import { observer } from "mobx-react";
|
||||
import { CatalogCategoryLabel } from "./catalog-category-label";
|
||||
|
||||
export interface CatalogMenuProps {
|
||||
activeItem: string;
|
||||
activeTab: string | undefined;
|
||||
onItemClick: (id: string) => void;
|
||||
}
|
||||
|
||||
@@ -54,9 +54,14 @@ export const CatalogMenu = observer((props: CatalogMenuProps) => {
|
||||
defaultExpanded={["catalog"]}
|
||||
defaultCollapseIcon={<Icon material="expand_more"/>}
|
||||
defaultExpandIcon={<Icon material="chevron_right" />}
|
||||
selected={props.activeItem || "browse"}
|
||||
selected={props.activeTab || "browse"}
|
||||
>
|
||||
<Item nodeId="browse" label="Browse" data-testid="*-tab" onClick={() => props.onItemClick("*")}/>
|
||||
<Item
|
||||
nodeId="browse"
|
||||
label="Browse"
|
||||
data-testid="*-tab"
|
||||
onClick={() => props.onItemClick("*")}
|
||||
/>
|
||||
<Item
|
||||
nodeId="catalog"
|
||||
label={<div className={styles.parent}>Categories</div>}
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ const catalogPreviousActiveTabStorageInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const createStorage = di.inject(createStorageInjectable);
|
||||
|
||||
return createStorage(
|
||||
return createStorage<string | null>(
|
||||
"catalog-previous-active-tab",
|
||||
browseCatalogTab,
|
||||
);
|
||||
|
||||
@@ -9,24 +9,25 @@ import userEvent from "@testing-library/user-event";
|
||||
import { Catalog } from "./catalog";
|
||||
import { mockWindow } from "../../../../__mocks__/windowMock";
|
||||
import type { CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog";
|
||||
import { CatalogCategoryRegistry, CatalogEntity } from "../../../common/catalog";
|
||||
import { CatalogEntityRegistry } from "../../api/catalog-entity-registry";
|
||||
import { CatalogEntity } from "../../../common/catalog";
|
||||
import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
|
||||
import { CatalogEntityDetailRegistry } from "../../../extensions/registries";
|
||||
import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import catalogEntityStoreInjectable from "./catalog-entity-store/catalog-entity-store.injectable";
|
||||
import catalogEntityRegistryInjectable from "../../api/catalog-entity-registry/catalog-entity-registry.injectable";
|
||||
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
|
||||
import type { DiRender } from "../test-utils/renderFor";
|
||||
import { renderFor } from "../test-utils/renderFor";
|
||||
import { ThemeStore } from "../../theme.store";
|
||||
import { UserStore } from "../../../common/user-store";
|
||||
import mockFs from "mock-fs";
|
||||
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||
import appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
||||
import type { AppEvent } from "../../../common/app-event-bus/event-bus";
|
||||
import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable";
|
||||
import { computed } from "mobx";
|
||||
import ipcRendererInjectable from "../../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
|
||||
import { UserStore } from "../../../common/user-store";
|
||||
|
||||
mockWindow();
|
||||
jest.mock("electron", () => ({
|
||||
@@ -60,57 +61,57 @@ class MockCatalogEntity extends CatalogEntity {
|
||||
constructor(data: CatalogEntityData, public onRun: (context: CatalogEntityActionContext) => void | Promise<void>) {
|
||||
super(data);
|
||||
}
|
||||
}
|
||||
|
||||
public onContextMenuOpen(): void | Promise<void> {}
|
||||
public onSettingsOpen(): void | Promise<void> {}
|
||||
function createMockCatalogEntity(onRun: (context: CatalogEntityActionContext) => void | Promise<void>) {
|
||||
return new MockCatalogEntity({
|
||||
metadata: {
|
||||
uid: "a_catalogEntity_uid",
|
||||
name: "a catalog entity",
|
||||
labels: {
|
||||
test: "label",
|
||||
},
|
||||
},
|
||||
status: {
|
||||
phase: "",
|
||||
},
|
||||
spec: {},
|
||||
}, onRun);
|
||||
}
|
||||
|
||||
describe("<Catalog />", () => {
|
||||
function createMockCatalogEntity(onRun: (context: CatalogEntityActionContext) => void | Promise<void>) {
|
||||
return new MockCatalogEntity({
|
||||
metadata: {
|
||||
uid: "a_catalogEntity_uid",
|
||||
name: "a catalog entity",
|
||||
labels: {
|
||||
test: "label",
|
||||
},
|
||||
},
|
||||
status: {
|
||||
phase: "",
|
||||
},
|
||||
spec: {},
|
||||
}, onRun);
|
||||
}
|
||||
|
||||
let di: DiContainer;
|
||||
let catalogEntityStore: CatalogEntityStore;
|
||||
let catalogEntityRegistry: CatalogEntityRegistry;
|
||||
let emitEvent: (event: AppEvent) => void;
|
||||
let onRun: jest.MockedFunction<(context: CatalogEntityActionContext) => void | Promise<void>>;
|
||||
let catalogEntityItem: MockCatalogEntity;
|
||||
let render: DiRender;
|
||||
|
||||
beforeEach(async () => {
|
||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||
|
||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||
di.permitSideEffects(appVersionInjectable);
|
||||
|
||||
await di.runSetups();
|
||||
|
||||
mockFs();
|
||||
|
||||
UserStore.createInstance();
|
||||
ThemeStore.createInstance();
|
||||
CatalogEntityDetailRegistry.createInstance();
|
||||
|
||||
render = renderFor(di);
|
||||
onRun = jest.fn();
|
||||
catalogEntityItem = createMockCatalogEntity(onRun);
|
||||
catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
||||
|
||||
const catalogCategoryRegistry = new CatalogCategoryRegistry();
|
||||
|
||||
catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
||||
UserStore.createInstance(); // TODO: replace with DI
|
||||
|
||||
di.override(catalogEntityRegistryInjectable, () => catalogEntityRegistry);
|
||||
di.override(ipcRendererInjectable, () => ({
|
||||
on: jest.fn(),
|
||||
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
|
||||
} as never));
|
||||
|
||||
emitEvent = jest.fn();
|
||||
|
||||
@@ -119,27 +120,20 @@ describe("<Catalog />", () => {
|
||||
}));
|
||||
|
||||
catalogEntityStore = di.inject(catalogEntityStoreInjectable);
|
||||
Object.assign(catalogEntityStore, {
|
||||
selectedItem: computed(() => catalogEntityItem),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
UserStore.resetInstance();
|
||||
ThemeStore.resetInstance();
|
||||
CatalogEntityDetailRegistry.resetInstance();
|
||||
|
||||
UserStore.resetInstance();
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it("can use catalogEntityRegistry.addOnBeforeRun to add hooks for catalog entities", (done) => {
|
||||
const onRun = jest.fn();
|
||||
const catalogEntityItem = createMockCatalogEntity(onRun);
|
||||
|
||||
// mock as if there is a selected item > the detail panel opens
|
||||
jest
|
||||
.spyOn(catalogEntityStore, "selectedItem", "get")
|
||||
.mockImplementation(() => catalogEntityItem);
|
||||
|
||||
catalogEntityRegistry.addOnBeforeRun(
|
||||
(event) => {
|
||||
expect(event.target.getId()).toBe("a_catalogEntity_uid");
|
||||
@@ -152,22 +146,12 @@ describe("<Catalog />", () => {
|
||||
},
|
||||
);
|
||||
|
||||
render(
|
||||
<Catalog />,
|
||||
);
|
||||
render(<Catalog />);
|
||||
|
||||
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
|
||||
});
|
||||
|
||||
it("onBeforeRun prevents event => onRun wont be triggered", (done) => {
|
||||
const onRun = jest.fn();
|
||||
const catalogEntityItem = createMockCatalogEntity(onRun);
|
||||
|
||||
// mock as if there is a selected item > the detail panel opens
|
||||
jest
|
||||
.spyOn(catalogEntityStore, "selectedItem", "get")
|
||||
.mockImplementation(() => catalogEntityItem);
|
||||
|
||||
catalogEntityRegistry.addOnBeforeRun(
|
||||
(e) => {
|
||||
setTimeout(() => {
|
||||
@@ -184,14 +168,6 @@ describe("<Catalog />", () => {
|
||||
});
|
||||
|
||||
it("addOnBeforeRun throw an exception => onRun will be triggered", (done) => {
|
||||
const onRun = jest.fn();
|
||||
const catalogEntityItem = createMockCatalogEntity(onRun);
|
||||
|
||||
// mock as if there is a selected item > the detail panel opens
|
||||
jest
|
||||
.spyOn(catalogEntityStore, "selectedItem", "get")
|
||||
.mockImplementation(() => catalogEntityItem);
|
||||
|
||||
catalogEntityRegistry.addOnBeforeRun(
|
||||
() => {
|
||||
setTimeout(() => {
|
||||
@@ -209,13 +185,7 @@ describe("<Catalog />", () => {
|
||||
});
|
||||
|
||||
it("addOnRunHook return a promise and does not prevent run event => onRun()", (done) => {
|
||||
const onRun = jest.fn(() => done());
|
||||
const catalogEntityItem = createMockCatalogEntity(onRun);
|
||||
|
||||
// mock as if there is a selected item > the detail panel opens
|
||||
jest
|
||||
.spyOn(catalogEntityStore, "selectedItem", "get")
|
||||
.mockImplementation(() => catalogEntityItem);
|
||||
onRun.mockImplementation(() => done());
|
||||
|
||||
catalogEntityRegistry.addOnBeforeRun(
|
||||
async () => {
|
||||
@@ -229,14 +199,6 @@ describe("<Catalog />", () => {
|
||||
});
|
||||
|
||||
it("addOnRunHook return a promise and prevents event wont be triggered", (done) => {
|
||||
const onRun = jest.fn();
|
||||
const catalogEntityItem = createMockCatalogEntity(onRun);
|
||||
|
||||
// mock as if there is a selected item > the detail panel opens
|
||||
jest
|
||||
.spyOn(catalogEntityStore, "selectedItem", "get")
|
||||
.mockImplementation(() => catalogEntityItem);
|
||||
|
||||
catalogEntityRegistry.addOnBeforeRun(
|
||||
async (e) => {
|
||||
expect(onRun).not.toBeCalled();
|
||||
@@ -256,14 +218,6 @@ describe("<Catalog />", () => {
|
||||
});
|
||||
|
||||
it("addOnRunHook return a promise and reject => onRun will be triggered", (done) => {
|
||||
const onRun = jest.fn();
|
||||
const catalogEntityItem = createMockCatalogEntity(onRun);
|
||||
|
||||
// mock as if there is a selected item > the detail panel opens
|
||||
jest
|
||||
.spyOn(catalogEntityStore, "selectedItem", "get")
|
||||
.mockImplementation(() => catalogEntityItem);
|
||||
|
||||
catalogEntityRegistry.addOnBeforeRun(
|
||||
async () => {
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -12,13 +12,12 @@ import type { IComputedValue } from "mobx";
|
||||
import { action, computed, makeObservable, observable, reaction, runInAction, when } from "mobx";
|
||||
import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store";
|
||||
import { MenuItem, MenuActions } from "../menu";
|
||||
import type { CatalogEntityContextMenuContext } from "../../api/catalog-entity";
|
||||
import type { HotbarStore } from "../../../common/hotbar-store";
|
||||
import type { CatalogEntity } from "../../../common/catalog";
|
||||
import { catalogCategoryRegistry } from "../../../common/catalog";
|
||||
import type { CatalogEntityContextMenu } from "../../api/catalog-entity";
|
||||
import type { CatalogCategory, CatalogCategoryRegistry, CatalogEntity } from "../../../common/catalog";
|
||||
import { CatalogAddButton } from "./catalog-add-button";
|
||||
import { Notifications } from "../notifications";
|
||||
import { MainLayout } from "../layout/main-layout";
|
||||
import type { StorageLayer } from "../../utils";
|
||||
import { prevDefault } from "../../utils";
|
||||
import { CatalogEntityDetails } from "./catalog-entity-details";
|
||||
import { CatalogMenu } from "./catalog-menu";
|
||||
@@ -29,8 +28,8 @@ import { Avatar } from "../avatar";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import catalogPreviousActiveTabStorageInjectable from "./catalog-previous-active-tab-storage/catalog-previous-active-tab-storage.injectable";
|
||||
import catalogEntityStoreInjectable from "./catalog-entity-store/catalog-entity-store.injectable";
|
||||
import type { GetCategoryColumnsParams, CategoryColumns } from "./get-category-columns.injectable";
|
||||
import getCategoryColumnsInjectable from "./get-category-columns.injectable";
|
||||
import type { GetCategoryColumnsParams, CategoryColumns } from "./columns/get.injectable";
|
||||
import getCategoryColumnsInjectable from "./columns/get.injectable";
|
||||
import type { RegisteredCustomCategoryViewDecl } from "./custom-views.injectable";
|
||||
import customCategoryViewsInjectable from "./custom-views.injectable";
|
||||
import type { CustomCategoryViewComponents } from "./custom-views";
|
||||
@@ -40,14 +39,18 @@ import catalogRouteParametersInjectable from "./catalog-route-parameters.injecta
|
||||
import { browseCatalogTab } from "./catalog-browse-tab";
|
||||
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/hotbar-store.injectable";
|
||||
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
|
||||
import type { HotbarStore } from "../../../common/hotbars/store";
|
||||
import type { VisitEntityContextMenu } from "../../../common/catalog/visit-entity-context-menu.injectable";
|
||||
import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable";
|
||||
import visitEntityContextMenuInjectable from "../../../common/catalog/visit-entity-context-menu.injectable";
|
||||
import type { Navigate } from "../../navigation/navigate.injectable";
|
||||
import navigateInjectable from "../../navigation/navigate.injectable";
|
||||
import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable";
|
||||
import normalizeCatalogEntityContextMenuInjectable from "../../catalog/normalize-menu-item.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
catalogPreviousActiveTabStorage: { set: (value: string ) => void; get: () => string };
|
||||
catalogPreviousActiveTabStorage: StorageLayer<string | null>;
|
||||
catalogEntityStore: CatalogEntityStore;
|
||||
getCategoryColumns: (params: GetCategoryColumnsParams) => CategoryColumns;
|
||||
customCategoryViews: IComputedValue<Map<string, Map<string, RegisteredCustomCategoryViewDecl>>>;
|
||||
@@ -58,13 +61,15 @@ interface Dependencies {
|
||||
};
|
||||
navigateToCatalog: NavigateToCatalog;
|
||||
hotbarStore: HotbarStore;
|
||||
catalogCategoryRegistry: CatalogCategoryRegistry;
|
||||
visitEntityContextMenu: VisitEntityContextMenu;
|
||||
navigate: Navigate;
|
||||
normalizeMenuItem: NormalizeCatalogEntityContextMenu;
|
||||
}
|
||||
|
||||
@observer
|
||||
class NonInjectedCatalog extends React.Component<Dependencies> {
|
||||
@observable private contextMenu: CatalogEntityContextMenuContext;
|
||||
private readonly menuItems = observable.array<CatalogEntityContextMenu>();
|
||||
@observable activeTab?: string;
|
||||
|
||||
constructor(props: Dependencies) {
|
||||
@@ -93,42 +98,50 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
this.contextMenu = {
|
||||
menuItems: observable.array([]),
|
||||
navigate: this.props.navigate,
|
||||
};
|
||||
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 = item;
|
||||
catalogEntityStore.activeCategory.set(item);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Notifications.error(<p>Unknown category: {routeTab}</p>);
|
||||
Notifications.error((
|
||||
<p>
|
||||
{"Unknown category: "}
|
||||
{routeTab}
|
||||
</p>
|
||||
));
|
||||
}
|
||||
}, { 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.getId())) {
|
||||
const item = catalogCategoryRegistry.filteredItems[0];
|
||||
|
||||
runInAction(() => {
|
||||
if (item) {
|
||||
this.activeTab = item.getId();
|
||||
this.props.catalogEntityStore.activeCategory = item;
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
this.props.emitEvent({
|
||||
name: "catalog",
|
||||
action: "open",
|
||||
@@ -144,15 +157,15 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
||||
}
|
||||
|
||||
onDetails = (entity: CatalogEntity) => {
|
||||
if (this.props.catalogEntityStore.selectedItemId) {
|
||||
this.props.catalogEntityStore.selectedItemId = null;
|
||||
if (this.props.catalogEntityStore.selectedItemId.get()) {
|
||||
this.props.catalogEntityStore.selectedItemId.set(undefined);
|
||||
} else {
|
||||
this.props.catalogEntityStore.onRun(entity);
|
||||
}
|
||||
};
|
||||
|
||||
get categories() {
|
||||
return catalogCategoryRegistry.items;
|
||||
return this.props.catalogCategoryRegistry.items;
|
||||
}
|
||||
|
||||
onTabChange = action((tabId: string | null) => {
|
||||
@@ -175,26 +188,25 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
||||
}
|
||||
});
|
||||
|
||||
renderNavigation() {
|
||||
return (
|
||||
<CatalogMenu activeItem={this.activeTab} onItemClick={this.onTabChange} />
|
||||
);
|
||||
}
|
||||
|
||||
renderItemMenu = (entity: CatalogEntity) => {
|
||||
const onOpen = () => {
|
||||
this.contextMenu.menuItems = [];
|
||||
|
||||
entity.onContextMenuOpen(this.contextMenu);
|
||||
this.menuItems.clear();
|
||||
this.props.visitEntityContextMenu(entity, {
|
||||
menuItems: this.menuItems,
|
||||
navigate: this.props.navigate,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<MenuActions onOpen={onOpen}>
|
||||
<MenuItem key="open-details" onClick={() => this.props.catalogEntityStore.selectedItemId = entity.getId()}>
|
||||
<MenuItem
|
||||
key="open-details"
|
||||
onClick={() => this.props.catalogEntityStore.selectedItemId.set(entity.getId())}
|
||||
>
|
||||
View Details
|
||||
</MenuItem>
|
||||
{
|
||||
this.contextMenu.menuItems
|
||||
this.menuItems
|
||||
.map(this.props.normalizeMenuItem)
|
||||
.map((menuItem, index) => (
|
||||
<MenuItem key={index} onClick={menuItem.onClick}>
|
||||
@@ -231,7 +243,6 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
||||
<Icon
|
||||
small
|
||||
className={styles.pinIcon}
|
||||
material={!isItemInHotbar && "push_pin"}
|
||||
svg={isItemInHotbar ? "push_off" : "push_pin"}
|
||||
tooltip={isItemInHotbar ? "Remove from Hotbar" : "Add to Hotbar"}
|
||||
onClick={prevDefault(() => isItemInHotbar ? this.removeFromHotbar(entity) : this.addToHotbar(entity))}
|
||||
@@ -240,15 +251,12 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
||||
);
|
||||
}
|
||||
|
||||
renderViews = () => {
|
||||
const { catalogEntityStore, customCategoryViews } = this.props;
|
||||
const { activeCategory } = catalogEntityStore;
|
||||
|
||||
renderViews = (activeCategory: CatalogCategory | undefined) => {
|
||||
if (!activeCategory) {
|
||||
return this.renderList();
|
||||
return this.renderList(activeCategory);
|
||||
}
|
||||
|
||||
const customViews = customCategoryViews.get()
|
||||
const customViews = this.props.customCategoryViews.get()
|
||||
.get(activeCategory.spec.group)
|
||||
?.get(activeCategory.spec.names.kind);
|
||||
const renderView = ({ View }: CustomCategoryViewComponents, index: number) => (
|
||||
@@ -261,15 +269,14 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
||||
return (
|
||||
<>
|
||||
{customViews?.before.map(renderView)}
|
||||
{this.renderList()}
|
||||
{this.renderList(activeCategory)}
|
||||
{customViews?.after.map(renderView)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
renderList() {
|
||||
renderList(activeCategory: CatalogCategory | undefined) {
|
||||
const { catalogEntityStore, getCategoryColumns } = this.props;
|
||||
const { activeCategory } = catalogEntityStore;
|
||||
const tableId = activeCategory
|
||||
? `catalog-items-${activeCategory.metadata.name.replace(" ", "")}`
|
||||
: "catalog-items";
|
||||
@@ -279,14 +286,15 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
||||
}
|
||||
|
||||
return (
|
||||
<ItemListLayout
|
||||
<ItemListLayout<CatalogEntity, false>
|
||||
className={styles.Catalog}
|
||||
tableId={tableId}
|
||||
renderHeaderTitle={activeCategory?.metadata.name ?? "Browse All"}
|
||||
isSelectable={false}
|
||||
isConfigurable={true}
|
||||
preloadStores={false}
|
||||
store={catalogEntityStore}
|
||||
getItems={() => catalogEntityStore.entities}
|
||||
getItems={() => catalogEntityStore.entities.get()}
|
||||
customizeTableRowProps={entity => ({
|
||||
disabled: !entity.isEnabled(),
|
||||
})}
|
||||
@@ -302,27 +310,37 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const selectedEntity = this.props.catalogEntityStore.selectedItem;
|
||||
const activeCategory = this.props.catalogEntityStore.activeCategory.get();
|
||||
const selectedItem = this.props.catalogEntityStore.selectedItem.get();
|
||||
|
||||
return (
|
||||
<MainLayout sidebar={this.renderNavigation()}>
|
||||
<MainLayout
|
||||
sidebar={(
|
||||
<CatalogMenu
|
||||
activeTab={this.activeTab}
|
||||
onItemClick={this.onTabChange}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<div className={styles.views}>
|
||||
{this.renderViews()}
|
||||
{this.renderViews(activeCategory)}
|
||||
</div>
|
||||
{
|
||||
selectedEntity
|
||||
? <CatalogEntityDetails
|
||||
entity={selectedEntity}
|
||||
hideDetails={() => this.props.catalogEntityStore.selectedItemId = null}
|
||||
onRun={() => this.props.catalogEntityStore.onRun(selectedEntity)}
|
||||
/>
|
||||
: (
|
||||
<RenderDelay>
|
||||
<CatalogAddButton
|
||||
category={this.props.catalogEntityStore.activeCategory}
|
||||
/>
|
||||
</RenderDelay>
|
||||
selectedItem
|
||||
? (
|
||||
<CatalogEntityDetails
|
||||
entity={selectedItem}
|
||||
hideDetails={() => this.props.catalogEntityStore.selectedItemId.set(undefined)}
|
||||
onRun={() => this.props.catalogEntityStore.onRun(selectedItem)}
|
||||
/>
|
||||
)
|
||||
: activeCategory
|
||||
? (
|
||||
<RenderDelay>
|
||||
<CatalogAddButton category={activeCategory} />
|
||||
</RenderDelay>
|
||||
)
|
||||
: null
|
||||
}
|
||||
</MainLayout>
|
||||
);
|
||||
@@ -340,6 +358,8 @@ export const Catalog = withInjectables<Dependencies>(NonInjectedCatalog, {
|
||||
navigateToCatalog: di.inject(navigateToCatalogInjectable),
|
||||
emitEvent: di.inject(appEventBusInjectable).emit,
|
||||
hotbarStore: di.inject(hotbarStoreInjectable),
|
||||
catalogCategoryRegistry: di.inject(catalogCategoryRegistryInjectable),
|
||||
visitEntityContextMenu: di.inject(visitEntityContextMenuInjectable),
|
||||
navigate: di.inject(navigateInjectable),
|
||||
normalizeMenuItem: di.inject(normalizeCatalogEntityContextMenuInjectable),
|
||||
}),
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import namedCategoryColumnInjectable from "./named-category.injectable";
|
||||
import defaultCategoryColumnsInjectable from "./default-category.injectable";
|
||||
|
||||
const defaultBrowseAllColumns: RegisteredAdditionalCategoryColumn[] = [
|
||||
{
|
||||
id: "kind",
|
||||
priority: 5,
|
||||
renderCell: entity => entity.kind,
|
||||
titleProps: {
|
||||
id: "kind",
|
||||
sortBy: "kind",
|
||||
title: "Kind",
|
||||
},
|
||||
sortCallback: entity => entity.kind,
|
||||
},
|
||||
];
|
||||
|
||||
const browseAllColumnsInjectable = getInjectable({
|
||||
id: "browse-all-columns",
|
||||
instantiate: (di) => [
|
||||
...defaultBrowseAllColumns,
|
||||
di.inject(namedCategoryColumnInjectable),
|
||||
...di.inject(defaultCategoryColumnsInjectable),
|
||||
],
|
||||
});
|
||||
|
||||
export default browseAllColumnsInjectable;
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import styles from "../catalog.module.scss";
|
||||
import React from "react";
|
||||
import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns";
|
||||
import { KubeObject } from "../../../../common/k8s-api/kube-object";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import getLabelBadgesInjectable from "../get-label-badges.injectable";
|
||||
|
||||
const defaultCategoryColumnsInjectable = getInjectable({
|
||||
id: "default-category-columns",
|
||||
instantiate: (di): RegisteredAdditionalCategoryColumn[] => {
|
||||
const getLabelBadges = di.inject(getLabelBadgesInjectable);
|
||||
|
||||
return [
|
||||
{
|
||||
id: "source",
|
||||
priority: 10,
|
||||
renderCell: entity => entity.getSource(),
|
||||
titleProps: {
|
||||
title: "Source",
|
||||
className: styles.sourceCell,
|
||||
id: "source",
|
||||
sortBy: "source",
|
||||
},
|
||||
sortCallback: entity => entity.getSource(),
|
||||
searchFilter: entity => `source=${entity.getSource()}`,
|
||||
},
|
||||
{
|
||||
id: "labels",
|
||||
priority: 20,
|
||||
renderCell: getLabelBadges,
|
||||
titleProps: {
|
||||
id: "labels",
|
||||
title: "Labels",
|
||||
className: `${styles.labelsCell} scrollable`,
|
||||
},
|
||||
searchFilter: entity => KubeObject.stringifyLabels(entity.metadata.labels),
|
||||
},
|
||||
{
|
||||
id: "status",
|
||||
priority: 30,
|
||||
renderCell: entity => (
|
||||
<span key="phase" className={entity.status.phase}>
|
||||
{entity.status.phase}
|
||||
</span>
|
||||
),
|
||||
titleProps: {
|
||||
title: "Status",
|
||||
className: styles.statusCell,
|
||||
id: "status",
|
||||
sortBy: "status",
|
||||
},
|
||||
searchFilter: entity => entity.status.phase,
|
||||
sortCallback: entity => entity.status.phase,
|
||||
},
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
export default defaultCategoryColumnsInjectable;
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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 { orderBy } from "lodash";
|
||||
import type { CatalogCategory, CatalogEntity } from "../../../../common/catalog";
|
||||
import type { ItemListLayoutProps } from "../../item-object-list";
|
||||
import browseAllColumnsInjectable from "./browse-all.injectable";
|
||||
import getColumnsForCategoryInjectable from "./specific-category.injectable";
|
||||
|
||||
export interface GetCategoryColumnsParams {
|
||||
activeCategory: CatalogCategory | null | undefined;
|
||||
}
|
||||
|
||||
export type CategoryColumns = Required<Pick<ItemListLayoutProps<CatalogEntity>, "sortingCallbacks" | "searchFilters" | "renderTableContents" | "renderTableHeader">>;
|
||||
export type GetCategoryColumns = (params: GetCategoryColumnsParams) => CategoryColumns;
|
||||
|
||||
const getCategoryColumnsInjectable = getInjectable({
|
||||
id: "get-category-columns",
|
||||
|
||||
instantiate: (di): GetCategoryColumns => {
|
||||
const getColumnsForCategory = di.inject(getColumnsForCategoryInjectable);
|
||||
const browseAllColumns = di.inject(browseAllColumnsInjectable);
|
||||
|
||||
return ({ activeCategory }) => {
|
||||
const allRegistrations = orderBy(
|
||||
activeCategory
|
||||
? getColumnsForCategory(activeCategory)
|
||||
: browseAllColumns,
|
||||
"priority",
|
||||
"asc",
|
||||
);
|
||||
|
||||
const sortingCallbacks: CategoryColumns["sortingCallbacks"] = {};
|
||||
const searchFilters: CategoryColumns["searchFilters"] = [];
|
||||
const renderTableHeader: CategoryColumns["renderTableHeader"] = [];
|
||||
const tableRowRenderers: ((entity: CatalogEntity) => React.ReactNode)[] = [];
|
||||
|
||||
for (const registration of allRegistrations) {
|
||||
if (registration.sortCallback) {
|
||||
sortingCallbacks[registration.id] = registration.sortCallback;
|
||||
}
|
||||
|
||||
if (registration.searchFilter) {
|
||||
searchFilters.push(registration.searchFilter);
|
||||
}
|
||||
|
||||
tableRowRenderers.push(registration.renderCell);
|
||||
renderTableHeader.push(registration.titleProps);
|
||||
}
|
||||
|
||||
return {
|
||||
sortingCallbacks,
|
||||
renderTableHeader,
|
||||
renderTableContents: entity => tableRowRenderers.map(fn => fn(entity)),
|
||||
searchFilters,
|
||||
};
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default getCategoryColumnsInjectable;
|
||||
+10
-12
@@ -3,15 +3,15 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import styles from "./catalog.module.scss";
|
||||
import type { CatalogEntity } from "../../../common/catalog";
|
||||
import { prevDefault } from "../../utils";
|
||||
import { Avatar } from "../avatar";
|
||||
import { Icon } from "../icon";
|
||||
import styles from "../catalog.module.scss";
|
||||
import type { CatalogEntity } from "../../../../common/catalog";
|
||||
import { prevDefault } from "../../../utils";
|
||||
import { Avatar } from "../../avatar";
|
||||
import { Icon } from "../../icon";
|
||||
import React from "react";
|
||||
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
|
||||
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
|
||||
import type { HotbarStore } from "../../../common/hotbar-store";
|
||||
import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns";
|
||||
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
|
||||
import type { HotbarStore } from "../../../../common/hotbars/store";
|
||||
|
||||
const renderEntityName = (hotbarStore: HotbarStore) => (entity: CatalogEntity) => {
|
||||
const isItemInHotbar = hotbarStore.isAddedToActive(entity);
|
||||
@@ -37,7 +37,6 @@ const renderEntityName = (hotbarStore: HotbarStore) => (entity: CatalogEntity) =
|
||||
<Icon
|
||||
small
|
||||
className={styles.pinIcon}
|
||||
material={!isItemInHotbar && "push_pin"}
|
||||
svg={isItemInHotbar ? "push_off" : "push_pin"}
|
||||
tooltip={isItemInHotbar ? "Remove from Hotbar" : "Add to Hotbar"}
|
||||
onClick={onClick}
|
||||
@@ -46,8 +45,7 @@ const renderEntityName = (hotbarStore: HotbarStore) => (entity: CatalogEntity) =
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const nameCategoryColumnInjectable = getInjectable({
|
||||
const namedCategoryColumnInjectable = getInjectable({
|
||||
id: "name-category-column",
|
||||
instantiate: (di): RegisteredAdditionalCategoryColumn => ({
|
||||
id: "name",
|
||||
@@ -64,4 +62,4 @@ const nameCategoryColumnInjectable = getInjectable({
|
||||
}),
|
||||
});
|
||||
|
||||
export default nameCategoryColumnInjectable;
|
||||
export default namedCategoryColumnInjectable;
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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 { CatalogCategory } from "../../../api/catalog-entity";
|
||||
import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns";
|
||||
import categoryColumnsInjectable from "../custom-category-columns.injectable";
|
||||
import defaultCategoryColumnsInjectable from "./default-category.injectable";
|
||||
import namedCategoryColumnInjectable from "./named-category.injectable";
|
||||
|
||||
export type GetColumnsForCategory = (activeCategory: CatalogCategory) => RegisteredAdditionalCategoryColumn[];
|
||||
|
||||
const getColumnsForCategoryInjectable = getInjectable({
|
||||
id: "get-columns-for-category",
|
||||
instantiate: (di): GetColumnsForCategory => {
|
||||
const extensionColumns = di.inject(categoryColumnsInjectable);
|
||||
const defaultCategoryColumns = di.inject(defaultCategoryColumnsInjectable);
|
||||
const nameCategoryColumn = di.inject(namedCategoryColumnInjectable);
|
||||
|
||||
return (activeCategory) => {
|
||||
const fromExtensions = (
|
||||
extensionColumns
|
||||
.get()
|
||||
.get(activeCategory.spec.group)
|
||||
?.get(activeCategory.spec.names.kind)
|
||||
?? []
|
||||
);
|
||||
const fromCategory = activeCategory.spec.displayColumns?.map(({ priority = 50, ...column }) => ({
|
||||
priority,
|
||||
...column,
|
||||
})) ?? defaultCategoryColumns;
|
||||
|
||||
return [
|
||||
nameCategoryColumn,
|
||||
...fromExtensions,
|
||||
...fromCategory,
|
||||
];
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default getColumnsForCategoryInjectable;
|
||||
@@ -1,98 +0,0 @@
|
||||
/**
|
||||
* 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 { orderBy } from "lodash";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import type { CatalogCategory, CatalogEntity } from "../../../common/catalog";
|
||||
import type { ItemListLayoutProps } from "../item-object-list";
|
||||
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
|
||||
import categoryColumnsInjectable from "./custom-category-columns.injectable";
|
||||
import { defaultCategoryColumns, browseAllColumns } from "./internal-category-columns";
|
||||
import nameCategoryColumnInjectable from "./name-category-column.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
extensionColumns: IComputedValue<Map<string, Map<string, RegisteredAdditionalCategoryColumn[]>>>;
|
||||
nameCategoryColumn: RegisteredAdditionalCategoryColumn;
|
||||
}
|
||||
|
||||
export interface GetCategoryColumnsParams {
|
||||
activeCategory: CatalogCategory | null | undefined;
|
||||
}
|
||||
|
||||
export type CategoryColumns = Required<Pick<ItemListLayoutProps<CatalogEntity>, "sortingCallbacks" | "searchFilters" | "renderTableContents" | "renderTableHeader">>;
|
||||
|
||||
function getSpecificCategoryColumns(activeCategory: CatalogCategory, extensionColumns: IComputedValue<Map<string, Map<string, RegisteredAdditionalCategoryColumn[]>>>, nameCategoryColumn: RegisteredAdditionalCategoryColumn): RegisteredAdditionalCategoryColumn[] {
|
||||
const fromExtensions = (
|
||||
extensionColumns
|
||||
.get()
|
||||
.get(activeCategory.spec.group)
|
||||
?.get(activeCategory.spec.names.kind)
|
||||
?? []
|
||||
);
|
||||
const fromCategory = activeCategory.spec.displayColumns?.map(({ priority = 50, ...column }) => ({
|
||||
priority,
|
||||
...column,
|
||||
})) ?? defaultCategoryColumns;
|
||||
|
||||
return [
|
||||
nameCategoryColumn,
|
||||
...fromExtensions,
|
||||
...fromCategory,
|
||||
];
|
||||
}
|
||||
|
||||
function getBrowseAllColumns(nameCategoryColumn: RegisteredAdditionalCategoryColumn): RegisteredAdditionalCategoryColumn[] {
|
||||
return [
|
||||
...browseAllColumns,
|
||||
nameCategoryColumn,
|
||||
...defaultCategoryColumns,
|
||||
];
|
||||
}
|
||||
|
||||
const getCategoryColumns = ({ extensionColumns, nameCategoryColumn }: Dependencies) => ({ activeCategory }: GetCategoryColumnsParams): CategoryColumns => {
|
||||
const allRegistrations = orderBy(
|
||||
activeCategory
|
||||
? getSpecificCategoryColumns(activeCategory, extensionColumns, nameCategoryColumn)
|
||||
: getBrowseAllColumns(nameCategoryColumn),
|
||||
"priority",
|
||||
"asc",
|
||||
);
|
||||
|
||||
const sortingCallbacks: CategoryColumns["sortingCallbacks"] = {};
|
||||
const searchFilters: CategoryColumns["searchFilters"] = [];
|
||||
const renderTableHeader: CategoryColumns["renderTableHeader"] = [];
|
||||
const tableRowRenderers: ((entity: CatalogEntity) => React.ReactNode)[] = [];
|
||||
|
||||
for (const registration of allRegistrations) {
|
||||
if (registration.sortCallback) {
|
||||
sortingCallbacks[registration.id] = registration.sortCallback;
|
||||
}
|
||||
|
||||
if (registration.searchFilter) {
|
||||
searchFilters.push(registration.searchFilter);
|
||||
}
|
||||
|
||||
tableRowRenderers.push(registration.renderCell);
|
||||
renderTableHeader.push(registration.titleProps);
|
||||
}
|
||||
|
||||
return {
|
||||
sortingCallbacks,
|
||||
renderTableHeader,
|
||||
renderTableContents: entity => tableRowRenderers.map(fn => fn(entity)),
|
||||
searchFilters,
|
||||
};
|
||||
};
|
||||
|
||||
const getCategoryColumnsInjectable = getInjectable({
|
||||
id: "get-category-columns",
|
||||
|
||||
instantiate: (di) => getCategoryColumns({
|
||||
extensionColumns: di.inject(categoryColumnsInjectable),
|
||||
nameCategoryColumn: di.inject(nameCategoryColumnInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
export default getCategoryColumnsInjectable;
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import styles from "./catalog.module.scss";
|
||||
import React from "react";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { CatalogEntity } from "../../api/catalog-entity";
|
||||
import searchUrlPageParamInjectable from "../input/search-url-page-param.injectable";
|
||||
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
import { Badge } from "../badge";
|
||||
|
||||
export type GetLabelBadges = (entity: CatalogEntity, onClick?: (evt: React.MouseEvent<any, MouseEvent>) => void) => JSX.Element[];
|
||||
|
||||
const getLabelBadgesInjectable = getInjectable({
|
||||
id: "get-label-badges",
|
||||
instantiate: (di): GetLabelBadges => {
|
||||
const searchUrlParam = di.inject(searchUrlPageParamInjectable);
|
||||
|
||||
return (entity, onClick) => (
|
||||
KubeObject.stringifyLabels(entity.metadata.labels)
|
||||
.map(label => (
|
||||
<Badge
|
||||
scrollable
|
||||
className={styles.badge}
|
||||
key={label}
|
||||
label={label}
|
||||
title={label}
|
||||
onClick={(event) => {
|
||||
searchUrlParam.set(label);
|
||||
onClick?.(event);
|
||||
event.stopPropagation();
|
||||
}}
|
||||
expandable={false}
|
||||
/>
|
||||
))
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default getLabelBadgesInjectable;
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import styles from "./catalog.module.scss";
|
||||
import React from "react";
|
||||
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
import type { CatalogEntity } from "../../api/catalog-entity";
|
||||
import { Badge } from "../badge";
|
||||
import { searchUrlParam } from "../input";
|
||||
|
||||
/**
|
||||
* @param entity The entity to render badge labels for
|
||||
*/
|
||||
export function getLabelBadges(entity: CatalogEntity, onClick?: (evt: React.MouseEvent<any, MouseEvent>) => void) {
|
||||
return KubeObject.stringifyLabels(entity.metadata.labels)
|
||||
.map(label => (
|
||||
<Badge
|
||||
scrollable
|
||||
className={styles.badge}
|
||||
key={label}
|
||||
label={label}
|
||||
title={label}
|
||||
onClick={(event) => {
|
||||
searchUrlParam.set(label);
|
||||
onClick?.(event);
|
||||
event.stopPropagation();
|
||||
}}
|
||||
expandable={false}
|
||||
/>
|
||||
));
|
||||
}
|
||||
@@ -9,17 +9,17 @@ import { MenuItem } from "../menu";
|
||||
|
||||
import type { CatalogEntity } from "../../api/catalog-entity";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
|
||||
import type { HotbarStore } from "../../../common/hotbar-store";
|
||||
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
|
||||
import type { HotbarStore } from "../../../common/hotbars/store";
|
||||
|
||||
interface Dependencies {
|
||||
hotbarStore: HotbarStore;
|
||||
}
|
||||
|
||||
interface HotbarToggleMenuItemProps {
|
||||
entity: CatalogEntity;
|
||||
addContent: ReactNode;
|
||||
removeContent: ReactNode;
|
||||
entity: CatalogEntity;
|
||||
addContent: ReactNode;
|
||||
removeContent: ReactNode;
|
||||
}
|
||||
|
||||
function NonInjectedHotbarToggleMenuItem({
|
||||
@@ -31,15 +31,17 @@ function NonInjectedHotbarToggleMenuItem({
|
||||
const [itemInHotbar, setItemInHotbar] = useState(hotbarStore.isAddedToActive(entity));
|
||||
|
||||
return (
|
||||
<MenuItem onClick={() => {
|
||||
if (itemInHotbar) {
|
||||
hotbarStore.removeFromHotbar(entity.getId());
|
||||
setItemInHotbar(false);
|
||||
} else {
|
||||
hotbarStore.addToHotbar(entity);
|
||||
setItemInHotbar(true);
|
||||
}
|
||||
}}>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
if (itemInHotbar) {
|
||||
hotbarStore.removeFromHotbar(entity.getId());
|
||||
setItemInHotbar(false);
|
||||
} else {
|
||||
hotbarStore.addToHotbar(entity);
|
||||
setItemInHotbar(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{itemInHotbar ? removeContent : addContent }
|
||||
</MenuItem>
|
||||
);
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import styles from "./catalog.module.scss";
|
||||
|
||||
import React from "react";
|
||||
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
|
||||
import { getLabelBadges } from "./helpers";
|
||||
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
|
||||
export const browseAllColumns: RegisteredAdditionalCategoryColumn[] = [
|
||||
{
|
||||
id: "kind",
|
||||
priority: 5,
|
||||
renderCell: entity => entity.kind,
|
||||
titleProps: {
|
||||
id: "kind",
|
||||
sortBy: "kind",
|
||||
title: "Kind",
|
||||
},
|
||||
sortCallback: entity => entity.kind,
|
||||
},
|
||||
];
|
||||
|
||||
export const defaultCategoryColumns: RegisteredAdditionalCategoryColumn[] = [
|
||||
{
|
||||
id: "source",
|
||||
priority: 10,
|
||||
renderCell: entity => entity.getSource(),
|
||||
titleProps: {
|
||||
title: "Source",
|
||||
className: styles.sourceCell,
|
||||
id: "source",
|
||||
sortBy: "source",
|
||||
},
|
||||
sortCallback: entity => entity.getSource(),
|
||||
searchFilter: entity => `source=${entity.getSource()}`,
|
||||
},
|
||||
{
|
||||
id: "labels",
|
||||
priority: 20,
|
||||
renderCell: getLabelBadges,
|
||||
titleProps: {
|
||||
id: "labels",
|
||||
title: "Labels",
|
||||
className: `${styles.labelsCell} scrollable`,
|
||||
},
|
||||
searchFilter: entity => KubeObject.stringifyLabels(entity.metadata.labels),
|
||||
},
|
||||
{
|
||||
id: "status",
|
||||
priority: 30,
|
||||
renderCell: entity => (
|
||||
<span key="phase" className={entity.status.phase}>
|
||||
{entity.status.phase}
|
||||
</span>
|
||||
),
|
||||
titleProps: {
|
||||
title: "Status",
|
||||
className: styles.statusCell,
|
||||
id: "status",
|
||||
sortBy: "status",
|
||||
},
|
||||
searchFilter: entity => entity.status.phase,
|
||||
sortCallback: entity => entity.status.phase,
|
||||
},
|
||||
];
|
||||
Reference in new issue
Block a user