diff --git a/website/src/features/toolboxes/modules/ImageAnnotationModule.jsx b/website/src/features/toolboxes/modules/ImageAnnotationModule.jsx index a3b1629..79696ff 100644 --- a/website/src/features/toolboxes/modules/ImageAnnotationModule.jsx +++ b/website/src/features/toolboxes/modules/ImageAnnotationModule.jsx @@ -12,7 +12,10 @@ function markerLabel(index, marker, textContent) { export function ImageAnnotationModule({ toolboxId, moduleId, context, editing, textExchangeOpen }) { const data = context.normalizeImageAnnotationData(context.getModuleData(toolboxId, moduleId, { image: "", markers: [] })); const textContent = context.moduleText?.imageAnnotation || {}; + const notify = context.notify; const previewMediaRef = useRef(null); + const temporaryDrawingsDirtyRef = useRef(false); + const sessionStorageFailureRef = useRef(""); const sessionKey = `sokkog:image-annotation-drawings:${toolboxId}:${moduleId}`; const [dragOver, setDragOver] = useState(false); const [drawingMode, setDrawingMode] = useState("permanent"); @@ -26,6 +29,8 @@ 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: [] }); @@ -36,12 +41,19 @@ export function ImageAnnotationModule({ toolboxId, moduleId, context, editing, t useEffect(() => { if (drawingMode !== "temporary") return; + if (!temporaryDrawingsDirtyRef.current) return; + const serializedDrawings = JSON.stringify(temporaryDrawings); try { - sessionStorage.setItem(sessionKey, JSON.stringify(temporaryDrawings)); + sessionStorage.setItem(sessionKey, serializedDrawings); + temporaryDrawingsDirtyRef.current = false; + sessionStorageFailureRef.current = ""; } catch { - context.notify?.(textContent.sessionStorageError || "Les annotations temporaires n'ont pas pu être sauvegardées."); + const failureKey = `${sessionKey}:${serializedDrawings}`; + if (sessionStorageFailureRef.current === failureKey) return; + sessionStorageFailureRef.current = failureKey; + notify?.(textContent.sessionStorageError || "Les annotations temporaires n'ont pas pu être sauvegardées."); } - }, [context, drawingMode, sessionKey, temporaryDrawings, textContent.sessionStorageError]); + }, [drawingMode, notify, sessionKey, temporaryDrawings, textContent.sessionStorageError]); function save(nextData) { context.setModuleData(toolboxId, moduleId, nextData); @@ -151,12 +163,18 @@ export function ImageAnnotationModule({ toolboxId, moduleId, context, editing, t onChangeMarkers: (nextMarkers) => save({ ...data, markers: nextMarkers }), onChangeDrawings: (nextDrawings, nextMode = drawingMode) => { if (nextMode === "permanent") save({ ...data, drawings: nextDrawings }); - else setTemporaryDrawings(nextDrawings); + else { + temporaryDrawingsDirtyRef.current = true; + setTemporaryDrawings(nextDrawings); + } }, onChangeDrawingMode: (nextMode, currentDrawings) => { setDrawingMode(nextMode); if (nextMode === "permanent") save({ ...data, drawings: currentDrawings }); - else setTemporaryDrawings(currentDrawings); + else { + temporaryDrawingsDirtyRef.current = true; + setTemporaryDrawings(currentDrawings); + } } })} aria-label={textContent.previewAriaLabel || "Agrandir l'image annotée"} diff --git a/website/src/features/toolboxes/modules/NotepadModule.jsx b/website/src/features/toolboxes/modules/NotepadModule.jsx index f61cf15..aee9c5e 100644 --- a/website/src/features/toolboxes/modules/NotepadModule.jsx +++ b/website/src/features/toolboxes/modules/NotepadModule.jsx @@ -23,12 +23,15 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen } const storedData = context.getModuleData(toolboxId, moduleId, { text: "" }); const normalizedData = useMemo(() => context.normalizeNotepadData(storedData), [context, storedData]); const textContent = context.moduleText?.notepad || {}; + const notify = context.notify; const activePage = normalizedData.pages.find((page) => page.id === normalizedData.activePageId) || normalizedData.pages[0]; const editorRef = useRef(null); const surfaceRef = useRef(null); const selectionOffsetRef = useRef(null); const localSaveUpdatedAtRef = useRef(""); const pageKeyRef = useRef(`${toolboxId}:${moduleId}:${activePage.id}`); + const temporaryStrokesDirtyRef = useRef(false); + const sessionStorageFailureRef = useRef(""); const [html, setHtml] = useState(activePage.html); const [text, setText] = useState(activePage.text); const [updatedAt, setUpdatedAt] = useState(activePage.updatedAt); @@ -113,6 +116,8 @@ 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 : []); @@ -123,12 +128,19 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen } useEffect(() => { if (drawingMode !== "temporary") return; + if (!temporaryStrokesDirtyRef.current) return; + const serializedStrokes = JSON.stringify(temporaryStrokes); try { - sessionStorage.setItem(sessionKey, JSON.stringify(temporaryStrokes)); + sessionStorage.setItem(sessionKey, serializedStrokes); + temporaryStrokesDirtyRef.current = false; + sessionStorageFailureRef.current = ""; } catch { - context.notify?.(textContent.sessionStorageError || "Les annotations temporaires n'ont pas pu être sauvegardées."); + const failureKey = `${sessionKey}:${serializedStrokes}`; + if (sessionStorageFailureRef.current === failureKey) return; + sessionStorageFailureRef.current = failureKey; + notify?.(textContent.sessionStorageError || "Les annotations temporaires n'ont pas pu être sauvegardées."); } - }, [context, drawingMode, sessionKey, temporaryStrokes, textContent.sessionStorageError]); + }, [drawingMode, notify, sessionKey, temporaryStrokes, textContent.sessionStorageError]); const activeStrokes = drawingMode === "permanent" ? activePage.drawings.strokes : temporaryStrokes; const formattedUpdatedAt = formatUpdatedAt(updatedAt); @@ -160,7 +172,10 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen } function saveDrawingMode(nextMode, nextStrokes) { setDrawingMode(nextMode); - if (nextMode === "temporary") setTemporaryStrokes(nextStrokes); + if (nextMode === "temporary") { + temporaryStrokesDirtyRef.current = true; + setTemporaryStrokes(nextStrokes); + } updateActivePage((page) => ({ ...page, html, @@ -182,6 +197,7 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen } drawings: { strokes: nextStrokes } })); } else { + temporaryStrokesDirtyRef.current = true; setTemporaryStrokes(nextStrokes); } }