From 2b409a457309caaa13f465d4f0e79ed553a51fab Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 14 Apr 2025 17:50:38 -0400 Subject: [PATCH 1/2] refactor: use D3 for space drag & update pattern - Used D3 for smooth dragging - Renamed functions to use the same pattern as option drag - Simplied dragging code refs: #270 --- src/js/space_drag.js | 163 ++++++++++++++++--------------------------- 1 file changed, 62 insertions(+), 101 deletions(-) diff --git a/src/js/space_drag.js b/src/js/space_drag.js index 8cc4af0..6efd5e2 100644 --- a/src/js/space_drag.js +++ b/src/js/space_drag.js @@ -6,15 +6,12 @@ Copyright (C) 2022 Petter Ericson, Yannis Rammos, Mehdi Merah, and the EPFL Dig MuseReduce is free software: you can redistribute it and/or modify it under the terms of the Affero General Public License as published by the Free Software Foundation. MuseReduce is distributed without explicit or implicit warranty. See the Affero General Public License at https://www.gnu.org/licenses/agpl-3.0.en.html for more details. */ -import $ from 'jquery' +import * as d3 from 'd3' import { getDrawContexts } from './app' -// Space drag implementation -let isDragging = false -let dragStartX = 0 -let dragStartY = 0 +// Space drag implementation using D3.js let currentContext = null -let initialTransform = null +let isDragging = false export function setupSpaceDrag() { const drawContexts = getDrawContexts() @@ -26,119 +23,70 @@ export function setupSpaceDrag() { layersContainer.style.cursor = 'grab' } + // Define the drag behavior + const drag = d3.drag() + .filter(() => window._spacePressed) // Only allow dragging when space is pressed + .on('start', startSpaceDrag) + .on('drag', moveSpaceDrag) + .on('end', endSpaceDrag) + drawContexts.forEach(context => { if (context && context.svg_elem) { - // Find the SVG container which is the parent of svg_elem - const svgContainer = context.svg_elem.closest('.svg_container') || context.svg_elem.parentNode - - // Change cursor on both elements + // Set cursor and store context context.svg_elem.style.cursor = 'grab' - if (svgContainer) { - svgContainer.style.cursor = 'grab' - - // Store context on both elements for easy access during drag - svgContainer._dragContext = context - context.svg_elem._dragContext = context + context.svg_elem._dragContext = context - // Attach mousedown to both container and SVG element - svgContainer.addEventListener('mousedown', startSpaceDrag) - context.svg_elem.addEventListener('mousedown', startSpaceDrag) + // Apply D3 drag behavior + d3.select(context.svg_elem).call(drag) - // Make sure svg element can receive events - context.svg_elem.style.pointerEvents = 'auto' - } + // Make sure svg element can receive events + context.svg_elem.style.pointerEvents = 'auto' } }) - - // Global event listeners - document.addEventListener('mousemove', moveSpaceDrag) - document.addEventListener('mouseup', endSpaceDrag) } -export function startSpaceDrag(event) { +function startSpaceDrag(event) { if (!window._spacePressed) return - event.preventDefault() + event.sourceEvent.preventDefault() + event.sourceEvent.stopPropagation() isDragging = true - dragStartX = event.clientX - dragStartY = event.clientY - - // First try to get the context from the element's _dragContext property - const container = event.currentTarget - - if (container && container._dragContext) { - currentContext = container._dragContext - } else { - // Fall back to finding context by traversing up the DOM - const svgContainer = event.target.closest('.svg_container') - - if (svgContainer) { - // Try to find the context from any stored context on the container - if (svgContainer._dragContext) { - currentContext = svgContainer._dragContext - } else { - // Fall back to search through all contexts - currentContext = getDrawContexts().find(ctx => { - return ctx.svg_elem.closest('.svg_container') === svgContainer - }) - } - } - } - if (currentContext) { - initialTransform = getComputedTransformMatrix(currentContext.svg_elem) + // Get the context from the element's _dragContext property + const container = event.sourceEvent.currentTarget + currentContext = container._dragContext + if (currentContext) { // Change cursor to indicate dragging document.body.style.cursor = 'grabbing' - if (currentContext.svg_elem) { - currentContext.svg_elem.style.cursor = 'grabbing' - } - - // Also change cursor on container - const container = currentContext.svg_elem.closest('.svg_container') - if (container) { - container.style.cursor = 'grabbing' - } + currentContext.svg_elem.style.cursor = 'grabbing' } } -export function moveSpaceDrag(event) { - if (!isDragging || !currentContext || !initialTransform) return - - const dx = event.clientX - dragStartX - const dy = event.clientY - dragStartY +function moveSpaceDrag(event) { + if (!isDragging || !currentContext) return - // Make sure we have the correct cursor during drag - document.body.style.cursor = 'grabbing' + // Get the current transform + const transformStr = currentContext.svg_elem.style.transform || 'matrix(1, 0, 0, 1, 0, 0)' + const matrix = parseCSSTransform(transformStr) - // Apply the new transform - const transform = initialTransform - const newTransform = `matrix(${transform.a}, ${transform.b}, ${transform.c}, ${transform.d}, ${transform.e + dx}, ${transform.f + dy})` + // Update the transform with the drag movement + const newTransform = `matrix(${matrix.a}, ${matrix.b}, ${matrix.c}, ${matrix.d}, ${matrix.e + event.dx}, ${matrix.f + event.dy})` currentContext.svg_elem.style.transform = newTransform } -export function endSpaceDrag() { +function endSpaceDrag() { if (!isDragging) return isDragging = false // Reset cursor styles document.body.style.cursor = '' - if (currentContext) { currentContext.svg_elem.style.cursor = 'grab' - - // Reset cursor on container too - const container = currentContext.svg_elem.closest('.svg_container') - if (container) { - container.style.cursor = 'grab' - } - currentContext = null } - - initialTransform = null } export function removeSpaceDrag() { @@ -157,8 +105,8 @@ export function removeSpaceDrag() { context.svg_elem.style.cursor = '' context.svg_elem.style.pointerEvents = '' - // Remove event listener from SVG element - context.svg_elem.removeEventListener('mousedown', startSpaceDrag) + // Remove D3 drag behavior + d3.select(context.svg_elem).on('.drag', null) // Clean up stored context on SVG element if (context.svg_elem._dragContext) { @@ -172,8 +120,8 @@ export function removeSpaceDrag() { // Reset cursor svgContainer.style.cursor = '' - // Remove event listener from container - svgContainer.removeEventListener('mousedown', startSpaceDrag) + // Remove D3 drag behavior + d3.select(svgContainer).on('.drag', null) // Clean up stored context on container if (svgContainer._dragContext) { @@ -183,26 +131,39 @@ export function removeSpaceDrag() { } }) - // Remove global event listeners - document.removeEventListener('mousemove', moveSpaceDrag) - document.removeEventListener('mouseup', endSpaceDrag) - + // Reset all state variables isDragging = false currentContext = null - initialTransform = null } -function getComputedTransformMatrix(element) { - try { - const style = window.getComputedStyle(element) +// Helper function to parse CSS transform matrix +function parseCSSTransform(transformStr) { + if (!transformStr || transformStr === 'none') { + return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 } // Identity matrix + } - // Handle case where transform is not set or "none" - if (!style.transform || style.transform === 'none') { - return new DOMMatrix() // Identity matrix + try { + // Handle matrix(...) format + if (transformStr.startsWith('matrix(')) { + const values = transformStr + .replace('matrix(', '') + .replace(')', '') + .split(',') + .map(v => parseFloat(v.trim())) + + return { + a: values[0], + b: values[1], + c: values[2], + d: values[3], + e: values[4], + f: values[5] + } } - return new DOMMatrix(style.transform) + // Return identity matrix if we can't parse it + return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 } } catch (e) { - return new DOMMatrix() // Return identity matrix on error + return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 } // Return identity matrix on error } } From ab7270e41c091fb95e1883f699cc9ce5de9118ed Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Tue, 15 Apr 2025 16:55:17 -0400 Subject: [PATCH 2/2] fix: make view listen to space drag instead of svg_container refs: #270 --- src/js/space_drag.js | 46 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/src/js/space_drag.js b/src/js/space_drag.js index 6efd5e2..5b15178 100644 --- a/src/js/space_drag.js +++ b/src/js/space_drag.js @@ -32,15 +32,19 @@ export function setupSpaceDrag() { drawContexts.forEach(context => { if (context && context.svg_elem) { + // Find the view element that contains this svg_elem + const viewElement = context.view_elem + if (!viewElement) return + // Set cursor and store context - context.svg_elem.style.cursor = 'grab' - context.svg_elem._dragContext = context + viewElement.style.cursor = 'grab' + viewElement._dragContext = context - // Apply D3 drag behavior - d3.select(context.svg_elem).call(drag) + // Apply D3 drag behavior to the view element instead of just the svg_elem + d3.select(viewElement).call(drag) - // Make sure svg element can receive events - context.svg_elem.style.pointerEvents = 'auto' + // Make sure both elements can receive events + viewElement.style.pointerEvents = 'auto' } }) } @@ -59,8 +63,7 @@ function startSpaceDrag(event) { if (currentContext) { // Change cursor to indicate dragging - document.body.style.cursor = 'grabbing' - currentContext.svg_elem.style.cursor = 'grabbing' + container.style.cursor = 'grabbing' } } @@ -83,8 +86,15 @@ function endSpaceDrag() { // Reset cursor styles document.body.style.cursor = '' + if (currentContext) { - currentContext.svg_elem.style.cursor = 'grab' + + // Find and reset the view element cursor as well + const viewElement = currentContext.view_elem + if (viewElement) { + viewElement.style.cursor = 'grab' + } + currentContext = null } } @@ -105,7 +115,23 @@ export function removeSpaceDrag() { context.svg_elem.style.cursor = '' context.svg_elem.style.pointerEvents = '' - // Remove D3 drag behavior + // Find the view element + const viewElement = context.view_elem + if (viewElement) { + // Reset cursor and pointer-events + viewElement.style.cursor = '' + viewElement.style.pointerEvents = '' + + // Remove D3 drag behavior + d3.select(viewElement).on('.drag', null) + + // Clean up stored context + if (viewElement._dragContext) { + delete viewElement._dragContext + } + } + + // Remove D3 drag behavior from SVG as well d3.select(context.svg_elem).on('.drag', null) // Clean up stored context on SVG element