diff --git a/src/common/item.store.ts b/src/common/item.store.ts index fee402491b61..68ef6813b9cd 100644 --- a/src/common/item.store.ts +++ b/src/common/item.store.ts @@ -21,7 +21,7 @@ export abstract class ItemStore { @observable isLoading = false; @observable isLoaded = false; @observable items = observable.array([], { deep: false }); - @observable selectedItemsIds = observable.map(); + @observable selectedItemsIds = observable.set(); constructor() { makeObservable(this); @@ -29,7 +29,11 @@ export abstract class ItemStore { } @computed get selectedItems(): Item[] { - return this.items.filter(item => this.selectedItemsIds.get(item.getId())); + return this.pickOnlySelected(this.items); + } + + public pickOnlySelected(items: Item[]): Item[] { + return items.filter(item => this.selectedItemsIds.has(item.getId())); } public getItems(): Item[] { @@ -152,12 +156,12 @@ export abstract class ItemStore { } isSelected(item: Item) { - return !!this.selectedItemsIds.get(item.getId()); + return this.selectedItemsIds.has(item.getId()); } @action select(item: Item) { - this.selectedItemsIds.set(item.getId(), true); + this.selectedItemsIds.add(item.getId()); } @action @@ -207,6 +211,8 @@ export abstract class ItemStore { async removeSelectedItems?(): Promise; + async removeItems?(items: Item[]): Promise; + * [Symbol.iterator]() { yield* this.items; } diff --git a/src/common/k8s-api/kube-object.store.ts b/src/common/k8s-api/kube-object.store.ts index 360229c79f90..d1582e8b5121 100644 --- a/src/common/k8s-api/kube-object.store.ts +++ b/src/common/k8s-api/kube-object.store.ts @@ -369,6 +369,10 @@ export abstract class KubeObjectStore extends ItemStore return Promise.all(this.selectedItems.map(this.remove)); } + async removeItems(items: T[]) { + await Promise.all(items.map(this.remove)); + } + // collect items from watch-api events to avoid UI blowing up with huge streams of data protected eventsBuffer = observable.array>([], { deep: false }); diff --git a/src/renderer/components/+helm-releases/releases.tsx b/src/renderer/components/+helm-releases/releases.tsx index 4d38cb5a20be..0017fa1ba8e9 100644 --- a/src/renderer/components/+helm-releases/releases.tsx +++ b/src/renderer/components/+helm-releases/releases.tsx @@ -134,10 +134,12 @@ class NonInjectedHelmReleases extends Component { return releases.get().filter((release) => release.isSelected); }, - removeSelectedItems() { - return Promise.all( - releases.get().filter((release) => release.isSelected).map((release) => release.delete()), - ); + pickOnlySelected: (releases) => { + return releases.filter(release => release.isSelected); + }, + + removeItems: async (releases) => { + await Promise.all(releases.map(release => release.delete())); }, } as ItemStore; diff --git a/src/renderer/components/item-object-list/content.tsx b/src/renderer/components/item-object-list/content.tsx index 773f70d74f40..8877f04e0210 100644 --- a/src/renderer/components/item-object-list/content.tsx +++ b/src/renderer/components/item-object-list/content.tsx @@ -136,19 +136,27 @@ export class ItemListLayoutContent extends React.Component } @boundMethod - removeItemsDialog() { + removeItemsDialog(selectedItems: I[]) { const { customizeRemoveDialog, store } = this.props; - const { selectedItems, removeSelectedItems } = store; const visibleMaxNamesCount = 5; const selectedNames = selectedItems.map(ns => ns.getName()).slice(0, visibleMaxNamesCount).join(", "); const dialogCustomProps = customizeRemoveDialog ? customizeRemoveDialog(selectedItems) : {}; const selectedCount = selectedItems.length; - const tailCount = selectedCount > visibleMaxNamesCount ? selectedCount - visibleMaxNamesCount : 0; - const tail = tailCount > 0 ? <>, and {tailCount} more : null; - const message = selectedCount <= 1 ?

Remove item {selectedNames}?

:

Remove {selectedCount} items {selectedNames}{tail}?

; + const tailCount = selectedCount > visibleMaxNamesCount + ? selectedCount - visibleMaxNamesCount + : 0; + const tail = tailCount > 0 + ? <>, and {tailCount} more + : null; + const message = selectedCount <= 1 + ?

Remove item {selectedNames}?

+ :

Remove {selectedCount} items {selectedNames}{tail}?

; + const onConfirm = store.removeItems + ? () => store.removeItems(selectedItems) + : store.removeSelectedItems; ConfirmDialog.open({ - ok: removeSelectedItems, + ok: onConfirm, labelOk: "Remove", message, ...dialogCustomProps, @@ -225,10 +233,12 @@ export class ItemListLayoutContent extends React.Component render() { const { store, hasDetailsView, addRemoveButtons = {}, virtual, sortingCallbacks, - detailsItem, className, tableProps = {}, tableId, + detailsItem, className, tableProps = {}, tableId, getItems, } = this.props; const selectedItemId = detailsItem && detailsItem.getId(); const classNames = cssNames(className, "box", "grow", ThemeStore.getInstance().activeTheme.type); + const items = getItems(); + const selectedItems = store.pickOnlySelected(items); return (
@@ -238,7 +248,7 @@ export class ItemListLayoutContent extends React.Component selectable={hasDetailsView} sortable={sortingCallbacks} getTableRow={this.getRow} - items={this.props.getItems()} + items={items} selectedItemId={selectedItemId} noItems={this.renderNoItems()} className={classNames} @@ -252,9 +262,11 @@ export class ItemListLayoutContent extends React.Component {() => ( 0 + ? () => this.removeItemsDialog(selectedItems) + : null } - removeTooltip={`Remove selected items (${store.selectedItems.length})`} + removeTooltip={`Remove selected items (${selectedItems.length})`} {...addRemoveButtons} /> )} diff --git a/src/renderer/components/item-object-list/list-layout.tsx b/src/renderer/components/item-object-list/list-layout.tsx index 959f814bcee3..ca8a4b129368 100644 --- a/src/renderer/components/item-object-list/list-layout.tsx +++ b/src/renderer/components/item-object-list/list-layout.tsx @@ -13,7 +13,7 @@ import { boundMethod, cssNames, IClassName, noop, StorageHelper } from "../../ut import type { AddRemoveButtonsProps } from "../add-remove-buttons"; import type { ItemObject, ItemStore } from "../../../common/item.store"; import type { SearchInputUrlProps } from "../input"; -import { Filter, FilterType, pageFilters } from "./page-filters.store"; +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"; @@ -202,19 +202,18 @@ class NonInjectedItemListLayout extends React.Component(this.filters, ({ type }) => type); - + const filterGroups = groupBy(this.filters, ({ type }) => type); const filterItems: ItemsFilter[] = []; - Object.entries(filterGroups).forEach(([type, filtersGroup]) => { + for (const [type, filtersGroup] of Object.entries(filterGroups)) { const filterCallback = this.filterCallbacks[type] ?? this.props.filterCallbacks?.[type]; if (filterCallback && filtersGroup.length > 0) { filterItems.push(filterCallback); } - }); + } - const items = this.props.getItems ? this.props.getItems() : (this.props.items ?? this.props.store.items); + const items = this.props.getItems?.() ?? this.props.items ?? this.props.store.items; return applyFilters(filterItems.concat(this.props.filterItems), items); } diff --git a/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx b/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx index ec0245c51270..5ecf4f142537 100644 --- a/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx +++ b/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx @@ -138,25 +138,14 @@ class NonInjectedKubeObjectListLayout extends React.Compon } } -const InjectedKubeObjectListLayout = withInjectables< - Dependencies, - KubeObjectListLayoutProps ->( - NonInjectedKubeObjectListLayout, - - { - getProps: (di, props) => ({ - clusterFrameContext: di.inject(clusterFrameContextInjectable), - subscribeToStores: di.inject(kubeWatchApiInjectable).subscribeStores, - ...props, - }), - }, -); - - -export function KubeObjectListLayout( - props: KubeObjectListLayoutProps, -) { - +const InjectedKubeObjectListLayout = withInjectables>(NonInjectedKubeObjectListLayout, { + getProps: (di, props) => ({ + clusterFrameContext: di.inject(clusterFrameContextInjectable), + subscribeToStores: di.inject(kubeWatchApiInjectable).subscribeStores, + ...props, + }), +}); + +export function KubeObjectListLayout(props: KubeObjectListLayoutProps) { return ; } diff --git a/src/renderer/port-forward/port-forward-store/port-forward-store.ts b/src/renderer/port-forward/port-forward-store/port-forward-store.ts index 070f6746b713..5ec58002e635 100644 --- a/src/renderer/port-forward/port-forward-store/port-forward-store.ts +++ b/src/renderer/port-forward/port-forward-store/port-forward-store.ts @@ -73,8 +73,8 @@ export class PortForwardStore extends ItemStore { }); } - async removeSelectedItems() { - return Promise.all(this.selectedItems.map(this.remove)); + async removeItems(items: PortForwardItem[]) { + await Promise.all(items.map(this.remove)); } getById(id: string) {