diff --git a/src/common/__tests__/user-store.test.ts b/src/common/__tests__/user-store.test.ts index b74941a790..45fc8f65c5 100644 --- a/src/common/__tests__/user-store.test.ts +++ b/src/common/__tests__/user-store.test.ts @@ -45,18 +45,6 @@ describe("user store tests", () => { expect(us.seenContexts.has("bar")).toBe(true); }); - it("allows setting and getting preferences", () => { - const us = UserStore.getInstance(); - - us.preferences.httpsProxy = "abcd://defg"; - - expect(us.preferences.httpsProxy).toBe("abcd://defg"); - expect(us.preferences.colorTheme).toBe(UserStore.defaultTheme); - - us.preferences.colorTheme = "light"; - expect(us.preferences.colorTheme).toBe("light"); - }); - it("correctly resets theme to default value", async () => { const us = UserStore.getInstance(); @@ -77,6 +65,32 @@ describe("user store tests", () => { }); }); + describe("preferences", () => { + it("allows setting and getting preferences", () => { + const us = UserStore.getInstance(); + + us.preferences.httpsProxy = "abcd://defg"; + + expect(us.preferences.httpsProxy).toBe("abcd://defg"); + expect(us.preferences.colorTheme).toBe(UserStore.defaultTheme); + + us.preferences.colorTheme = "light"; + expect(us.preferences.colorTheme).toBe("light"); + }); + + it("allows setting and getting table column sizes", () => { + const us = UserStore.getInstance(); + const [clusterId, tableId] = ["foo", "bar"]; + + expect( us.getTableSizing(clusterId, tableId) ).toStrictEqual([]); + + const sizes = [100, 150, 70, 400]; + + us.setTableSizing(clusterId, tableId, sizes); + expect( us.getTableSizing(clusterId, tableId) ).toStrictEqual(sizes); + }); + }); + describe("migrations", () => { beforeEach(() => { UserStore.resetInstance(); diff --git a/src/common/table-size-config.ts b/src/common/table-size-config.ts index 922c03e872..7fe83fe533 100644 --- a/src/common/table-size-config.ts +++ b/src/common/table-size-config.ts @@ -1,10 +1,13 @@ import { ClusterId } from "./cluster-store"; -export type TableId = string; -export type TableSizeRecord = Record; - /** * Stores a configuration of table column sizes, set by the user, - * for each table by `TableId`, for each cluster by `ClusterId` + * for each talbe by `tableId` + */ +export type TableSizeRecord = Record; + +/** + * Stores a configuration of table column sizes, set by the user, + * for each table by `tableId`, for each cluster by `ClusterId` */ export type TableSizeConfig = Record; diff --git a/src/common/user-store.ts b/src/common/user-store.ts index 479e7e3fdf..064833e3cb 100644 --- a/src/common/user-store.ts +++ b/src/common/user-store.ts @@ -92,6 +92,23 @@ export class UserStore extends BaseStore { return semver.gt(getAppVersion(), this.lastSeenAppVersion); } + @action + setTableSizing(clusterId: string, tableId: string, sizes: number[]) { + const { tableSizeConfig: config } = this.preferences; + + if (!config[clusterId]) { + config[clusterId] = { [tableId]: sizes }; + } else { + config[clusterId][tableId] = sizes; + } + } + + getTableSizing(clusterId: string, tableId: string): number[] { + const { tableSizeConfig: config } = this.preferences; + + return config[clusterId]?.[tableId] || []; + } + @action setHiddenTableColumns(tableId: string, names: Set | string[]) { this.preferences.hiddenTableColumns[tableId] = Array.from(names); diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index f5ad684595..d758717101 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -51,6 +51,31 @@ import { CommandContainer } from "./command-palette/command-container"; import { KubeObjectStore } from "../kube-object.store"; import { clusterContext } from "./context"; +/** + * Hacking into document event listeners to detect leaks + * TODO: remove this + */ +(function watchEvent() { + const addOne = document.addEventListener; + const removeOne = document.removeEventListener; + + (document as any)["listeners"] = new Map(); + + document.addEventListener = (type: string, listener: any) => { + const registry: Map = (document as any)["listeners"]; + + registry.set(listener, type); + addOne(type, listener); + }; + + document.removeEventListener = (type: string, listener: any) => { + const registry: Map = (document as any)["listeners"]; + + registry.delete(listener); + removeOne(type, listener); + }; +})(); + @observer export class App extends React.Component { static async init() { diff --git a/src/renderer/components/item-object-list/item-list-layout.tsx b/src/renderer/components/item-object-list/item-list-layout.tsx index d52ec9e9ab..298c0b5d6f 100644 --- a/src/renderer/components/item-object-list/item-list-layout.tsx +++ b/src/renderer/components/item-object-list/item-list-layout.tsx @@ -37,6 +37,7 @@ export interface IHeaderPlaceholders { export interface ItemListLayoutProps { tableId?: string; + clusterId?: string; className: IClassName; items?: T[]; store: ItemStore; @@ -112,6 +113,18 @@ export class ItemListLayout extends React.Component { } @observable cellSizes: number[]; + get persistentCellSizes(): number[] { + const { clusterId, tableId, renderTableHeader: { length } } = this.props; + const savedSizes = userStore.getTableSizing(clusterId, tableId); + + return savedSizes.length !== length ? new Array(length) : savedSizes; + } + + set persistentCellSizes(sizes: number[]) { + const { clusterId, tableId } = this.props; + + userStore.setTableSizing(clusterId, tableId, sizes); + } constructor(props: ItemListLayoutProps) { super(props); @@ -121,12 +134,16 @@ export class ItemListLayout extends React.Component { } async componentDidMount() { - const { isClusterScoped, isConfigurable, tableId, preloadStores } = this.props; + const { isClusterScoped, isConfigurable, tableId, preloadStores, isResizable } = this.props; if (isConfigurable && !tableId) { throw new Error("[ItemListLayout]: configurable list require props.tableId to be specified"); } + if (isResizable) { + this.cellSizes = this.persistentCellSizes; + } + if (preloadStores) { this.loadStores(); @@ -218,6 +235,11 @@ export class ItemListLayout extends React.Component { return this.applyFilters(filterItems.concat(this.props.filterItems), items); } + persistCellSizes() { + console.log("Persisting sizes"); + this.persistentCellSizes = this.cellSizes; + } + handleCellResize(index: number, width: number) { this.cellSizes[index] = width; } @@ -268,6 +290,7 @@ export class ItemListLayout extends React.Component { if (resizable) { cellProps._onResize = width => this.handleCellResize(index, width); + cellProps._onResizeComplete = () => this.persistCellSizes(); } if (!headCell || !this.isHiddenColumn(headCell)) { @@ -404,7 +427,7 @@ export class ItemListLayout extends React.Component { } renderTableHeader() { - const { customizeTableRowProps, renderTableHeader, isSelectable, isConfigurable, store, isResizable: resizable } = this.props; + const { customizeTableRowProps, renderTableHeader, isSelectable, isConfigurable, store, isResizable } = this.props; if (!renderTableHeader) { return; @@ -423,9 +446,13 @@ export class ItemListLayout extends React.Component { )} {renderTableHeader.map((cellProps, index) => { const cellSize = this.cellSizes[index]; - const _cellProps = resizable ? - { ...cellProps, _onResize: (width: number) => this.handleCellResize(index, width), resizable } : - cellProps; + const _cellProps = isResizable ? + { + ...cellProps, + _onResize: (width: number) => this.handleCellResize(index, width), + _onResizeComplete: () => this.persistCellSizes(), + isResizable + } : cellProps; if (cellSize !== undefined) { _cellProps.size = cellSize; diff --git a/src/renderer/components/kube-object/kube-object-list-layout.tsx b/src/renderer/components/kube-object/kube-object-list-layout.tsx index c8948603f6..689d8523a6 100644 --- a/src/renderer/components/kube-object/kube-object-list-layout.tsx +++ b/src/renderer/components/kube-object/kube-object-list-layout.tsx @@ -49,6 +49,7 @@ export class KubeObjectListLayout extends React.Component { _sort?(sortBy: TableSortBy): void; // sort function, don't use this prop outside (!) _nowrap?: boolean; // indicator, might come from parent , don't use this prop outside (!) _onResize?(width: number): void; //
resize callbalck, don't use this prop outside (!) + _onResizeComplete?(): void; // triggers a callback when user stops resizing } type ResizeHandlerState = { @@ -104,6 +105,8 @@ export class TableCell extends React.Component { mouseUpHandler() { this.removeMouseEventListeners(); + console.log((document as any)["listeners"]); + this.props?._onResizeComplete(); this.resizeHandlerState = undefined; }