Add calendar toolbox with weekly and monthly scheduling
All checks were successful
Deploy Sokko G / deploy (push) Successful in 14s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 14s
This commit is contained in:
parent
4f73e0068b
commit
992665f4ea
39 changed files with 4331 additions and 36 deletions
99
website/src/hooks/useToolboxAlerts.js
Normal file
99
website/src/hooks/useToolboxAlerts.js
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
// 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]);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue