Fix Notepad render loop and stabilize toolbox modules
Some checks failed
Deploy Sokko G / deploy (push) Failing after 2s
Some checks failed
Deploy Sokko G / deploy (push) Failing after 2s
This commit is contained in:
parent
c096cc07fe
commit
6c61e11f9a
8 changed files with 118 additions and 94 deletions
31
website/src/components/AppErrorBoundary.jsx
Normal file
31
website/src/components/AppErrorBoundary.jsx
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Rôle : récupère les erreurs de rendu et de chargement lazy au niveau de l'application.
|
||||||
|
import { Component } from "react";
|
||||||
|
|
||||||
|
export class AppErrorBoundary extends Component {
|
||||||
|
state = { error: null };
|
||||||
|
|
||||||
|
static getDerivedStateFromError(error) {
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(previousProps) {
|
||||||
|
if (previousProps.route !== this.props.route && this.state.error) {
|
||||||
|
this.setState({ error: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.state.error) return this.props.children;
|
||||||
|
return (
|
||||||
|
<main className="startup-error">
|
||||||
|
<section className="empty nds-empty-state" role="alert">
|
||||||
|
<h1>Cette page n'a pas pu être affichée</h1>
|
||||||
|
<p>Une erreur est survenue pendant le chargement de cet outil.</p>
|
||||||
|
<button className="nds-button" data-size="sm" type="button" onClick={() => window.location.reload()}>
|
||||||
|
Recharger la page
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -45,6 +45,7 @@ export function DrawingOverlay({ active, erase, strokes, color, width, onChange,
|
||||||
const [size, setSize] = useState({ width: 0, height: 0 });
|
const [size, setSize] = useState({ width: 0, height: 0 });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!active && !strokes.length) return undefined;
|
||||||
function syncSize() {
|
function syncSize() {
|
||||||
const nextSize = getSurfaceSize?.() || { width: 0, height: 0 };
|
const nextSize = getSurfaceSize?.() || { width: 0, height: 0 };
|
||||||
const nextWidth = Math.max(0, Math.ceil(nextSize.width || 0));
|
const nextWidth = Math.max(0, Math.ceil(nextSize.width || 0));
|
||||||
|
|
@ -63,7 +64,7 @@ export function DrawingOverlay({ active, erase, strokes, color, width, onChange,
|
||||||
observer?.disconnect();
|
observer?.disconnect();
|
||||||
window.removeEventListener("resize", syncSize);
|
window.removeEventListener("resize", syncSize);
|
||||||
};
|
};
|
||||||
}, [getSurfaceSize]);
|
}, [active, getSurfaceSize, strokes.length]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,12 @@ import {
|
||||||
restoreSelectionTextOffset
|
restoreSelectionTextOffset
|
||||||
} from "./notepadUtils.js";
|
} from "./notepadUtils.js";
|
||||||
|
|
||||||
|
const EMPTY_NOTEPAD_DATA = { text: "" };
|
||||||
|
|
||||||
export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }) {
|
export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }) {
|
||||||
const storedData = context.getModuleData(toolboxId, moduleId, { text: "" });
|
const storedData = context.getModuleData(toolboxId, moduleId, EMPTY_NOTEPAD_DATA);
|
||||||
const normalizedData = useMemo(() => normalizeNotepadData(storedData), [storedData]);
|
const normalizedData = useMemo(() => normalizeNotepadData(storedData), [storedData]);
|
||||||
|
const notepadDataRef = useRef(normalizedData);
|
||||||
const textContent = context.moduleText?.notepad || {};
|
const textContent = context.moduleText?.notepad || {};
|
||||||
const notify = context.notify;
|
const notify = context.notify;
|
||||||
const activePage = normalizedData.pages.find((page) => page.id === normalizedData.activePageId) || normalizedData.pages[0];
|
const activePage = normalizedData.pages.find((page) => page.id === normalizedData.activePageId) || normalizedData.pages[0];
|
||||||
|
|
@ -57,12 +60,18 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
|
||||||
const sessionKey = `sokkog:notepad-drawings:${toolboxId}:${moduleId}:${activePage.id}`;
|
const sessionKey = `sokkog:notepad-drawings:${toolboxId}:${moduleId}:${activePage.id}`;
|
||||||
const [temporaryStrokes, setTemporaryStrokes] = useState([]);
|
const [temporaryStrokes, setTemporaryStrokes] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
notepadDataRef.current = normalizedData;
|
||||||
|
}, [normalizedData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const pageKey = `${toolboxId}:${moduleId}:${activePage.id}`;
|
const pageKey = `${toolboxId}:${moduleId}:${activePage.id}`;
|
||||||
if (pageKeyRef.current !== pageKey) {
|
const pageChanged = pageKeyRef.current !== pageKey;
|
||||||
|
if (pageChanged) {
|
||||||
pageKeyRef.current = pageKey;
|
pageKeyRef.current = pageKey;
|
||||||
localSaveUpdatedAtRef.current = "";
|
localSaveUpdatedAtRef.current = "";
|
||||||
}
|
}
|
||||||
|
if (!pageChanged && document.activeElement === editorRef.current) return;
|
||||||
if (activePage.updatedAt && activePage.updatedAt === localSaveUpdatedAtRef.current) return;
|
if (activePage.updatedAt && activePage.updatedAt === localSaveUpdatedAtRef.current) return;
|
||||||
const editor = editorRef.current;
|
const editor = editorRef.current;
|
||||||
if (document.activeElement === editor && getPlainText(editor) === activePage.text) {
|
if (document.activeElement === editor && getPlainText(editor) === activePage.text) {
|
||||||
|
|
@ -81,23 +90,18 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!editorRef.current || editorRef.current.innerHTML === html) return;
|
if (!editorRef.current || editorRef.current.innerHTML === html) return;
|
||||||
if (document.activeElement === editorRef.current && getPlainText(editorRef.current) === text) return;
|
if (document.activeElement === editorRef.current) return;
|
||||||
editorRef.current.innerHTML = html;
|
editorRef.current.innerHTML = html;
|
||||||
if (document.activeElement === editorRef.current) restoreSelectionTextOffset(editorRef.current, selectionOffsetRef.current);
|
restoreSelectionTextOffset(editorRef.current, selectionOffsetRef.current);
|
||||||
}, [html, text]);
|
}, [html, text]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function updateActiveFormats() {
|
function updateActiveFormats() {
|
||||||
const editor = editorRef.current;
|
const editor = editorRef.current;
|
||||||
const selection = document.getSelection();
|
const selection = document.getSelection();
|
||||||
if (!editor || !selection?.rangeCount || !editor.contains(selection.anchorNode)) {
|
if (!editor || document.activeElement !== editor || !selection?.rangeCount || !editor.contains(selection.anchorNode)) return;
|
||||||
setActiveFormats((current) => Object.values(current).some(Boolean)
|
|
||||||
? { bold: false, italic: false, underline: false, strikeThrough: false, heading: false, bulletList: false, numberList: false }
|
|
||||||
: current);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const block = queryBlockFormat();
|
const block = queryBlockFormat();
|
||||||
setActiveFormats({
|
const nextFormats = {
|
||||||
bold: queryCommandState("bold"),
|
bold: queryCommandState("bold"),
|
||||||
italic: queryCommandState("italic"),
|
italic: queryCommandState("italic"),
|
||||||
underline: queryCommandState("underline"),
|
underline: queryCommandState("underline"),
|
||||||
|
|
@ -105,7 +109,10 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
|
||||||
heading: block === "h3" || block === "heading 3",
|
heading: block === "h3" || block === "heading 3",
|
||||||
bulletList: queryCommandState("insertUnorderedList"),
|
bulletList: queryCommandState("insertUnorderedList"),
|
||||||
numberList: queryCommandState("insertOrderedList")
|
numberList: queryCommandState("insertOrderedList")
|
||||||
});
|
};
|
||||||
|
setActiveFormats((current) => Object.keys(nextFormats).every((key) => current[key] === nextFormats[key])
|
||||||
|
? current
|
||||||
|
: nextFormats);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("selectionchange", updateActiveFormats);
|
document.addEventListener("selectionchange", updateActiveFormats);
|
||||||
|
|
@ -147,8 +154,11 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateActivePage(updater, nextActivePageId = normalizedData.activePageId) {
|
function updateActivePage(updater, nextActivePageId = normalizedData.activePageId) {
|
||||||
const pages = normalizedData.pages.map((page) => page.id === activePage.id ? updater(page) : page);
|
const currentData = notepadDataRef.current;
|
||||||
saveNotepad({ ...normalizedData, activePageId: nextActivePageId, pages });
|
const pages = currentData.pages.map((page) => page.id === activePage.id ? updater(page) : page);
|
||||||
|
const nextData = { ...currentData, activePageId: nextActivePageId, pages };
|
||||||
|
notepadDataRef.current = nextData;
|
||||||
|
saveNotepad(nextData);
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveContent(nextHtml, nextText) {
|
function saveContent(nextHtml, nextText) {
|
||||||
|
|
@ -204,7 +214,6 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
|
||||||
const nextHtml = editorRef.current?.innerHTML || "";
|
const nextHtml = editorRef.current?.innerHTML || "";
|
||||||
const nextText = getPlainText(editorRef.current);
|
const nextText = getPlainText(editorRef.current);
|
||||||
saveContent(nextHtml, nextText);
|
saveContent(nextHtml, nextText);
|
||||||
requestAnimationFrame(() => document.dispatchEvent(new Event("selectionchange")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function runCommand(command, value = null) {
|
function runCommand(command, value = null) {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { Icon } from "../../../../components/Icon.jsx";
|
||||||
import { RANDOMIZER_LIMITS, addRandomizerHistory, clampNumber, getDiceShape, rollDice } from "../randomizerUtils.js";
|
import { RANDOMIZER_LIMITS, addRandomizerHistory, clampNumber, getDiceShape, rollDice } from "../randomizerUtils.js";
|
||||||
import { ResultChips, RandomizerHistory } from "./RandomizerShared.jsx";
|
import { ResultChips, RandomizerHistory } from "./RandomizerShared.jsx";
|
||||||
import { DICE_ROLL_DELAYS, DRAW_PAUSE_MS, hasReducedMotion } from "./randomizerUiUtils.js";
|
import { DICE_ROLL_DELAYS, DRAW_PAUSE_MS, hasReducedMotion } from "./randomizerUiUtils.js";
|
||||||
|
import { useTimeoutQueue } from "./useTimeoutQueue.js";
|
||||||
|
|
||||||
export function DiceRandomizer({ data, textContent, onChange, context }) {
|
export function DiceRandomizer({ data, textContent, onChange, context }) {
|
||||||
const [displayValue, setDisplayValue] = useState(data.results[0] || 1);
|
const [displayValue, setDisplayValue] = useState(data.results[0] || 1);
|
||||||
|
|
@ -11,7 +12,7 @@ export function DiceRandomizer({ data, textContent, onChange, context }) {
|
||||||
const [rolling, setRolling] = useState(false);
|
const [rolling, setRolling] = useState(false);
|
||||||
const [rollingIndex, setRollingIndex] = useState(-1);
|
const [rollingIndex, setRollingIndex] = useState(-1);
|
||||||
const [rollDuration, setRollDuration] = useState(DICE_ROLL_DELAYS[0]);
|
const [rollDuration, setRollDuration] = useState(DICE_ROLL_DELAYS[0]);
|
||||||
const timersRef = useRef([]);
|
const { queueTimer, clearTimers: clearDiceTimers } = useTimeoutQueue();
|
||||||
const skipDiceSyncRef = useRef(false);
|
const skipDiceSyncRef = useRef(false);
|
||||||
const shape = getDiceShape(data.faces);
|
const shape = getDiceShape(data.faces);
|
||||||
|
|
||||||
|
|
@ -25,22 +26,6 @@ export function DiceRandomizer({ data, textContent, onChange, context }) {
|
||||||
setDisplayResults(data.results);
|
setDisplayResults(data.results);
|
||||||
}, [data.results, rolling]);
|
}, [data.results, rolling]);
|
||||||
|
|
||||||
useEffect(() => () => {
|
|
||||||
timersRef.current.forEach((timerId) => window.clearTimeout(timerId));
|
|
||||||
timersRef.current = [];
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
function queueTimer(callback, delay) {
|
|
||||||
const timerId = window.setTimeout(callback, delay);
|
|
||||||
timersRef.current.push(timerId);
|
|
||||||
return timerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearDiceTimers() {
|
|
||||||
timersRef.current.forEach((timerId) => window.clearTimeout(timerId));
|
|
||||||
timersRef.current = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopDice() {
|
function stopDice() {
|
||||||
clearDiceTimers();
|
clearDiceTimers();
|
||||||
skipDiceSyncRef.current = true;
|
skipDiceSyncRef.current = true;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
// Rôle : gère l'onglet équipes équilibrées du Randomizer et ses exclusions.
|
// Rôle : gère l'onglet équipes équilibrées du Randomizer et ses exclusions.
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { CompactDropdown } from "../../../../components/CompactDropdown.jsx";
|
import { CompactDropdown } from "../../../../components/CompactDropdown.jsx";
|
||||||
import { Icon } from "../../../../components/Icon.jsx";
|
import { Icon } from "../../../../components/Icon.jsx";
|
||||||
import { useDraftForm } from "../../../../hooks/useDraftForm.js";
|
import { useDraftForm } from "../../../../hooks/useDraftForm.js";
|
||||||
import { RANDOMIZER_LIMITS, addRandomizerHistory, clampNumber, createBalancedTeams, filterExclusionsForPlayers } from "../randomizerUtils.js";
|
import { RANDOMIZER_LIMITS, addRandomizerHistory, clampNumber, createBalancedTeams, filterExclusionsForPlayers } from "../randomizerUtils.js";
|
||||||
import { RandomizerHistory } from "./RandomizerShared.jsx";
|
import { RandomizerHistory } from "./RandomizerShared.jsx";
|
||||||
import { DEFAULT_EXCLUSION_DRAFT, TEAM_REEL_DELAYS, hasReducedMotion, pickRandomItems } from "./randomizerUiUtils.js";
|
import { DEFAULT_EXCLUSION_DRAFT, TEAM_REEL_DELAYS, hasReducedMotion, pickRandomItems } from "./randomizerUiUtils.js";
|
||||||
|
import { useTimeoutQueue } from "./useTimeoutQueue.js";
|
||||||
|
|
||||||
export function TeamsRandomizer({ data, textContent, onChange, context }) {
|
export function TeamsRandomizer({ data, textContent, onChange, context }) {
|
||||||
const exclusionDraft = useDraftForm(DEFAULT_EXCLUSION_DRAFT);
|
const exclusionDraft = useDraftForm(DEFAULT_EXCLUSION_DRAFT);
|
||||||
|
|
@ -15,7 +16,7 @@ export function TeamsRandomizer({ data, textContent, onChange, context }) {
|
||||||
const [reelIndex, setReelIndex] = useState(0);
|
const [reelIndex, setReelIndex] = useState(0);
|
||||||
const [reelDuration, setReelDuration] = useState(TEAM_REEL_DELAYS[0]);
|
const [reelDuration, setReelDuration] = useState(TEAM_REEL_DELAYS[0]);
|
||||||
const [namesText, setNamesText] = useState("");
|
const [namesText, setNamesText] = useState("");
|
||||||
const timersRef = useRef([]);
|
const { queueTimer, clearTimers: clearTeamTimers } = useTimeoutQueue();
|
||||||
const activePlayers = data.players.filter((player) => player.active);
|
const activePlayers = data.players.filter((player) => player.active);
|
||||||
const storedSelectionCount = clampNumber(data.playerSelectionCount, data.teamCount, RANDOMIZER_LIMITS.teamPlayersMax, data.teamCount);
|
const storedSelectionCount = clampNumber(data.playerSelectionCount, data.teamCount, RANDOMIZER_LIMITS.teamPlayersMax, data.teamCount);
|
||||||
const [selectionCountDraft, setSelectionCountDraft] = useState(String(storedSelectionCount));
|
const [selectionCountDraft, setSelectionCountDraft] = useState(String(storedSelectionCount));
|
||||||
|
|
@ -27,22 +28,6 @@ export function TeamsRandomizer({ data, textContent, onChange, context }) {
|
||||||
setSelectionCountDraft(String(storedSelectionCount));
|
setSelectionCountDraft(String(storedSelectionCount));
|
||||||
}, [storedSelectionCount]);
|
}, [storedSelectionCount]);
|
||||||
|
|
||||||
useEffect(() => () => {
|
|
||||||
timersRef.current.forEach((timerId) => window.clearTimeout(timerId));
|
|
||||||
timersRef.current = [];
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
function queueTimer(callback, delay) {
|
|
||||||
const timerId = window.setTimeout(callback, delay);
|
|
||||||
timersRef.current.push(timerId);
|
|
||||||
return timerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearTeamTimers() {
|
|
||||||
timersRef.current.forEach((timerId) => window.clearTimeout(timerId));
|
|
||||||
timersRef.current = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function commitSelectionCount(value = selectionCountDraft) {
|
function commitSelectionCount(value = selectionCountDraft) {
|
||||||
const nextSelectionCount = clampNumber(value, data.teamCount, RANDOMIZER_LIMITS.teamPlayersMax, storedSelectionCount);
|
const nextSelectionCount = clampNumber(value, data.teamCount, RANDOMIZER_LIMITS.teamPlayersMax, storedSelectionCount);
|
||||||
setSelectionCountDraft(String(nextSelectionCount));
|
setSelectionCountDraft(String(nextSelectionCount));
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { Icon } from "../../../../components/Icon.jsx";
|
||||||
import { RANDOMIZER_LIMITS, addRandomizerHistory, buildWheelSegments, clampNumber, drawWeightedEntries } from "../randomizerUtils.js";
|
import { RANDOMIZER_LIMITS, addRandomizerHistory, buildWheelSegments, clampNumber, drawWeightedEntries } from "../randomizerUtils.js";
|
||||||
import { ResultChips, RandomizerHistory } from "./RandomizerShared.jsx";
|
import { ResultChips, RandomizerHistory } from "./RandomizerShared.jsx";
|
||||||
import { DRAW_PAUSE_MS, WHEEL_COLORS, WHEEL_SPIN_MS, describeWheelArc, getWheelTargetRotation, hasReducedMotion } from "./randomizerUiUtils.js";
|
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 }) {
|
export function WheelRandomizer({ data, textContent, onChange, context }) {
|
||||||
const [rotation, setRotation] = useState(0);
|
const [rotation, setRotation] = useState(0);
|
||||||
|
|
@ -11,7 +12,7 @@ export function WheelRandomizer({ data, textContent, onChange, context }) {
|
||||||
const [drawing, setDrawing] = useState(false);
|
const [drawing, setDrawing] = useState(false);
|
||||||
const [displayResults, setDisplayResults] = useState(data.results);
|
const [displayResults, setDisplayResults] = useState(data.results);
|
||||||
const [entriesText, setEntriesText] = useState("");
|
const [entriesText, setEntriesText] = useState("");
|
||||||
const timersRef = useRef([]);
|
const { queueTimer, clearTimers: clearWheelTimers } = useTimeoutQueue();
|
||||||
const skipWheelSyncRef = useRef(false);
|
const skipWheelSyncRef = useRef(false);
|
||||||
const segments = useMemo(() => buildWheelSegments(data.entries), [data.entries]);
|
const segments = useMemo(() => buildWheelSegments(data.entries), [data.entries]);
|
||||||
|
|
||||||
|
|
@ -24,22 +25,6 @@ export function WheelRandomizer({ data, textContent, onChange, context }) {
|
||||||
setDisplayResults(data.results);
|
setDisplayResults(data.results);
|
||||||
}, [data.results, drawing]);
|
}, [data.results, drawing]);
|
||||||
|
|
||||||
useEffect(() => () => {
|
|
||||||
timersRef.current.forEach((timerId) => window.clearTimeout(timerId));
|
|
||||||
timersRef.current = [];
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
function queueTimer(callback, delay) {
|
|
||||||
const timerId = window.setTimeout(callback, delay);
|
|
||||||
timersRef.current.push(timerId);
|
|
||||||
return timerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearWheelTimers() {
|
|
||||||
timersRef.current.forEach((timerId) => window.clearTimeout(timerId));
|
|
||||||
timersRef.current = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopWheel() {
|
function stopWheel() {
|
||||||
clearWheelTimers();
|
clearWheelTimers();
|
||||||
skipWheelSyncRef.current = true;
|
skipWheelSyncRef.current = true;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Rôle : planifie et nettoie les timers d'animation du Randomizer.
|
||||||
|
import { useCallback, useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
export function useTimeoutQueue() {
|
||||||
|
const timersRef = useRef(new Set());
|
||||||
|
|
||||||
|
const clearTimers = useCallback(() => {
|
||||||
|
timersRef.current.forEach((timerId) => window.clearTimeout(timerId));
|
||||||
|
timersRef.current.clear();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const queueTimer = useCallback((callback, delay) => {
|
||||||
|
let timerId;
|
||||||
|
timerId = window.setTimeout(() => {
|
||||||
|
timersRef.current.delete(timerId);
|
||||||
|
callback();
|
||||||
|
}, delay);
|
||||||
|
timersRef.current.add(timerId);
|
||||||
|
return timerId;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => clearTimers, [clearTimers]);
|
||||||
|
|
||||||
|
return { queueTimer, clearTimers };
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import { lazy, Suspense, useCallback, useEffect, useState } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import "./styles/main.scss";
|
import "./styles/main.scss";
|
||||||
import { AppOverlays } from "./components/AppOverlays.jsx";
|
import { AppOverlays } from "./components/AppOverlays.jsx";
|
||||||
|
import { AppErrorBoundary } from "./components/AppErrorBoundary.jsx";
|
||||||
import { Shell } from "./components/Shell.jsx";
|
import { Shell } from "./components/Shell.jsx";
|
||||||
import { useIndexedToolboxes } from "./features/toolboxes/storage/useIndexedToolboxes.js";
|
import { useIndexedToolboxes } from "./features/toolboxes/storage/useIndexedToolboxes.js";
|
||||||
import { useToolboxActions } from "./features/toolboxes/useToolboxActions.js";
|
import { useToolboxActions } from "./features/toolboxes/useToolboxActions.js";
|
||||||
|
|
@ -103,32 +104,34 @@ function App() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Shell route={route} content={siteContent} games={games} toolboxes={store.toolboxes} links={store.links} drawerGameId={drawerGameId} actions={actions}>
|
<Shell route={route} content={siteContent} games={games} toolboxes={store.toolboxes} links={store.links} drawerGameId={drawerGameId} actions={actions}>
|
||||||
<RouteContent
|
<AppErrorBoundary route={route}>
|
||||||
route={route}
|
<RouteContent
|
||||||
siteContent={siteContent}
|
route={route}
|
||||||
games={games}
|
siteContent={siteContent}
|
||||||
gamesError={gamesError}
|
games={games}
|
||||||
mhwilds={mhwilds}
|
gamesError={gamesError}
|
||||||
diablo4={diablo4}
|
mhwilds={mhwilds}
|
||||||
filters={filters}
|
diablo4={diablo4}
|
||||||
setFilters={setFilters}
|
filters={filters}
|
||||||
t={t}
|
setFilters={setFilters}
|
||||||
toolboxes={store.toolboxes}
|
t={t}
|
||||||
links={store.links}
|
toolboxes={store.toolboxes}
|
||||||
getGame={getGame}
|
links={store.links}
|
||||||
getToolboxGame={getToolboxGame}
|
getGame={getGame}
|
||||||
actions={actions}
|
getToolboxGame={getToolboxGame}
|
||||||
storageUsage={store.storageUsage}
|
actions={actions}
|
||||||
storageReady={store.ready}
|
storageUsage={store.storageUsage}
|
||||||
toastPosition={store.toastPosition}
|
storageReady={store.ready}
|
||||||
setToastPosition={store.setToastPosition}
|
toastPosition={store.toastPosition}
|
||||||
getModuleData={store.getModuleData}
|
setToastPosition={store.setToastPosition}
|
||||||
updateToolbox={actions.updateToolbox}
|
getModuleData={store.getModuleData}
|
||||||
updateModuleData={store.updateModuleData}
|
updateToolbox={actions.updateToolbox}
|
||||||
addImageFiles={actions.addImageFiles}
|
updateModuleData={store.updateModuleData}
|
||||||
pendingTutorialToolboxId={pendingTutorialToolboxId}
|
addImageFiles={actions.addImageFiles}
|
||||||
setPendingTutorialToolboxId={setPendingTutorialToolboxId}
|
pendingTutorialToolboxId={pendingTutorialToolboxId}
|
||||||
/>
|
setPendingTutorialToolboxId={setPendingTutorialToolboxId}
|
||||||
|
/>
|
||||||
|
</AppErrorBoundary>
|
||||||
{drawerGameId && (
|
{drawerGameId && (
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<ToolboxDrawer
|
<ToolboxDrawer
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue