From 4c98de2eef5f741d7c4b7c63ea25f0995e997b7f Mon Sep 17 00:00:00 2001 From: Yaro Kasear Date: Fri, 19 Dec 2025 10:52:47 -0600 Subject: [PATCH] Trying to make UX improvements. --- inventory/static/css/components/draw.css | 31 ++++++++++++++++++++ inventory/static/js/components/draw.js | 37 +++++++++++++++++++++--- inventory/templates/components/draw.html | 37 ++++++++++++++++-------- 3 files changed, 89 insertions(+), 16 deletions(-) diff --git a/inventory/static/css/components/draw.css b/inventory/static/css/components/draw.css index 36d6bc0..90a32be 100644 --- a/inventory/static/css/components/draw.css +++ b/inventory/static/css/components/draw.css @@ -2,6 +2,7 @@ width: 100%; height: 80vh; position: relative; + overflow: hidden; } .grid-widget [data-grid] { @@ -30,6 +31,8 @@ } .grid-widget [data-toolbar] { + width: min(100%, var(--grid-maxw, 100%)); + box-sizing: border-box; margin: 0 auto; } @@ -37,4 +40,32 @@ transform: translate(-50%, -50%); z-index: 10000; pointer-events: none; +} + +.grid-widget .toolbar { + display: flex; + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + gap: .5rem; + align-items: center; +} + +.grid-widget .toolbar-range { + width: 120px; + margin: 0; + flex: 0 0 120px; +} + +.grid-widget [data-toolbar] .badge, +.grid-widget [data-toolbar] .input-group-text { + white-space: nowrap; +} + +.grid-widget [data-toolbar]>* { + align-self: center; +} + +.grid-widget [data-toolbar] .vr { + align-self: stretch; } \ No newline at end of file diff --git a/inventory/static/js/components/draw.js b/inventory/static/js/components/draw.js index b968cc7..68b266f 100644 --- a/inventory/static/js/components/draw.js +++ b/inventory/static/js/components/draw.js @@ -13,6 +13,7 @@ document.addEventListener('keydown', (e) => { function initGridWidget(root, opts = {}) { const DEFAULT_DOC = { version: 1, cellSize: 25, shapes: [] }; + const MAX_HISTORY = 100; const canvasEl = root.querySelector('[data-canvas]'); const clearEl = root.querySelector('[data-clear]'); @@ -30,6 +31,15 @@ function initGridWidget(root, opts = {}) { const fillOpacityEl = root.querySelector('[data-fill-opacity]'); const strokeOpacityEl = root.querySelector('[data-stroke-opacity]'); const strokeWidthEl = root.querySelector('[data-stroke-width]'); + const cellSizeValEl = root.querySelector('[data-cell-size-val]'); + const fillValEl = root.querySelector('[data-fill-opacity-val]'); + const strokeValEl = root.querySelector('[data-stroke-opacity-val]'); + const widthValEl = root.querySelector('[data-stroke-width-val]'); + + if (cellSizeEl && cellSizeValEl) bindRangeWithLabel(cellSizeEl, cellSizeValEl, v => `${v}px`); + if (fillOpacityEl && fillValEl) bindRangeWithLabel(fillOpacityEl, fillValEl, v => `${Number(v) * 100}%`); + if (strokeOpacityEl && strokeValEl) bindRangeWithLabel(strokeOpacityEl, strokeValEl, v => `${Number(v) * 100}%`); + if (strokeWidthEl && widthValEl) bindRangeWithLabel(strokeWidthEl, widthValEl, v => `${Number(v) * 100}%`); const storageKey = opts.storageKey ?? 'gridDoc'; @@ -112,6 +122,13 @@ function initGridWidget(root, opts = {}) { activeGridWidget = api; }, { capture: true }); + function bindRangeWithLabel(inputEl, labelEl, format = (v) => v) { + const sync = () => { labelEl.textContent = format(inputEl.value); }; + inputEl.addEventListener('input', sync); + inputEl.addEventListener('change', sync); + sync(); + } + function finishPointer(e) { if (!currentShape) return; if (activePointerId !== null && e.pointerId !== activePointerId) return; @@ -170,8 +187,17 @@ function initGridWidget(root, opts = {}) { function commit(nextShapes) { history.splice(historyIndex + 1); + history.push(structuredClone(nextShapes)); historyIndex++; + + if (history.length > MAX_HISTORY) { + const overflow = history.length - MAX_HISTORY; + history.splice(0, overflow); + historyIndex -= overflow; + if (historyIndex < 0) historyIndex = 0; + } + shapes = nextShapes; saveDoc({ ...doc, shapes, cellSize }); @@ -224,7 +250,7 @@ function initGridWidget(root, opts = {}) { type: 'path', points, color, - width: normStroke(s.width, 0.12), + strokeWidth: normStroke(s.strokeWidth, 0.12), strokeOpacity }]; } @@ -274,7 +300,7 @@ function initGridWidget(root, opts = {}) { gridEl.style.width = `${snappedW}px`; gridEl.style.height = `${snappedH}px`; - toolBarEl.style.width = `${snappedW}px`; + toolBarEl.style.maxWidth = `${snappedW}px`; gridEl.getBoundingClientRect(); resizeAndSetupCanvas(); @@ -343,6 +369,9 @@ function initGridWidget(root, opts = {}) { const maxIx = Math.floor(rect.width / grid); const maxIy = Math.floor(rect.height / grid); + // Math.round means we actually snap to the nearest dot. Math.floor + // makes for ugly snapping behavior where it only goes to the top left + // of the cell. Cells are not where we draw to, so floor is not good. const ix = Math.min(Math.max(Math.round(localX / grid), 0), maxIx); const iy = Math.min(Math.max(Math.round(localY / grid), 0), maxIy); @@ -496,7 +525,7 @@ function initGridWidget(root, opts = {}) { ctx.globalAlpha = clamp01(shape.strokeOpacity, 1); - ctx.lineWidth = Math.max(1, toPx(shape.width ?? 0.12)); + ctx.lineWidth = Math.max(1, toPx(shape.strokeWidth ?? 0.12)); ctx.lineJoin = 'round'; ctx.lineCap = 'round'; @@ -869,7 +898,7 @@ function initGridWidget(root, opts = {}) { type: 'path', points: [p], color: selectedColor, - width: currentStrokeWidth, + strokeWidth: currentStrokeWidth, strokeOpacity: currentStrokeOpacity }; } diff --git a/inventory/templates/components/draw.html b/inventory/templates/components/draw.html index 8b7af44..f38ff8d 100644 --- a/inventory/templates/components/draw.html +++ b/inventory/templates/components/draw.html @@ -3,21 +3,34 @@
-
- Cell Size: - +
+
+ Spacing + + 25px +
- Fill Opacity: - +
+ Fill + + 100% +
- Stroke Opacity: - +
+ Stroke + + 100% +
- Stroke Width: - +
+ Width + + 12% +