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
@@ -13,49 +13,51 @@ import type { ConfirmDialogParams } from "../confirm-dialog";
|
||||
import type { TableCellProps, TableProps, TableRowProps, TableSortCallbacks } from "../table";
|
||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||
import type { IClassName } from "../../utils";
|
||||
import { autoBind, cssNames, isReactNode, prevDefault, stopPropagation } from "../../utils";
|
||||
import { autoBind, cssNames, isDefined, isReactNode, noop, prevDefault, stopPropagation } from "../../utils";
|
||||
import type { AddRemoveButtonsProps } from "../add-remove-buttons";
|
||||
import { AddRemoveButtons } from "../add-remove-buttons";
|
||||
import { NoItems } from "../no-items";
|
||||
import { Spinner } from "../spinner";
|
||||
import type { ItemObject, ItemStore } from "../../../common/item.store";
|
||||
import type { Filter } from "./page-filters.store";
|
||||
import { pageFilters } from "./page-filters.store";
|
||||
import { ThemeStore } from "../../theme.store";
|
||||
import type { ItemObject } from "../../../common/item.store";
|
||||
import type { Filter, PageFiltersStore } from "./page-filters/store";
|
||||
import type { ThemeStore } from "../../themes/store";
|
||||
import { MenuActions } from "../menu/menu-actions";
|
||||
import { MenuItem } from "../menu";
|
||||
import { Checkbox } from "../checkbox";
|
||||
import { UserStore } from "../../../common/user-store";
|
||||
import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable";
|
||||
import type { ItemListStore } from "./list-layout";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import themeStoreInjectable from "../../themes/store.injectable";
|
||||
import pageFiltersStoreInjectable from "./page-filters/store.injectable";
|
||||
import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable";
|
||||
import openConfirmDialogInjectable from "../confirm-dialog/open.injectable";
|
||||
|
||||
export interface ItemListLayoutContentProps<I extends ItemObject> {
|
||||
export interface ItemListLayoutContentProps<Item extends ItemObject, PreLoadStores extends boolean> {
|
||||
getFilters: () => Filter[];
|
||||
tableId?: string;
|
||||
className: IClassName;
|
||||
getItems: () => I[];
|
||||
store: ItemStore<I>;
|
||||
getItems: () => Item[];
|
||||
store: ItemListStore<Item, PreLoadStores>;
|
||||
getIsReady: () => boolean; // show loading indicator while not ready
|
||||
isSelectable?: boolean; // show checkbox in rows for selecting items
|
||||
isConfigurable?: boolean;
|
||||
copyClassNameFromHeadCells?: boolean;
|
||||
sortingCallbacks?: TableSortCallbacks<I>;
|
||||
tableProps?: Partial<TableProps<I>>; // low-level table configuration
|
||||
renderTableHeader: TableCellProps[] | null;
|
||||
renderTableContents: (item: I) => (ReactNode | TableCellProps)[];
|
||||
renderItemMenu?: (item: I, store: ItemStore<I>) => ReactNode;
|
||||
customizeTableRowProps?: (item: I) => Partial<TableRowProps>;
|
||||
sortingCallbacks?: TableSortCallbacks<Item>;
|
||||
tableProps?: Partial<TableProps<Item>>; // low-level table configuration
|
||||
renderTableHeader?: (TableCellProps | undefined | null)[];
|
||||
renderTableContents: (item: Item) => (ReactNode | TableCellProps)[];
|
||||
renderItemMenu?: (item: Item, store: ItemListStore<Item, PreLoadStores>) => ReactNode;
|
||||
customizeTableRowProps?: (item: Item) => Partial<TableRowProps<Item>>;
|
||||
addRemoveButtons?: Partial<AddRemoveButtonsProps>;
|
||||
virtual?: boolean;
|
||||
|
||||
// item details view
|
||||
hasDetailsView?: boolean;
|
||||
detailsItem?: I;
|
||||
onDetails?: (item: I) => void;
|
||||
detailsItem?: Item;
|
||||
onDetails?: (item: Item) => void;
|
||||
|
||||
// other
|
||||
customizeRemoveDialog?: (selectedItems: I[]) => Partial<ConfirmDialogParams>;
|
||||
customizeRemoveDialog?: (selectedItems: Item[]) => Partial<ConfirmDialogParams>;
|
||||
|
||||
/**
|
||||
* Message to display when a store failed to load
|
||||
@@ -66,12 +68,17 @@ export interface ItemListLayoutContentProps<I extends ItemObject> {
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
themeStore: ThemeStore;
|
||||
pageFiltersStore: PageFiltersStore;
|
||||
openConfirmDialog: OpenConfirmDialog;
|
||||
}
|
||||
|
||||
@observer
|
||||
class NonInjectedItemListLayoutContent<I extends ItemObject> extends React.Component<ItemListLayoutContentProps<I> & Dependencies> {
|
||||
constructor(props: ItemListLayoutContentProps<I> & Dependencies) {
|
||||
class NonInjectedItemListLayoutContent<
|
||||
Item extends ItemObject,
|
||||
PreLoadStores extends boolean,
|
||||
> extends React.Component<ItemListLayoutContentProps<Item, PreLoadStores> & Dependencies> {
|
||||
constructor(props: ItemListLayoutContentProps<Item, PreLoadStores> & Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
autoBind(this);
|
||||
@@ -81,15 +88,15 @@ class NonInjectedItemListLayoutContent<I extends ItemObject> extends React.Compo
|
||||
return this.props.store.failedLoading;
|
||||
}
|
||||
|
||||
renderRow(item: I) {
|
||||
renderRow(item: Item) {
|
||||
return this.getTableRow(item);
|
||||
}
|
||||
|
||||
getTableRow(item: I) {
|
||||
getTableRow(item: Item) {
|
||||
const {
|
||||
isSelectable, renderTableHeader, renderTableContents, renderItemMenu,
|
||||
store, hasDetailsView, onDetails,
|
||||
copyClassNameFromHeadCells, customizeTableRowProps, detailsItem,
|
||||
copyClassNameFromHeadCells, customizeTableRowProps = () => ({}), detailsItem,
|
||||
} = this.props;
|
||||
const { isSelected } = store;
|
||||
|
||||
@@ -99,7 +106,7 @@ class NonInjectedItemListLayoutContent<I extends ItemObject> extends React.Compo
|
||||
searchItem={item}
|
||||
sortItem={item}
|
||||
selected={detailsItem && detailsItem.getId() === item.getId()}
|
||||
onClick={hasDetailsView ? prevDefault(() => onDetails(item)) : undefined}
|
||||
onClick={hasDetailsView ? prevDefault(() => onDetails?.(item)) : undefined}
|
||||
{...customizeTableRowProps(item)}
|
||||
>
|
||||
{isSelectable && (
|
||||
@@ -155,7 +162,7 @@ class NonInjectedItemListLayoutContent<I extends ItemObject> extends React.Compo
|
||||
);
|
||||
}
|
||||
|
||||
removeItemsDialog(selectedItems: I[]) {
|
||||
removeItemsDialog(selectedItems: Item[]) {
|
||||
const { customizeRemoveDialog, store, openConfirmDialog } = this.props;
|
||||
const visibleMaxNamesCount = 5;
|
||||
const selectedNames = selectedItems.map(ns => ns.getName()).slice(0, visibleMaxNamesCount).join(", ");
|
||||
@@ -165,14 +172,38 @@ class NonInjectedItemListLayoutContent<I extends ItemObject> extends React.Compo
|
||||
? selectedCount - visibleMaxNamesCount
|
||||
: 0;
|
||||
const tail = tailCount > 0
|
||||
? <>, and <b>{tailCount}</b> more</>
|
||||
? (
|
||||
<>
|
||||
{", and "}
|
||||
<b>{tailCount}</b>
|
||||
{" more"}
|
||||
</>
|
||||
)
|
||||
: null;
|
||||
const message = selectedCount <= 1
|
||||
? <p>Remove item <b>{selectedNames}</b>?</p>
|
||||
: <p>Remove <b>{selectedCount}</b> items <b>{selectedNames}</b>{tail}?</p>;
|
||||
const onConfirm = store.removeItems
|
||||
? () => store.removeItems(selectedItems)
|
||||
: store.removeSelectedItems;
|
||||
? (
|
||||
<p>
|
||||
{"Remove item "}
|
||||
<b>{selectedNames}</b>
|
||||
?
|
||||
</p>
|
||||
)
|
||||
: (
|
||||
<p>
|
||||
Remove
|
||||
<b>{selectedCount}</b>
|
||||
{" items "}
|
||||
<b>{selectedNames}</b>
|
||||
{tail}
|
||||
?
|
||||
</p>
|
||||
);
|
||||
const { removeSelectedItems, removeItems } = store;
|
||||
const onConfirm = typeof removeItems === "function"
|
||||
? () => removeItems.apply(store, [selectedItems])
|
||||
: typeof removeSelectedItems === "function"
|
||||
? removeSelectedItems.bind(store)
|
||||
: noop;
|
||||
|
||||
openConfirmDialog({
|
||||
ok: onConfirm,
|
||||
@@ -196,7 +227,7 @@ class NonInjectedItemListLayoutContent<I extends ItemObject> extends React.Compo
|
||||
<NoItems>
|
||||
No items found.
|
||||
<p>
|
||||
<a onClick={() => pageFilters.reset()} className="contrast">
|
||||
<a onClick={() => this.props.pageFiltersStore.reset()} className="contrast">
|
||||
Reset filters?
|
||||
</a>
|
||||
</p>
|
||||
@@ -222,7 +253,7 @@ class NonInjectedItemListLayoutContent<I extends ItemObject> extends React.Compo
|
||||
return null;
|
||||
}
|
||||
|
||||
const enabledItems = this.props.getItems().filter(item => !customizeTableRowProps(item).disabled);
|
||||
const enabledItems = this.props.getItems().filter(item => !customizeTableRowProps?.(item).disabled);
|
||||
|
||||
return (
|
||||
<TableHead showTopLine nowrap>
|
||||
@@ -237,11 +268,15 @@ class NonInjectedItemListLayoutContent<I extends ItemObject> extends React.Compo
|
||||
)}
|
||||
</Observer>
|
||||
)}
|
||||
{renderTableHeader.map((cellProps, index) => (
|
||||
this.showColumn(cellProps) && (
|
||||
<TableCell key={cellProps.id ?? index} {...cellProps} />
|
||||
)
|
||||
))}
|
||||
{
|
||||
renderTableHeader
|
||||
.filter(isDefined)
|
||||
.map((cellProps, index) => (
|
||||
this.showColumn(cellProps) && (
|
||||
<TableCell key={cellProps.id ?? index} {...cellProps} />
|
||||
)
|
||||
))
|
||||
}
|
||||
<TableCell className="menu">
|
||||
{isConfigurable && this.renderColumnVisibilityMenu()}
|
||||
</TableCell>
|
||||
@@ -252,10 +287,10 @@ class NonInjectedItemListLayoutContent<I extends ItemObject> extends React.Compo
|
||||
render() {
|
||||
const {
|
||||
store, hasDetailsView, addRemoveButtons = {}, virtual, sortingCallbacks,
|
||||
detailsItem, className, tableProps = {}, tableId, getItems,
|
||||
detailsItem, className, tableProps = {}, tableId, getItems, themeStore,
|
||||
} = this.props;
|
||||
const selectedItemId = detailsItem && detailsItem.getId();
|
||||
const classNames = cssNames(className, "box", "grow", ThemeStore.getInstance().activeTheme.type);
|
||||
const classNames = cssNames(className, "box", "grow", themeStore.activeTheme.type);
|
||||
const items = getItems();
|
||||
const selectedItems = store.pickOnlySelected(items);
|
||||
|
||||
@@ -284,7 +319,7 @@ class NonInjectedItemListLayoutContent<I extends ItemObject> extends React.Compo
|
||||
onRemove={
|
||||
(store.removeItems || store.removeSelectedItems) && selectedItems.length > 0
|
||||
? () => this.removeItemsDialog(selectedItems)
|
||||
: null
|
||||
: undefined
|
||||
}
|
||||
removeTooltip={`Remove selected items (${selectedItems.length})`}
|
||||
{...addRemoveButtons}
|
||||
@@ -298,33 +333,47 @@ class NonInjectedItemListLayoutContent<I extends ItemObject> extends React.Compo
|
||||
showColumn({ id: columnId, showWithColumn }: TableCellProps): boolean {
|
||||
const { tableId, isConfigurable } = this.props;
|
||||
|
||||
return !isConfigurable || !UserStore.getInstance().isTableColumnHidden(tableId, columnId, showWithColumn);
|
||||
return !isConfigurable || !tableId || !UserStore.getInstance().isTableColumnHidden(tableId, columnId, showWithColumn);
|
||||
}
|
||||
|
||||
renderColumnVisibilityMenu() {
|
||||
const { renderTableHeader, tableId } = this.props;
|
||||
const { renderTableHeader = [], tableId } = this.props;
|
||||
|
||||
return (
|
||||
<MenuActions className="ItemListLayoutVisibilityMenu" toolbar={false} autoCloseOnSelect={false}>
|
||||
{renderTableHeader.map((cellProps, index) => (
|
||||
!cellProps.showWithColumn && (
|
||||
<MenuItem key={index} className="input">
|
||||
<Checkbox
|
||||
label={cellProps.title ?? `<${cellProps.className}>`}
|
||||
value={this.showColumn(cellProps)}
|
||||
onChange={() => UserStore.getInstance().toggleTableColumnVisibility(tableId, cellProps.id)}
|
||||
/>
|
||||
</MenuItem>
|
||||
)
|
||||
))}
|
||||
<MenuActions
|
||||
className="ItemListLayoutVisibilityMenu"
|
||||
toolbar={false}
|
||||
autoCloseOnSelect={false}
|
||||
>
|
||||
{
|
||||
renderTableHeader
|
||||
.filter(isDefined)
|
||||
.map((cellProps, index) => (
|
||||
!cellProps.showWithColumn && (
|
||||
<MenuItem key={index} className="input">
|
||||
<Checkbox
|
||||
label={cellProps.title ?? `<${cellProps.className}>`}
|
||||
value={this.showColumn(cellProps)}
|
||||
onChange={(
|
||||
tableId
|
||||
? (() => cellProps.id && UserStore.getInstance().toggleTableColumnVisibility(tableId, cellProps.id))
|
||||
: undefined
|
||||
)}
|
||||
/>
|
||||
</MenuItem>
|
||||
)
|
||||
))
|
||||
}
|
||||
</MenuActions>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const ItemListLayoutContent = withInjectables<Dependencies, ItemListLayoutContentProps<ItemObject>>(NonInjectedItemListLayoutContent, {
|
||||
export const ItemListLayoutContent = withInjectables<Dependencies, ItemListLayoutContentProps<ItemObject, boolean>>(NonInjectedItemListLayoutContent, {
|
||||
getProps: (di, props) => ({
|
||||
...props,
|
||||
themeStore: di.inject(themeStoreInjectable),
|
||||
pageFiltersStore: di.inject(pageFiltersStoreInjectable),
|
||||
openConfirmDialog: di.inject(openConfirmDialogInjectable),
|
||||
}),
|
||||
}) as <I extends ItemObject>(props: ItemListLayoutContentProps<I>) => React.ReactElement;
|
||||
}) as <Item extends ItemObject, PreLoadStores extends boolean>(props: ItemListLayoutContentProps<Item, PreLoadStores>) => React.ReactElement;
|
||||
@@ -6,7 +6,7 @@
|
||||
import React from "react";
|
||||
import type { IconProps } from "../icon";
|
||||
import { Icon } from "../icon";
|
||||
import { FilterType } from "./page-filters.store";
|
||||
import { FilterType } from "./page-filters/store";
|
||||
|
||||
export interface FilterIconProps extends Partial<IconProps> {
|
||||
type: FilterType;
|
||||
@@ -17,9 +17,21 @@ export function FilterIcon(props: FilterIconProps) {
|
||||
|
||||
switch (type) {
|
||||
case FilterType.SEARCH:
|
||||
return <Icon small material="search" {...iconProps}/>;
|
||||
return (
|
||||
<Icon
|
||||
small
|
||||
material="search"
|
||||
{...iconProps}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return <Icon small material="filter_list" {...iconProps}/>;
|
||||
return (
|
||||
<Icon
|
||||
small
|
||||
material="filter_list"
|
||||
{...iconProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,9 @@
|
||||
import "./item-list-layout.scss";
|
||||
|
||||
import React from "react";
|
||||
import { PageFiltersList } from "./page-filters-list";
|
||||
import { PageFiltersList } from "./page-filters/list";
|
||||
import { observer } from "mobx-react";
|
||||
import type { Filter } from "./page-filters.store";
|
||||
import type { Filter } from "./page-filters/store";
|
||||
|
||||
export interface ItemListLayoutFilterProps {
|
||||
getIsReady: () => boolean;
|
||||
|
||||
@@ -9,18 +9,18 @@ import type { ReactNode } from "react";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import type { IClassName } from "../../utils";
|
||||
import { cssNames } from "../../utils";
|
||||
import type { ItemObject, ItemStore } from "../../../common/item.store";
|
||||
import type { Filter } from "./page-filters.store";
|
||||
import type { HeaderCustomizer, HeaderPlaceholders, SearchFilter } from "./list-layout";
|
||||
import { cssNames, isDefined } from "../../utils";
|
||||
import type { ItemObject } from "../../../common/item.store";
|
||||
import type { Filter } from "./page-filters/store";
|
||||
import type { HeaderCustomizer, HeaderPlaceholders, ItemListStore, SearchFilter } from "./list-layout";
|
||||
import { SearchInputUrl } from "../input";
|
||||
|
||||
export interface ItemListLayoutHeaderProps<I extends ItemObject> {
|
||||
export interface ItemListLayoutHeaderProps<I extends ItemObject, PreLoadStores extends boolean> {
|
||||
getItems: () => I[];
|
||||
getFilters: () => Filter[];
|
||||
toggleFilters: () => void;
|
||||
|
||||
store: ItemStore<I>;
|
||||
store: ItemListStore<I, PreLoadStores>;
|
||||
searchFilters?: SearchFilter<I>[];
|
||||
|
||||
// header (title, filtering, searching, etc.)
|
||||
@@ -28,21 +28,19 @@ export interface ItemListLayoutHeaderProps<I extends ItemObject> {
|
||||
headerClassName?: IClassName;
|
||||
renderHeaderTitle?:
|
||||
| ReactNode
|
||||
| ((parent: ItemListLayoutHeader<I>) => ReactNode);
|
||||
| (() => ReactNode);
|
||||
customizeHeader?: HeaderCustomizer | HeaderCustomizer[];
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ItemListLayoutHeader<I extends ItemObject> extends React.Component<
|
||||
ItemListLayoutHeaderProps<I>
|
||||
> {
|
||||
export class ItemListLayoutHeader<I extends ItemObject, PreLoadStores extends boolean> extends React.Component<ItemListLayoutHeaderProps<I, PreLoadStores>> {
|
||||
render() {
|
||||
const {
|
||||
showHeader,
|
||||
customizeHeader,
|
||||
renderHeaderTitle,
|
||||
headerClassName,
|
||||
searchFilters,
|
||||
searchFilters = [],
|
||||
getItems,
|
||||
store,
|
||||
getFilters,
|
||||
@@ -60,7 +58,8 @@ export class ItemListLayoutHeader<I extends ItemObject> extends React.Component<
|
||||
if (getFilters().length > 0) {
|
||||
return (
|
||||
<>
|
||||
<a onClick={toggleFilters}>Filtered</a>: {itemsCount} / {allItemsCount}
|
||||
<a onClick={toggleFilters}>Filtered</a>
|
||||
{`: ${itemsCount} / ${allItemsCount}`}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -70,9 +69,9 @@ export class ItemListLayoutHeader<I extends ItemObject> extends React.Component<
|
||||
: `${allItemsCount} items`;
|
||||
};
|
||||
|
||||
const customizeHeaderFunctions = [customizeHeader].flat().filter(Boolean);
|
||||
const customizeHeaderFunctions = [customizeHeader].flat().filter(isDefined);
|
||||
const renderedTitle = typeof renderHeaderTitle === "function"
|
||||
? renderHeaderTitle(this)
|
||||
? renderHeaderTitle()
|
||||
: renderHeaderTitle;
|
||||
|
||||
const {
|
||||
|
||||
@@ -7,18 +7,18 @@ import "./item-list-layout.scss";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import React from "react";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { computed, makeObservable, untracked } from "mobx";
|
||||
import type { ConfirmDialogParams } from "../confirm-dialog";
|
||||
import type { TableCellProps, TableProps, TableRowProps, TableSortCallbacks } from "../table";
|
||||
import type { IClassName, StorageHelper } from "../../utils";
|
||||
import type { IClassName, SingleOrMany, StorageLayer } from "../../utils";
|
||||
import { autoBind, cssNames, noop } from "../../utils";
|
||||
import type { AddRemoveButtonsProps } from "../add-remove-buttons";
|
||||
import type { ItemObject, ItemStore } from "../../../common/item.store";
|
||||
import type { ItemObject } from "../../../common/item.store";
|
||||
import type { SearchInputUrlProps } from "../input";
|
||||
import { FilterType, pageFilters } from "./page-filters.store";
|
||||
import { PageFiltersList } from "./page-filters-list";
|
||||
import type { NamespaceStore } from "../+namespaces/namespace-store/namespace.store";
|
||||
import namespaceStoreInjectable from "../+namespaces/namespace-store/namespace-store.injectable";
|
||||
import type { PageFiltersStore } from "./page-filters/store";
|
||||
import { FilterType } from "./page-filters/store";
|
||||
import { PageFiltersList } from "./page-filters/list";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import itemListLayoutStorageInjectable from "./storage.injectable";
|
||||
import { ItemListLayoutContent } from "./content";
|
||||
@@ -26,8 +26,12 @@ import { ItemListLayoutHeader } from "./header";
|
||||
import groupBy from "lodash/groupBy";
|
||||
import { ItemListLayoutFilters } from "./filters";
|
||||
import { observer } from "mobx-react";
|
||||
import type { Primitive } from "type-fest";
|
||||
import type { SubscribableStore } from "../../kube-watch-api/kube-watch-api";
|
||||
import selectedFilterNamespacesInjectable from "../../../common/k8s-api/selected-filter-namespaces.injectable";
|
||||
import pageFiltersStoreInjectable from "./page-filters/store.injectable";
|
||||
|
||||
export type SearchFilter<I extends ItemObject> = (item: I) => string | number | (string | number)[];
|
||||
export type SearchFilter<I extends ItemObject> = (item: I) => SingleOrMany<string | number | undefined | null>;
|
||||
export type SearchFilters<I extends ItemObject> = Record<string, SearchFilter<I>>;
|
||||
export type ItemsFilter<I extends ItemObject> = (items: I[]) => I[];
|
||||
export type ItemsFilters<I extends ItemObject> = Record<string, ItemsFilter<I>>;
|
||||
@@ -39,23 +43,59 @@ export interface HeaderPlaceholders {
|
||||
info?: ReactNode;
|
||||
}
|
||||
|
||||
function normalizeText(value: Primitive) {
|
||||
return String(value).toLowerCase();
|
||||
}
|
||||
|
||||
export type ItemListStore<I extends ItemObject, PreLoadStores extends boolean> = {
|
||||
readonly isLoaded: boolean;
|
||||
readonly failedLoading: boolean;
|
||||
getTotalCount: () => number;
|
||||
isSelected: (item: I) => boolean;
|
||||
toggleSelection: (item: I) => void;
|
||||
isSelectedAll: (items: I[]) => boolean;
|
||||
toggleSelectionAll: (enabledItems: I[]) => void;
|
||||
pickOnlySelected: (items: I[]) => I[];
|
||||
} & ({
|
||||
removeItems: (selectedItems: I[]) => Promise<void>;
|
||||
readonly selectedItems: I[];
|
||||
removeSelectedItems?: unknown;
|
||||
} | {
|
||||
removeSelectedItems: () => Promise<void>;
|
||||
selectedItems?: unknown;
|
||||
removeItems?: unknown;
|
||||
}) & (
|
||||
PreLoadStores extends true
|
||||
? {
|
||||
loadAll: (selectedNamespaces: readonly string[]) => Promise<void>;
|
||||
}
|
||||
: {
|
||||
loadAll?: unknown;
|
||||
}
|
||||
);
|
||||
|
||||
export type RenderHeaderTitle<
|
||||
Item extends ItemObject,
|
||||
PreLoadStores extends boolean,
|
||||
> = ReactNode | ((parent: NonInjectedItemListLayout<Item, PreLoadStores>) => ReactNode);
|
||||
|
||||
export type HeaderCustomizer = (placeholders: HeaderPlaceholders) => HeaderPlaceholders;
|
||||
export interface ItemListLayoutProps<I extends ItemObject> {
|
||||
export type ItemListLayoutProps<Item extends ItemObject, PreLoadStores extends boolean = boolean> = {
|
||||
tableId?: string;
|
||||
className: IClassName;
|
||||
getItems: () => I[];
|
||||
store: ItemStore<I>;
|
||||
dependentStores?: ItemStore<ItemObject>[];
|
||||
getItems: () => Item[];
|
||||
store: ItemListStore<Item, PreLoadStores>;
|
||||
dependentStores?: SubscribableStore[];
|
||||
preloadStores?: boolean;
|
||||
hideFilters?: boolean;
|
||||
searchFilters?: SearchFilter<I>[];
|
||||
searchFilters?: SearchFilter<Item>[];
|
||||
/** @deprecated */
|
||||
filterItems?: ItemsFilter<I>[];
|
||||
filterItems?: ItemsFilter<Item>[];
|
||||
|
||||
// header (title, filtering, searching, etc.)
|
||||
showHeader?: boolean;
|
||||
headerClassName?: IClassName;
|
||||
renderHeaderTitle?: ReactNode | ((parent: NonInjectedItemListLayout<I>) => ReactNode);
|
||||
renderHeaderTitle?: RenderHeaderTitle<Item, PreLoadStores>;
|
||||
customizeHeader?: HeaderCustomizer | HeaderCustomizer[];
|
||||
|
||||
// items list configuration
|
||||
@@ -63,23 +103,23 @@ export interface ItemListLayoutProps<I extends ItemObject> {
|
||||
isSelectable?: boolean; // show checkbox in rows for selecting items
|
||||
isConfigurable?: boolean;
|
||||
copyClassNameFromHeadCells?: boolean;
|
||||
sortingCallbacks?: TableSortCallbacks<I>;
|
||||
tableProps?: Partial<TableProps<I>>; // low-level table configuration
|
||||
renderTableHeader: TableCellProps[] | null;
|
||||
renderTableContents: (item: I) => (ReactNode | TableCellProps)[];
|
||||
renderItemMenu?: (item: I, store: ItemStore<I>) => ReactNode;
|
||||
customizeTableRowProps?: (item: I) => Partial<TableRowProps>;
|
||||
sortingCallbacks?: TableSortCallbacks<Item>;
|
||||
tableProps?: Partial<TableProps<Item>>; // low-level table configuration
|
||||
renderTableHeader?: (TableCellProps | undefined | null)[];
|
||||
renderTableContents: (item: Item) => (ReactNode | TableCellProps)[];
|
||||
renderItemMenu?: (item: Item, store: ItemListStore<Item, PreLoadStores>) => ReactNode;
|
||||
customizeTableRowProps?: (item: Item) => Partial<TableRowProps<Item>>;
|
||||
addRemoveButtons?: Partial<AddRemoveButtonsProps>;
|
||||
virtual?: boolean;
|
||||
|
||||
// item details view
|
||||
hasDetailsView?: boolean;
|
||||
detailsItem?: I;
|
||||
onDetails?: (item: I) => void;
|
||||
detailsItem?: Item;
|
||||
onDetails?: (item: Item) => void;
|
||||
|
||||
// other
|
||||
customizeRemoveDialog?: (selectedItems: I[]) => Partial<ConfirmDialogParams>;
|
||||
renderFooter?: (parent: NonInjectedItemListLayout<I>) => React.ReactNode;
|
||||
customizeRemoveDialog?: (selectedItems: Item[]) => Partial<ConfirmDialogParams>;
|
||||
renderFooter?: (parent: NonInjectedItemListLayout<Item, PreLoadStores>) => React.ReactNode;
|
||||
|
||||
/**
|
||||
* Message to display when a store failed to load
|
||||
@@ -88,10 +128,18 @@ export interface ItemListLayoutProps<I extends ItemObject> {
|
||||
*/
|
||||
failedToLoadMessage?: React.ReactNode;
|
||||
|
||||
filterCallbacks?: ItemsFilters<I>;
|
||||
}
|
||||
filterCallbacks?: ItemsFilters<Item>;
|
||||
} & (
|
||||
PreLoadStores extends true
|
||||
? {
|
||||
preloadStores?: true;
|
||||
}
|
||||
: {
|
||||
preloadStores: false;
|
||||
}
|
||||
);
|
||||
|
||||
const defaultProps: Partial<ItemListLayoutProps<ItemObject>> = {
|
||||
const defaultProps: Partial<ItemListLayoutProps<ItemObject, true>> = {
|
||||
showHeader: true,
|
||||
isSelectable: true,
|
||||
isConfigurable: false,
|
||||
@@ -108,16 +156,21 @@ const defaultProps: Partial<ItemListLayoutProps<ItemObject>> = {
|
||||
failedToLoadMessage: "Failed to load items",
|
||||
};
|
||||
|
||||
export interface ItemListLayoutStorage {
|
||||
showFilters: boolean;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
namespaceStore: NamespaceStore;
|
||||
itemListLayoutStorage: StorageHelper<{ showFilters: boolean }>;
|
||||
selectedFilterNamespaces: IComputedValue<string[]>;
|
||||
itemListLayoutStorage: StorageLayer<ItemListLayoutStorage>;
|
||||
pageFiltersStore: PageFiltersStore;
|
||||
}
|
||||
|
||||
@observer
|
||||
class NonInjectedItemListLayout<I extends ItemObject> extends React.Component<ItemListLayoutProps<I> & Dependencies> {
|
||||
class NonInjectedItemListLayout<I extends ItemObject, PreLoadStores extends boolean> extends React.Component<ItemListLayoutProps<I, PreLoadStores> & Dependencies> {
|
||||
static defaultProps = defaultProps as object;
|
||||
|
||||
constructor(props: ItemListLayoutProps<I> & Dependencies) {
|
||||
constructor(props: ItemListLayoutProps<I, PreLoadStores> & Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
autoBind(this);
|
||||
@@ -131,17 +184,13 @@ class NonInjectedItemListLayout<I extends ItemObject> extends React.Component<It
|
||||
}
|
||||
|
||||
if (preloadStores) {
|
||||
this.loadStores();
|
||||
const { store, dependentStores = [] } = this.props;
|
||||
const stores = Array.from(new Set([store, ...dependentStores])) as ItemListStore<I, true>[];
|
||||
|
||||
stores.forEach(store => store.loadAll(this.props.selectedFilterNamespaces.get()));
|
||||
}
|
||||
}
|
||||
|
||||
private loadStores() {
|
||||
const { store, dependentStores } = this.props;
|
||||
const stores = Array.from(new Set([store, ...dependentStores]));
|
||||
|
||||
stores.forEach(store => store.loadAll(this.props.namespaceStore.contextNamespaces));
|
||||
}
|
||||
|
||||
get showFilters(): boolean {
|
||||
return this.props.itemListLayoutStorage.get().showFilters;
|
||||
}
|
||||
@@ -151,8 +200,8 @@ class NonInjectedItemListLayout<I extends ItemObject> extends React.Component<It
|
||||
}
|
||||
|
||||
@computed get filters() {
|
||||
let { activeFilters } = pageFilters;
|
||||
const { searchFilters } = this.props;
|
||||
let { activeFilters } = this.props.pageFiltersStore;
|
||||
const { searchFilters = [] } = this.props;
|
||||
|
||||
if (searchFilters.length === 0) {
|
||||
activeFilters = activeFilters.filter(({ type }) => type !== FilterType.SEARCH);
|
||||
@@ -182,20 +231,20 @@ class NonInjectedItemListLayout<I extends ItemObject> extends React.Component<It
|
||||
|
||||
private filterCallbacks: ItemsFilters<I> = {
|
||||
[FilterType.SEARCH]: items => {
|
||||
const { searchFilters } = this.props;
|
||||
const search = pageFilters.getValues(FilterType.SEARCH)[0] || "";
|
||||
const { searchFilters = [] } = this.props;
|
||||
const search = this.props.pageFiltersStore.getValues(FilterType.SEARCH)[0] || "";
|
||||
|
||||
if (search && searchFilters.length) {
|
||||
const normalizeText = (text: string) => String(text).toLowerCase();
|
||||
const searchTexts = [search].map(normalizeText);
|
||||
|
||||
return items.filter(item => {
|
||||
return searchFilters.some(getTexts => {
|
||||
const sourceTexts: string[] = [getTexts(item)].flat().map(normalizeText);
|
||||
|
||||
return sourceTexts.some(source => searchTexts.some(search => source.includes(search)));
|
||||
});
|
||||
});
|
||||
return items.filter(item => (
|
||||
searchFilters.some(getTexts => (
|
||||
[getTexts(item)]
|
||||
.flat()
|
||||
.map(normalizeText)
|
||||
.some(source => searchTexts.some(search => source.includes(search)))
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
return items;
|
||||
@@ -216,10 +265,12 @@ class NonInjectedItemListLayout<I extends ItemObject> extends React.Component<It
|
||||
|
||||
const items = this.props.getItems();
|
||||
|
||||
return applyFilters(filterItems.concat(this.props.filterItems), items);
|
||||
return applyFilters(filterItems.concat(this.props.filterItems ?? []), items);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { renderHeaderTitle } = this.props;
|
||||
|
||||
return untracked(() => (
|
||||
<div
|
||||
className={cssNames("ItemListLayout flex column", this.props.className)}
|
||||
@@ -232,7 +283,11 @@ class NonInjectedItemListLayout<I extends ItemObject> extends React.Component<It
|
||||
searchFilters={this.props.searchFilters}
|
||||
showHeader={this.props.showHeader}
|
||||
headerClassName={this.props.headerClassName}
|
||||
renderHeaderTitle={this.props.renderHeaderTitle}
|
||||
renderHeaderTitle={(
|
||||
typeof renderHeaderTitle === "function"
|
||||
? () => renderHeaderTitle(this)
|
||||
: renderHeaderTitle
|
||||
)}
|
||||
customizeHeader={this.props.customizeHeader}
|
||||
/>
|
||||
|
||||
@@ -240,10 +295,10 @@ class NonInjectedItemListLayout<I extends ItemObject> extends React.Component<It
|
||||
getIsReady={() => this.isReady}
|
||||
getFilters={() => this.filters}
|
||||
getFiltersAreShown={() => this.showFilters}
|
||||
hideFilters={this.props.hideFilters}
|
||||
hideFilters={this.props.hideFilters ?? false}
|
||||
/>
|
||||
|
||||
<ItemListLayoutContent
|
||||
<ItemListLayoutContent<I, PreLoadStores>
|
||||
getItems={() => this.items}
|
||||
getFilters={() => this.filters}
|
||||
tableId={this.props.tableId}
|
||||
@@ -274,17 +329,14 @@ class NonInjectedItemListLayout<I extends ItemObject> extends React.Component<It
|
||||
}
|
||||
}
|
||||
|
||||
const InjectedItemListLayout = withInjectables<Dependencies, ItemListLayoutProps<ItemObject>>(NonInjectedItemListLayout, {
|
||||
export const ItemListLayout = withInjectables<Dependencies, ItemListLayoutProps<ItemObject, boolean>>(NonInjectedItemListLayout, {
|
||||
getProps: (di, props) => ({
|
||||
namespaceStore: di.inject(namespaceStoreInjectable),
|
||||
itemListLayoutStorage: di.inject(itemListLayoutStorageInjectable),
|
||||
...props,
|
||||
selectedFilterNamespaces: di.inject(selectedFilterNamespacesInjectable),
|
||||
itemListLayoutStorage: di.inject(itemListLayoutStorageInjectable),
|
||||
pageFiltersStore: di.inject(pageFiltersStoreInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
export function ItemListLayout<I extends ItemObject>(props: ItemListLayoutProps<I>) {
|
||||
return <InjectedItemListLayout {...props} />;
|
||||
}
|
||||
}) as <I extends ItemObject, PreLoadStores extends boolean = true>(props: ItemListLayoutProps<I, PreLoadStores>) => React.ReactElement;
|
||||
|
||||
function applyFilters<I extends ItemObject>(filters: ItemsFilter<I>[], items: I[]): I[] {
|
||||
if (!filters || !filters.length) {
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./page-filters-list.scss";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Badge } from "../badge";
|
||||
import { cssNames } from "../../utils";
|
||||
import type { Filter } from "./page-filters.store";
|
||||
import { pageFilters } from "./page-filters.store";
|
||||
import { FilterIcon } from "./filter-icon";
|
||||
import { Icon } from "../icon";
|
||||
import { searchUrlParam } from "../input";
|
||||
|
||||
export interface PageFiltersListProps {
|
||||
filters?: Filter[];
|
||||
}
|
||||
|
||||
@observer
|
||||
export class PageFiltersList extends React.Component<PageFiltersListProps> {
|
||||
static defaultProps: PageFiltersListProps = {
|
||||
get filters() {
|
||||
return pageFilters.activeFilters;
|
||||
},
|
||||
};
|
||||
|
||||
reset = () => pageFilters.reset();
|
||||
remove = (filter: Filter) => {
|
||||
pageFilters.removeFilter(filter);
|
||||
searchUrlParam.clear();
|
||||
};
|
||||
|
||||
renderContent() {
|
||||
const { filters } = this.props;
|
||||
|
||||
if (!filters.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="header flex gaps">
|
||||
<span>Currently applied filters:</span>
|
||||
<a onClick={this.reset} className="reset">
|
||||
Reset
|
||||
</a>
|
||||
</div>
|
||||
<div className="labels">
|
||||
{filters.map(filter => {
|
||||
const { value, type } = filter;
|
||||
|
||||
return (
|
||||
<Badge
|
||||
key={`${type}-${value}`}
|
||||
title={type}
|
||||
className={cssNames("Badge flex gaps filter align-center", type)}
|
||||
label={(
|
||||
<>
|
||||
<FilterIcon type={type}/>
|
||||
<span className="value">{value}</span>
|
||||
<Icon
|
||||
small
|
||||
material="close"
|
||||
onClick={() => this.remove(filter)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="PageFiltersList">
|
||||
{this.renderContent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./list.scss";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Badge } from "../../badge";
|
||||
import { cssNames } from "../../../utils";
|
||||
import type { Filter, PageFiltersStore } from "./store";
|
||||
import { FilterIcon } from "../filter-icon";
|
||||
import { Icon } from "../../icon";
|
||||
import type { PageParam } from "../../../navigation";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import pageFiltersStoreInjectable from "./store.injectable";
|
||||
import searchUrlPageParamInjectable from "../../input/search-url-page-param.injectable";
|
||||
|
||||
export interface PageFiltersListProps {
|
||||
filters?: Filter[];
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
pageFiltersStore: PageFiltersStore;
|
||||
searchUrlParam: PageParam<string>;
|
||||
}
|
||||
|
||||
const NonInjectedPageFiltersList = observer(({
|
||||
pageFiltersStore,
|
||||
searchUrlParam,
|
||||
filters: rawFilters,
|
||||
}: Dependencies & PageFiltersListProps) => {
|
||||
const filters = rawFilters ?? pageFiltersStore.activeFilters;
|
||||
|
||||
const reset = () => pageFiltersStore.reset();
|
||||
const remove = (filter: Filter) => {
|
||||
pageFiltersStore.removeFilter(filter);
|
||||
searchUrlParam.clear();
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
if (filters.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="header flex gaps">
|
||||
<span>Currently applied filters:</span>
|
||||
<a onClick={reset} className="reset">
|
||||
Reset
|
||||
</a>
|
||||
</div>
|
||||
<div className="labels">
|
||||
{filters.map(filter => {
|
||||
const { value, type } = filter;
|
||||
|
||||
return (
|
||||
<Badge
|
||||
key={`${type}-${value}`}
|
||||
title={type}
|
||||
className={cssNames("Badge flex gaps filter align-center", type)}
|
||||
label={(
|
||||
<>
|
||||
<FilterIcon type={type}/>
|
||||
<span className="value">{value}</span>
|
||||
<Icon
|
||||
small
|
||||
material="close"
|
||||
onClick={() => remove(filter)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="PageFiltersList">
|
||||
{renderContent()}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export const PageFiltersList = withInjectables<Dependencies, PageFiltersListProps>(NonInjectedPageFiltersList, {
|
||||
getProps: (di, props) => ({
|
||||
...props,
|
||||
pageFiltersStore: di.inject(pageFiltersStoreInjectable),
|
||||
searchUrlParam: di.inject(searchUrlPageParamInjectable),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* 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 searchUrlPageParamInjectable from "../../input/search-url-page-param.injectable";
|
||||
import { PageFiltersStore } from "./store";
|
||||
|
||||
const pageFiltersStoreInjectable = getInjectable({
|
||||
id: "page-filters-store",
|
||||
instantiate: (di) => new PageFiltersStore({
|
||||
searchUrlParam: di.inject(searchUrlPageParamInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
export default pageFiltersStoreInjectable;
|
||||
+11
-9
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
|
||||
import { computed, observable, reaction, makeObservable, action } from "mobx";
|
||||
import { autoBind } from "../../utils";
|
||||
import { searchUrlParam } from "../input/search-input-url";
|
||||
import type { PageParam } from "../../../navigation";
|
||||
import { autoBind } from "../../../utils";
|
||||
|
||||
export enum FilterType {
|
||||
SEARCH = "search",
|
||||
@@ -16,6 +16,10 @@ export interface Filter {
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
readonly searchUrlParam: PageParam<string>;
|
||||
}
|
||||
|
||||
export class PageFiltersStore {
|
||||
protected filters = observable.array<Filter>([], { deep: false });
|
||||
protected isDisabled = observable.map<FilterType, boolean>();
|
||||
@@ -24,7 +28,7 @@ export class PageFiltersStore {
|
||||
return this.filters.filter(filter => !this.isDisabled.get(filter.type));
|
||||
}
|
||||
|
||||
constructor() {
|
||||
constructor(protected readonly dependencies: Dependencies) {
|
||||
makeObservable(this);
|
||||
autoBind(this);
|
||||
|
||||
@@ -33,8 +37,8 @@ export class PageFiltersStore {
|
||||
|
||||
protected syncWithGlobalSearch() {
|
||||
const disposers = [
|
||||
reaction(() => this.getValues(FilterType.SEARCH)[0], search => searchUrlParam.set(search)),
|
||||
reaction(() => searchUrlParam.get(), search => {
|
||||
reaction(() => this.getValues(FilterType.SEARCH)[0], search => this.dependencies.searchUrlParam.set(search)),
|
||||
reaction(() => this.dependencies.searchUrlParam.get(), search => {
|
||||
const filter = this.getByType(FilterType.SEARCH);
|
||||
|
||||
if (filter) {
|
||||
@@ -69,7 +73,7 @@ export class PageFiltersStore {
|
||||
}
|
||||
}
|
||||
|
||||
getByType(type: FilterType, value?: any): Filter {
|
||||
getByType(type: FilterType, value?: any): Filter | undefined {
|
||||
return this.filters.find(filter => filter.type === type && (
|
||||
arguments.length > 1 ? filter.value === value : true
|
||||
));
|
||||
@@ -102,12 +106,10 @@ export class PageFiltersStore {
|
||||
@action
|
||||
reset() {
|
||||
if (this.isEnabled(FilterType.SEARCH)) {
|
||||
searchUrlParam.clear();
|
||||
this.dependencies.searchUrlParam.clear();
|
||||
}
|
||||
|
||||
this.filters.length = 0;
|
||||
this.isDisabled.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export const pageFilters = new PageFiltersStore();
|
||||
Reference in new issue
Block a user