diff --git a/inventory/routes/testing.py b/inventory/routes/testing.py
index 2510f71..2823c82 100644
--- a/inventory/routes/testing.py
+++ b/inventory/routes/testing.py
@@ -7,6 +7,6 @@ bp_testing = Blueprint("testing", __name__)
def init_testing_routes(app):
@bp_testing.get('/testing')
def test_page():
- return render_template('testing.html')
+ return render_template('testing.html', grid_size=25)
app.register_blueprint(bp_testing)
\ No newline at end of file
diff --git a/inventory/templates/testing.html b/inventory/templates/testing.html
index 3b7ae8b..269eb16 100644
--- a/inventory/templates/testing.html
+++ b/inventory/templates/testing.html
@@ -1,21 +1,241 @@
{% extends 'base.html' %}
+{% set dot_size = [grid_size * 1.25, 32]|max|int %}
{% block style %}
-#outer {
- border-style: dashed !important;
- display: grid;
- height: 85vh;
+:root {
+ --grid: {{ grid_size }}px;
}
-.cell {
- border-style: dashed !important;
+#grid {
+ background-image:
+ linear-gradient(to right, #ccc 1px, transparent 1px),
+ linear-gradient(to bottom, #ccc 1px, transparent 1px);
+ background-size: var(--grid) var(--grid);
+ cursor: none;
+ height: calc(round(nearest, 80vh, var(--grid)) + 1px);
+ width: calc(round(nearest, 100%, var(--grid)) + 1px);
+}
+
+#toolBar {
+ top: 10px;
+ transform: translateX(-50%);
+ z-index: 10000;
+}
+
+#coords {
+ bottom: 10px;
+ pointer-events: none;
+ left: 10px;
+}
+
+#overlay {
+ z-index: 9999;
+ pointer-events: none;
+ inset: 0;
}
{% endblock %}
{% block main %}
-
+
{% endblock %}
{% block script %}
-{% endblock %}
+ const canvasEl = document.getElementById('overlay');
+ const coordsEl = document.getElementById('coords');
+ const dotEl = document.getElementById('dot');
+ const dotSVGEl = document.getElementById('dotSVG');
+ const gridEl = document.getElementById('grid');
+
+ let ctx;
+ let selectedColor = '#000000';
+
+ let currentRect = null;
+ let rects = [];
+
+ resizeAndSetupCanvas();
+ window.addEventListener('resize', resizeAndSetupCanvas);
+
+ function snapToGrid(x, y) {
+ const rect = canvasEl.getBoundingClientRect();
+ const clampedX = Math.min(Math.max(x, rect.left), rect.right);
+ const clampedY = Math.min(Math.max(y, rect.top), rect.bottom);
+
+ const localX = clampedX - rect.left;
+ const localY = clampedY - rect.top;
+
+ const ix = Math.round(localX / {{ grid_size }});
+ const iy = Math.round(localY / {{ grid_size }});
+ return {
+ ix,
+ iy,
+ x: ix * {{ grid_size }},
+ y: iy * {{ grid_size }}
+ };
+ }
+
+ function normalizeRect(rect) {
+ return {
+ x: Math.min(rect.x1, rect.x2),
+ y: Math.min(rect.y1, rect.y2),
+ w: Math.abs(rect.x2 - rect.x1),
+ h: Math.abs(rect.y2 - rect.y1),
+ color: rect.color
+ };
+ }
+
+ function resizeAndSetupCanvas() {
+ const dpr = window.devicePixelRatio || 1;
+ const rect = canvasEl.getBoundingClientRect();
+
+ canvasEl.width = rect.width * dpr;
+ canvasEl.height = rect.height * dpr;
+
+ ctx = canvasEl.getContext('2d');
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+
+ redrawAll();
+ }
+
+ function redrawAll() {
+ if (!ctx) return;
+
+ clearCanvas();
+ rects.forEach(drawRect);
+ }
+
+ function drawRect(rect) {
+ ctx.strokeStyle = rect.color;
+ ctx.strokeRect(rect.x, rect.y, rect.w, rect.h);
+ if (rect.fill) {
+ ctx.globalAlpha = 0.15;
+ ctx.fillStyle = rect.color;
+ ctx.fillRect(rect.x, rect.y, rect.w, rect.h);
+ ctx.globalAlpha = 1;
+ }
+ }
+
+ function clearCanvas() {
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
+ ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
+ const dpr = window.devicePixelRatio || 1;
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+ }
+
+ window.setColor = function setColor() {
+ selectedColor = document.getElementById('color').value;
+ }
+
+ document.addEventListener('keydown', (e) => {
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'z') {
+ rects.pop();
+ redrawAll();
+ }
+ });
+
+ gridEl.addEventListener('mousemove', (e) => {
+ const { ix, iy, x: snapX, y: snapY } = snapToGrid(e.clientX, e.clientY);
+
+ coordsEl.innerText = `(${ix}, ${iy})`;
+
+ const renderX = snapX - {{ dot_size }} / 2;
+ const renderY = snapY - {{ dot_size }} / 2;
+
+ coordsEl.classList.remove('d-none');
+
+ dotEl.classList.remove('d-none');
+ dotEl.style.top = `${renderY}px`;
+ dotEl.style.left = `${renderX}px`;
+
+ if (currentRect) {
+ const previewRect = normalizeRect({
+ ...currentRect,
+ x2: snapX,
+ y2: snapY
+ });
+
+ clearCanvas();
+ rects.forEach(drawRect);
+ ctx.setLineDash([5, 3]);
+ drawRect(previewRect);
+ ctx.setLineDash([]);
+ }
+ });
+
+ gridEl.addEventListener('mouseleave', (e) => {
+ coordsEl.classList.add('d-none');
+ dotEl.classList.add('d-none');
+ });
+
+ gridEl.addEventListener('mousedown', (e) => {
+ if (e.button !== 0) return;
+
+ e.preventDefault();
+
+ if (e.target.closest('#toolBar')) return;
+
+ const {ix, iy, x: snapX, y: snapY} = snapToGrid(e.clientX, e.clientY);
+
+ currentRect = {
+ x1: snapX,
+ y1: snapY,
+ x2: snapX,
+ y2: snapY,
+ color: selectedColor
+ };
+ });
+
+ gridEl.addEventListener('mouseup', (e) => {
+ if (!currentRect) return;
+
+ const {ix, iy, x: snapX, y: snapY } = snapToGrid(e.clientX, e.clientY);
+
+ currentRect.x2 = snapX;
+ currentRect.y2 = snapY;
+
+ const finalRect = normalizeRect(currentRect);
+
+ if (finalRect.w > 0 && finalRect.h > 0) {
+ rects.push(finalRect);
+ }
+ clearCanvas();
+ rects.forEach(drawRect);
+
+ currentRect = null;
+ });
+{% endblock %}
\ No newline at end of file