Trying to make UX improvements.

This commit is contained in:
Yaro Kasear 2025-12-19 10:52:47 -06:00
parent 03804cc476
commit 4c98de2eef
3 changed files with 89 additions and 16 deletions

View file

@ -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;
}

View file

@ -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
};
}

View file

@ -3,21 +3,34 @@
<div class="grid-widget" data-grid-widget>
<div data-toolbar
class="btn-toolbar bg-light border border-bottom-0 rounded-bottom-0 border-secondary-subtle rounded p-1 align-items-center flex-nowrap overflow-auto toolbar">
<div class="input-group input-group-sm w-auto flex-nowrap">
<span class="input-group-text">Cell Size:</span>
<input type="number" min="1" value="25" class="form-control form-control-sm" data-cell-size>
<div class="d-flex flex-wrap align-items-center gap-2">
<div class="d-flex align-items-center gap-2">
<span class="badge text-bg-light border">Spacing</span>
<input type="range" min="1" max="100" step="1" value="25" data-cell-size
class="form-range toolbar-range">
<span class="badge text-bg-secondary" data-cell-size-val>25px</span>
</div>
<span class="input-group-text">Fill Opacity:</span>
<input type="number" min="0" max="1" step=".01" value="1" name="fillOpacity" data-fill-opacity
class="form-control form-control-sm">
<div class="d-flex align-items-center gap-2">
<span class="badge text-bg-light border">Fill</span>
<input type="range" min="0" max="1" step="0.01" value="1" data-fill-opacity
class="form-range toolbar-range">
<span class="badge text-bg-secondary" data-fill-opacity-val>100%</span>
</div>
<span class="input-group-text">Stroke Opacity:</span>
<input type="number" min="0" max="1" step=".01" value="1" name="strokeOpacity" data-stroke-opacity
class="form-control form-control-sm">
<div class="d-flex align-items-center gap-2">
<span class="badge text-bg-light border">Stroke</span>
<input type="range" min="0" max="1" step="0.01" value="1" data-stroke-opacity
class="form-range toolbar-range">
<span class="badge text-bg-secondary" data-stroke-opacity-val>100%</span>
</div>
<span class="input-group-text">Stroke Width:</span>
<input type="number" min="0" max="1" step=".01" value="0.12" name="strokeWidth" data-stroke-width
class="form-control form-control-sm">
<div class="d-flex align-items-center gap-2">
<span class="badge text-bg-light border">Width</span>
<input type="range" min="0" max="1" step="0.01" value="0.12" data-stroke-width
class="form-range toolbar-range">
<span class="badge text-bg-secondary" data-stroke-width-val>12%</span>
</div>
</div>
<div class="vr mx-1"></div>
<input type="color" class="form-control form-control-sm form-control-color" data-color>