Initialize session state by page and module identity
Some checks failed
Deploy Sokko G / deploy (push) Failing after 3s
Some checks failed
Deploy Sokko G / deploy (push) Failing after 3s
This commit is contained in:
parent
4f644ca116
commit
7c0b72f914
8 changed files with 177 additions and 19 deletions
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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("");
|
||||
|
|
|
|||
21
website/src/hooks/useKeyedState.js
Normal file
21
website/src/hooks/useKeyedState.js
Normal 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];
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue