fix(widgets): stabilize table layout hydration

This commit is contained in:
Thomas Camlong committed 2026-07-31 06:03:45 +02:00
1 parent 6f59a556e0
commit ce75171a7c
2 files changed
+67 -23

No files matched your search

@@ -338,7 +338,21 @@ export default function BeszelSystemTableWidget({
];
return cols.filter(Boolean) as DataTableColumn<SystemRowWithKey>[];
}, [options, t, size]);
}, [
options.showAgent,
options.showBattery,
options.showCpu,
options.showDisk,
options.showGpu,
options.showLoadAvg,
options.showMemory,
options.showNet,
options.showServices,
options.showTemp,
options.showUptime,
size,
t,
]);
const { effectiveColumns, storeKey } = usePersistedTableLayout({
columns,
@@ -21,6 +21,29 @@ interface UsePersistedTableLayoutProps<T> {
const layoutSaveDelay = 350;
const toPixelWidth = (width: string | number): number | undefined => {
if (typeof width === "number" && Number.isFinite(width) && width > 0) return width;
if (typeof width !== "string" || !width.endsWith("px")) return undefined;
const parsedWidth = Number.parseInt(width, 10);
return Number.isFinite(parsedWidth) && parsedWidth > 0 ? parsedWidth : undefined;
};
const getColumnWidthMap = (
columnsWidth: Record<string, string | number>[],
visibleAccessorSet: ReadonlySet<string>,
): Record<string, number> => {
const widths: Record<string, number> = {};
for (const entry of columnsWidth) {
for (const [accessor, width] of Object.entries(entry)) {
if (!visibleAccessorSet.has(accessor)) continue;
const pixelWidth = toPixelWidth(width);
if (pixelWidth !== undefined) widths[accessor] = pixelWidth;
}
}
return widths;
};
export const parseColumnOrder = (value: string, columnAccessors: readonly string[]): string[] => {
if (!value) return [];
try {
@@ -84,6 +107,8 @@ export const usePersistedTableLayout = <T>({
const lastStoreKey = useRef(storeKey);
const hydrated = useRef(false);
const previousOrder = useRef(columnsOrder);
const previousWidths = useRef(columnsWidth);
useEffect(() => {
if (lastStoreKey.current !== storeKey) {
lastStoreKey.current = storeKey;
@@ -91,20 +116,35 @@ export const usePersistedTableLayout = <T>({
}
if (hydrated.current) return;
if (restoredOrder.length > 0) setColumnsOrder(restoredOrder);
const visibleWidths = Object.entries(savedWidths)
.filter(([accessor]) => visibleAccessorSet.has(accessor))
.map(([accessor, width]) => ({ accessor, width }));
if (visibleWidths.length > 0) setMultipleColumnWidths(visibleWidths);
const orderMatches =
columnsOrder.length === restoredOrder.length &&
columnsOrder.every((accessor, index) => accessor === restoredOrder[index]);
const currentWidths = getColumnWidthMap(columnsWidth, visibleAccessorSet);
const visibleWidths = Object.entries(savedWidths).filter(([accessor]) => visibleAccessorSet.has(accessor));
const widthsMatch = visibleWidths.every(([accessor, width]) => currentWidths[accessor] === width);
const animationFrame = requestAnimationFrame(() => {
if (orderMatches && widthsMatch) {
previousOrder.current = columnsOrder;
previousWidths.current = columnsWidth;
hydrated.current = true;
});
return () => cancelAnimationFrame(animationFrame);
}, [restoredOrder, savedWidths, setColumnsOrder, setMultipleColumnWidths, storeKey, visibleAccessorSet]);
return;
}
if (!orderMatches) setColumnsOrder(restoredOrder);
if (!widthsMatch) {
setMultipleColumnWidths(visibleWidths.map(([accessor, width]) => ({ accessor, width })));
}
}, [
columnsOrder,
columnsWidth,
restoredOrder,
savedWidths,
setColumnsOrder,
setMultipleColumnWidths,
storeKey,
visibleAccessorSet,
]);
const previousOrder = useRef(columnsOrder);
const previousWidths = useRef(columnsWidth);
const pendingLayout = useRef<TableLayoutOptions>({});
const saveTimeout = useRef<number | undefined>(undefined);
useEffect(
@@ -133,17 +173,7 @@ export const usePersistedTableLayout = <T>({
}
if (widthsChanged) {
const widthMap: Record<string, number> = { ...savedWidths };
for (const entry of columnsWidth) {
const accessor = Object.keys(entry)[0];
if (!accessor || !visibleAccessorSet.has(accessor)) continue;
const width = entry[accessor];
if (typeof width === "number" && Number.isFinite(width) && width > 0) {
widthMap[accessor] = width;
} else if (typeof width === "string" && width.endsWith("px")) {
const parsedWidth = Number.parseInt(width, 10);
if (Number.isFinite(parsedWidth) && parsedWidth > 0) widthMap[accessor] = parsedWidth;
}
}
Object.assign(widthMap, getColumnWidthMap(columnsWidth, visibleAccessorSet));
newLayout.columnWidths = JSON.stringify(widthMap);
}