Initialize session state by page and module identity
Some checks failed
Deploy Sokko G / deploy (push) Failing after 3s

This commit is contained in:
Shinuwa 2026-09-11 14:05:41 +02:00
parent 4f644ca116
commit 7c0b72f914
8 changed files with 177 additions and 19 deletions

View file

@ -4,6 +4,16 @@ import { Icon } from "../../../components/Icon.jsx";
import { Tooltip } from "../../../components/Tooltip.jsx";
import { DrawingOverlay } from "./DrawingOverlay.jsx";
import { TextExchangeActions } from "./TextExchangeActions.jsx";
import { useKeyedState } from "../../../hooks/useKeyedState.js";
function readTemporaryDrawings(sessionKey) {
try {
const stored = JSON.parse(sessionStorage.getItem(sessionKey) || "null");
return Array.isArray(stored?.strokes) ? stored : { strokes: [] };
} catch {
return { strokes: [] };
}
}
function markerLabel(index, marker, textContent) {
return marker.label || `${textContent.markerPrefix || "Marqueur"} ${index + 1}`;
@ -19,7 +29,7 @@ export function ImageAnnotationModule({ toolboxId, moduleId, context, editing, t
const sessionKey = `sokkog:image-annotation-drawings:${toolboxId}:${moduleId}`;
const [dragOver, setDragOver] = useState(false);
const [drawingMode, setDrawingMode] = useState("permanent");
const [temporaryDrawings, setTemporaryDrawings] = useState({ strokes: [] });
const [temporaryDrawings, setTemporaryDrawings] = useKeyedState(sessionKey, readTemporaryDrawings);
const markers = data.markers;
const drawings = drawingMode === "permanent" ? data.drawings || { strokes: [] } : temporaryDrawings;
const pastePlaceholder = textContent.pastePlaceholder || "Coller une image ici";
@ -31,12 +41,6 @@ export function ImageAnnotationModule({ toolboxId, moduleId, context, editing, t
useEffect(() => {
temporaryDrawingsDirtyRef.current = false;
sessionStorageFailureRef.current = "";
try {
const stored = JSON.parse(sessionStorage.getItem(sessionKey) || "{\"strokes\":[]}");
setTemporaryDrawings(stored?.strokes ? stored : { strokes: [] });
} catch {
setTemporaryDrawings({ strokes: [] });
}
}, [sessionKey]);
useEffect(() => {

View file

@ -7,6 +7,7 @@ import { DrawingOverlay } from "./DrawingOverlay.jsx";
import { NotepadToolbar } from "./NotepadToolbar.jsx";
import { TextExchangeActions } from "./TextExchangeActions.jsx";
import { normalizeNotepadData } from "../storage/toolboxStorage.js";
import { useKeyedState } from "../../../hooks/useKeyedState.js";
import {
DRAWING_WIDTHS,
HIGHLIGHT_COLORS,
@ -22,6 +23,15 @@ import {
const EMPTY_NOTEPAD_DATA = { text: "" };
function readTemporaryStrokes(sessionKey) {
try {
const stored = JSON.parse(sessionStorage.getItem(sessionKey) || "[]");
return Array.isArray(stored) ? stored : [];
} catch {
return [];
}
}
export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }) {
const storedData = context.getModuleData(toolboxId, moduleId, EMPTY_NOTEPAD_DATA);
const normalizedData = useMemo(() => normalizeNotepadData(storedData), [storedData]);
@ -58,7 +68,7 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
numberList: false
});
const sessionKey = `sokkog:notepad-drawings:${toolboxId}:${moduleId}:${activePage.id}`;
const [temporaryStrokes, setTemporaryStrokes] = useState([]);
const [temporaryStrokes, setTemporaryStrokes] = useKeyedState(sessionKey, readTemporaryStrokes);
useEffect(() => {
notepadDataRef.current = normalizedData;
@ -122,12 +132,6 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
useEffect(() => {
temporaryStrokesDirtyRef.current = false;
sessionStorageFailureRef.current = "";
try {
const stored = JSON.parse(sessionStorage.getItem(sessionKey) || "[]");
setTemporaryStrokes(Array.isArray(stored) ? stored : []);
} catch {
setTemporaryStrokes([]);
}
}, [sessionKey]);
useEffect(() => {

View file

@ -3,6 +3,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
import { Icon } from "../../../components/Icon.jsx";
import { Tabs } from "../../../components/Tabs.jsx";
import { useDraftForm } from "../../../hooks/useDraftForm.js";
import { useKeyedState } from "../../../hooks/useKeyedState.js";
import { TextExchangeActions } from "./TextExchangeActions.jsx";
import {
RNG_SIMULATOR_DEFAULT_RATE,
@ -156,7 +157,7 @@ export function RngSimulatorModule({ toolboxId, moduleId, context, editing = fal
], [textContent.forecastsTabLabel, textContent.simulationTabLabel, textContent.trackingTabLabel]);
const historyStorageKey = `rng-simulator:${toolboxId}:${moduleId}:history`;
const [activeTab, setActiveTab] = useState("tracking");
const [history, setHistory] = useState(() => readSimulationHistory(historyStorageKey));
const [history, setHistory] = useKeyedState(historyStorageKey, readSimulationHistory);
const [editingRateId, setEditingRateId] = useState("");
const [multiplierDraft, setMultiplierDraft] = useState(null);
const [costDraft, setCostDraft] = useState(null);
@ -196,10 +197,6 @@ export function RngSimulatorModule({ toolboxId, moduleId, context, editing = fal
const averageExceededDraws = Math.max(0, Math.round(averageDrawsDelta));
const averageDrawsTone = realDrawsSummary?.averageExceeded ? (averageExceededDraws > 0 ? "negative" : "") : "positive";
useEffect(() => {
setHistory(readSimulationHistory(historyStorageKey));
}, [historyStorageKey]);
useEffect(() => {
const wasEditing = previousEditingRef.current;
if (wasEditing && !editing) setEditingRateId("");

View file

@ -0,0 +1,21 @@
// Rôle : initialise un état local par clé et ignore les setters d'une identité remplacée.
import { useCallback, useState } from "react";
export function useKeyedState(key, initialize) {
const [state, setState] = useState(() => ({ key, value: initialize(key) }));
let current = state;
if (!Object.is(state.key, key)) {
current = { key, value: initialize(key) };
setState(current);
}
const setValue = useCallback((value) => {
setState((previous) => {
if (!Object.is(previous.key, key)) return previous;
const next = typeof value === "function" ? value(previous.value) : value;
return Object.is(previous.value, next) ? previous : { key, value: next };
});
}, [key]);
return [current.value, setValue];
}