feat(ui): scale block content responsively

main
AlexsandrSnytkin 15 hours ago
parent 332fe1c125
commit 18b77b6212

@ -1282,6 +1282,9 @@ HTML = """<!doctype html>
.config-toolbar-title { margin-right: auto; color: var(--ui-muted); font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
.config-toolbar button, .config-restore-panel button { min-height: 26px; padding: 0 8px; border: 1px solid var(--ui-border); border-radius: 7px; background: var(--ui-input); color: var(--ui-muted); font: 700 10px/1 ui-sans-serif, system-ui, sans-serif; cursor: pointer; transition: border-color .18s ease, color .18s ease, background .18s ease, transform .18s ease; }
.config-toolbar button:hover, .config-toolbar button:focus-visible, .config-restore-panel button:hover, .config-restore-panel button:focus-visible { outline: 0; border-color: var(--ui-accent); color: var(--ui-accent); transform: translateY(-1px); }
.config-scale-control { display: inline-flex !important; align-items: center; gap: 6px !important; min-width: 148px; color: var(--ui-muted) !important; font-size: 10px !important; white-space: nowrap; }
.config-scale-control input { width: 74px; height: 16px; margin: 0; accent-color: var(--ui-accent); cursor: pointer; }
.config-scale-control output { min-width: 34px; color: var(--ui-text); font-variant-numeric: tabular-nums; text-align: right; }
.config-layout-state { min-width: 0; color: var(--ui-muted); font-size: 10px; }
.config-workspace { display: grid; grid-template-columns: minmax(0, 1fr); min-width: 0; min-height: 0; }
.config-workspace.has-structure { grid-template-columns: minmax(190px, 260px) minmax(0, 1fr); align-items: start; }
@ -1443,6 +1446,11 @@ HTML = """<!doctype html>
.config-canvas > .config-block.settings-group > summary, .config-canvas > .config-block.settings-group > label, .config-canvas > .config-block.settings-group > .control-fields, .config-canvas > .config-block.settings-group > .packet-builder, .config-canvas > .config-block.settings-group > .settings-advanced { width: 100%; flex: 0 0 auto; }
.config-canvas > .config-block.settings-group > label { display: grid; }
.config-canvas > .config-block.settings-group > .control-fields { display: grid !important; }
.config-canvas > .config-block.settings-group { --block-scale: 1; padding: calc(9px * var(--block-scale)) calc(10px * var(--block-scale)); gap: calc(8px * var(--block-scale)); }
.config-canvas > .config-block.settings-group > summary { padding-bottom: calc(4px * var(--block-scale)); font-size: clamp(10px, calc(11px * var(--block-scale)), 16px); }
.config-block.settings-group label, .config-block.settings-group .file-picked { gap: calc(2px * var(--block-scale)); font-size: clamp(9px, calc(10px * var(--block-scale)), 15px); }
.config-block.settings-group input:not([type="checkbox"]):not([type="file"]), .config-block.settings-group select, .config-block.settings-group button { min-height: calc(27px * var(--block-scale)); height: calc(27px * var(--block-scale)); padding-left: calc(7px * var(--block-scale)); padding-right: calc(7px * var(--block-scale)); font-size: clamp(10px, calc(12px * var(--block-scale)), 17px); }
.config-block.settings-group .control-fields, .config-block.settings-group .advanced-grid, .config-block.settings-group .packet-grid { gap: calc(6px * var(--block-scale)) calc(8px * var(--block-scale)); }
@keyframes appearance-in { from { opacity: 0; transform: translateY(-5px) scale(.98); } to { opacity: 1; transform: none; } }
@media (max-width: 760px) {
.appearance-trigger { min-width: 38px; width: 38px; padding: 0; justify-content: center; }
@ -1531,6 +1539,7 @@ HTML = """<!doctype html>
<span class="config-toolbar-title">Блоки конфигурации</span>
<button id="configResetLayout" type="button">сбросить раскладку</button>
<button id="configRestoreToggle" type="button" aria-expanded="false">структура</button>
<label class="config-scale-control" title="Масштаб элементов и текста внутри блоков"><span>масштаб</span><input id="configContentScale" type="range" min="80" max="125" step="5" value="100" aria-label="Масштаб содержимого блоков"><output id="configContentScaleValue">100%</output></label>
<span id="configLayoutState" class="config-layout-state">перетаскивайте за заголовок или свободное место</span>
</div>
<div id="configWorkspace" class="config-workspace">
@ -1997,6 +2006,8 @@ HTML = """<!doctype html>
let restorePanelOpen = false;
let deletedPanelOpen = false;
let configZIndex = 1;
let configContentScale = 1;
let configBlockObserver = null;
function readConfigLayout() {
try {
const saved = JSON.parse(localStorage.getItem('fpv-config-layout-v1') || '{}');
@ -2015,6 +2026,21 @@ HTML = """<!doctype html>
function configCanvasWidth() {
return Math.max(configMinWidth(), (configCanvas?.clientWidth || 0) - 12);
}
function updateConfigBlockScale(block) {
if (!block) return;
const width = Math.max(configMinWidth(), block.clientWidth || block.offsetWidth || configMinWidth());
const height = Math.max(110, block.clientHeight || block.offsetHeight || 110);
const area = Math.max(.56, Math.min(1.85, (width / 720) * (height / 320)));
const scale = Math.max(.74, Math.min(1.35, Math.sqrt(area) * configContentScale));
block.style.setProperty('--block-scale', scale.toFixed(3));
}
function applyConfigContentScale(value) {
configContentScale = Math.max(.8, Math.min(1.25, (Number(value) || 100) / 100));
setValue('configContentScale', Math.round(configContentScale * 100));
setText('configContentScaleValue', `${Math.round(configContentScale * 100)}%`);
configBlocks.forEach(updateConfigBlockScale);
try { localStorage.setItem('fpv-config-content-scale', String(Math.round(configContentScale * 100))); } catch (_error) {}
}
function configTitle(block) {
return block.querySelector(':scope > summary .block-title')?.textContent || block.dataset.section || 'Блок';
}
@ -2139,7 +2165,7 @@ HTML = """<!doctype html>
if (Number.isFinite(Number(entry.z))) { block.style.zIndex = String(Math.max(1, Number(entry.z))); configZIndex = Math.max(configZIndex, Number(entry.z)); }
});
let fitted = false;
configBlocks.forEach(block => { fitted = fitConfigBlockContent(block) || fitted; });
configBlocks.forEach(block => { fitted = fitConfigBlockContent(block) || fitted; updateConfigBlockScale(block); });
if (fitted) saveConfigLayout();
updateConfigCanvasHeight();
}
@ -2284,6 +2310,7 @@ HTML = """<!doctype html>
}
configCanvas.appendChild(clone);
configBlocks.push(clone);
configBlockObserver?.observe(clone);
const entry = configLayout[key] || {};
configLayout[key] = {...entry, custom: true, template: templateKey, title: titleNodeText(clone)};
return clone;
@ -2297,6 +2324,7 @@ HTML = """<!doctype html>
block.style.left = '12px';
block.style.top = `${Math.max(12, bottom + 18)}px`;
block.style.width = `${Math.min(width, Math.max(configMinWidth(), Math.round(width * .46)))}px`;
updateConfigBlockScale(block);
}
function duplicateConfigBlock(block) {
const clone = createCustomConfigBlock(configTemplateKey(block), `${configTitle(block)} · копия`);
@ -2404,6 +2432,7 @@ HTML = """<!doctype html>
block.style.top = `${Math.round(top)}px`;
block.style.width = `${Math.round(width)}px`;
block.style.height = `${Math.round(height)}px`;
updateConfigBlockScale(block);
updateConfigCanvasHeight();
};
const end = () => {
@ -2551,9 +2580,16 @@ HTML = """<!doctype html>
});
window.addEventListener('resize', applyStoredConfigLayout);
if (window.ResizeObserver) {
const observer = new ResizeObserver(updateConfigCanvasHeight);
configBlocks.forEach(block => observer.observe(block));
configBlockObserver = new ResizeObserver(entries => {
entries.forEach(entry => updateConfigBlockScale(entry.target));
updateConfigCanvasHeight();
});
configBlocks.forEach(block => configBlockObserver.observe(block));
}
let savedScale = 100;
try { savedScale = Number(localStorage.getItem('fpv-config-content-scale') || 100); } catch (_error) {}
applyConfigContentScale(savedScale);
$('configContentScale')?.addEventListener('input', event => applyConfigContentScale(event.target.value));
}
function parseFrameSize(value) {
const match = /^(\\d{1,5})x(\\d{1,5})$/i.exec(String(value || '').trim());
@ -2816,7 +2852,7 @@ HTML = """<!doctype html>
setText('uploadRunVideo', dumpFile ? 'загрузить выбранный UDP-дамп' : 'загрузить выбранное видео');
if (configCanvas) window.requestAnimationFrame(() => {
let fitted = false;
configBlocks.forEach(block => { fitted = fitConfigBlockContent(block) || fitted; });
configBlocks.forEach(block => { fitted = fitConfigBlockContent(block) || fitted; updateConfigBlockScale(block); });
if (fitted) saveConfigLayout();
updateConfigCanvasHeight();
});

Loading…
Cancel
Save