Better grid resizing behavior.
This commit is contained in:
parent
3f4aee73a3
commit
0b85715c1e
1 changed files with 202 additions and 148 deletions
|
|
@ -2,24 +2,21 @@
|
||||||
{% set dot_size = [grid_size * 1.25, 32]|max|int %}
|
{% set dot_size = [grid_size * 1.25, 32]|max|int %}
|
||||||
|
|
||||||
{% block style %}
|
{% block style %}
|
||||||
:root {
|
|
||||||
--grid: {{ grid_size }}px;
|
#gridWrap {
|
||||||
|
width: 100%;
|
||||||
|
height: 80vh;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
#grid {
|
#grid {
|
||||||
|
position: relative;
|
||||||
cursor: crosshair;
|
cursor: crosshair;
|
||||||
height: 80vh;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@supports (height: calc(round(nearest, 80vh, {{ grid_size }}px))) {
|
|
||||||
#grid {
|
|
||||||
height: calc(round(nearest, 80vh, var(--grid)) + 1px);
|
|
||||||
width: calc(round(nearest, 100%, var(--grid)) + 1px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#toolBar {
|
#toolBar {
|
||||||
top: 10px;
|
top: 10px;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
|
|
@ -46,143 +43,145 @@
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div id="grid" class="position-relative overflow-hidden">
|
<div id="gridWrap">
|
||||||
<div id="toolBar"
|
<div id="grid" class="position-relative overflow-hidden">
|
||||||
class="btn-toolbar bg-light position-absolute border border-secondary-subtle rounded start-50 p-1 align-items-center flex-nowrap overflow-auto">
|
<div id="toolBar"
|
||||||
<div class="input-group input-group-sm w-auto">
|
class="btn-toolbar bg-light position-absolute border border-secondary-subtle rounded start-50 p-1 align-items-center flex-nowrap overflow-auto">
|
||||||
<span class="input-group-text">Grid Size:</span>
|
<div class="input-group input-group-sm w-auto">
|
||||||
<input type="number" min="1" value="{{ grid_size }}" name="gridSize" id="gridSize" class="form-control form-control-sm">
|
<span class="input-group-text">Grid Size:</span>
|
||||||
</div>
|
<input type="number" min="1" value="{{ grid_size }}" name="gridSize" id="gridSize" class="form-control form-control-sm">
|
||||||
<div class="vr mx-1"></div>
|
</div>
|
||||||
<input type="color" class="form-control form-control-sm form-control-color" id="color">
|
<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">
|
||||||
<div class="btn-group">
|
<div class="vr mx-1"></div>
|
||||||
<input type="radio" class="btn-check" id="outline" name="tool" checked>
|
<div class="btn-group">
|
||||||
<label for="outline"
|
<input type="radio" class="btn-check" id="outline" name="tool" checked>
|
||||||
class="btn btn-sm btn-light border d-inline-flex align-items-center justify-content-center">
|
<label for="outline"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
class="btn btn-sm btn-light border d-inline-flex align-items-center justify-content-center">
|
||||||
class="bi bi-square" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||||
<path
|
class="bi bi-square" viewBox="0 0 16 16">
|
||||||
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" />
|
<path
|
||||||
</svg>
|
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" />
|
||||||
</label>
|
</svg>
|
||||||
|
</label>
|
||||||
|
|
||||||
<input type="radio" class="btn-check" id="filled" name="tool">
|
<input type="radio" class="btn-check" id="filled" name="tool">
|
||||||
<label for="filled"
|
<label for="filled"
|
||||||
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-fill" viewBox="0 0 16 16">
|
class="bi bi-square-fill" viewBox="0 0 16 16">
|
||||||
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2z" />
|
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2z" />
|
||||||
</svg>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<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" viewBox="0 0 16 16">
|
class="bi bi-circle" 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>
|
||||||
|
|
||||||
<input type="radio" class="btn-check" id="filledEllipse" name="tool">
|
<input type="radio" class="btn-check" id="filledEllipse" name="tool">
|
||||||
<label for="filledEllipse"
|
<label for="filledEllipse"
|
||||||
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-fill" viewBox="0 0 16 16">
|
class="bi bi-circle-fill" viewBox="0 0 16 16">
|
||||||
<circle cx="8" cy="8" r="8" />
|
<circle cx="8" cy="8" r="8" />
|
||||||
</svg>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<input type="radio" class="btn-check" id="line" name="tool">
|
<input type="radio" class="btn-check" id="line" name="tool">
|
||||||
<label for="line"
|
<label for="line"
|
||||||
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">
|
||||||
⎯
|
⎯
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="vr mx-1"></div>
|
<div class="vr mx-1"></div>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<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" viewBox="0 0 16 16">
|
class="bi bi-border" 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>
|
||||||
</label>
|
</label>
|
||||||
<input type="radio" class="btn-check" name="gridType" id="horizontalGrid">
|
<input type="radio" class="btn-check" name="gridType" id="horizontalGrid">
|
||||||
<label for="horizontalGrid"
|
<label for="horizontalGrid"
|
||||||
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-center" viewBox="0 0 16 16">
|
class="bi bi-border-center" viewBox="0 0 16 16">
|
||||||
<path
|
<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" />
|
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>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
<input type="radio" class="btn-check" name="gridType" id="verticalGrid">
|
<input type="radio" class="btn-check" name="gridType" id="verticalGrid">
|
||||||
<label for="verticalGrid"
|
<label for="verticalGrid"
|
||||||
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-middle" viewBox="0 0 16 16">
|
class="bi bi-border-middle" viewBox="0 0 16 16">
|
||||||
<path
|
<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" />
|
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>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
<input type="radio" class="btn-check" name="gridType" id="fullGrid">
|
<input type="radio" class="btn-check" name="gridType" id="fullGrid">
|
||||||
<label for="fullGrid"
|
<label for="fullGrid"
|
||||||
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-all" viewBox="0 0 16 16">
|
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" />
|
<path d="M0 0h16v16H0zm1 1v6.5h6.5V1zm7.5 0v6.5H15V1zM15 8.5H8.5V15H15zM7.5 15V8.5H1V15z" />
|
||||||
</svg>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<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
|
||||||
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
|
||||||
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" />
|
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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<input type="file" id="import" accept="application/json" class="d-none">
|
<input type="file" id="import" accept="application/json" class="d-none">
|
||||||
<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" viewBox="0 0 16 16">
|
class="bi bi-upload" 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
|
||||||
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" />
|
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"
|
||||||
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"
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||||
class="bi bi-x-lg" 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>
|
||||||
|
<span id="dot" class="position-absolute p-0 m-0 d-none">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="{{ dot_size }}" height="{{ dot_size }}" viewBox="0 0 32 32"
|
||||||
|
id="dotSVG">
|
||||||
|
<circle cx="16" cy="16" r="4" fill="black" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<div id="coords"
|
||||||
|
class="border border-black position-absolute d-none bg-warning-subtle px-1 py-0 user-select-none"></div>
|
||||||
|
<canvas id="overlay" class="position-absolute w-100 h-100"></canvas>
|
||||||
</div>
|
</div>
|
||||||
<span id="dot" class="position-absolute p-0 m-0 d-none">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="{{ dot_size }}" height="{{ dot_size }}" viewBox="0 0 32 32"
|
|
||||||
id="dotSVG">
|
|
||||||
<circle cx="16" cy="16" r="4" fill="black" />
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<div id="coords"
|
|
||||||
class="border border-black position-absolute d-none bg-warning-subtle px-1 py-0 user-select-none"></div>
|
|
||||||
<canvas id="overlay" class="position-absolute w-100 h-100"></canvas>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
@ -199,6 +198,7 @@ const gridEl = document.getElementById('grid');
|
||||||
const importButtonEl = document.getElementById('importButton');
|
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');
|
||||||
|
|
||||||
let gridSize = Number(gridSizeEl.value) || {{ grid_size }};
|
let gridSize = Number(gridSizeEl.value) || {{ grid_size }};
|
||||||
let dotSize = Math.floor(Math.max(gridSize * 1.25, 32));
|
let dotSize = Math.floor(Math.max(gridSize * 1.25, 32));
|
||||||
|
|
@ -210,8 +210,11 @@ let selectedColor;
|
||||||
let currentShape = null;
|
let currentShape = null;
|
||||||
let shapes = loadShapes();
|
let shapes = loadShapes();
|
||||||
|
|
||||||
const ro = new ResizeObserver(() => resizeAndSetupCanvas());
|
let sizingRAF = 0;
|
||||||
ro.observe(gridEl);
|
let lastApplied = { w: 0, h: 0 };
|
||||||
|
|
||||||
|
const ro = new ResizeObserver(scheduleSnappedGridSize);
|
||||||
|
ro.observe(gridWrapEl);
|
||||||
|
|
||||||
const savedTool = localStorage.getItem('gridTool');
|
const savedTool = localStorage.getItem('gridTool');
|
||||||
if (savedTool) {
|
if (savedTool) {
|
||||||
|
|
@ -228,12 +231,43 @@ window.addEventListener('resize', resizeAndSetupCanvas);
|
||||||
|
|
||||||
setGrid();
|
setGrid();
|
||||||
|
|
||||||
|
function snapDown(n, step) {
|
||||||
|
return Math.floor(n / step) * step;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySnappedGridSize() {
|
||||||
|
sizingRAF = 0;
|
||||||
|
|
||||||
|
const grid = gridSize;
|
||||||
|
if (!Number.isFinite(grid) || grid < 1) return;
|
||||||
|
|
||||||
|
const w = gridWrapEl.clientWidth;
|
||||||
|
const h = gridWrapEl.clientHeight;
|
||||||
|
|
||||||
|
const snappedW = snapDown(w, grid);
|
||||||
|
const snappedH = snapDown(h, grid);
|
||||||
|
|
||||||
|
if (snappedW === lastApplied.w && snappedH === lastApplied.h) return;
|
||||||
|
|
||||||
|
lastApplied = { w: snappedW, h: snappedH };
|
||||||
|
|
||||||
|
gridEl.style.width = `${snappedW}px`;
|
||||||
|
gridEl.style.height = `${snappedH}px`;
|
||||||
|
|
||||||
|
resizeAndSetupCanvas();
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleSnappedGridSize() {
|
||||||
|
if (sizingRAF) return;
|
||||||
|
sizingRAF = requestAnimationFrame(applySnappedGridSize);
|
||||||
|
}
|
||||||
|
|
||||||
function applyGridSize(newSize) {
|
function applyGridSize(newSize) {
|
||||||
const n = Number(newSize);
|
const n = Number(newSize);
|
||||||
if (!Number.isFinite(n) || n < 1) return;
|
if (!Number.isFinite(n) || n < 1) return;
|
||||||
|
|
||||||
gridSize = n;
|
gridSize = n;
|
||||||
document.documentElement.style.setProperty('--grid', `${gridSize}px`);
|
snapSizeToGrid();
|
||||||
|
|
||||||
dotSize = Math.floor(Math.max(gridSize * 1.25, 32));
|
dotSize = Math.floor(Math.max(gridSize * 1.25, 32));
|
||||||
|
|
||||||
|
|
@ -241,7 +275,7 @@ function applyGridSize(newSize) {
|
||||||
dotSVGEl.setAttribute('height', dotSize);
|
dotSVGEl.setAttribute('height', dotSize);
|
||||||
|
|
||||||
setGrid();
|
setGrid();
|
||||||
resizeAndSetupCanvas();
|
scheduleSnappedGridSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
function pxToGrid(v) {
|
function pxToGrid(v) {
|
||||||
|
|
@ -274,7 +308,29 @@ function setActiveType(typeId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function snapSizeToGrid() {
|
||||||
|
const grid = gridSize;
|
||||||
|
const rect = gridEl.getBoundingClientRect();
|
||||||
|
|
||||||
|
const targetW = rect.width;
|
||||||
|
const targetH = rect.height;
|
||||||
|
|
||||||
|
const snappedW = Math.floor(targetW / grid) * grid + 1;
|
||||||
|
const snappedH = Math.floor(targetH / grid) * grid + 1;
|
||||||
|
|
||||||
|
gridEl.style.width = `${snappedW}px`;
|
||||||
|
gridEl.style.height = `${snappedH}px`;
|
||||||
|
}
|
||||||
|
|
||||||
function snapToGrid(x, y) {
|
function snapToGrid(x, y) {
|
||||||
|
/*
|
||||||
|
For portability, we do not allow pixel coordinates in the data model
|
||||||
|
and only use pixels for rendering. We display both spaces on the screen
|
||||||
|
to ensure no matter the mode, the user is reasoning in the same two
|
||||||
|
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();
|
||||||
const clampedX = Math.min(Math.max(x, rect.left), rect.right);
|
const clampedX = Math.min(Math.max(x, rect.left), rect.right);
|
||||||
const clampedY = Math.min(Math.max(y, rect.top), rect.bottom);
|
const clampedY = Math.min(Math.max(y, rect.top), rect.bottom);
|
||||||
|
|
@ -344,6 +400,7 @@ function normalizeLine(shape) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function resizeAndSetupCanvas() {
|
function resizeAndSetupCanvas() {
|
||||||
|
snapSizeToGrid();
|
||||||
dpr = window.devicePixelRatio || 1;
|
dpr = window.devicePixelRatio || 1;
|
||||||
const rect = canvasEl.getBoundingClientRect();
|
const rect = canvasEl.getBoundingClientRect();
|
||||||
|
|
||||||
|
|
@ -581,9 +638,6 @@ document.addEventListener('keydown', (e) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
gridEl.addEventListener('pointermove', (e) => {
|
gridEl.addEventListener('pointermove', (e) => {
|
||||||
// Note to certain minds that keep thinking we want to mix grid and pixel coordinates.
|
|
||||||
// No. No we do not. Pixels are not portable. Stop it.
|
|
||||||
|
|
||||||
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 } = snapToGrid(e.clientX, e.clientY);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue