Some more improvements.
This commit is contained in:
parent
8d5ddca229
commit
9d22c55aba
1 changed files with 197 additions and 160 deletions
|
|
@ -17,13 +17,6 @@
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#toolBar {
|
|
||||||
top: 10px;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
z-index: 10000;
|
|
||||||
max-width: calc(100% - 20px);
|
|
||||||
}
|
|
||||||
|
|
||||||
#toolBar::-webkit-scrollbar {
|
#toolBar::-webkit-scrollbar {
|
||||||
height: 8px;
|
height: 8px;
|
||||||
}
|
}
|
||||||
|
|
@ -39,17 +32,24 @@
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#toolBar {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div id="gridWrap">
|
|
||||||
<div id="grid" class="position-relative overflow-hidden">
|
|
||||||
<div id="toolBar"
|
<div id="toolBar"
|
||||||
class="btn-toolbar bg-light position-absolute border border-secondary-subtle rounded start-50 p-1 align-items-center flex-nowrap overflow-auto">
|
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">
|
||||||
<div class="input-group input-group-sm w-auto">
|
<div class="input-group input-group-sm w-auto flex-nowrap">
|
||||||
<span class="input-group-text">Grid Size:</span>
|
<span class="input-group-text">Grid Size:</span>
|
||||||
<input type="number" min="1" value="25" name="gridSize" id="gridSize" class="form-control form-control-sm">
|
<input type="number" min="1" value="25" name="gridSize" id="gridSize" class="form-control form-control-sm">
|
||||||
|
|
||||||
|
<span class="input-group-text">Fill Opacity:</span>
|
||||||
|
<input type="number" min="0" max="1" step=".01" name="fillOpacity" id="fillOpacity"
|
||||||
|
class="form-control form-control-sm">
|
||||||
</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" id="color">
|
<input type="color" class="form-control form-control-sm form-control-color" id="color">
|
||||||
|
|
@ -58,8 +58,8 @@
|
||||||
<input type="radio" class="btn-check" id="outline" name="tool" checked>
|
<input type="radio" class="btn-check" id="outline" name="tool" checked>
|
||||||
<label for="outline"
|
<label for="outline"
|
||||||
class="btn btn-sm btn-light border d-inline-flex align-items-center justify-content-center">
|
class="btn btn-sm btn-light border d-inline-flex align-items-center justify-content-center">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-square"
|
||||||
class="bi bi-square" viewBox="0 0 16 16">
|
viewBox="0 0 16 16">
|
||||||
<path
|
<path
|
||||||
d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2z" />
|
d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
@ -77,8 +77,8 @@
|
||||||
<input type="radio" class="btn-check" id="outlineEllipse" name="tool">
|
<input type="radio" class="btn-check" id="outlineEllipse" name="tool">
|
||||||
<label for="outlineEllipse"
|
<label for="outlineEllipse"
|
||||||
class="btn btn-sm btn-light border d-inline-flex align-items-center justify-content-center">
|
class="btn btn-sm btn-light border d-inline-flex align-items-center justify-content-center">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-circle"
|
||||||
class="bi bi-circle" viewBox="0 0 16 16">
|
viewBox="0 0 16 16">
|
||||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16" />
|
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16" />
|
||||||
</svg>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -103,8 +103,8 @@
|
||||||
<input type="radio" class="btn-check" name="gridType" id="noGrid" checked>
|
<input type="radio" class="btn-check" name="gridType" id="noGrid" checked>
|
||||||
<label for="noGrid"
|
<label for="noGrid"
|
||||||
class="btn btn-sm btn-light border d-inline-flex align-items-center justify-content-center">
|
class="btn btn-sm btn-light border d-inline-flex align-items-center justify-content-center">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-border"
|
||||||
class="bi bi-border" viewBox="0 0 16 16">
|
viewBox="0 0 16 16">
|
||||||
<path
|
<path
|
||||||
d="M0 0h.969v.5H1v.469H.969V1H.5V.969H0zm2.844 1h-.938V0h.938zm1.875 0H3.78V0h.938v1zm1.875 0h-.938V0h.938zm.937 0V.969H7.5V.5h.031V0h.938v.5H8.5v.469h-.031V1zm2.813 0h-.938V0h.938zm1.875 0h-.938V0h.938zm1.875 0h-.938V0h.938zM15.5 1h-.469V.969H15V.5h.031V0H16v.969h-.5zM1 1.906v.938H0v-.938zm6.5.938v-.938h1v.938zm7.5 0v-.938h1v.938zM1 3.78v.938H0V3.78zm6.5.938V3.78h1v.938zm7.5 0V3.78h1v.938zM1 5.656v.938H0v-.938zm6.5.938v-.938h1v.938zm7.5 0v-.938h1v.938zM.969 8.5H.5v-.031H0V7.53h.5V7.5h.469v.031H1v.938H.969zm1.875 0h-.938v-1h.938zm1.875 0H3.78v-1h.938v1zm1.875 0h-.938v-1h.938zm1.875-.031V8.5H7.53v-.031H7.5V7.53h.031V7.5h.938v.031H8.5v.938zm1.875.031h-.938v-1h.938zm1.875 0h-.938v-1h.938zm1.875 0h-.938v-1h.938zm1.406 0h-.469v-.031H15V7.53h.031V7.5h.469v.031h.5v.938h-.5zM0 10.344v-.938h1v.938zm7.5 0v-.938h1v.938zm8.5-.938v.938h-1v-.938zM0 12.22v-.938h1v.938zm7.5 0v-.938h1v.938zm8.5-.938v.938h-1v-.938zM0 14.094v-.938h1v.938zm7.5 0v-.938h1v.938zm8.5-.938v.938h-1v-.938zM.969 16H0v-.969h.5V15h.469v.031H1v.469H.969zm1.875 0h-.938v-1h.938zm1.875 0H3.78v-1h.938v1zm1.875 0h-.938v-1h.938zm.937 0v-.5H7.5v-.469h.031V15h.938v.031H8.5v.469h-.031v.5zm2.813 0h-.938v-1h.938zm1.875 0h-.938v-1h.938zm1.875 0h-.938v-1h.938zm.937 0v-.5H15v-.469h.031V15h.469v.031h.5V16z" />
|
d="M0 0h.969v.5H1v.469H.969V1H.5V.969H0zm2.844 1h-.938V0h.938zm1.875 0H3.78V0h.938v1zm1.875 0h-.938V0h.938zm.937 0V.969H7.5V.5h.031V0h.938v.5H8.5v.469h-.031V1zm2.813 0h-.938V0h.938zm1.875 0h-.938V0h.938zm1.875 0h-.938V0h.938zM15.5 1h-.469V.969H15V.5h.031V0H16v.969h-.5zM1 1.906v.938H0v-.938zm6.5.938v-.938h1v.938zm7.5 0v-.938h1v.938zM1 3.78v.938H0V3.78zm6.5.938V3.78h1v.938zm7.5 0V3.78h1v.938zM1 5.656v.938H0v-.938zm6.5.938v-.938h1v.938zm7.5 0v-.938h1v.938zM.969 8.5H.5v-.031H0V7.53h.5V7.5h.469v.031H1v.938H.969zm1.875 0h-.938v-1h.938zm1.875 0H3.78v-1h.938v1zm1.875 0h-.938v-1h.938zm1.875-.031V8.5H7.53v-.031H7.5V7.53h.031V7.5h.938v.031H8.5v.938zm1.875.031h-.938v-1h.938zm1.875 0h-.938v-1h.938zm1.875 0h-.938v-1h.938zm1.406 0h-.469v-.031H15V7.53h.031V7.5h.469v.031h.5v.938h-.5zM0 10.344v-.938h1v.938zm7.5 0v-.938h1v.938zm8.5-.938v.938h-1v-.938zM0 12.22v-.938h1v.938zm7.5 0v-.938h1v.938zm8.5-.938v.938h-1v-.938zM0 14.094v-.938h1v.938zm7.5 0v-.938h1v.938zm8.5-.938v.938h-1v-.938zM.969 16H0v-.969h.5V15h.469v.031H1v.469H.969zm1.875 0h-.938v-1h.938zm1.875 0H3.78v-1h.938v1zm1.875 0h-.938v-1h.938zm.937 0v-.5H7.5v-.469h.031V15h.938v.031H8.5v.469h-.031v.5zm2.813 0h-.938v-1h.938zm1.875 0h-.938v-1h.938zm1.875 0h-.938v-1h.938zm.937 0v-.5H15v-.469h.031V15h.469v.031h.5V16z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
@ -139,8 +139,7 @@
|
||||||
<div class="vr mx-1"></div>
|
<div class="vr mx-1"></div>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn btn-sm btn-light border d-inline-flex align-items-center justify-content-center"
|
class="btn btn-sm btn-light border d-inline-flex align-items-center justify-content-center" id="export">
|
||||||
id="export">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||||
class="bi bi-download" viewBox="0 0 16 16">
|
class="bi bi-download" viewBox="0 0 16 16">
|
||||||
<path
|
<path
|
||||||
|
|
@ -153,8 +152,8 @@
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn btn-sm btn-light border d-inline-flex align-items-center justify-content-center"
|
class="btn btn-sm btn-light border d-inline-flex align-items-center justify-content-center"
|
||||||
id="importButton">
|
id="importButton">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-upload"
|
||||||
class="bi bi-upload" viewBox="0 0 16 16">
|
viewBox="0 0 16 16">
|
||||||
<path
|
<path
|
||||||
d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5" />
|
d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5" />
|
||||||
<path
|
<path
|
||||||
|
|
@ -162,19 +161,20 @@
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn btn-sm btn-danger border d-inline-flex align-items-center justify-content-center"
|
class="btn btn-sm btn-danger border d-inline-flex align-items-center justify-content-center" id="clear">
|
||||||
id="clear">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x-lg"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
viewBox="0 0 16 16">
|
||||||
class="bi bi-x-lg" viewBox="0 0 16 16">
|
|
||||||
<path
|
<path
|
||||||
d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z" />
|
d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="gridWrap">
|
||||||
|
<div id="grid" class="position-relative overflow-hidden">
|
||||||
|
|
||||||
<span id="dot" class="position-absolute p-0 m-0 d-none">
|
<span id="dot" class="position-absolute p-0 m-0 d-none">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" id="dotSVG">
|
||||||
id="dotSVG">
|
|
||||||
<circle cx="16" cy="16" r="4" fill="black" />
|
<circle cx="16" cy="16" r="4" fill="black" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -201,6 +201,7 @@ const importButtonEl = document.getElementById('importButton');
|
||||||
const importEl = document.getElementById('import');
|
const importEl = document.getElementById('import');
|
||||||
const gridSizeEl = document.getElementById('gridSize');
|
const gridSizeEl = document.getElementById('gridSize');
|
||||||
const gridWrapEl = document.getElementById('gridWrap');
|
const gridWrapEl = document.getElementById('gridWrap');
|
||||||
|
const toolBarEl = document.getElementById('toolBar');
|
||||||
|
|
||||||
let doc = loadDoc();
|
let doc = loadDoc();
|
||||||
let gridSize = Number(doc.gridSize) || 25;
|
let gridSize = Number(doc.gridSize) || 25;
|
||||||
|
|
@ -234,10 +235,27 @@ resizeAndSetupCanvas();
|
||||||
window.addEventListener('resize', resizeAndSetupCanvas);
|
window.addEventListener('resize', resizeAndSetupCanvas);
|
||||||
|
|
||||||
setGrid();
|
setGrid();
|
||||||
|
scheduleSnappedGridSize();
|
||||||
|
|
||||||
|
function isFiniteNum(n) { return Number.isFinite(Number(n)); }
|
||||||
|
|
||||||
|
function sanitizeShapes(list) {
|
||||||
|
return list.filter(s => {
|
||||||
|
if (!s || typeof s !== 'object') return false;
|
||||||
|
if (!['rect','ellipse','line'].include(s.type)) return false;
|
||||||
|
if (!s.color) s.color = '#000000';
|
||||||
|
|
||||||
|
if (s.type === 'line') {
|
||||||
|
return ['x1','y1','x2','y2'].every(k => isFiniteNum(s[k]));
|
||||||
|
} else {
|
||||||
|
return ['x','y','w','h'].every(k => isFiniteNum(s[k]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function loadDoc() {
|
function loadDoc() {
|
||||||
try { return JSON.parse(localStorage.getItem("gridDoc")) || DEFAULT_DOC; }
|
try { return JSON.parse(localStorage.getItem("gridDoc")) || structuredClone(DEFAULT_DOC); }
|
||||||
catch { return DEFAULT_DOC; }
|
catch { return structuredClone(DEFAULT_DOC); }
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveDoc(nextDoc = doc) {
|
function saveDoc(nextDoc = doc) {
|
||||||
|
|
@ -267,7 +285,10 @@ function applySnappedGridSize() {
|
||||||
gridEl.style.width = `${snappedW}px`;
|
gridEl.style.width = `${snappedW}px`;
|
||||||
gridEl.style.height = `${snappedH}px`;
|
gridEl.style.height = `${snappedH}px`;
|
||||||
|
|
||||||
requestAnimationFrame(resizeAndSetupCanvas);
|
toolBarEl.style.width = `${snappedW}px`;
|
||||||
|
|
||||||
|
gridEl.getBoundingClientRect();
|
||||||
|
resizeAndSetupCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
function scheduleSnappedGridSize() {
|
function scheduleSnappedGridSize() {
|
||||||
|
|
@ -280,8 +301,7 @@ function applyGridSize(newSize) {
|
||||||
if (!Number.isFinite(n) || n < 1) return;
|
if (!Number.isFinite(n) || n < 1) return;
|
||||||
|
|
||||||
gridSize = n;
|
gridSize = n;
|
||||||
doc.gridSize = gridSize;
|
saveDoc({ ...doc, shapes, gridSize });
|
||||||
saveDoc({ ...doc, shapes });
|
|
||||||
|
|
||||||
dotSize = Math.floor(Math.max(gridSize * 1.25, 32));
|
dotSize = Math.floor(Math.max(gridSize * 1.25, 32));
|
||||||
|
|
||||||
|
|
@ -324,11 +344,9 @@ function setActiveType(typeId) {
|
||||||
|
|
||||||
function snapToGrid(x, y) {
|
function snapToGrid(x, y) {
|
||||||
/*
|
/*
|
||||||
For portability, we do not allow pixel coordinates in the data model
|
Shapes are stored in grid units (document units), not pixels.
|
||||||
and only use pixels for rendering. We display both spaces on the screen
|
1 unit renders as gridSize pixels, so changing gridSize rescales (zooms) the whole drawing.
|
||||||
to ensure no matter the mode, the user is reasoning in the same two
|
Grid modes only affect snapping/visuals; storage is always in document units for portability.
|
||||||
coordinate spaces as they need. Thus, snapping will happen even if
|
|
||||||
the tool doesn't use it.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const rect = gridEl.getBoundingClientRect();
|
const rect = gridEl.getBoundingClientRect();
|
||||||
|
|
@ -362,7 +380,9 @@ function snapToGrid(x, y) {
|
||||||
ix,
|
ix,
|
||||||
iy,
|
iy,
|
||||||
x: snapX,
|
x: snapX,
|
||||||
y: snapY
|
y: snapY,
|
||||||
|
localX,
|
||||||
|
localY
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -401,10 +421,15 @@ function normalizeLine(shape) {
|
||||||
|
|
||||||
function resizeAndSetupCanvas() {
|
function resizeAndSetupCanvas() {
|
||||||
dpr = window.devicePixelRatio || 1;
|
dpr = window.devicePixelRatio || 1;
|
||||||
const rect = canvasEl.getBoundingClientRect();
|
|
||||||
|
|
||||||
canvasEl.width = rect.width * dpr;
|
const w = gridEl.clientWidth;
|
||||||
canvasEl.height = rect.height * dpr;
|
const h = gridEl.clientHeight;
|
||||||
|
|
||||||
|
canvasEl.width = Math.round(w * dpr);
|
||||||
|
canvasEl.height = Math.round(h * dpr);
|
||||||
|
|
||||||
|
canvasEl.style.width = `${w}px`;
|
||||||
|
canvasEl.style.height = `${h}px`;
|
||||||
|
|
||||||
ctx = canvasEl.getContext('2d');
|
ctx = canvasEl.getContext('2d');
|
||||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||||
|
|
@ -538,6 +563,7 @@ document.querySelectorAll('input[name="gridType"]').forEach(input => {
|
||||||
localStorage.setItem('gridType', input.id);
|
localStorage.setItem('gridType', input.id);
|
||||||
}
|
}
|
||||||
setGrid();
|
setGrid();
|
||||||
|
redrawAll();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -563,7 +589,7 @@ importEl.addEventListener('change', (e) => {
|
||||||
const loadedShapes = Array.isArray(data) ? data : data.shapes;
|
const loadedShapes = Array.isArray(data) ? data : data.shapes;
|
||||||
if (!Array.isArray(loadedShapes)) return;
|
if (!Array.isArray(loadedShapes)) return;
|
||||||
|
|
||||||
shapes = loadedShapes;
|
shapes = sanitizeShapes(loadedShapes);
|
||||||
|
|
||||||
doc = {
|
doc = {
|
||||||
version: Number(data?.version) || 1,
|
version: Number(data?.version) || 1,
|
||||||
|
|
@ -571,7 +597,7 @@ importEl.addEventListener('change', (e) => {
|
||||||
shapes
|
shapes
|
||||||
};
|
};
|
||||||
|
|
||||||
saveDoc(data);
|
saveDoc(doc);
|
||||||
redrawAll();
|
redrawAll();
|
||||||
} catch {
|
} catch {
|
||||||
toastMessage('Failed to load data from JSON file.', 'danger');
|
toastMessage('Failed to load data from JSON file.', 'danger');
|
||||||
|
|
@ -598,9 +624,7 @@ exportEl.addEventListener('click', () => {
|
||||||
clearEl.addEventListener('click', () => {
|
clearEl.addEventListener('click', () => {
|
||||||
gridSize = 25;
|
gridSize = 25;
|
||||||
shapes = [];
|
shapes = [];
|
||||||
doc.gridSize = gridSize;
|
saveDoc({...doc, shapes, gridSize});
|
||||||
doc.shapes = shapes;
|
|
||||||
saveDoc(doc);
|
|
||||||
gridSizeEl.value = 25;
|
gridSizeEl.value = 25;
|
||||||
applyGridSize(25);
|
applyGridSize(25);
|
||||||
redrawAll();
|
redrawAll();
|
||||||
|
|
@ -621,7 +645,7 @@ document.addEventListener('keydown', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (shapes.length > 0) {
|
if (shapes.length > 0) {
|
||||||
shapes.pop();
|
shapes.pop();
|
||||||
saveShapes();
|
saveDoc({ ...doc, shapes, gridSize });
|
||||||
redrawAll();
|
redrawAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -632,10 +656,20 @@ document.addEventListener('keydown', (e) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gridEl.addEventListener('pointercancel', () => {
|
||||||
|
currentShape = null;
|
||||||
|
redrawAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
gridEl.addEventListener('lostpointercapture', () => {
|
||||||
|
currentShape = null;
|
||||||
|
redrawAll();
|
||||||
|
});
|
||||||
|
|
||||||
gridEl.addEventListener('pointermove', (e) => {
|
gridEl.addEventListener('pointermove', (e) => {
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
|
|
||||||
const { ix, iy, x: snapX, y: snapY } = snapToGrid(e.clientX, e.clientY);
|
const { ix, iy, x: snapX, y: snapY, localX, localY } = snapToGrid(e.clientX, e.clientY);
|
||||||
|
|
||||||
|
|
||||||
const renderX = snapX - dotSize / 2;
|
const renderX = snapX - dotSize / 2;
|
||||||
|
|
@ -649,7 +683,11 @@ gridEl.addEventListener('pointermove', (e) => {
|
||||||
dotEl.style.left = `${renderX}px`;
|
dotEl.style.left = `${renderX}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getActiveType() == 'noGrid') {
|
||||||
|
coordsEl.innerText = `(px x=${Math.round(localX)} y=${Math.round(localY)})`;
|
||||||
|
} else {
|
||||||
coordsEl.innerText = `(x=${ix} (${snapX}px) y=${iy} (${snapY}px))`;
|
coordsEl.innerText = `(x=${ix} (${snapX}px) y=${iy} (${snapY}px))`;
|
||||||
|
}
|
||||||
|
|
||||||
if (currentShape) {
|
if (currentShape) {
|
||||||
const tool = currentShape.tool;
|
const tool = currentShape.tool;
|
||||||
|
|
@ -725,7 +763,7 @@ gridEl.addEventListener('pointerdown', (e) => {
|
||||||
x2: snapX,
|
x2: snapX,
|
||||||
y2: snapY,
|
y2: snapY,
|
||||||
color: selectedColor,
|
color: selectedColor,
|
||||||
fill: document.getElementById('filled').checked
|
fill: (tool === 'filled' || tool === 'filledEllipse')
|
||||||
};
|
};
|
||||||
} else if (tool === 'outlineEllipse' || tool === 'filledEllipse') {
|
} else if (tool === 'outlineEllipse' || tool === 'filledEllipse') {
|
||||||
currentShape = {
|
currentShape = {
|
||||||
|
|
@ -773,8 +811,7 @@ window.addEventListener('pointerup', (e) => {
|
||||||
|
|
||||||
if (finalShape) {
|
if (finalShape) {
|
||||||
shapes.push(finalShape);
|
shapes.push(finalShape);
|
||||||
doc.shapes = shapes;
|
saveDoc({...doc, shapes, gridSize});
|
||||||
saveDoc(doc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clearCanvas();
|
clearCanvas();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue