From 00f7b4f1ce09e2bf8dae20863a42bb7fdc3cfc9c Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Thu, 2 Jul 2026 11:46:36 +0200 Subject: [PATCH] fix: Drop-Position bei Zoom korrigiert und Canvas-Panning mit rechter Maustaste MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Drop- und Klickkoordinaten über das transformierte precanvas-Rect umrechnen (berücksichtigt transform-origin in der Canvas-Mitte) - Greif-Offset in Canvas-Einheiten abziehen, damit der Knoten in jeder Zoomstufe unter dem Mauszeiger erscheint - Rechtsklick auf freien Canvas verschiebt jetzt die gesamte Ansicht Co-Authored-By: Claude Fable 5 --- src/main/frontend/node-editor/node-editor.js | 62 ++++++++++++-------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/src/main/frontend/node-editor/node-editor.js b/src/main/frontend/node-editor/node-editor.js index 4a3f2f8..888dad5 100644 --- a/src/main/frontend/node-editor/node-editor.js +++ b/src/main/frontend/node-editor/node-editor.js @@ -284,14 +284,12 @@ function attach(host) { const def = state.registry.get(typeId); if (!def) return; - const rect = container.getBoundingClientRect(); - const zoom = editor.zoom; - // Greif-Offset abziehen, damit der Knoten an seiner gezogenen Position - // liegt (linke obere Ecke wie während des Ziehens, nicht an der Maus). - const px = (e.clientX - rect.left - editor.canvas_x - dragGrabOffset.x) / zoom; - const py = (e.clientY - rect.top - editor.canvas_y - dragGrabOffset.y) / zoom; - - addNodeInternal(host, def, px, py); + // Greif-Offset in Canvas-Einheiten abziehen (nicht in Bildschirm- + // Pixeln): So bleibt der Mauszeiger in jeder Zoomstufe an derselben + // relativen Stelle im erzeugten Knoten, statt dass der Knoten beim + // Herauszoomen um den hochgerechneten Offset neben der Maus landet. + const { x, y } = toCanvasPoint(state, e.clientX, e.clientY); + addNodeInternal(host, def, x - dragGrabOffset.x, y - dragGrabOffset.y); }); editor.on('nodeSelected', id => { @@ -376,26 +374,40 @@ function attach(host) { selectConnection(state, path); }, true); - // Verschieben von Knoten mit der RECHTEN Maustaste. Drawflow zieht Knoten - // nur mit links; das Ziehen mit rechts wird hier eigenständig umgesetzt. + // Rechte Maustaste: auf einem Knoten wird der Knoten verschoben (Drawflow + // zieht Knoten nur mit links), auf dem freien Canvas wird die gesamte + // Ansicht verschoben (Panning). let rightDrag = null; + let rightPan = null; let suppressContextMenu = false; container.addEventListener('mousedown', e => { if (e.button !== 2) return; - const nodeEl = e.target instanceof Element ? e.target.closest('.drawflow-node') : null; - if (!nodeEl) return; e.preventDefault(); e.stopPropagation(); // Drawflows eigene (Links-)Drag-Logik nicht auslösen - rightDrag = { - nodeEl, - id: nodeEl.id.replace('node-', ''), - lastX: e.clientX, - lastY: e.clientY - }; + const nodeEl = e.target instanceof Element ? e.target.closest('.drawflow-node') : null; + if (nodeEl) { + rightDrag = { + nodeEl, + id: nodeEl.id.replace('node-', ''), + lastX: e.clientX, + lastY: e.clientY + }; + } else { + rightPan = { lastX: e.clientX, lastY: e.clientY }; + } suppressContextMenu = true; }, true); const onRightMove = e => { + if (rightPan) { + editor.canvas_x += e.clientX - rightPan.lastX; + editor.canvas_y += e.clientY - rightPan.lastY; + rightPan.lastX = e.clientX; + rightPan.lastY = e.clientY; + editor.precanvas.style.transform = + 'translate(' + editor.canvas_x + 'px, ' + editor.canvas_y + 'px) scale(' + editor.zoom + ')'; + return; + } if (!rightDrag) return; const zoom = editor.zoom || 1; const newLeft = rightDrag.nodeEl.offsetLeft + (e.clientX - rightDrag.lastX) / zoom; @@ -408,11 +420,11 @@ function attach(host) { if (data) { data.pos_x = newLeft; data.pos_y = newTop; } editor.updateConnectionNodes('node-' + rightDrag.id); }; - const onRightUp = () => { rightDrag = null; }; + const onRightUp = () => { rightDrag = null; rightPan = null; }; document.addEventListener('mousemove', onRightMove); document.addEventListener('mouseup', onRightUp); - // Browser-Kontextmenü nach einem Rechts-Ziehen auf einem Knoten unterdrücken. + // Browser-Kontextmenü nach Rechts-Ziehen (Knoten oder Canvas) unterdrücken. container.addEventListener('contextmenu', e => { if (suppressContextMenu) { e.preventDefault(); @@ -466,14 +478,16 @@ function attach(host) { } // Wandelt Client-Koordinaten in das (unskalierte) Canvas-Koordinatensystem um, -// in dem auch die Verbindungspfade definiert sind. +// in dem auch die Verbindungspfade definiert sind. Das Rect des precanvas +// enthält bereits translate UND scale (inkl. transform-origin in der Mitte), +// daher genügt es, den skalierten Abstand zurückzurechnen. function toCanvasPoint(state, clientX, clientY) { const editor = state.editor; - const rect = state.container.getBoundingClientRect(); + const rect = editor.precanvas.getBoundingClientRect(); const zoom = editor.zoom || 1; return { - x: (clientX - rect.left - editor.canvas_x) / zoom, - y: (clientY - rect.top - editor.canvas_y) / zoom, + x: (clientX - rect.left) / zoom, + y: (clientY - rect.top) / zoom, zoom }; }