mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
209dd61442
Move domain-agnostic, feature-free helpers out of the flat utils/ and store/ roots into src/lib/ (plan M4, §3 lib/ layer): - lib/format/: formatBytes, formatClockTime, formatDuration, formatHumanDuration, relativeTime (pure format/date/byte/clock helpers) - lib/i18n.ts: i18next bootstrap (global app infra) - lib/util/: sanitizeHtml, platform, dedupeById, safeStorage (pure helpers + the zustand storage adapter) playbackScheduleFormat stays in utils/format (runtime usePlayerStore dep = audio-core coupling, not generic). formatClockTime keeps a type-only @/store/authStoreTypes import (erased, no runtime inversion — accepted per the deviceSync precedent). Pure move via deep @/lib/* specifiers (no barrel → no mock-collapse surface). tsc 0, lint 0/0, full suite 319 files / 2353 tests green. Tooling note: lib_move.py regex extended to also rewrite side-effect imports (import './i18n') and vi.importActual paths — both were silent gaps.
17 lines
540 B
TypeScript
17 lines
540 B
TypeScript
/**
|
|
* Keeps the first occurrence of each `id`. Subsonic responses (and merged pages)
|
|
* occasionally repeat the same album/song id; duplicate React keys then warn and
|
|
* break reconciliation.
|
|
*/
|
|
export function dedupeById<T extends { id: string; serverId?: string }>(items: T[]): T[] {
|
|
const seen = new Set<string>();
|
|
const out: T[] = [];
|
|
for (const item of items) {
|
|
const key = item.serverId ? `${item.serverId}:${item.id}` : item.id;
|
|
if (seen.has(key)) continue;
|
|
seen.add(key);
|
|
out.push(item);
|
|
}
|
|
return out;
|
|
}
|