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]>
59 lines
1.9 KiB
TypeScript
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;
|
|
}
|