// Rôle : surveille globalement les alertes internes des outils timer et calendrier. import { useEffect, useRef } from "react"; import { getCalendarAlertOffsets, getCurrentCalendarOccurrence, getNextCalendarAlertTime } from "../features/toolboxes/modules/calendarUtils.js"; 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 isAlertAllowed(alertMode, toolboxId, visibleToolboxIds) { if (alertMode === "off") return false; if (alertMode === "visible") return visibleToolboxIds.has(toolboxId); return alertMode === "site"; } function formatAlertMessage(template, toolbox, label) { return template .replaceAll("{label}", label) .replaceAll("{toolbox}", toolbox.name); } export function useToolboxAlerts({ toolboxes, moduleData, links, route, drawerGameId, normalizeTimerData, normalizeCalendarData, textContent, notify }) { const previousTargetsRef = useRef(new Map()); const resumedCalendarEventsRef = useRef(new Set()); const lastTickRef = useRef(Date.now()); useEffect(() => { const visibleToolboxIds = getVisibleToolboxIds(route, drawerGameId, links); const timerAlertTemplate = textContent?.timer?.alertMessage || "Compte à rebours terminé : {label}"; const calendarAlertTemplate = textContent?.calendar?.alertMessage || "Événement à venir : {label}"; const calendarResumeTemplate = textContent?.calendar?.resumeAlertMessage || "Événement en cours : {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") { const data = normalizeTimerData(moduleData[moduleStorageKey(toolbox.id, module.id)]); data.countdowns.forEach((countdown) => { if (!isAlertAllowed(countdown.alertMode, toolbox.id, visibleToolboxIds)) 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(formatAlertMessage(timerAlertTemplate, toolbox, countdown.label)); }); } if (module.type === "calendar") { const data = normalizeCalendarData(moduleData[moduleStorageKey(toolbox.id, module.id)]); data.events.forEach((event) => { if (!isAlertAllowed(event.alertMode, toolbox.id, visibleToolboxIds)) return; const key = `${toolbox.id}:${module.id}:${event.id}`; getCalendarAlertOffsets(event).forEach((offset) => { const alertKey = `${key}:alert:${offset}`; const alertMs = getNextCalendarAlertTime({ ...event, alertOffsetMinutes: offset }, nowMs); const previousAlertMs = previousTargetsRef.current.get(alertKey) || alertMs; if (alertMs) nextTargets.set(alertKey, alertMs); if (alertMs && previousAlertMs > lastTickMs && previousAlertMs <= nowMs) { notify(formatAlertMessage(calendarAlertTemplate, toolbox, event.title)); } }); const currentOccurrence = getCurrentCalendarOccurrence(event, nowMs); if (!currentOccurrence) return; const resumeKey = `${key}:${currentOccurrence.startMs}`; if (resumedCalendarEventsRef.current.has(resumeKey)) return; resumedCalendarEventsRef.current.add(resumeKey); notify(formatAlertMessage(calendarResumeTemplate, toolbox, event.title)); }); } }); }); previousTargetsRef.current = nextTargets; lastTickRef.current = nowMs; } tick(); const intervalId = window.setInterval(tick, 500); return () => window.clearInterval(intervalId); }, [drawerGameId, links, moduleData, normalizeCalendarData, normalizeTimerData, notify, route, textContent, toolboxes]); }