feat: recalc coordinates flow

This commit is contained in:
2026-06-23 20:04:03 +05:00
parent f8aff92927
commit 630a33ee7c
3 changed files with 273 additions and 10 deletions
+27 -3
View File
@@ -37,10 +37,34 @@
<div class="right-col">
<div class="panel">
<div class="panel-header">
<h2>Основное поле</h2>
<div class="field-row">
<label><span>W</span><input id="inpFieldW" type="number" value="1920" min="1" /></label>
<label><span>H</span><input id="inpFieldH" type="number" value="1080" min="1" /></label>
<button class="btn btn-edit-field" id="btnEditField">Редактировать</button>
</div>
<div class="field-size-stack">
<div class="chain-link" id="chainLink" title="Keep aspect ratio">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path class="chain-locked" d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path class="chain-locked" d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
<path class="chain-unlocked" d="M13.5 10.5l3-3" style="display:none" />
<path class="chain-unlocked" d="M10.5 13.5l-3 3" style="display:none" />
<path class="chain-unlocked" d="M7.5 7.5l9 9" style="display:none" />
</svg>
</div>
<div class="field-stack-inputs">
<label><span>W</span><input id="inpFieldW" type="number" value="1920" min="1" disabled /></label>
<label><span>H</span><input id="inpFieldH" type="number" value="1080" min="1" disabled /></label>
</div>
</div>
<div class="field-edit-options" id="fieldEditOptions">
<label class="checkbox-label">
<input type="checkbox" id="chkRescaleCoords" checked />
<span>Пересчитать координаты</span>
</label>
<div class="field-save-row">
<button class="btn btn-cancel-field" id="btnCancelField">Отменить</button>
<button class="btn btn-save-field" id="btnSaveField">Сохранить</button>
</div>
</div>
</div>
+96 -6
View File
@@ -286,14 +286,103 @@
$('cssOutput').value = toMatrix3dCSS(H);
}
// ===== Input events =====
$('inpFieldW').addEventListener('input', () => {
const v = parseInt($('inpFieldW').value);
if (v > 0) { state.fieldW = v; updateAll(); }
// ===== Field editing =====
let fieldEditing = false;
let fieldEditW = state.fieldW;
let fieldEditH = state.fieldH;
let keepAspect = true;
function updateChainIcon() {
const locked = $('chainLink').querySelectorAll('.chain-locked');
const unlocked = $('chainLink').querySelectorAll('.chain-unlocked');
locked.forEach(el => el.style.display = keepAspect ? '' : 'none');
unlocked.forEach(el => el.style.display = keepAspect ? 'none' : '');
$('chainLink').classList.toggle('unlocked', !keepAspect);
}
function setFieldEditing(editing) {
fieldEditing = editing;
const inpW = $('inpFieldW');
const inpH = $('inpFieldH');
const options = $('fieldEditOptions');
const btnEdit = $('btnEditField');
const chain = $('chainLink');
if (editing) {
fieldEditW = state.fieldW;
fieldEditH = state.fieldH;
inpW.disabled = false;
inpH.disabled = false;
options.classList.add('visible');
btnEdit.style.display = 'none';
chain.classList.add('visible');
updateChainIcon();
} else {
inpW.value = state.fieldW;
inpW.disabled = true;
inpH.value = state.fieldH;
inpH.disabled = true;
options.classList.remove('visible');
btnEdit.style.display = '';
chain.classList.remove('visible');
}
}
$('chainLink').addEventListener('click', () => {
keepAspect = !keepAspect;
updateChainIcon();
});
$('btnEditField').addEventListener('click', () => {
setFieldEditing(true);
$('inpFieldW').focus();
});
$('btnCancelField').addEventListener('click', () => {
setFieldEditing(false);
});
$('inpFieldW').addEventListener('input', () => {
if (!fieldEditing) return;
const newW = parseInt($('inpFieldW').value);
if (newW <= 0) return;
if (keepAspect) {
fieldEditH = Math.round(newW * state.fieldH / state.fieldW);
$('inpFieldH').value = fieldEditH;
}
fieldEditW = newW;
});
$('inpFieldH').addEventListener('input', () => {
const v = parseInt($('inpFieldH').value);
if (v > 0) { state.fieldH = v; updateAll(); }
if (!fieldEditing) return;
const newH = parseInt($('inpFieldH').value);
if (newH <= 0) return;
if (keepAspect) {
fieldEditW = Math.round(newH * state.fieldW / state.fieldH);
$('inpFieldW').value = fieldEditW;
}
fieldEditH = newH;
});
$('btnSaveField').addEventListener('click', () => {
const newW = parseInt($('inpFieldW').value);
const newH = parseInt($('inpFieldH').value);
if (newW <= 0 || newH <= 0) return;
if ($('chkRescaleCoords').checked && (newW !== state.fieldW || newH !== state.fieldH)) {
const sx = newW / state.fieldW;
const sy = newH / state.fieldH;
state.corners.forEach(c => {
c.x = Math.round(c.x * sx);
c.y = Math.round(c.y * sy);
});
}
state.fieldW = newW;
state.fieldH = newH;
setFieldEditing(false);
buildCornerInputs();
updateAll();
});
$('inpWidgetW').addEventListener('input', () => {
const v = parseInt($('inpWidgetW').value);
@@ -324,6 +413,7 @@
// ===== Reset =====
$('resetBtn').addEventListener('click', () => {
setFieldEditing(false);
state.fieldW = defaultState.fieldW;
state.fieldH = defaultState.fieldH;
state.widgetW = defaultState.widgetW;
+149
View File
@@ -107,6 +107,16 @@ main {
margin-bottom: 12px;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.panel-header h2 {
margin-bottom: 0;
}
.left-col {
display: flex;
flex-direction: column;
@@ -275,6 +285,145 @@ main {
background: #484f58;
}
.btn-edit-field {
padding: 4px 12px;
border: 1px solid var(--accent);
border-radius: 4px;
background: transparent;
color: var(--accent);
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.btn-edit-field:hover {
background: rgba(88, 166, 255, 0.1);
}
.btn-save-field {
padding: 4px 12px;
border: none;
border-radius: 4px;
background: var(--btn-bg);
color: white;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: background 0.15s;
}
.btn-save-field:hover {
background: var(--btn-hover);
}
.btn-cancel-field {
padding: 4px 12px;
border: 1px solid var(--border);
border-radius: 4px;
background: transparent;
color: var(--text);
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: background 0.15s;
}
.btn-cancel-field:hover {
background: var(--border);
}
.field-size-stack {
display: flex;
align-items: stretch;
gap: 8px;
}
.chain-link {
display: none;
align-items: center;
justify-content: center;
width: 32px;
min-height: 56px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--input-bg);
color: var(--accent);
cursor: pointer;
flex-shrink: 0;
transition: background 0.15s, color 0.15s;
}
.chain-link:hover {
background: rgba(88, 166, 255, 0.1);
}
.chain-link.unlocked {
color: #8b949e;
}
.chain-link.visible {
display: flex;
}
.field-stack-inputs {
display: flex;
flex-direction: column;
gap: 6px;
flex: 1;
}
.field-stack-inputs label {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: #8b949e;
}
.field-stack-inputs label span {
min-width: 14px;
}
.field-stack-inputs input {
width: 100%;
padding: 6px 8px;
background: var(--input-bg);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--heading);
font-size: 14px;
font-family: monospace;
outline: none;
}
.field-stack-inputs input:focus {
border-color: var(--accent);
}
.field-stack-inputs input:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.field-edit-options {
display: none;
margin-top: 10px;
flex-direction: column;
gap: 8px;
}
.field-edit-options.visible {
display: flex;
}
.field-save-row {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: #8b949e;
cursor: pointer;
}
.checkbox-label input[type="checkbox"] {
accent-color: var(--accent);
}
#fieldContainer {
width: 100%;
overflow: hidden;