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:
Sebastian Malton authored and GitHub committed 2022-05-16 07:17:57 -04:00
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;
@@ -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>}
@@ -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(() => {
+91 -71
View File
@@ -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;
@@ -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,
},
];