Files
UHD_Media_Tool/apps/web/app/components/console/data-table-groups.ts
T
rootandClaude Opus 5.5 312e70d0bc feat(web): DataTable groupBy with collapsible counted group headers
Groups keep first-seen order (or a custom order), headers show counts and
toggle with aria-expanded; collapsed groups persist per storageKey in
localStorage (read after mount, failures ignored). The trash list groups by
registered directory when items span more than one.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 20:09:29 +08:00

59 lines
1.9 KiB
TypeScript

// DataTable groupBy 的纯逻辑:按键分组(保持首次出现顺序或自定义顺序)与展开态持久化。
export type RowGroup<T> = { key: string; rows: T[] };
/** 按 key 分组;缺省按首次出现顺序,给 order 时按 order 排组(组内保持原顺序) */
export function groupRows<T>(
rows: readonly T[],
keyOf: (row: T) => string,
order?: (a: string, b: string) => number,
): RowGroup<T>[] {
const map = new Map<string, T[]>();
for (const r of rows) {
const k = keyOf(r);
const list = map.get(k);
if (list) list.push(r);
else map.set(k, [r]);
}
const groups = [...map].map(([key, rs]) => ({ key, rows: rs }));
return order ? groups.sort((a, b) => order(a.key, b.key)) : groups;
}
const PREFIX = "umt.table-groups:";
type Store = Pick<Storage, "getItem" | "setItem">;
const defaultStore = (): Store | undefined => globalThis.localStorage;
/** 读折叠的组(localStorage 不可用或内容损坏时为 null,由调用方取缺省) */
export function readCollapsed(storageKey: string | undefined, store = defaultStore): Set<string> | null {
if (!storageKey) return null;
try {
const raw = store()?.getItem(PREFIX + storageKey);
if (!raw) return null;
const v = JSON.parse(raw) as unknown;
return Array.isArray(v) ? new Set(v.filter((x): x is string => typeof x === "string")) : null;
} catch {
return null;
}
}
export function writeCollapsed(
storageKey: string | undefined,
collapsed: ReadonlySet<string>,
store = defaultStore,
): void {
if (!storageKey) return;
try {
store()?.setItem(PREFIX + storageKey, JSON.stringify([...collapsed]));
} catch {
// 私密窗口 / 配额满:只是不记住
}
}
/** 切换一个组的折叠态,返回新集合 */
export function toggleCollapsed(collapsed: ReadonlySet<string>, key: string): Set<string> {
const next = new Set(collapsed);
if (next.has(key)) next.delete(key);
else next.add(key);
return next;
}