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);