From f1fde867fe2b1c863fac6042e19786878ee16e8a Mon Sep 17 00:00:00 2001 From: miteruzo Date: Sun, 10 Aug 2025 02:39:17 +0900 Subject: [PATCH] #3 --- frontend/src/App.tsx | 2 +- .../src/components/threads/ThreadCanvas.tsx | 109 ++++++++++++------ 2 files changed, 76 insertions(+), 35 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5c77f1c..1469f65 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -78,6 +78,7 @@ export default () => { return ( +
@@ -105,7 +106,6 @@ export default () => {
- } /> } /> } /> diff --git a/frontend/src/components/threads/ThreadCanvas.tsx b/frontend/src/components/threads/ThreadCanvas.tsx index 652d603..9ed2090 100644 --- a/frontend/src/components/threads/ThreadCanvas.tsx +++ b/frontend/src/components/threads/ThreadCanvas.tsx @@ -29,6 +29,27 @@ const Mode = { type Mode = (typeof Mode)[keyof typeof Mode] +const isLayer = (obj: unknown): obj is Layer => ( + typeof obj === 'object' + && obj !== null + && typeof (obj as Layer).id === 'string' + && typeof (obj as Layer).name === 'string' + && Array.isArray ((obj as Layer).lines) + && (obj as Layer).lines.every (isLine) + && Array.isArray ((obj as Layer).history) + && Array.isArray ((obj as Layer).future)) + + +const isLine = (obj: unknown): obj is Line => ( + typeof obj === 'object' + && obj !== null + && Array.isArray ((obj as Line).points) + && (obj as Line).points.every (n => typeof n === 'number') + && typeof (obj as Line).stroke === 'string' + && typeof (obj as Line).strokeWidth === 'number' + && Object.values (Mode).includes ((obj as Line).mode)) + + export default () => { const drawingRef = useRef (false) const fileInputRef = useRef (null) @@ -84,19 +105,21 @@ export default () => { const handleUndo = () => { if (!(activeLayer) || activeLayer.lines.length === 0) return - const newLines = [...activeLayer.lines] - newLines.pop() + + const prev = activeLayer.lines.slice (0, -1) + const popped = activeLayer.lines[activeLayer.lines.length - 1] updateActiveLayerHistory ([...activeLayer.history, activeLayer.lines]) - updateActiveLayerFuture ([]) - updateActiveLayerLines (newLines) + updateActiveLayerFuture ([popped, ...activeLayer.future]) + updateActiveLayerLines (prev) } const handleRedo = () => { - if (!(activeLayer) || activeLayer.history.length === 0) + if (!(activeLayer) || activeLayer.future.length === 0) return - const prev = activeLayer.history[activeLayer.history.length - 1] - updateActiveLayerLines (prev) - updateActiveLayerHistory (activeLayer.history.slice (0, -1)) + + const [redoStroke, ...rest] = activeLayer.future + updateActiveLayerLines ([...activeLayer.lines, redoStroke]) + updateActiveLayerFuture (rest) } const handleImportImage: ChangeEventHandler = ev => { @@ -152,19 +175,25 @@ export default () => { return layer } + const canMoveLayer = (direction: number) => { + const idx = layers.findIndex (l => l.id === activeLayerId) + if (idx < 0) + return false + + const target = idx + direction + return 0 <= target && target < layers.length + } + const moveLayer = (direction: number) => { - setLayers(prev => { - const idx = prev.findIndex (l => l.id === activeLayerId) - if (idx < 0) - return prev - const target = idx + direction - if (target < 0 || target >= prev.length) - return prev - const next = [...prev] - const [item] = next.splice(idx, 1) - next.splice(target, 0, item) - return next - }) + if (!(canMoveLayer (direction))) + return + + const idx = layers.findIndex (l => l.id === activeLayerId) + const target = idx + direction + const next = [...layers] + const [item] = next.splice (idx, 1) + next.splice (target, 0, item) + setLayers(next) } const clearCanvas = () => { @@ -183,16 +212,25 @@ export default () => { useEffect (() => { try { - const paintJSON = localStorage.getItem ('paint') - if (paintJSON == null) + const raw = localStorage.getItem ('paint') + if (!(raw)) throw new Error - const paint = JSON.parse (paintJSON) - if (!(paint instanceof Array)) + const parsed: unknown = JSON.parse (raw) + if (!(Array.isArray (parsed))) throw new Error - setLayers (paint) - setActiveLayerId (paint[0].id) + const restored: Layer[] = parsed.filter (isLayer).map (l => ({ + ...l, lines: l.lines.map (s => ({ + ...s, mode: (Object.values (Mode).includes (s.mode) + ? s.mode + : Mode.Pen) })) })) + if (restored.length === 0) + throw new Error + + setLayers (restored) + setActiveLayerId (restored[0].id) + setLayerCnt (restored.length) } catch { @@ -207,6 +245,7 @@ export default () => { if (layer.image && !(layer.id in images)) { const image = new window.Image + image.crossOrigin = 'anonymous' image.src = layer.image.src image.onload = () => { setImages (prev => ({ ...prev, [layer.id]: image })) @@ -256,7 +295,7 @@ export default () => {
- -
@@ -331,8 +372,8 @@ export default () => { onChange={() => setActiveLayerId (layer.id)} /> {layer.name} ))} - - -