Adding grid settings buttons.
This commit is contained in:
parent
fc95c87e84
commit
87ec637ac1
1 changed files with 132 additions and 29 deletions
|
|
@ -76,15 +76,66 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="vr mx-1"></div>
|
<div class="vr mx-1"></div>
|
||||||
|
<div class="btn-group">
|
||||||
|
<input type="radio" class="btn-check" name="gridType" id="fullGrid">
|
||||||
|
<label for="fullGrid"
|
||||||
|
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"
|
||||||
|
class="bi bi-border-all" viewBox="0 0 16 16">
|
||||||
|
<path d="M0 0h16v16H0zm1 1v6.5h6.5V1zm7.5 0v6.5H15V1zM15 8.5H8.5V15H15zM7.5 15V8.5H1V15z" />
|
||||||
|
</svg>
|
||||||
|
</label>
|
||||||
|
<input type="radio" class="btn-check" name="gridType" id="horizontalGrid">
|
||||||
|
<label for="horizontalGrid"
|
||||||
|
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"
|
||||||
|
class="bi bi-border-center" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M.969 0H0v.969h.5V1h.469V.969H1V.5H.969zm.937 1h.938V0h-.938zm1.875 0h.938V0H3.78v1zm1.875 0h.938V0h-.938zM7.531.969V1h.938V.969H8.5V.5h-.031V0H7.53v.5H7.5v.469zM9.406 1h.938V0h-.938zm1.875 0h.938V0h-.938zm1.875 0h.938V0h-.938zm1.875 0h.469V.969h.5V0h-.969v.5H15v.469h.031zM1 2.844v-.938H0v.938zm6.5-.938v.938h1v-.938zm7.5 0v.938h1v-.938zM1 4.719V3.78H0v.938h1zm6.5-.938v.938h1V3.78h-1zm7.5 0v.938h1V3.78h-1zM1 6.594v-.938H0v.938zm6.5-.938v.938h1v-.938zm7.5 0v.938h1v-.938zM0 8.5v-1h16v1zm0 .906v.938h1v-.938zm7.5 0v.938h1v-.938zm8.5.938v-.938h-1v.938zm-16 .937v.938h1v-.938zm7.5 0v.938h1v-.938zm8.5.938v-.938h-1v.938zm-16 .937v.938h1v-.938zm7.5 0v.938h1v-.938zm8.5.938v-.938h-1v.938zM0 16h.969v-.5H1v-.469H.969V15H.5v.031H0zm1.906 0h.938v-1h-.938zm1.875 0h.938v-1H3.78v1zm1.875 0h.938v-1h-.938zm1.875-.5v.5h.938v-.5H8.5v-.469h-.031V15H7.53v.031H7.5v.469zm1.875.5h.938v-1h-.938zm1.875 0h.938v-1h-.938zm1.875 0h.938v-1h-.938zm1.875-.5v.5H16v-.969h-.5V15h-.469v.031H15v.469z" />
|
||||||
|
</svg>
|
||||||
|
</label>
|
||||||
|
<input type="radio" class="btn-check" name="gridType" id="verticalGrid">
|
||||||
|
<label for="verticalGrid"
|
||||||
|
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"
|
||||||
|
class="bi bi-border-middle" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M.969 0H0v.969h.5V1h.469V.969H1V.5H.969zm.937 1h.938V0h-.938zm1.875 0h.938V0H3.78v1zm1.875 0h.938V0h-.938zM8.5 16h-1V0h1zm.906-15h.938V0h-.938zm1.875 0h.938V0h-.938zm1.875 0h.938V0h-.938zm1.875 0h.469V.969h.5V0h-.969v.5H15v.469h.031zM1 2.844v-.938H0v.938zm14-.938v.938h1v-.938zM1 4.719V3.78H0v.938h1zm14-.938v.938h1V3.78h-1zM1 6.594v-.938H0v.938zm14-.938v.938h1v-.938zM.5 8.5h.469v-.031H1V7.53H.969V7.5H.5v.031H0v.938h.5zm1.406 0h.938v-1h-.938zm1.875 0h.938v-1H3.78v1zm1.875 0h.938v-1h-.938zm3.75 0h.938v-1h-.938zm1.875 0h.938v-1h-.938zm1.875 0h.938v-1h-.938zm1.875 0h.469v-.031h.5V7.53h-.5V7.5h-.469v.031H15v.938h.031zM0 9.406v.938h1v-.938zm16 .938v-.938h-1v.938zm-16 .937v.938h1v-.938zm16 .938v-.938h-1v.938zm-16 .937v.938h1v-.938zm16 .938v-.938h-1v.938zM0 16h.969v-.5H1v-.469H.969V15H.5v.031H0zm1.906 0h.938v-1h-.938zm1.875 0h.938v-1H3.78v1zm1.875 0h.938v-1h-.938zm3.75 0h.938v-1h-.938zm1.875 0h.938v-1h-.938zm1.875 0h.938v-1h-.938zm1.875-.5v.5H16v-.969h-.5V15h-.469v.031H15v.469z" />
|
||||||
|
</svg>
|
||||||
|
</label>
|
||||||
|
<input type="radio" class="btn-check" name="gridType" id="noGrid">
|
||||||
|
<label for="noGrid"
|
||||||
|
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"
|
||||||
|
class="bi bi-border" viewBox="0 0 16 16">
|
||||||
|
<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" />
|
||||||
|
</svg>
|
||||||
|
</label>
|
||||||
|
</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-floppy" viewBox="0 0 16 16">
|
class="bi bi-download" viewBox="0 0 16 16">
|
||||||
<path d="M11 2H9v3h2z" />
|
|
||||||
<path
|
<path
|
||||||
d="M1.5 0h11.586a1.5 1.5 0 0 1 1.06.44l1.415 1.414A1.5 1.5 0 0 1 16 2.914V14.5a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 0 14.5v-13A1.5 1.5 0 0 1 1.5 0M1 1.5v13a.5.5 0 0 0 .5.5H2v-4.5A1.5 1.5 0 0 1 3.5 9h9a1.5 1.5 0 0 1 1.5 1.5V15h.5a.5.5 0 0 0 .5-.5V2.914a.5.5 0 0 0-.146-.353l-1.415-1.415A.5.5 0 0 0 13.086 1H13v4.5A1.5 1.5 0 0 1 11.5 7h-7A1.5 1.5 0 0 1 3 5.5V1H1.5a.5.5 0 0 0-.5.5m3 4a.5.5 0 0 0 .5.5h7a.5.5 0 0 0 .5-.5V1H4zM3 15h10v-4.5a.5.5 0 0 0-.5-.5h-9a.5.5 0 0 0-.5.5z" />
|
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
|
||||||
|
d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<input type="file" id="import" accept="application/json" class="d-none">
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-sm btn-light border d-inline-flex align-items-center justify-content-center"
|
||||||
|
id="importButton">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||||
|
class="bi bi-upload" viewBox="0 0 16 16">
|
||||||
|
<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" />
|
||||||
|
<path
|
||||||
|
d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708z" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
|
|
@ -120,6 +171,8 @@ const dotEl = document.getElementById('dot');
|
||||||
const dotSVGEl = document.getElementById('dotSVG');
|
const dotSVGEl = document.getElementById('dotSVG');
|
||||||
const exportEl = document.getElementById('export');
|
const exportEl = document.getElementById('export');
|
||||||
const gridEl = document.getElementById('grid');
|
const gridEl = document.getElementById('grid');
|
||||||
|
const importButtonEl = document.getElementById('importButton');
|
||||||
|
const importEl = document.getElementById('import');
|
||||||
|
|
||||||
let ctx;
|
let ctx;
|
||||||
let dpr = 1;
|
let dpr = 1;
|
||||||
|
|
@ -172,29 +225,39 @@ function snapToGrid(x, y) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeRect(shape) {
|
function normalizeRect(shape) {
|
||||||
const x = Math.min(shape.x1, shape.x2);
|
const ix1 = shape.x1 / {{ grid_size }};
|
||||||
const y = Math.min(shape.y1, shape.y2);
|
const iy1 = shape.y1 / {{ grid_size }};
|
||||||
const w = Math.abs(shape.x2 - shape.x1);
|
const ix2 = shape.x2 / {{ grid_size }};
|
||||||
const h = Math.abs(shape.y2 - shape.y1);
|
const iy2 = shape.y2 / {{ grid_size }};
|
||||||
|
|
||||||
|
const ix = Math.min(ix1, ix2);
|
||||||
|
const iy = Math.min(iy1, iy2);
|
||||||
|
const iw = Math.abs(ix2 - ix1);
|
||||||
|
const ih = Math.abs(iy2 - iy1);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'rect',
|
type: 'rect',
|
||||||
x,
|
ix,
|
||||||
y,
|
iy,
|
||||||
w,
|
iw,
|
||||||
h,
|
ih,
|
||||||
color: shape.color,
|
color: shape.color,
|
||||||
fill: shape.fill
|
fill: shape.fill
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeLine(shape) {
|
function normalizeLine(shape) {
|
||||||
|
const ix1 = shape.x1 / {{ grid_size }};
|
||||||
|
const iy1 = shape.y1 / {{ grid_size }};
|
||||||
|
const ix2 = shape.x2 / {{ grid_size }};
|
||||||
|
const iy2 = shape.y2 / {{ grid_size }};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
x1: shape.x1,
|
ix1,
|
||||||
y1: shape.y1,
|
iy1,
|
||||||
x2: shape.x2,
|
ix2,
|
||||||
y2: shape.y2,
|
iy2,
|
||||||
color: shape.color
|
color: shape.color
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -232,18 +295,28 @@ function drawShape(shape) {
|
||||||
ctx.strokeStyle = shape.color || '#000000';
|
ctx.strokeStyle = shape.color || '#000000';
|
||||||
|
|
||||||
if (shape.type === 'rect') {
|
if (shape.type === 'rect') {
|
||||||
ctx.strokeRect(shape.x, shape.y, shape.w, shape.h);
|
const x = shape.ix * {{ grid_size }};
|
||||||
|
const y = shape.iy * {{ grid_size }};
|
||||||
|
const w = shape.iw * {{ grid_size }};
|
||||||
|
const h = shape.ih * {{ grid_size }};
|
||||||
|
|
||||||
|
ctx.strokeRect(x, y, w, h);
|
||||||
|
|
||||||
if (shape.fill) {
|
if (shape.fill) {
|
||||||
ctx.globalAlpha = 0.15;
|
ctx.globalAlpha = 0.15;
|
||||||
ctx.fillStyle = shape.color;
|
ctx.fillStyle = shape.color;
|
||||||
ctx.fillRect(shape.x, shape.y, shape.w, shape.h);
|
ctx.fillRect(x, y, w, h);
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
}
|
}
|
||||||
} else if (shape.type === 'line') {
|
} else if (shape.type === 'line') {
|
||||||
|
const x1 = shape.ix1 * {{ grid_size }};
|
||||||
|
const y1 = shape.iy1 * {{ grid_size }};
|
||||||
|
const x2 = shape.ix2 * {{ grid_size }};
|
||||||
|
const y2 = shape.iy2 * {{ grid_size }};
|
||||||
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(shape.x1, shape.y1);
|
ctx.moveTo(x1, y1);
|
||||||
ctx.lineTo(shape.x2, shape.y2);
|
ctx.lineTo(x2, y2);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -279,8 +352,36 @@ document.querySelectorAll('input[name="tool"]').forEach(input => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
importButtonEl.addEventListener('click', () => importEl.click());
|
||||||
|
|
||||||
|
importEl.addEventListener('change', (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(reader.result);
|
||||||
|
const loadedShapes = Array.isArray(data) ? data : data.shapes;
|
||||||
|
if (!Array.isArray(loadedShapes)) return;
|
||||||
|
|
||||||
|
shapes = loadedShapes;
|
||||||
|
saveShapes();
|
||||||
|
redrawAll();
|
||||||
|
} catch {
|
||||||
|
toastMessage('Failed to load data from JSON file.', 'danger');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
});
|
||||||
|
|
||||||
exportEl.addEventListener('click', () => {
|
exportEl.addEventListener('click', () => {
|
||||||
const blob = new Blob([JSON.stringify(shapes, null,2)], {type: 'application/json'});
|
const payload = {
|
||||||
|
version: 1,
|
||||||
|
gridSize: {{ grid_size }},
|
||||||
|
shapes
|
||||||
|
};
|
||||||
|
const blob = new Blob([JSON.stringify(payload, null,2)], {type: 'application/json'});
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
|
|
@ -291,7 +392,7 @@ exportEl.addEventListener('click', () => {
|
||||||
|
|
||||||
clearEl.addEventListener('click', () => {
|
clearEl.addEventListener('click', () => {
|
||||||
shapes = [];
|
shapes = [];
|
||||||
localStorage.removeItem('gridShapes');
|
saveShapes();
|
||||||
redrawAll();
|
redrawAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -321,7 +422,9 @@ document.addEventListener('keydown', (e) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
gridEl.addEventListener('mousemove', (e) => {
|
gridEl.addEventListener('pointermove', (e) => {
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
const { ix, iy, x: snapX, y: snapY } = snapToGrid(e.clientX, e.clientY);
|
const { ix, iy, x: snapX, y: snapY } = snapToGrid(e.clientX, e.clientY);
|
||||||
|
|
||||||
coordsEl.innerText = `(${ix}, ${iy})`;
|
coordsEl.innerText = `(${ix}, ${iy})`;
|
||||||
|
|
@ -345,14 +448,14 @@ gridEl.addEventListener('mousemove', (e) => {
|
||||||
ctx.setLineDash([5, 3]);
|
ctx.setLineDash([5, 3]);
|
||||||
|
|
||||||
if (tool === 'line') {
|
if (tool === 'line') {
|
||||||
const previewLine = {
|
const previewLine = normalizeLine({
|
||||||
type: 'line',
|
type: 'line',
|
||||||
x1: currentShape.x1,
|
x1: currentShape.x1,
|
||||||
y1: currentShape.y1,
|
y1: currentShape.y1,
|
||||||
x2: snapX,
|
x2: snapX,
|
||||||
y2: snapY,
|
y2: snapY,
|
||||||
color: currentShape.color
|
color: currentShape.color
|
||||||
};
|
});
|
||||||
drawShape(previewLine);
|
drawShape(previewLine);
|
||||||
} else {
|
} else {
|
||||||
const previewRect = normalizeRect({
|
const previewRect = normalizeRect({
|
||||||
|
|
@ -368,12 +471,12 @@ gridEl.addEventListener('mousemove', (e) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
gridEl.addEventListener('mouseleave', (e) => {
|
gridEl.addEventListener('pointerleave', (e) => {
|
||||||
coordsEl.classList.add('d-none');
|
coordsEl.classList.add('d-none');
|
||||||
dotEl.classList.add('d-none');
|
dotEl.classList.add('d-none');
|
||||||
});
|
});
|
||||||
|
|
||||||
gridEl.addEventListener('mousedown', (e) => {
|
gridEl.addEventListener('pointerdown', (e) => {
|
||||||
if (e.button !== 0) return;
|
if (e.button !== 0) return;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -406,7 +509,7 @@ gridEl.addEventListener('mousedown', (e) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('mouseup', (e) => {
|
window.addEventListener('pointerup', (e) => {
|
||||||
if (!currentShape) return;
|
if (!currentShape) return;
|
||||||
|
|
||||||
const { x: snapX, y: snapY } = snapToGrid(e.clientX, e.clientY);
|
const { x: snapX, y: snapY } = snapToGrid(e.clientX, e.clientY);
|
||||||
|
|
@ -419,13 +522,13 @@ window.addEventListener('mouseup', (e) => {
|
||||||
if (currentShape.tool === 'line') {
|
if (currentShape.tool === 'line') {
|
||||||
const line = normalizeLine(currentShape);
|
const line = normalizeLine(currentShape);
|
||||||
|
|
||||||
if (line.x1 !== line.x2 || line.y1 !== line.y2) {
|
if (line.ix1 !== line.ix2 || line.iy1 !== line.iy2) {
|
||||||
finalShape = line;
|
finalShape = line;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const rect = normalizeRect(currentShape);
|
const rect = normalizeRect(currentShape);
|
||||||
|
|
||||||
if (rect.w > 0 && rect.h > 0) {
|
if (rect.iw > 0 && rect.ih > 0) {
|
||||||
finalShape = rect;
|
finalShape = rect;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue