feat(ui): embed block zoom controls

main
AlexsandrSnytkin 7 hours ago
parent 8ca6eddc87
commit ba503d52fd

@ -1287,8 +1287,12 @@ HTML = """<!doctype html>
.config-scale-control input { width: 74px; height: 16px; margin: 0; accent-color: var(--ui-accent); cursor: pointer; } .config-scale-control input { width: 74px; height: 16px; margin: 0; accent-color: var(--ui-accent); cursor: pointer; }
.config-scale-control input:disabled { opacity: .45; cursor: default; } .config-scale-control input:disabled { opacity: .45; cursor: default; }
.config-scale-control output { min-width: 34px; color: var(--ui-text); font-variant-numeric: tabular-nums; text-align: right; } .config-scale-control output { min-width: 34px; color: var(--ui-text); font-variant-numeric: tabular-nums; text-align: right; }
.block-scale-toggle { flex: 0 0 auto !important; width: auto !important; min-width: 48px !important; min-height: 23px !important; height: 23px !important; margin-left: auto; padding: 0 6px !important; border: 1px solid var(--ui-border) !important; border-radius: 6px !important; background: color-mix(in srgb, var(--ui-accent) 8%, var(--ui-panel)) !important; color: var(--ui-muted) !important; font: 800 9px/1 ui-sans-serif, system-ui, sans-serif !important; letter-spacing: .02em; cursor: pointer !important; } .block-zoom { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 2px; margin-left: auto; padding: 2px; border: 1px solid color-mix(in srgb, var(--ui-accent) 42%, var(--ui-border)); border-radius: 999px; background: color-mix(in srgb, var(--ui-panel) 92%, var(--ui-accent)); box-shadow: 0 3px 10px color-mix(in srgb, var(--ui-bg) 16%, transparent); }
.block-scale-toggle:hover, .block-scale-toggle[aria-pressed="true"] { color: #fff !important; background: var(--ui-accent-bg) !important; border-color: var(--ui-accent) !important; } .block-zoom button, .block-zoom output { flex: 0 0 auto !important; width: 22px !important; min-width: 22px !important; height: 20px !important; min-height: 20px !important; margin: 0 !important; padding: 0 !important; border: 0 !important; border-radius: 999px !important; background: transparent !important; color: var(--ui-muted) !important; font: 800 10px/20px ui-sans-serif, system-ui, sans-serif !important; text-align: center; }
.block-zoom output { width: 42px !important; min-width: 42px !important; color: var(--ui-text) !important; font-variant-numeric: tabular-nums; }
.block-zoom button { cursor: pointer !important; }
.block-zoom button:hover, .block-zoom button:focus-visible, .block-zoom button[aria-pressed="true"] { outline: 0; color: #fff !important; background: var(--ui-accent-bg) !important; }
.block-zoom .block-zoom-toggle { font-size: 12px !important; }
.config-layout-state { min-width: 0; color: var(--ui-muted); font-size: 10px; } .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 { 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; } .config-workspace.has-structure { grid-template-columns: minmax(190px, 260px) minmax(0, 1fr); align-items: start; }
@ -1497,7 +1501,7 @@ HTML = """<!doctype html>
.network-block.is-deleted, .network-block.is-hidden, .network-canvas > .network-block[data-block-state="hidden"], .network-canvas > .network-block[data-block-state="deleted"] { display: none !important; } .network-block.is-deleted, .network-block.is-hidden, .network-canvas > .network-block[data-block-state="hidden"], .network-canvas > .network-block[data-block-state="deleted"] { display: none !important; }
.network-summary { display: flex; align-items: center; gap: 6px; margin: -2px 0 8px; padding-bottom: 8px; border-bottom: 1px solid color-mix(in srgb, var(--ui-border) 80%, transparent); color: var(--ui-text); font-size: 12px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; } .network-summary { display: flex; align-items: center; gap: 6px; margin: -2px 0 8px; padding-bottom: 8px; border-bottom: 1px solid color-mix(in srgb, var(--ui-border) 80%, transparent); color: var(--ui-text); font-size: 12px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
.network-summary .block-title::before { display: none; } .network-summary .block-title::before { display: none; }
.network-summary .block-scale-toggle { margin-left: auto; } .network-summary .block-zoom { margin-left: auto; }
.network-summary::before { content: ""; color: var(--ui-accent); font-size: 11px; } .network-summary::before { content: ""; color: var(--ui-accent); font-size: 11px; }
.network-block.settings-group { --block-scale: 1; padding: calc(14px * var(--block-scale)); gap: calc(8px * var(--block-scale)); } .network-block.settings-group { --block-scale: 1; padding: calc(14px * var(--block-scale)); gap: calc(8px * var(--block-scale)); }
.network-block.settings-group label, .network-block.settings-group .model-state { gap: calc(3px * var(--block-scale)); font-size: clamp(8px, calc(11px * var(--block-scale)), 22px); } .network-block.settings-group label, .network-block.settings-group .model-state { gap: calc(3px * var(--block-scale)); font-size: clamp(8px, calc(11px * var(--block-scale)), 22px); }
@ -2145,9 +2149,10 @@ HTML = """<!doctype html>
} }
function updateConfigScaleToggle(block) { function updateConfigScaleToggle(block) {
const button = block?.querySelector(':scope > summary .block-scale-toggle'); const button = block?.querySelector(':scope > summary .block-scale-toggle');
if (!button) return; const output = block?.querySelector(':scope > summary .block-zoom-value');
if (!button || !output) return;
const value = Math.round(Number(configLayout[block.dataset.section]?.scale) || 100); const value = Math.round(Number(configLayout[block.dataset.section]?.scale) || 100);
button.textContent = ` ${value}%`; output.textContent = `${value}%`;
button.setAttribute('aria-pressed', String(!!block._configScaleWheelEnabled)); button.setAttribute('aria-pressed', String(!!block._configScaleWheelEnabled));
button.title = block._configScaleWheelEnabled ? 'Масштаб колесом включён · нажмите, чтобы выключить' : 'Включить масштаб колесом для этого блока'; button.title = block._configScaleWheelEnabled ? 'Масштаб колесом включён · нажмите, чтобы выключить' : 'Включить масштаб колесом для этого блока';
} }
@ -2574,11 +2579,16 @@ HTML = """<!doctype html>
const titleNode = document.createElement('span'); const titleNode = document.createElement('span');
titleNode.className = 'block-title'; titleNode.className = 'block-title';
titleNode.textContent = title; titleNode.textContent = title;
const zoom = document.createElement('span');
zoom.className = 'block-zoom';
zoom.setAttribute('role', 'group');
zoom.setAttribute('aria-label', `Масштаб блока «${title}»`);
zoom.addEventListener('pointerdown', event => { event.preventDefault(); event.stopPropagation(); });
const scaleToggle = document.createElement('button'); const scaleToggle = document.createElement('button');
scaleToggle.className = 'block-scale-toggle'; scaleToggle.className = 'block-scale-toggle';
scaleToggle.type = 'button'; scaleToggle.type = 'button';
scaleToggle.setAttribute('aria-label', `Масштаб блока «${title}» колесом`); scaleToggle.textContent = '';
scaleToggle.addEventListener('pointerdown', event => { event.preventDefault(); event.stopPropagation(); }); scaleToggle.setAttribute('aria-label', `Включить масштаб колесом для блока «${title}»`);
scaleToggle.addEventListener('click', event => { scaleToggle.addEventListener('click', event => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
@ -2587,7 +2597,25 @@ HTML = """<!doctype html>
block._configScaleWheelEnabled = !block._configScaleWheelEnabled; block._configScaleWheelEnabled = !block._configScaleWheelEnabled;
updateConfigScaleToggle(block); updateConfigScaleToggle(block);
}); });
summary.append(titleNode, scaleToggle); const makeStep = (label, delta) => {
const step = document.createElement('button');
step.className = 'block-zoom-step';
step.type = 'button';
step.textContent = label;
step.setAttribute('aria-label', `${delta > 0 ? 'Увеличить' : 'Уменьшить'} масштаб блока`);
step.addEventListener('click', event => {
event.preventDefault();
event.stopPropagation();
selectConfigBlock(block);
bringConfigBlockFront(block);
applyConfigContentScale((Number(configLayout[block.dataset.section]?.scale) || 100) + delta);
});
return step;
};
const output = document.createElement('output');
output.className = 'block-zoom-value';
zoom.append(scaleToggle, makeStep('', -5), output, makeStep('+', 5));
summary.append(titleNode, zoom);
updateConfigScaleToggle(block); updateConfigScaleToggle(block);
summary.setAttribute('aria-label', `${title}. Перетаскивайте заголовок для перемещения`); summary.setAttribute('aria-label', `${title}. Перетаскивайте заголовок для перемещения`);
summary.setAttribute('aria-expanded', 'true'); summary.setAttribute('aria-expanded', 'true');
@ -2690,7 +2718,7 @@ HTML = """<!doctype html>
const nestedSummary = target.closest('summary'); const nestedSummary = target.closest('summary');
if (nestedSummary && nestedSummary !== summary) return true; if (nestedSummary && nestedSummary !== summary) return true;
if (nestedSummary === summary) return false; if (nestedSummary === summary) return false;
return !!target.closest('input, select, textarea, button, a, [contenteditable="true"], .drop-zone, .packet-byte-map, .packet-field-row'); return !!target.closest('input, select, textarea, button, a, [contenteditable="true"], .block-zoom, .drop-zone, .packet-byte-map, .packet-field-row');
} }
function startConfigBlockDrag(block, event) { function startConfigBlockDrag(block, event) {
if (event.button !== 0 || !configCanvas || configDragBlocked(block, event)) return; if (event.button !== 0 || !configCanvas || configDragBlocked(block, event)) return;
@ -2929,9 +2957,10 @@ HTML = """<!doctype html>
} }
function updateNetworkScaleToggle(block) { function updateNetworkScaleToggle(block) {
const button = block?.querySelector(':scope > .network-summary .block-scale-toggle'); const button = block?.querySelector(':scope > .network-summary .block-scale-toggle');
if (!button) return; const output = block?.querySelector(':scope > .network-summary .block-zoom-value');
if (!button || !output) return;
const value = Math.round(Number(networkLayout[block.dataset.section]?.scale) || 100); const value = Math.round(Number(networkLayout[block.dataset.section]?.scale) || 100);
button.textContent = ` ${value}%`; output.textContent = `${value}%`;
button.setAttribute('aria-pressed', String(!!block._networkScaleWheelEnabled)); button.setAttribute('aria-pressed', String(!!block._networkScaleWheelEnabled));
button.title = block._networkScaleWheelEnabled ? 'Масштаб колесом включён · нажмите, чтобы выключить' : 'Включить масштаб колесом для этого блока'; button.title = block._networkScaleWheelEnabled ? 'Масштаб колесом включён · нажмите, чтобы выключить' : 'Включить масштаб колесом для этого блока';
} }
@ -3171,7 +3200,7 @@ HTML = """<!doctype html>
function networkDragBlocked(block, event) { function networkDragBlocked(block, event) {
const target = event.target; const target = event.target;
if (!(target instanceof Element)) return true; if (!(target instanceof Element)) return true;
return !!target.closest('input, select, textarea, button, a, iframe, [contenteditable="true"], .netron-panel'); return !!target.closest('input, select, textarea, button, a, iframe, [contenteditable="true"], .block-zoom, .netron-panel');
} }
function networkTrashDrop(event) { function networkTrashDrop(event) {
const trash = $('networkTrash'); const trash = $('networkTrash');
@ -3253,11 +3282,16 @@ HTML = """<!doctype html>
const titleNode = document.createElement('span'); const titleNode = document.createElement('span');
titleNode.className = 'block-title'; titleNode.className = 'block-title';
titleNode.textContent = title; titleNode.textContent = title;
const zoom = document.createElement('span');
zoom.className = 'block-zoom';
zoom.setAttribute('role', 'group');
zoom.setAttribute('aria-label', `Масштаб блока «${title}»`);
zoom.addEventListener('pointerdown', event => { event.preventDefault(); event.stopPropagation(); });
const scaleToggle = document.createElement('button'); const scaleToggle = document.createElement('button');
scaleToggle.className = 'block-scale-toggle'; scaleToggle.className = 'block-scale-toggle';
scaleToggle.type = 'button'; scaleToggle.type = 'button';
scaleToggle.setAttribute('aria-label', `Масштаб блока «${networkTitle(block)}» колесом`); scaleToggle.textContent = '';
scaleToggle.addEventListener('pointerdown', event => { event.preventDefault(); event.stopPropagation(); }); scaleToggle.setAttribute('aria-label', `Включить масштаб колесом для блока «${title}»`);
scaleToggle.addEventListener('click', event => { scaleToggle.addEventListener('click', event => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
@ -3266,7 +3300,25 @@ HTML = """<!doctype html>
block._networkScaleWheelEnabled = !block._networkScaleWheelEnabled; block._networkScaleWheelEnabled = !block._networkScaleWheelEnabled;
updateNetworkScaleToggle(block); updateNetworkScaleToggle(block);
}); });
summary.append(titleNode, scaleToggle); const makeStep = (label, delta) => {
const step = document.createElement('button');
step.className = 'block-zoom-step';
step.type = 'button';
step.textContent = label;
step.setAttribute('aria-label', `${delta > 0 ? 'Увеличить' : 'Уменьшить'} масштаб блока`);
step.addEventListener('click', event => {
event.preventDefault();
event.stopPropagation();
selectNetworkBlock(block);
bringNetworkBlockFront(block);
applyNetworkContentScale((Number(networkLayout[block.dataset.section]?.scale) || 100) + delta);
});
return step;
};
const output = document.createElement('output');
output.className = 'block-zoom-value';
zoom.append(scaleToggle, makeStep('', -5), output, makeStep('+', 5));
summary.append(titleNode, zoom);
updateNetworkScaleToggle(block); updateNetworkScaleToggle(block);
} }
block.addEventListener('pointerdown', event => { block.addEventListener('pointerdown', event => {

Loading…
Cancel
Save