fix(ui): drag blocks by their shell

main
AlexsandrSnytkin 17 hours ago
parent 1a73a47623
commit 512de06903

@ -80,6 +80,8 @@ class UIServerTests(unittest.TestCase):
self.assertIn('createCustomConfigBlock', HTML) self.assertIn('createCustomConfigBlock', HTML)
self.assertIn('duplicateConfigBlock', HTML) self.assertIn('duplicateConfigBlock', HTML)
self.assertIn('focusConfigBlock', HTML) self.assertIn('focusConfigBlock', HTML)
self.assertIn('startConfigBlockDrag', HTML)
self.assertIn('configDragBlocked', HTML)
self.assertIn('positionNewConfigBlock', HTML) self.assertIn('positionNewConfigBlock', HTML)
self.assertIn('finishConfigDrag', HTML) self.assertIn('finishConfigDrag', HTML)
self.assertIn('fpv-config-layout-v1', HTML) self.assertIn('fpv-config-layout-v1', HTML)

@ -1309,6 +1309,7 @@ HTML = """<!doctype html>
.config-restore-panel[hidden], .config-trash[hidden] { display: none !important; } .config-restore-panel[hidden], .config-trash[hidden] { display: none !important; }
.config-canvas { position: relative; display: block; min-height: 760px; padding: 8px 6px 18px; overflow: auto; overscroll-behavior: contain; } .config-canvas { position: relative; display: block; min-height: 760px; padding: 8px 6px 18px; overflow: auto; overscroll-behavior: contain; }
.config-canvas > .config-block { position: absolute; box-sizing: border-box; container: config-block / inline-size; margin: 0; min-width: 240px; min-height: 110px; border: 1px solid var(--ui-border) !important; border-radius: 10px; background: var(--ui-panel) !important; box-shadow: 0 6px 18px rgba(0, 0, 0, .1); overflow: auto; transition: border-color .2s ease, box-shadow .2s ease, opacity .2s ease; } .config-canvas > .config-block { position: absolute; box-sizing: border-box; container: config-block / inline-size; margin: 0; min-width: 240px; min-height: 110px; border: 1px solid var(--ui-border) !important; border-radius: 10px; background: var(--ui-panel) !important; box-shadow: 0 6px 18px rgba(0, 0, 0, .1); overflow: auto; transition: border-color .2s ease, box-shadow .2s ease, opacity .2s ease; }
.config-canvas > .config-block { cursor: grab; touch-action: none; }
.config-canvas > .config-block:hover { border-color: color-mix(in srgb, var(--ui-accent) 45%, var(--ui-border)) !important; } .config-canvas > .config-block:hover { border-color: color-mix(in srgb, var(--ui-accent) 45%, var(--ui-border)) !important; }
.config-canvas > .config-block.is-dragging, .config-canvas > .config-block.is-resizing { z-index: 5; border-color: var(--ui-accent) !important; box-shadow: 0 10px 28px color-mix(in srgb, var(--ui-accent) 16%, transparent); transition: none; } .config-canvas > .config-block.is-dragging, .config-canvas > .config-block.is-resizing { z-index: 5; border-color: var(--ui-accent) !important; box-shadow: 0 10px 28px color-mix(in srgb, var(--ui-accent) 16%, transparent); transition: none; }
.config-canvas > .config-custom-block { border-style: dashed !important; } .config-canvas > .config-custom-block { border-style: dashed !important; }
@ -1318,6 +1319,8 @@ HTML = """<!doctype html>
.config-block > summary { cursor: grab; touch-action: none; } .config-block > summary { cursor: grab; touch-action: none; }
.config-block > summary:active, .config-block.is-dragging > summary { cursor: grabbing; } .config-block > summary:active, .config-block.is-dragging > summary { cursor: grabbing; }
.config-block.is-dragging, .config-block.is-resizing { user-select: none; } .config-block.is-dragging, .config-block.is-resizing { user-select: none; }
.config-block.is-dragging { cursor: grabbing; }
.config-block input, .config-block select, .config-block textarea, .config-block button, .config-block a { cursor: auto; touch-action: auto; }
.config-block label, .config-block input, .config-block select, .config-block button, .config-block pre { min-width: 0; max-width: 100%; box-sizing: border-box; } .config-block label, .config-block input, .config-block select, .config-block button, .config-block pre { min-width: 0; max-width: 100%; box-sizing: border-box; }
.config-block .control-fields, .config-block .advanced-grid, .config-block .packet-grid, .config-block .packet-field-list { min-width: 0; } .config-block .control-fields, .config-block .advanced-grid, .config-block .packet-grid, .config-block .packet-field-list { min-width: 0; }
@container config-block (max-width: 620px) { @container config-block (max-width: 620px) {
@ -2237,7 +2240,7 @@ HTML = """<!doctype html>
block.addEventListener('pointerdown', event => { block.addEventListener('pointerdown', event => {
bringConfigBlockFront(block); bringConfigBlockFront(block);
const edge = configResizeEdge(event, block); const edge = configResizeEdge(event, block);
if (!edge || (event.target.closest('summary') && !edge.includes('n'))) return; if (!edge || configDragBlocked(block, event)) return;
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
const startX = event.clientX; const startX = event.clientX;
@ -2290,41 +2293,9 @@ HTML = """<!doctype html>
} }
}); });
block.addEventListener('pointerleave', () => { if (!block.classList.contains('is-resizing')) block.style.cursor = ''; }); block.addEventListener('pointerleave', () => { if (!block.classList.contains('is-resizing')) block.style.cursor = ''; });
summary.addEventListener('pointerdown', event => { block.addEventListener('pointerdown', event => {
if (event.button !== 0 || !configCanvas) return; if (event.defaultPrevented || block.classList.contains('is-resizing')) return;
const startX = event.clientX; startConfigBlockDrag(block, event);
const startY = event.clientY;
const startLeft = parseFloat(block.style.left) || block.offsetLeft || 0;
const startTop = parseFloat(block.style.top) || block.offsetTop || 0;
let dragged = false;
summary.setPointerCapture?.(event.pointerId);
const move = moveEvent => {
if (!dragged && Math.hypot(moveEvent.clientX - startX, moveEvent.clientY - startY) < 4) return;
if (!dragged) {
dragged = true;
event.preventDefault();
block.classList.add('is-dragging');
const trash = $('configTrash');
if (trash) { trash.hidden = false; trash.classList.remove('is-over'); }
}
event.preventDefault();
const maxLeft = Math.max(0, configCanvasWidth() - block.offsetWidth);
block.style.left = `${Math.max(0, Math.min(maxLeft, Math.round(startLeft + moveEvent.clientX - startX)))}px`;
block.style.top = `${Math.max(0, Math.round(startTop + moveEvent.clientY - startY))}px`;
setTrashHover(moveEvent);
updateConfigCanvasHeight();
};
const end = endEvent => {
summary.releasePointerCapture?.(event.pointerId);
summary.removeEventListener('pointermove', move);
summary.removeEventListener('pointerup', end);
summary.removeEventListener('pointercancel', end);
block.classList.remove('is-dragging');
finishConfigDrag(block, endEvent, dragged);
};
summary.addEventListener('pointermove', move);
summary.addEventListener('pointerup', end, {once: true});
summary.addEventListener('pointercancel', end, {once: true});
}); });
block.addEventListener('toggle', () => { block.addEventListener('toggle', () => {
if (!block.open) block.open = true; if (!block.open) block.open = true;
@ -2332,6 +2303,51 @@ HTML = """<!doctype html>
updateConfigCanvasHeight(); updateConfigCanvasHeight();
}); });
} }
function configDragBlocked(block, event) {
const target = event.target;
if (!(target instanceof Element)) return true;
const summary = block.firstElementChild;
const nestedSummary = target.closest('summary');
if (nestedSummary && nestedSummary !== summary) return true;
if (nestedSummary === summary) return false;
return !!target.closest('input, select, textarea, button, a, [contenteditable="true"], .drop-zone, .packet-byte-map, .packet-field-row');
}
function startConfigBlockDrag(block, event) {
if (event.button !== 0 || !configCanvas || configDragBlocked(block, event)) return;
const startX = event.clientX;
const startY = event.clientY;
const startLeft = parseFloat(block.style.left) || block.offsetLeft || 0;
const startTop = parseFloat(block.style.top) || block.offsetTop || 0;
let dragged = false;
block.setPointerCapture?.(event.pointerId);
const move = moveEvent => {
if (!dragged && Math.hypot(moveEvent.clientX - startX, moveEvent.clientY - startY) < 4) return;
if (!dragged) {
dragged = true;
event.preventDefault();
block.classList.add('is-dragging');
const trash = $('configTrash');
if (trash) { trash.hidden = false; trash.classList.remove('is-over'); }
}
event.preventDefault();
const maxLeft = Math.max(0, configCanvasWidth() - block.offsetWidth);
block.style.left = `${Math.max(0, Math.min(maxLeft, Math.round(startLeft + moveEvent.clientX - startX)))}px`;
block.style.top = `${Math.max(0, Math.round(startTop + moveEvent.clientY - startY))}px`;
setTrashHover(moveEvent);
updateConfigCanvasHeight();
};
const end = endEvent => {
block.releasePointerCapture?.(event.pointerId);
block.removeEventListener('pointermove', move);
block.removeEventListener('pointerup', end);
block.removeEventListener('pointercancel', end);
block.classList.remove('is-dragging');
finishConfigDrag(block, endEvent, dragged);
};
block.addEventListener('pointermove', move);
block.addEventListener('pointerup', end, {once: true});
block.addEventListener('pointercancel', end, {once: true});
}
function resetConfigLayout() { function resetConfigLayout() {
const custom = {}; const custom = {};
configBlocks.forEach(block => { configBlocks.forEach(block => {

Loading…
Cancel
Save