Trying to make UX improvements.
This commit is contained in:
parent
03804cc476
commit
4c98de2eef
3 changed files with 89 additions and 16 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 80vh;
|
height: 80vh;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-widget [data-grid] {
|
.grid-widget [data-grid] {
|
||||||
|
|
@ -30,6 +31,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-widget [data-toolbar] {
|
.grid-widget [data-toolbar] {
|
||||||
|
width: min(100%, var(--grid-maxw, 100%));
|
||||||
|
box-sizing: border-box;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,4 +40,32 @@
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
pointer-events: none;
|
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;
|
||||||
}
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ document.addEventListener('keydown', (e) => {
|
||||||
|
|
||||||
function initGridWidget(root, opts = {}) {
|
function initGridWidget(root, opts = {}) {
|
||||||
const DEFAULT_DOC = { version: 1, cellSize: 25, shapes: [] };
|
const DEFAULT_DOC = { version: 1, cellSize: 25, shapes: [] };
|
||||||
|
const MAX_HISTORY = 100;
|
||||||
|
|
||||||
const canvasEl = root.querySelector('[data-canvas]');
|
const canvasEl = root.querySelector('[data-canvas]');
|
||||||
const clearEl = root.querySelector('[data-clear]');
|
const clearEl = root.querySelector('[data-clear]');
|
||||||
|
|
@ -30,6 +31,15 @@ function initGridWidget(root, opts = {}) {
|
||||||
const fillOpacityEl = root.querySelector('[data-fill-opacity]');
|
const fillOpacityEl = root.querySelector('[data-fill-opacity]');
|
||||||
const strokeOpacityEl = root.querySelector('[data-stroke-opacity]');
|
const strokeOpacityEl = root.querySelector('[data-stroke-opacity]');
|
||||||
const strokeWidthEl = root.querySelector('[data-stroke-width]');
|
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';
|
const storageKey = opts.storageKey ?? 'gridDoc';
|
||||||
|
|
||||||
|
|
@ -112,6 +122,13 @@ function initGridWidget(root, opts = {}) {
|
||||||
activeGridWidget = api;
|
activeGridWidget = api;
|
||||||
}, { capture: true });
|
}, { 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) {
|
function finishPointer(e) {
|
||||||
if (!currentShape) return;
|
if (!currentShape) return;
|
||||||
if (activePointerId !== null && e.pointerId !== activePointerId) return;
|
if (activePointerId !== null && e.pointerId !== activePointerId) return;
|
||||||
|
|
@ -170,8 +187,17 @@ function initGridWidget(root, opts = {}) {
|
||||||
|
|
||||||
function commit(nextShapes) {
|
function commit(nextShapes) {
|
||||||
history.splice(historyIndex + 1);
|
history.splice(historyIndex + 1);
|
||||||
|
|
||||||
history.push(structuredClone(nextShapes));
|
history.push(structuredClone(nextShapes));
|
||||||
historyIndex++;
|
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;
|
shapes = nextShapes;
|
||||||
|
|
||||||
saveDoc({ ...doc, shapes, cellSize });
|
saveDoc({ ...doc, shapes, cellSize });
|
||||||
|
|
@ -224,7 +250,7 @@ function initGridWidget(root, opts = {}) {
|
||||||
type: 'path',
|
type: 'path',
|
||||||
points,
|
points,
|
||||||
color,
|
color,
|
||||||
width: normStroke(s.width, 0.12),
|
strokeWidth: normStroke(s.strokeWidth, 0.12),
|
||||||
strokeOpacity
|
strokeOpacity
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
@ -274,7 +300,7 @@ function initGridWidget(root, opts = {}) {
|
||||||
gridEl.style.width = `${snappedW}px`;
|
gridEl.style.width = `${snappedW}px`;
|
||||||
gridEl.style.height = `${snappedH}px`;
|
gridEl.style.height = `${snappedH}px`;
|
||||||
|
|
||||||
toolBarEl.style.width = `${snappedW}px`;
|
toolBarEl.style.maxWidth = `${snappedW}px`;
|
||||||
|
|
||||||
gridEl.getBoundingClientRect();
|
gridEl.getBoundingClientRect();
|
||||||
resizeAndSetupCanvas();
|
resizeAndSetupCanvas();
|
||||||
|
|
@ -343,6 +369,9 @@ function initGridWidget(root, opts = {}) {
|
||||||
const maxIx = Math.floor(rect.width / grid);
|
const maxIx = Math.floor(rect.width / grid);
|
||||||
const maxIy = Math.floor(rect.height / 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 ix = Math.min(Math.max(Math.round(localX / grid), 0), maxIx);
|
||||||
const iy = Math.min(Math.max(Math.round(localY / grid), 0), maxIy);
|
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.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.lineJoin = 'round';
|
||||||
ctx.lineCap = 'round';
|
ctx.lineCap = 'round';
|
||||||
|
|
||||||
|
|
@ -869,7 +898,7 @@ function initGridWidget(root, opts = {}) {
|
||||||
type: 'path',
|
type: 'path',
|
||||||
points: [p],
|
points: [p],
|
||||||
color: selectedColor,
|
color: selectedColor,
|
||||||
width: currentStrokeWidth,
|
strokeWidth: currentStrokeWidth,
|
||||||
strokeOpacity: currentStrokeOpacity
|
strokeOpacity: currentStrokeOpacity
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,34 @@
|
||||||
<div class="grid-widget" data-grid-widget>
|
<div class="grid-widget" data-grid-widget>
|
||||||
<div data-toolbar
|
<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">
|
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">
|
<div class="d-flex flex-wrap align-items-center gap-2">
|
||||||
<span class="input-group-text">Cell Size:</span>
|
<div class="d-flex align-items-center gap-2">
|
||||||
<input type="number" min="1" value="25" class="form-control form-control-sm" data-cell-size>
|
<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>
|
<div class="d-flex align-items-center gap-2">
|
||||||
<input type="number" min="0" max="1" step=".01" value="1" name="fillOpacity" data-fill-opacity
|
<span class="badge text-bg-light border">Fill</span>
|
||||||
class="form-control form-control-sm">
|
<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>
|
<div class="d-flex align-items-center gap-2">
|
||||||
<input type="number" min="0" max="1" step=".01" value="1" name="strokeOpacity" data-stroke-opacity
|
<span class="badge text-bg-light border">Stroke</span>
|
||||||
class="form-control form-control-sm">
|
<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>
|
<div class="d-flex align-items-center gap-2">
|
||||||
<input type="number" min="0" max="1" step=".01" value="0.12" name="strokeWidth" data-stroke-width
|
<span class="badge text-bg-light border">Width</span>
|
||||||
class="form-control form-control-sm">
|
<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>
|
||||||
<div class="vr mx-1"></div>
|
<div class="vr mx-1"></div>
|
||||||
<input type="color" class="form-control form-control-sm form-control-color" data-color>
|
<input type="color" class="form-control form-control-sm form-control-color" data-color>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue