From 87f468bfd86a6dd1b330e6b8f7be2cb507de1e4d Mon Sep 17 00:00:00 2001 From: AlexsandrSnytkin Date: Mon, 21 Sep 2026 17:48:39 +0700 Subject: [PATCH] fix(ui): prevent block content overlap --- ui_server.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/ui_server.py b/ui_server.py index 19b34c7..4226f07 100644 --- a/ui_server.py +++ b/ui_server.py @@ -2133,6 +2133,31 @@ HTML = """ const scale = Math.max(.5, Math.min(2, Math.sqrt(area) * block._configUserScale)); block.style.setProperty('--block-scale', scale.toFixed(3)); } + function resolveConfigOverlaps() { + const gap = 14; + const items = configBlocks.filter(block => block && !configBlockState(block)).sort((a, b) => { + const top = (block => parseFloat(block.style.top) || block.offsetTop || 0); + return top(a) - top(b) || (parseFloat(a.style.left) || a.offsetLeft || 0) - (parseFloat(b.style.left) || b.offsetLeft || 0); + }); + let moved = false; + items.forEach((block, index) => { + let top = parseFloat(block.style.top) || block.offsetTop || 0; + const left = parseFloat(block.style.left) || block.offsetLeft || 0; + const width = block.offsetWidth; + const height = block.offsetHeight; + items.slice(0, index).forEach(previous => { + const previousLeft = parseFloat(previous.style.left) || previous.offsetLeft || 0; + const previousTop = parseFloat(previous.style.top) || previous.offsetTop || 0; + const horizontal = left < previousLeft + previous.offsetWidth && previousLeft < left + width; + if (horizontal && top < previousTop + previous.offsetHeight + gap && top + height > previousTop) top = previousTop + previous.offsetHeight + gap; + }); + if (top <= (parseFloat(block.style.top) || block.offsetTop || 0) + .5) return; + block.style.top = `${Math.round(top)}px`; + captureConfigLayout(block); + moved = true; + }); + return moved; + } function applyConfigContentScale(value) { configContentScale = Math.max(.5, Math.min(2, (Number(value) || 100) / 100)); const block = selectedConfigBlock; @@ -2145,6 +2170,8 @@ HTML = """ setText('configContentScaleValue', `${Math.round(configContentScale * 100)}%`); updateConfigBlockScale(block); updateConfigScaleToggle(block); + resolveConfigOverlaps(); + updateConfigCanvasHeight(); saveConfigLayout(); } function updateConfigScaleToggle(block) { @@ -2316,7 +2343,8 @@ HTML = """ }); let fitted = false; configBlocks.forEach(block => { fitted = fitConfigBlockContent(block) || fitted; updateConfigBlockScale(block); }); - if (fitted) saveConfigLayout(); + const separated = resolveConfigOverlaps(); + if (fitted || separated) saveConfigLayout(); updateConfigCanvasHeight(); } function setConfigBlockStates(blocks, state) { @@ -2562,6 +2590,7 @@ HTML = """ group.forEach(item => captureConfigLayout(item)); if (droppedOnTrash) setConfigBlockStates(group, 'deleted'); else { + resolveConfigOverlaps(); saveConfigLayout(); setText('configLayoutState', `${configBlocks.length - configBlocks.filter(item => configBlockState(item)).length} блоков · раскладка сохранена`); } @@ -2678,6 +2707,7 @@ HTML = """ block.removeEventListener('pointercancel', end); block.classList.remove('is-resizing'); captureConfigLayout(block); + resolveConfigOverlaps(); saveConfigLayout(); block._suppressSummaryClick = true; window.setTimeout(() => { block._suppressSummaryClick = false; }, 0); @@ -2910,6 +2940,31 @@ HTML = """ block._networkUserScale = Math.max(.5, Math.min(2, userScale)); block.style.setProperty('--block-scale', Math.max(.5, Math.min(2, Math.sqrt(area) * block._networkUserScale)).toFixed(3)); } + function resolveNetworkOverlaps() { + const gap = 14; + const items = networkBlocks.filter(block => block && !networkState(block)).sort((a, b) => { + const top = block => parseFloat(block.style.top) || block.offsetTop || 0; + return top(a) - top(b) || (parseFloat(a.style.left) || a.offsetLeft || 0) - (parseFloat(b.style.left) || b.offsetLeft || 0); + }); + let moved = false; + items.forEach((block, index) => { + let top = parseFloat(block.style.top) || block.offsetTop || 0; + const left = parseFloat(block.style.left) || block.offsetLeft || 0; + const width = block.offsetWidth; + const height = block.offsetHeight; + items.slice(0, index).forEach(previous => { + const previousLeft = parseFloat(previous.style.left) || previous.offsetLeft || 0; + const previousTop = parseFloat(previous.style.top) || previous.offsetTop || 0; + const horizontal = left < previousLeft + previous.offsetWidth && previousLeft < left + width; + if (horizontal && top < previousTop + previous.offsetHeight + gap && top + height > previousTop) top = previousTop + previous.offsetHeight + gap; + }); + if (top <= (parseFloat(block.style.top) || block.offsetTop || 0) + .5) return; + block.style.top = `${Math.round(top)}px`; + captureNetworkLayout(block); + moved = true; + }); + return moved; + } function selectNetworkBlock(block) { selectedNetworkBlocks = new Set(block && !networkState(block) ? [block] : []); updateNetworkSelection(block); @@ -2953,6 +3008,8 @@ HTML = """ setText('networkContentScaleValue', `${Math.round(networkContentScale * 100)}%`); updateNetworkBlockScale(block); updateNetworkScaleToggle(block); + resolveNetworkOverlaps(); + updateNetworkCanvasHeight(); saveNetworkLayout(); } function updateNetworkScaleToggle(block) { @@ -3022,6 +3079,8 @@ HTML = """ if (Number.isFinite(Number(entry.z))) { block.style.zIndex = String(Math.max(1, Number(entry.z))); networkZIndex = Math.max(networkZIndex, Number(entry.z)); } updateNetworkBlockScale(block); }); + const separated = resolveNetworkOverlaps(); + if (separated) saveNetworkLayout(); updateNetworkCanvasHeight(); } function bringNetworkBlockFront(block) { @@ -3219,7 +3278,7 @@ HTML = """ if (!dragged) return; group.forEach(item => captureNetworkLayout(item)); if (droppedOnTrash) setNetworkBlockStates(group, 'deleted'); - else { saveNetworkLayout(); renderNetworkStructure(); } + else { resolveNetworkOverlaps(); saveNetworkLayout(); renderNetworkStructure(); } group.forEach(item => { item._networkSuppressClick = true; window.setTimeout(() => { item._networkSuppressClick = false; }, 0); @@ -3370,6 +3429,7 @@ HTML = """ block.removeEventListener('pointercancel', end); block.classList.remove('is-resizing'); captureNetworkLayout(block); + resolveNetworkOverlaps(); saveNetworkLayout(); block._networkSuppressClick = true; window.setTimeout(() => { block._networkSuppressClick = false; }, 0);