chore: format schedule_board

This commit is contained in:
2026-05-28 11:28:02 +02:00
committed by Milas Holsting
parent fbf94970fa
commit bd979cdb68

View File

@@ -1,8 +1,8 @@
export {}; export {};
type WeekStart = 'monday' | 'sunday' | 'saturday'; type WeekStart = "monday" | "sunday" | "saturday";
type TimeFormat = '24' | '12'; type TimeFormat = "24" | "12";
type SortBy = 'time' | 'alpha' | 'score'; type SortBy = "time" | "alpha" | "score";
interface ScheduleSettings { interface ScheduleSettings {
weekStart: WeekStart; weekStart: WeekStart;
@@ -12,13 +12,13 @@ interface ScheduleSettings {
weekOffset: number; weekOffset: number;
} }
const settingsKey = 'schedule_settings_v1'; const settingsKey = "schedule_settings_v1";
const defaultSettings: ScheduleSettings = { const defaultSettings: ScheduleSettings = {
weekStart: 'monday', weekStart: "monday",
timeFormat: '24', timeFormat: "24",
showImages: true, showImages: true,
sortBy: 'time', sortBy: "time",
weekOffset: 0, weekOffset: 0,
}; };
@@ -32,22 +32,22 @@ const parseJSON = (value: string | null): unknown => {
}; };
const isWeekStart = (value: unknown): value is WeekStart => const isWeekStart = (value: unknown): value is WeekStart =>
value === 'monday' || value === 'sunday' || value === 'saturday'; value === "monday" || value === "sunday" || value === "saturday";
const isTimeFormat = (value: unknown): value is TimeFormat => value === '24' || value === '12'; const isTimeFormat = (value: unknown): value is TimeFormat => value === "24" || value === "12";
const isSortBy = (value: unknown): value is SortBy => const isSortBy = (value: unknown): value is SortBy =>
value === 'time' || value === 'alpha' || value === 'score'; value === "time" || value === "alpha" || value === "score";
const loadSettings = (): ScheduleSettings => { const loadSettings = (): ScheduleSettings => {
const raw = parseJSON(localStorage.getItem(settingsKey)); const raw = parseJSON(localStorage.getItem(settingsKey));
if (!raw || typeof raw !== 'object') return { ...defaultSettings }; if (!raw || typeof raw !== "object") return { ...defaultSettings };
const obj = raw as Record<string, unknown>; const obj = raw as Record<string, unknown>;
const weekStart = isWeekStart(obj.weekStart) ? obj.weekStart : defaultSettings.weekStart; const weekStart = isWeekStart(obj.weekStart) ? obj.weekStart : defaultSettings.weekStart;
const timeFormat = isTimeFormat(obj.timeFormat) ? obj.timeFormat : defaultSettings.timeFormat; const timeFormat = isTimeFormat(obj.timeFormat) ? obj.timeFormat : defaultSettings.timeFormat;
const showImages = const showImages =
typeof obj.showImages === 'boolean' ? obj.showImages : defaultSettings.showImages; typeof obj.showImages === "boolean" ? obj.showImages : defaultSettings.showImages;
const sortBy = isSortBy(obj.sortBy) ? obj.sortBy : defaultSettings.sortBy; const sortBy = isSortBy(obj.sortBy) ? obj.sortBy : defaultSettings.sortBy;
const weekOffset = Number.isFinite(obj.weekOffset) const weekOffset = Number.isFinite(obj.weekOffset)
? Number(obj.weekOffset) ? Number(obj.weekOffset)
@@ -86,7 +86,7 @@ const parseHHMM = (value: string | null): ParsedTime | null => {
const normalizeWeekday = (value: string | null): number | null => { const normalizeWeekday = (value: string | null): number | null => {
if (!value) return null; if (!value) return null;
const key = value.trim().toLowerCase().replace(/s$/, ''); const key = value.trim().toLowerCase().replace(/s$/, "");
const map: Record<string, number> = { const map: Record<string, number> = {
sun: 0, sun: 0,
sunday: 0, sunday: 0,
@@ -106,13 +106,13 @@ const normalizeWeekday = (value: string | null): number | null => {
sat: 6, sat: 6,
saturday: 6, saturday: 6,
}; };
return typeof map[key] === 'number' ? map[key] : null; return typeof map[key] === "number" ? map[key] : null;
}; };
const isJstTimezone = (tz: string | null): boolean => { const isJstTimezone = (tz: string | null): boolean => {
const normalized = (tz ?? '').trim().toLowerCase(); const normalized = (tz ?? "").trim().toLowerCase();
if (!normalized) return true; if (!normalized) return true;
return normalized === 'asia/tokyo' || normalized === 'jst'; return normalized === "asia/tokyo" || normalized === "jst";
}; };
interface LocalSlot { interface LocalSlot {
@@ -122,12 +122,12 @@ interface LocalSlot {
} }
const formatLocalTime = (hour: number, minute: number, format: TimeFormat): string => { const formatLocalTime = (hour: number, minute: number, format: TimeFormat): string => {
const use12h = format === '12'; const use12h = format === "12";
const date = new Date(); const date = new Date();
date.setHours(hour, minute, 0, 0); date.setHours(hour, minute, 0, 0);
const formatter = new Intl.DateTimeFormat(undefined, { const formatter = new Intl.DateTimeFormat(undefined, {
hour: 'numeric', hour: "numeric",
minute: '2-digit', minute: "2-digit",
hour12: use12h, hour12: use12h,
}); });
return formatter.format(date); return formatter.format(date);
@@ -137,7 +137,7 @@ const getLocalSlot = (
broadcastDay: string | null, broadcastDay: string | null,
broadcastTime: string | null, broadcastTime: string | null,
broadcastTimezone: string | null, broadcastTimezone: string | null,
timeFormat: TimeFormat timeFormat: TimeFormat,
): LocalSlot | null => { ): LocalSlot | null => {
const sourceDayIndex = normalizeWeekday(broadcastDay); const sourceDayIndex = normalizeWeekday(broadcastDay);
const parsed = parseHHMM(broadcastTime); const parsed = parseHHMM(broadcastTime);
@@ -164,8 +164,8 @@ const getLocalSlot = (
}; };
const orderedDayIndexes = (weekStart: WeekStart): number[] => { const orderedDayIndexes = (weekStart: WeekStart): number[] => {
if (weekStart === 'sunday') return [0, 1, 2, 3, 4, 5, 6]; if (weekStart === "sunday") return [0, 1, 2, 3, 4, 5, 6];
if (weekStart === 'saturday') return [6, 0, 1, 2, 3, 4, 5]; if (weekStart === "saturday") return [6, 0, 1, 2, 3, 4, 5];
return [1, 2, 3, 4, 5, 6, 0]; return [1, 2, 3, 4, 5, 6, 0];
}; };
@@ -181,71 +181,71 @@ const startOfWeek = (weekStart: WeekStart, weekOffset: number): Date => {
}; };
const dayName = (index: number): string => const dayName = (index: number): string =>
['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][index] ?? 'Day'; ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][index] ?? "Day";
const wireControls = ( const wireControls = (
root: HTMLElement, root: HTMLElement,
settings: ScheduleSettings, settings: ScheduleSettings,
rerender: () => void rerender: () => void,
): void => { ): void => {
const sync = (): void => { const sync = (): void => {
const buttons = root.querySelectorAll<HTMLElement>( const buttons = root.querySelectorAll<HTMLElement>(
'[data-schedule-setting][data-schedule-value]' "[data-schedule-setting][data-schedule-value]",
); );
for (const button of buttons) { for (const button of buttons) {
const key = button.getAttribute('data-schedule-setting'); const key = button.getAttribute("data-schedule-setting");
const value = button.getAttribute('data-schedule-value'); const value = button.getAttribute("data-schedule-value");
if (!key || value === null) continue; if (!key || value === null) continue;
const isActive = const isActive =
(key === 'timeFormat' && value === settings.timeFormat) || (key === "timeFormat" && value === settings.timeFormat) ||
(key === 'showImages' && value === String(settings.showImages)); (key === "showImages" && value === String(settings.showImages));
button.classList.toggle('bg-background-button-hover', isActive); button.classList.toggle("bg-background-button-hover", isActive);
} }
const selects = root.querySelectorAll<HTMLSelectElement>('select[data-schedule-setting]'); const selects = root.querySelectorAll<HTMLSelectElement>("select[data-schedule-setting]");
for (const select of selects) { for (const select of selects) {
const key = select.getAttribute('data-schedule-setting'); const key = select.getAttribute("data-schedule-setting");
if (!key) continue; if (!key) continue;
if (key === 'sortBy') select.value = settings.sortBy; if (key === "sortBy") select.value = settings.sortBy;
if (key === 'weekStart') select.value = settings.weekStart; if (key === "weekStart") select.value = settings.weekStart;
} }
}; };
root.addEventListener('click', event => { root.addEventListener("click", (event) => {
const target = const target =
event.target instanceof Element ? event.target.closest('[data-schedule-setting]') : null; event.target instanceof Element ? event.target.closest("[data-schedule-setting]") : null;
if (!target) return; if (!target) return;
const key = target.getAttribute('data-schedule-setting'); const key = target.getAttribute("data-schedule-setting");
const value = target.getAttribute('data-schedule-value'); const value = target.getAttribute("data-schedule-value");
if (!key || value === null) return; if (!key || value === null) return;
if (key === 'timeFormat' && isTimeFormat(value)) settings.timeFormat = value; if (key === "timeFormat" && isTimeFormat(value)) settings.timeFormat = value;
if (key === 'showImages') settings.showImages = value === 'true'; if (key === "showImages") settings.showImages = value === "true";
saveSettings(settings); saveSettings(settings);
sync(); sync();
rerender(); rerender();
}); });
root.addEventListener('change', event => { root.addEventListener("change", (event) => {
const target = event.target instanceof HTMLElement ? event.target : null; const target = event.target instanceof HTMLElement ? event.target : null;
if (!(target instanceof HTMLSelectElement)) return; if (!(target instanceof HTMLSelectElement)) return;
const key = target.getAttribute('data-schedule-setting'); const key = target.getAttribute("data-schedule-setting");
if (!key) return; if (!key) return;
if (key === 'sortBy' && isSortBy(target.value)) settings.sortBy = target.value; if (key === "sortBy" && isSortBy(target.value)) settings.sortBy = target.value;
if (key === 'weekStart' && isWeekStart(target.value)) settings.weekStart = target.value; if (key === "weekStart" && isWeekStart(target.value)) settings.weekStart = target.value;
saveSettings(settings); saveSettings(settings);
sync(); sync();
rerender(); rerender();
}); });
root.addEventListener('click', event => { root.addEventListener("click", (event) => {
const target = const target =
event.target instanceof Element ? event.target.closest('[data-schedule-week-nav]') : null; event.target instanceof Element ? event.target.closest("[data-schedule-week-nav]") : null;
if (!target) return; if (!target) return;
const delta = Number.parseInt(target.getAttribute('data-schedule-week-nav') ?? '0', 10); const delta = Number.parseInt(target.getAttribute("data-schedule-week-nav") ?? "0", 10);
if (!Number.isFinite(delta) || delta === 0) return; if (!Number.isFinite(delta) || delta === 0) return;
settings.weekOffset += delta; settings.weekOffset += delta;
saveSettings(settings); saveSettings(settings);
@@ -265,8 +265,8 @@ interface Item {
} }
const buildBoard = (section: HTMLElement): void => { const buildBoard = (section: HTMLElement): void => {
const board = section.querySelector<HTMLElement>('[data-schedule-board]'); const board = section.querySelector<HTMLElement>("[data-schedule-board]");
const source = section.querySelector<HTMLElement>('[data-schedule-items-source]'); const source = section.querySelector<HTMLElement>("[data-schedule-items-source]");
if (!board || !source) return; if (!board || !source) return;
const settings = loadSettings(); const settings = loadSettings();
@@ -285,49 +285,49 @@ const buildBoard = (section: HTMLElement): void => {
// Reorder day columns to match week start // Reorder day columns to match week start
const ordered = dayOrder const ordered = dayOrder
.map(i => daySections.get(i)) .map((i) => daySections.get(i))
.filter((n): n is HTMLElement => n instanceof HTMLElement); .filter((n): n is HTMLElement => n instanceof HTMLElement);
for (const node of ordered) board.appendChild(node); for (const node of ordered) board.appendChild(node);
const weekStartDate = startOfWeek(settings.weekStart, settings.weekOffset); const weekStartDate = startOfWeek(settings.weekStart, settings.weekOffset);
const dateFormatter = new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric' }); const dateFormatter = new Intl.DateTimeFormat(undefined, { month: "short", day: "numeric" });
for (const dayIndex of dayOrder) { for (const dayIndex of dayOrder) {
const daySection = daySections.get(dayIndex); const daySection = daySections.get(dayIndex);
if (!daySection) continue; if (!daySection) continue;
const labelNode = daySection.querySelector<HTMLElement>('[data-schedule-day-label]'); const labelNode = daySection.querySelector<HTMLElement>("[data-schedule-day-label]");
if (labelNode) labelNode.textContent = dayName(dayIndex); if (labelNode) labelNode.textContent = dayName(dayIndex);
const dateNode = daySection.querySelector<HTMLElement>('[data-schedule-date-label]'); const dateNode = daySection.querySelector<HTMLElement>("[data-schedule-date-label]");
if (dateNode) { if (dateNode) {
const date = new Date(weekStartDate); const date = new Date(weekStartDate);
const dayOffset = (dayIndex - dayOrder[0] + 7) % 7; const dayOffset = (dayIndex - dayOrder[0] + 7) % 7;
date.setDate(date.getDate() + dayOffset); date.setDate(date.getDate() + dayOffset);
dateNode.textContent = dateFormatter.format(date); dateNode.textContent = dateFormatter.format(date);
} }
const itemsNode = daySection.querySelector<HTMLElement>('[data-schedule-day-items]'); const itemsNode = daySection.querySelector<HTMLElement>("[data-schedule-day-items]");
if (itemsNode) itemsNode.textContent = ''; if (itemsNode) itemsNode.textContent = "";
const countNode = daySection.querySelector<HTMLElement>('[data-schedule-count]'); const countNode = daySection.querySelector<HTMLElement>("[data-schedule-count]");
if (countNode) countNode.textContent = '0'; if (countNode) countNode.textContent = "0";
} }
const rawItems = Array.from(source.querySelectorAll<HTMLElement>('[data-schedule-item]')); const rawItems = Array.from(source.querySelectorAll<HTMLElement>("[data-schedule-item]"));
const parsed: Item[] = []; const parsed: Item[] = [];
for (const node of rawItems) { for (const node of rawItems) {
const title = node.getAttribute('data-title') ?? ''; const title = node.getAttribute("data-title") ?? "";
const score = Number(node.getAttribute('data-score') ?? '0'); const score = Number(node.getAttribute("data-score") ?? "0");
const slot = getLocalSlot( const slot = getLocalSlot(
node.getAttribute('data-broadcast-day'), node.getAttribute("data-broadcast-day"),
node.getAttribute('data-broadcast-time'), node.getAttribute("data-broadcast-time"),
node.getAttribute('data-broadcast-timezone'), node.getAttribute("data-broadcast-timezone"),
settings.timeFormat settings.timeFormat,
); );
if (!slot) continue; if (!slot) continue;
const timeNode = node.querySelector<HTMLElement>('[data-schedule-time]'); const timeNode = node.querySelector<HTMLElement>("[data-schedule-time]");
if (timeNode) timeNode.textContent = slot.timeLabel; if (timeNode) timeNode.textContent = slot.timeLabel;
const poster = node.querySelector<HTMLElement>('[data-schedule-poster]'); const poster = node.querySelector<HTMLElement>("[data-schedule-poster]");
if (poster) poster.classList.toggle('hidden', !settings.showImages); if (poster) poster.classList.toggle("hidden", !settings.showImages);
parsed.push({ parsed.push({
node, node,
@@ -340,8 +340,8 @@ const buildBoard = (section: HTMLElement): void => {
} }
const sorter = (a: Item, b: Item): number => { const sorter = (a: Item, b: Item): number => {
if (settings.sortBy === 'alpha') return a.title.localeCompare(b.title); if (settings.sortBy === "alpha") return a.title.localeCompare(b.title);
if (settings.sortBy === 'score') if (settings.sortBy === "score")
return (b.score || 0) - (a.score || 0) || a.title.localeCompare(b.title); return (b.score || 0) - (a.score || 0) || a.title.localeCompare(b.title);
return a.minutes - b.minutes || a.title.localeCompare(b.title); return a.minutes - b.minutes || a.title.localeCompare(b.title);
}; };
@@ -351,13 +351,13 @@ const buildBoard = (section: HTMLElement): void => {
for (const dayIndex of dayOrder) { for (const dayIndex of dayOrder) {
const daySection = daySections.get(dayIndex); const daySection = daySections.get(dayIndex);
if (!daySection) continue; if (!daySection) continue;
const itemsNode = daySection.querySelector<HTMLElement>('[data-schedule-day-items]'); const itemsNode = daySection.querySelector<HTMLElement>("[data-schedule-day-items]");
if (!itemsNode) continue; if (!itemsNode) continue;
const items = parsed.filter(i => i.dayIndex === dayIndex).sort(sorter); const items = parsed.filter((i) => i.dayIndex === dayIndex).sort(sorter);
for (const item of items) itemsNode.appendChild(item.node); for (const item of items) itemsNode.appendChild(item.node);
const countNode = daySection.querySelector<HTMLElement>('[data-schedule-count]'); const countNode = daySection.querySelector<HTMLElement>("[data-schedule-count]");
if (countNode) countNode.textContent = String(items.length); if (countNode) countNode.textContent = String(items.length);
} }
}; };
@@ -368,12 +368,12 @@ const buildBoard = (section: HTMLElement): void => {
const initScheduleBoard = (): void => { const initScheduleBoard = (): void => {
const run = (): void => { const run = (): void => {
const sections = document.querySelectorAll<HTMLElement>('[data-schedule-section]'); const sections = document.querySelectorAll<HTMLElement>("[data-schedule-section]");
sections.forEach(buildBoard); sections.forEach(buildBoard);
}; };
document.addEventListener('DOMContentLoaded', run); document.addEventListener("DOMContentLoaded", run);
document.body.addEventListener('htmx:afterSwap', run); document.body.addEventListener("htmx:afterSwap", run);
}; };
initScheduleBoard(); initScheduleBoard();