// Rôle : gère l'onglet roue pondérée du Randomizer et son animation. import { useEffect, useMemo, useRef, useState } from "react"; import { Icon } from "../../../../components/Icon.jsx"; import { RANDOMIZER_LIMITS, addRandomizerHistory, buildWheelSegments, clampNumber, drawWeightedEntries } from "../randomizerUtils.js"; import { ResultChips, RandomizerHistory } from "./RandomizerShared.jsx"; import { DRAW_PAUSE_MS, WHEEL_COLORS, WHEEL_SPIN_MS, describeWheelArc, getWheelTargetRotation, hasReducedMotion } from "./randomizerUiUtils.js"; import { useTimeoutQueue } from "./useTimeoutQueue.js"; export function WheelRandomizer({ data, textContent, onChange, context }) { const [rotation, setRotation] = useState(0); const [spinning, setSpinning] = useState(false); const [drawing, setDrawing] = useState(false); const [displayResults, setDisplayResults] = useState(data.results); const [entriesText, setEntriesText] = useState(""); const { queueTimer, clearTimers: clearWheelTimers } = useTimeoutQueue(); const skipWheelSyncRef = useRef(false); const segments = useMemo(() => buildWheelSegments(data.entries), [data.entries]); useEffect(() => { if (drawing) return; if (skipWheelSyncRef.current) { skipWheelSyncRef.current = false; return; } setDisplayResults(data.results); }, [data.results, drawing]); function stopWheel() { clearWheelTimers(); skipWheelSyncRef.current = true; setSpinning(false); setDrawing(false); } function addEntries(event) { event.preventDefault(); const availableSlots = RANDOMIZER_LIMITS.wheelEntriesMax - data.entries.length; const labels = entriesText .split(/\r?\n/) .map((label) => label.trim()) .filter(Boolean) .slice(0, availableSlots); if (!labels.length) return; onChange({ ...data, entries: [ ...data.entries, ...labels.map((label) => ({ id: context.uid("randomizer"), label, weight: 1 })) ] }); setEntriesText(""); } function updateEntry(entryId, patch) { onChange({ ...data, entries: data.entries.map((entry) => entry.id === entryId ? { ...entry, ...patch } : entry) }); } function removeEntry(entryId) { onChange({ ...data, entries: data.entries.filter((entry) => entry.id !== entryId) }); } function clearEntries() { onChange({ ...data, entries: [], results: [] }); } function clearWheelHistory() { onChange({ ...data, history: [] }); } function removeWheelHistoryEntry(entryId) { onChange({ ...data, history: data.history.filter((entry) => entry.id !== entryId) }); } function launchWheel() { clearWheelTimers(); skipWheelSyncRef.current = false; const drawnEntries = drawWeightedEntries(data.entries, data.draws); if (!drawnEntries.length) { context.notify?.(textContent.wheelInvalid || "Ajoutez au moins deux valeurs."); return; } const results = drawnEntries.map((entry) => entry.label); const historyEntry = { id: context.uid("randomizer"), at: new Date().toISOString(), results }; if (hasReducedMotion()) { setDisplayResults(results); onChange({ ...data, results, history: addRandomizerHistory(data.history, historyEntry) }); return; } setDrawing(true); setDisplayResults([]); let rotationCursor = rotation; function animateDraw(index) { const drawnEntry = drawnEntries[index]; const winnerSegment = segments.find((segment) => segment.entry.id === drawnEntry.id) || segments[0]; rotationCursor = getWheelTargetRotation(rotationCursor, winnerSegment.midAngle); setSpinning(true); setRotation(rotationCursor); queueTimer(() => { setDisplayResults((current) => [...current, drawnEntry.label]); setSpinning(false); if (index < drawnEntries.length - 1) { queueTimer(() => animateDraw(index + 1), DRAW_PAUSE_MS); return; } setDrawing(false); onChange({ ...data, results, history: addRandomizerHistory(data.history, historyEntry) }); }, WHEEL_SPIN_MS); } animateDraw(0); } return (