diff --git a/inventory/templates/testing.html b/inventory/templates/testing.html
index b4c9076..1a23a81 100644
--- a/inventory/templates/testing.html
+++ b/inventory/templates/testing.html
@@ -44,11 +44,11 @@
- Grid Size:
-
+ Cell Size:
+
Fill Opacity:
-
@@ -187,7 +187,7 @@
{% endblock %}
{% block script %}
-const DEFAULT_DOC = { version: 1, gridSize: 25, shapes: [] };
+const DEFAULT_DOC = { version: 1, cellSize: 25, shapes: [] };
const canvasEl = document.getElementById('overlay');
const clearEl = document.getElementById('clear');
@@ -199,18 +199,20 @@ const exportEl = document.getElementById('export');
const gridEl = document.getElementById('grid');
const importButtonEl = document.getElementById('importButton');
const importEl = document.getElementById('import');
-const gridSizeEl = document.getElementById('gridSize');
+const cellSizeEl = document.getElementById('cellSize');
const gridWrapEl = document.getElementById('gridWrap');
const toolBarEl = document.getElementById('toolBar');
+const fillOpacityEl = document.getElementById('fillOpacity');
let doc = loadDoc();
-let gridSize = Number(doc.gridSize) || 25;
-gridSizeEl.value = gridSize;
-let dotSize = Math.floor(Math.max(gridSize * 1.25, 32));
+let cellSize = Number(doc.cellSize) || 25;
+cellSizeEl.value = cellSize;
+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 currentShape = null;
let shapes = Array.isArray(doc.shapes) ? doc.shapes : [];
@@ -218,7 +220,7 @@ let shapes = Array.isArray(doc.shapes) ? doc.shapes : [];
let sizingRAF = 0;
let lastApplied = { w: 0, h: 0 };
-const ro = new ResizeObserver(scheduleSnappedGridSize);
+const ro = new ResizeObserver(scheduleSnappedcellSize);
ro.observe(gridWrapEl);
const savedTool = localStorage.getItem('gridTool');
@@ -235,7 +237,12 @@ resizeAndSetupCanvas();
window.addEventListener('resize', resizeAndSetupCanvas);
setGrid();
-scheduleSnappedGridSize();
+scheduleSnappedcellSize();
+
+function clamp01(n, fallback = 0.15) {
+ const x = Number(n);
+ return Number.isFinite(x) ? Math.min(1, Math.max(0, x)) : fallback;
+}
function isFiniteNum(n) { return Number.isFinite(Number(n)); }
@@ -248,6 +255,9 @@ function sanitizeShapes(list) {
if (!s.color) s.color = '#000000';
+ 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 {
@@ -270,10 +280,10 @@ function snapDown(n, step) {
return Math.floor(n / step) * step;
}
-function applySnappedGridSize() {
+function applySnappedcellSize() {
sizingRAF = 0;
- const grid = gridSize;
+ const grid = cellSize;
if (!Number.isFinite(grid) || grid < 1) return;
const w = gridWrapEl.clientWidth;
@@ -294,29 +304,29 @@ function applySnappedGridSize() {
resizeAndSetupCanvas();
}
-function scheduleSnappedGridSize() {
+function scheduleSnappedcellSize() {
if (sizingRAF) return;
- sizingRAF = requestAnimationFrame(applySnappedGridSize);
+ sizingRAF = requestAnimationFrame(applySnappedcellSize);
}
-function applyGridSize(newSize) {
+function applycellSize(newSize) {
const n = Number(newSize);
if (!Number.isFinite(n) || n < 1) return;
- gridSize = n;
- saveDoc({ ...doc, shapes, gridSize });
+ cellSize = n;
+ saveDoc({ ...doc, shapes, cellSize });
- dotSize = Math.floor(Math.max(gridSize * 1.25, 32));
+ dotSize = Math.floor(Math.max(cellSize * 1.25, 32));
dotSVGEl.setAttribute('width', dotSize);
dotSVGEl.setAttribute('height', dotSize);
setGrid();
- scheduleSnappedGridSize();
+ scheduleSnappedcellSize();
}
function pxToGrid(v) {
- return v / gridSize;
+ return v / cellSize;
}
function getActiveTool() {
@@ -348,7 +358,7 @@ function setActiveType(typeId) {
function snapToGrid(x, y) {
/*
Shapes are stored in grid units (document units), not pixels.
- 1 unit renders as gridSize pixels, so changing gridSize rescales (zooms) the whole drawing.
+ 1 unit renders as cellSize pixels, so changing cellSize rescales (zooms) the whole drawing.
Grid modes only affect snapping/visuals; storage is always in document units for portability.
*/
@@ -359,7 +369,7 @@ function snapToGrid(x, y) {
const localX = clampedX - rect.left;
const localY = clampedY - rect.top;
- const grid = gridSize;
+ const grid = cellSize;
const maxIx = Math.floor(rect.width / grid);
const maxIy = Math.floor(rect.height / grid);
@@ -402,7 +412,8 @@ function normalizeRect(shape) {
w: Math.abs(x2 - x1),
h: Math.abs(y2 - y1),
color: shape.color,
- fill: shape.fill
+ fill: shape.fill,
+ opacity: clamp01(shape.opacity, 0.15)
};
}
@@ -456,7 +467,7 @@ function redrawAll() {
function drawShape(shape) {
if (!ctx) return;
- const toPx = (v) => v * gridSize;
+ const toPx = (v) => v * cellSize;
ctx.save();
ctx.strokeStyle = shape.color || '#000000';
@@ -478,7 +489,7 @@ function drawShape(shape) {
}
if (shape.fill) {
- ctx.globalAlpha = 0.15;
+ ctx.globalAlpha = clamp01(shape.opacity, 0.15);
ctx.fillStyle = shape.color;
if (shape.type === 'rect') {
ctx.fillRect(x, y, w, h);
@@ -524,13 +535,13 @@ function setGrid() {
gridEl.style.backgroundImage =
"linear-gradient(to right, #ccc 1px, transparent 1px)," +
"linear-gradient(to bottom, #ccc 1px, transparent 1px)";
- gridEl.style.backgroundSize = `${gridSize}px ${gridSize}px`;
+ gridEl.style.backgroundSize = `${cellSize}px ${cellSize}px`;
gridEl.style.boxShadow = "inset 0 0 0 1px #ccc"; // full frame
} else if (type === 'horizontalGrid') {
gridEl.style.backgroundImage =
"linear-gradient(to bottom, #ccc 1px, transparent 1px)";
- gridEl.style.backgroundSize = `100% ${gridSize}px`;
+ gridEl.style.backgroundSize = `100% ${cellSize}px`;
// left + right borders only
gridEl.style.boxShadow =
@@ -539,7 +550,7 @@ function setGrid() {
} else if (type === 'verticalGrid') {
gridEl.style.backgroundImage =
"linear-gradient(to right, #ccc 1px, transparent 1px)";
- gridEl.style.backgroundSize = `${gridSize}px 100%`;
+ gridEl.style.backgroundSize = `${cellSize}px 100%`;
// top + bottom borders only
gridEl.style.boxShadow =
@@ -570,8 +581,8 @@ document.querySelectorAll('input[name="gridType"]').forEach(input => {
});
});
-gridSizeEl.addEventListener('input', () => applyGridSize(gridSizeEl.value));
-gridSizeEl.addEventListener('change', () => applyGridSize(gridSizeEl.value));
+cellSizeEl.addEventListener('input', () => applycellSize(cellSizeEl.value));
+cellSizeEl.addEventListener('change', () => applycellSize(cellSizeEl.value));
importButtonEl.addEventListener('click', () => importEl.click());
@@ -584,9 +595,9 @@ importEl.addEventListener('change', (e) => {
try {
const data = JSON.parse(reader.result);
- if (Number.isFinite(Number(data.gridSize)) && Number(data.gridSize) >= 1) {
- gridSizeEl.value = data.gridSize;
- applyGridSize(data.gridSize);
+ if (Number.isFinite(Number(data.cellSize)) && Number(data.cellSize) >= 1) {
+ cellSizeEl.value = data.cellSize;
+ applycellSize(data.cellSize);
}
const loadedShapes = Array.isArray(data) ? data : data.shapes;
@@ -596,7 +607,7 @@ importEl.addEventListener('change', (e) => {
doc = {
version: Number(data?.version) || 1,
- gridSize: Number(data?.gridSize) || gridSize,
+ cellSize: Number(data?.cellSize) || cellSize,
shapes
};
@@ -612,7 +623,7 @@ importEl.addEventListener('change', (e) => {
exportEl.addEventListener('click', () => {
const payload = {
version: 1,
- gridSize: gridSize,
+ cellSize: cellSize,
shapes
};
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
@@ -625,11 +636,11 @@ exportEl.addEventListener('click', () => {
});
clearEl.addEventListener('click', () => {
- gridSize = 25;
+ cellSize = 25;
shapes = [];
- saveDoc({...doc, shapes, gridSize});
- gridSizeEl.value = 25;
- applyGridSize(25);
+ saveDoc({...doc, shapes, cellSize});
+ cellSizeEl.value = 25;
+ applycellSize(25);
redrawAll();
});
@@ -648,7 +659,7 @@ document.addEventListener('keydown', (e) => {
e.preventDefault();
if (shapes.length > 0) {
shapes.pop();
- saveDoc({ ...doc, shapes, gridSize });
+ saveDoc({ ...doc, shapes, cellSize });
redrawAll();
}
}
@@ -659,6 +670,14 @@ document.addEventListener('keydown', (e) => {
}
});
+fillOpacityEl?.addEventListener('input', () => {
+ currentOpacity = clamp01(fillOpacityEl.value, 0.15);
+});
+
+fillOpacityEl?.addEventListener('change', () => {
+ currentOpacity = clamp01(fillOpacityEl.value, 0.15);
+});
+
gridEl.addEventListener('pointercancel', () => {
currentShape = null;
redrawAll();
@@ -766,7 +785,8 @@ gridEl.addEventListener('pointerdown', (e) => {
x2: snapX,
y2: snapY,
color: selectedColor,
- fill: (tool === 'filled' || tool === 'filledEllipse')
+ fill: (tool === 'filled'),
+ opacity: currentOpacity
};
} else if (tool === 'outlineEllipse' || tool === 'filledEllipse') {
currentShape = {
@@ -776,7 +796,8 @@ gridEl.addEventListener('pointerdown', (e) => {
x2: snapX,
y2: snapY,
color: selectedColor,
- fill: (tool === 'filledEllipse')
+ fill: (tool === 'filledEllipse'),
+ opacity: currentOpacity
};
}
});
@@ -814,7 +835,7 @@ window.addEventListener('pointerup', (e) => {
if (finalShape) {
shapes.push(finalShape);
- saveDoc({...doc, shapes, gridSize});
+ saveDoc({...doc, shapes, cellSize});
}
clearCanvas();