Files
ps5upload/client/src/lib/formatDate.ts
T
phantomptrandClaude Opus 5.5 6f823594c3 Client: shared usePoll, batched engine log, schedule catch-up, capture delete, formatDate, kernel log windowing
- lib/usePoll: one poll primitive that never overlaps itself and pauses
  while the window is hidden or a transfer to that console runs; Hardware
  (power/storage, PS5 clock, SMP meta), Notifications and the kernel log use it.
- Engine log bridge appends a whole tick in one store update; the log store
  keeps an error counter so Sidebar/More stop re-filtering 500 lines.
- Schedules fire whatever came due since the last tick (15 min catch-up),
  and record later ones as missed instead of silently skipping the day.
- Screenshots / Video clips: Delete for the selection (confirm, engine
  delete like Files, thumbnail too), Retry on list errors, previews in the
  shared Modal.
- formatDate: dates follow the app language.
- Kernel log renders only the rows on screen (fixed-height windowing).
- Logs: Open logs folder shows its error. Settings and Connection load lazily;
  the vendor-react chunk matches exact package names.
- Modal reads onClose through a ref so an inline arrow doesn't re-grab focus
  on every render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
2026-10-10 10:50:55 -07:00

56 lines
2.0 KiB
TypeScript

import { useLangStore } from "../state/lang";
/**
* Dates and times in the app's language, not the operating system's.
*
* A bare `toLocaleString()` formats with the system locale, so someone who
* runs the app in Japanese on an English Windows saw Japanese labels next to
* "10/9/2026, 3:04:05 PM". Every date on screen goes through here instead.
*
* Explicit fields rather than `dateStyle`/`timeStyle`: the oldest WebViews we
* ship to (safari13 target) don't know those options.
*/
export type DateFormat = "datetime" | "date" | "time" | "time-short";
const OPTIONS: Record<DateFormat, Intl.DateTimeFormatOptions> = {
datetime: { year: "numeric", month: "short", day: "numeric", hour: "numeric", minute: "2-digit" },
date: { year: "numeric", month: "short", day: "numeric" },
time: { hour: "numeric", minute: "2-digit", second: "2-digit" },
"time-short": { hour: "numeric", minute: "2-digit" },
};
const cache = new Map<string, Intl.DateTimeFormat>();
function formatter(lang: string, format: DateFormat): Intl.DateTimeFormat {
const key = `${lang}|${format}`;
let f = cache.get(key);
if (!f) {
let locale: string | undefined = lang;
try {
if (Intl.DateTimeFormat.supportedLocalesOf([lang]).length === 0) locale = undefined;
} catch {
locale = undefined;
}
f = new Intl.DateTimeFormat(locale, OPTIONS[format]);
cache.set(key, f);
}
return f;
}
/** Format a Date or epoch milliseconds. Invalid input gives "". */
export function formatDate(
value: Date | number,
format: DateFormat = "datetime",
lang: string = useLangStore.getState().lang,
): string {
const d = typeof value === "number" ? new Date(value) : value;
if (Number.isNaN(d.getTime())) return "";
return formatter(lang, format).format(d);
}
/** `formatDate` bound to the current language; re-renders on a switch. */
export function useFormatDate(): (value: Date | number, format?: DateFormat) => string {
const lang = useLangStore((s) => s.lang);
return (value, format = "datetime") => formatDate(value, format, lang);
}