Files
siftlode/frontend/src/lib/playlistView.ts
T
peter aac79ffa53 fix(i18n): numbers, units, dates and order in the UI language, and after a switch (E30 S3)
Every locale formatter takes the language as an argument. Under the React
Compiler a formatter that read the i18n singleton was memoized on its other
arguments, so a card kept "2 hr 4 min" after a switch to Hungarian; plexLang()
had the same flaw and left Plex queries in the old language. Components now
take the language from useTranslation, and the signature makes forgetting it
a type error.

- ratings, file sizes, stream stats: the language's decimal mark ("8,3")
- view and subscriber counts compact in the language ("1,2 E")
- seconds as the language's unit ("6 mp") in the sliders and the stats
- a person's birth and death dates as dates, not ISO strings
- A-Z lists by the shown name in the language's alphabet (compareText),
  the tag catalog re-sorted by its translated names
- lint local/locale-format (was date-format) also refuses Intl.NumberFormat
  outside lib/format.ts, localeCompare, and a component reading the language
  off the i18n singleton
2026-09-29 22:39:57 +02:00

45 lines
1.7 KiB
TypeScript

import { sortRows, type SortState } from "./columnSort";
import { compareText } from "./collate";
// The playlist detail's VIEW derivation: what the reader sees, as opposed to the playlist's stored
// order. Kept structural (no `Column`, no component imports) like `columnSort.ts`, so it stays pure
// and testable — the "save this order" button writes whatever `applyView` returned, which is reason
// enough for this to be covered rather than eyeballed.
interface Grouped {
channel_title?: string | null;
}
interface Sortable<T> {
key: string;
sortValue?: (row: T) => string | number;
}
/** Filters are applied by the caller; this is sort, then optional channel grouping. Groups are
* ordered by channel name in the sort's direction, and the sort applies WITHIN each group (with no
* sort, each group keeps its stored order). */
export function applyView<T extends Grouped>(
rows: T[],
columns: Sortable<T>[],
sort: SortState,
group: boolean,
lang: string,
): T[] {
const sorted = sortRows(rows, columns, sort, lang);
if (!group) return sorted;
const groups = new Map<string, T[]>();
for (const v of sorted) {
const k = v.channel_title ?? "";
const g = groups.get(k);
if (g) g.push(v);
else groups.set(k, [v]);
}
const sign = sort?.dir === "desc" ? -1 : 1;
const keys = [...groups.keys()].sort((a, b) => sign * compareText(a, b, lang));
return keys.flatMap((k) => groups.get(k)!);
}
/** Same items in the same positions. Identity is the id, so a refetch that changed only a title
* doesn't read as a reorder — and a shorter list (a filtered view) is never "the same order". */
export const sameOrder = (a: { id: string }[], b: { id: string }[]) =>
a.length === b.length && a.every((v, i) => v.id === b[i]!.id);