diff --git a/inventory/templates/testing.html b/inventory/templates/testing.html index 1a23a81..5272505 100644 --- a/inventory/templates/testing.html +++ b/inventory/templates/testing.html @@ -205,6 +205,9 @@ const toolBarEl = document.getElementById('toolBar'); const fillOpacityEl = document.getElementById('fillOpacity'); let doc = loadDoc(); +let shapes = sanitizeShapes(Array.isArray(doc.shapes) ? doc.shapes : []); +saveDoc({ ...doc, shapes }); + let cellSize = Number(doc.cellSize) || 25; cellSizeEl.value = cellSize; let dotSize = Math.floor(Math.max(cellSize * 1.25, 32)); @@ -212,10 +215,11 @@ let dotSize = Math.floor(Math.max(cellSize * 1.25, 32)); let ctx; let dpr = 1; let selectedColor; -let currentOpacity = clamp01(fillOpacity?.value ?? 0.15, 0.15); +let currentOpacity = clamp01(fillOpacityEl?.value ?? 0.15, 0.15); let currentShape = null; -let shapes = Array.isArray(doc.shapes) ? doc.shapes : []; +const history = [structuredClone(shapes)]; +let historyIndex = 0 let sizingRAF = 0; let lastApplied = { w: 0, h: 0 }; @@ -234,11 +238,36 @@ if (savedType) { } resizeAndSetupCanvas(); -window.addEventListener('resize', resizeAndSetupCanvas); setGrid(); scheduleSnappedcellSize(); +function undo() { + if (historyIndex <= 0) return; + historyIndex--; + shapes = structuredClone(history[historyIndex]); + saveDoc({ ...doc, shapes, cellSize }); + redrawAll(); +} + +function redo() { + if (historyIndex >= history.length - 1) return; + historyIndex++; + shapes = structuredClone(history[historyIndex]); + saveDoc({ ...doc, shapes, cellSize }); + redrawAll(); +} + +function commit(nextShapes) { + history.splice(historyIndex + 1); + history.push(structuredClone(nextShapes)); + historyIndex++; + shapes = nextShapes; + + saveDoc({ ...doc, shapes, cellSize }); + redrawAll(); +} + function clamp01(n, fallback = 0.15) { const x = Number(n); return Number.isFinite(x) ? Math.min(1, Math.max(0, x)) : fallback; @@ -247,23 +276,27 @@ function clamp01(n, fallback = 0.15) { function isFiniteNum(n) { return Number.isFinite(Number(n)); } function sanitizeShapes(list) { - const allowed = ['rect', 'ellipse', 'line']; + const allowed = new Set(['rect','ellipse','line']); - return list.filter(s => { - if (!s || typeof s !== 'object') return false; - if (!allowed.includes(s.type)) return false; + return list.flatMap((s) => { + if (!s || typeof s !== 'object' || !allowed.has(s.type)) return []; + const color = typeof s.color === 'string' ? s.color : '#000000'; + const opacity = clamp01(s.opacity, 0.15); - if (!s.color) s.color = '#000000'; + if (s.type === 'line') { + if (!['x1','y1','x2','y2'].every(k => isFiniteNum(s[k]))) return []; + return [{ type:'line', x1:+s.x1, y1:+s.y1, x2:+s.x2, y2:+s.y2, color }]; + } - if (s.opacity == null) s.opacity = 0.15; - s.opacity = clamp01(s.opacity, 0.15); - - if (s.type === 'line') { - return ['x1','y1','x2','y2'].every(k => isFiniteNum(s[k])); - } else { - return ['x','y','w','h'].every(k => isFiniteNum(s[k])); - } - }); + if (!['x','y','w','h'].every(k => isFiniteNum(s[k]))) return []; + return [{ + type: s.type, + x:+s.x, y:+s.y, w:+s.w, h:+s.h, + color, + fill: !!s.fill, + opacity + }]; + }); } function loadDoc() { @@ -382,11 +415,11 @@ function snapToGrid(x, y) { let snapY = localY; if (type === 'fullGrid' || type === 'verticalGrid') { - snapX = ix * grid; + snapX = Math.min(ix * grid, rect.width); } if (type === 'fullGrid' || type === 'horizontalGrid') { - snapY = iy * grid; + snapY = Math.min(iy * grid, rect.height); } return { @@ -637,11 +670,9 @@ exportEl.addEventListener('click', () => { clearEl.addEventListener('click', () => { cellSize = 25; - shapes = []; - saveDoc({...doc, shapes, cellSize}); cellSizeEl.value = 25; applycellSize(25); - redrawAll(); + commit([]); }); colorEl.addEventListener('input', () => { @@ -657,11 +688,11 @@ document.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && key === 'z') { e.preventDefault(); - if (shapes.length > 0) { - shapes.pop(); - saveDoc({ ...doc, shapes, cellSize }); - redrawAll(); - } + undo(); + } + + if ((e.ctrlKey || e.metaKey) && (key === 'y' || (e.shiftKey && key === 'z'))) { + e.preventDefault(); redo(); } if (key === 'escape' && currentShape) { @@ -833,10 +864,7 @@ window.addEventListener('pointerup', (e) => { if (ellipse.w > 0 && ellipse.h > 0) finalShape = ellipse; } - if (finalShape) { - shapes.push(finalShape); - saveDoc({...doc, shapes, cellSize}); - } + if (finalShape) commit([...shapes, finalShape]); clearCanvas(); shapes.forEach(drawShape);