Clean up dead toolbox code and harden routing and static serving

This commit is contained in:
Shinuwa 2026-09-09 15:52:17 +02:00
parent 184314d913
commit cce3f3c7f8
9 changed files with 25 additions and 98 deletions

View file

@ -1,3 +0,0 @@
// Rôle : conserve les exports publics historiques des sous-composants task planner.
export { TaskPlannerBranch } from "./TaskPlannerBranch.jsx";
export { TaskPlannerItem } from "./TaskPlannerItem.jsx";

View file

@ -1,4 +0,0 @@
// Rôle : conserve les exports publics historiques des sous-composants timer.
export { CountdownControls, CountdownList } from "./TimerCountdown.jsx";
export { EditableTimerLabel, TimerAlertModeSwitch, TimerValue, TimePartsInput } from "./TimerSharedControls.jsx";
export { StopwatchControls, StopwatchLapList } from "./TimerStopwatch.jsx";

View file

@ -191,11 +191,6 @@ export function getEventDurationMs(event) {
return Number.isFinite(startMinutes) && Number.isFinite(endMinutes) && endMinutes > startMinutes ? (endMinutes - startMinutes) * MINUTE_MS : 0;
}
export function getEventEndMs(event, startMs) {
const durationMs = getEventDurationMs(event);
return durationMs > 0 ? startMs + durationMs : startMs;
}
export function getCalendarAlertOffsets(event) {
const rawOffsets = Array.isArray(event?.alertOffsetMinutes) ? event.alertOffsetMinutes : [event?.alertOffsetMinutes];
const offsets = [...new Set(rawOffsets.map(Number).filter((offset) => CALENDAR_ALERT_OFFSETS.includes(offset)))];

View file

@ -1,20 +1,11 @@
// Rôle : normalise et compacte les cellules et libellés de l'outil table.
import { columnIndexToName } from "../../modules/tableFormulaEngine.js";
function clampTableSize(value, fallback, max) {
const parsed = Number.parseInt(value, 10);
return Number.isInteger(parsed) ? Math.min(max, Math.max(1, parsed)) : fallback;
}
function columnIndexToName(index) {
let value = Math.max(0, Number(index) || 0) + 1;
let name = "";
while (value > 0) {
const remainder = (value - 1) % 26;
name = String.fromCharCode(65 + remainder) + name;
value = Math.floor((value - 1) / 26);
}
return name;
}
function tableCellAddress(rowIndex, columnIndex) {
return `${columnIndexToName(columnIndex)}${rowIndex + 1}`;
}

View file

@ -19,7 +19,10 @@ export function useAppData(route) {
setSiteContentError(error.message);
return null;
}),
fetch("/data/games.json").then((response) => response.ok ? response.json() : { games: [] }).catch((error) => {
fetch("/data/games.json").then((response) => {
if (!response.ok) throw new Error("Impossible de charger la liste des jeux.");
return response.json();
}).catch((error) => {
setGamesError(error.message);
return { games: [] };
})

View file

@ -1,63 +0,0 @@
// Rôle : surveille globalement les comptes à rebours actifs et déclenche les alertes internes autorisées.
import { useEffect, useRef } from "react";
import { getCountdownTargetMs } from "../features/toolboxes/modules/timerUtils.js";
import { moduleStorageKey } from "../features/toolboxes/storage/toolboxStorage.js";
function getVisibleToolboxIds(route, drawerGameId, links) {
const visibleIds = new Set();
const toolboxMatch = route.match(/^\/toolbox\/([^/]+)/);
if (toolboxMatch?.[1]) visibleIds.add(toolboxMatch[1]);
const drawerToolboxId = drawerGameId ? links[drawerGameId] : "";
if (drawerToolboxId) visibleIds.add(drawerToolboxId);
return visibleIds;
}
function getAlertMessage(template, toolbox, countdown) {
return template
.replaceAll("{label}", countdown.label)
.replaceAll("{toolbox}", toolbox.name);
}
export function useTimerAlerts({ toolboxes, moduleData, links, route, drawerGameId, normalizeTimerData, textContent, notify }) {
const previousTargetsRef = useRef(new Map());
const lastTickRef = useRef(Date.now());
useEffect(() => {
const visibleToolboxIds = getVisibleToolboxIds(route, drawerGameId, links);
const alertMessageTemplate = textContent?.alertMessage || "Compte à rebours terminé : {label}";
function tick() {
const nowMs = Date.now();
const lastTickMs = lastTickRef.current;
const nextTargets = new Map();
toolboxes.forEach((toolbox) => {
toolbox.modules.forEach((module) => {
if (module.type !== "timer") return;
const data = normalizeTimerData(moduleData[moduleStorageKey(toolbox.id, module.id)]);
data.countdowns.forEach((countdown) => {
if (countdown.alertMode === "off") return;
if (countdown.alertMode === "visible" && !visibleToolboxIds.has(toolbox.id)) return;
const key = `${toolbox.id}:${module.id}:${countdown.id}`;
const targetMs = getCountdownTargetMs(countdown, nowMs);
const previousTargetMs = previousTargetsRef.current.get(key) || targetMs;
nextTargets.set(key, targetMs);
if (!targetMs || previousTargetMs <= lastTickMs || previousTargetMs > nowMs) return;
notify(getAlertMessage(alertMessageTemplate, toolbox, countdown));
});
});
});
previousTargetsRef.current = nextTargets;
lastTickRef.current = nowMs;
}
tick();
const intervalId = window.setInterval(tick, 500);
return () => window.clearInterval(intervalId);
}, [drawerGameId, links, moduleData, normalizeTimerData, notify, route, textContent, toolboxes]);
}

View file

@ -1,5 +1,5 @@
// Rôle : choisit la page à rendre selon la route active.
import { lazy, Suspense } from "react";
import { lazy, Suspense, useEffect } from "react";
import { AboutPage } from "../pages/AboutPage.jsx";
import { HomePage } from "../pages/HomePage.jsx";
import { navigate } from "./hashRouter.js";
@ -17,6 +17,13 @@ function RouteLoader() {
return <section className="empty nds-empty-state"><p>Chargement...</p></section>;
}
function UnknownRouteRedirect() {
useEffect(() => {
navigate("/");
}, []);
return <RouteLoader />;
}
export function RouteContent(props) {
const { route } = props;
if (route === "/") return <HomePage {...props} />;
@ -27,6 +34,5 @@ export function RouteContent(props) {
if (route.startsWith("/toolbox/")) return <Suspense fallback={<RouteLoader />}><ToolboxPage {...props} toolboxId={route.split("/")[2]} /></Suspense>;
if (route === "/games") return <Suspense fallback={<RouteLoader />}><GamesPage {...props} /></Suspense>;
if (route.startsWith("/games/")) return <Suspense fallback={<RouteLoader />}><GameRoute {...props} gameId={route.split("/")[2]} category={route.split("/")[3] || ""} /></Suspense>;
navigate("/");
return null;
return <UnknownRouteRedirect />;
}