From 0896400e1ae6af4ee7b77be6001d3f8ab107f783 Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Sat, 23 Nov 2024 18:16:18 +0000 Subject: [PATCH 1/3] fix(lib-classifier): canvas context for SVG drawing tools Make sure that `SVGContext.canvas` is a reference to the SVG `rect` element that's used as a drawing canvas, so that creating new drawing marks and editing existing marks both use the same screen CTM when calculating the pointer position. This fixes a bug in Firefox where the `draggable` decorator doesn't correctly track the active pointer, in the context of a zoomed SVG image. --- .../InteractionLayer/InteractionLayer.js | 86 +++++++++---------- .../SingleImageViewer/SingleImageViewer.js | 5 +- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/InteractionLayer/InteractionLayer.js b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/InteractionLayer/InteractionLayer.js index 1ec9ee978b..5eb561b376 100644 --- a/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/InteractionLayer/InteractionLayer.js +++ b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/InteractionLayer/InteractionLayer.js @@ -1,7 +1,9 @@ import cuid from 'cuid' import PropTypes from 'prop-types' -import { useRef, useState } from 'react'; +import { useContext, useRef, useState } from 'react'; import styled, { css } from 'styled-components' + +import SVGContext from '@plugins/drawingTools/shared/SVGContext' import DrawingToolMarks from './components/DrawingToolMarks' import TranscribedLines from './components/TranscribedLines' import SubTaskPopup from './components/SubTaskPopup' @@ -24,6 +26,38 @@ function cancelEvent(event) { event.stopPropagation() } +function createPoint(event) { + const { clientX, clientY } = event + // SVG 2 uses DOMPoint + if (window.DOMPointReadOnly) { + return new DOMPointReadOnly(clientX, clientY) + } + // jsdom doesn't support SVG + return { + x: clientX, + y: clientY + } +} + +function getEventOffset(event, canvas) { + const svgPoint = createPoint(event) + const svgEventOffset = svgPoint.matrixTransform + ? svgPoint.matrixTransform(canvas.getScreenCTM().inverse()) + : svgPoint + return svgEventOffset +} + +function convertEvent(event, canvas) { + const svgEventOffset = getEventOffset(event, canvas) + const svgCoordinateEvent = { + type: event.type, + x: svgEventOffset.x, + y: svgEventOffset.y + } + + return svgCoordinateEvent +} + function InteractionLayer({ activeMark, activeTool, @@ -43,7 +77,9 @@ function InteractionLayer({ duration }) { const [creating, setCreating] = useState(false) - const canvas = useRef() + const svgContext = useContext(SVGContext) + const canvasRef = useRef() + svgContext.canvas = canvasRef.current if (creating && !activeMark) { setCreating(false) @@ -54,42 +90,6 @@ function InteractionLayer({ setActiveMark(undefined) } - function convertEvent(event) { - const type = event.type - - const svgEventOffset = getEventOffset(event) - - const svgCoordinateEvent = { - pointerId: event.pointerId, - type, - x: svgEventOffset.x, - y: svgEventOffset.y - } - - return svgCoordinateEvent - } - - function createPoint(event) { - const { clientX, clientY } = event - // SVG 2 uses DOMPoint - if (window.DOMPointReadOnly) { - return new DOMPointReadOnly(clientX, clientY) - } - // jsdom doesn't support SVG - return { - x: clientX, - y: clientY - } - } - - function getEventOffset(event) { - const svgPoint = createPoint(event) - const svgEventOffset = svgPoint.matrixTransform - ? svgPoint.matrixTransform(canvas.current?.getScreenCTM().inverse()) - : svgPoint - return svgEventOffset - } - function createMark(event) { const timeStamp = getFixedNumber(played, 5) const mark = activeTool.createMark({ @@ -99,7 +99,7 @@ function InteractionLayer({ toolIndex: activeToolIndex }) - mark.initialPosition(convertEvent(event)) + mark.initialPosition(convertEvent(event, canvasRef.current)) setActiveMark(mark) setCreating(true) mark.setSubTaskVisibility(false) @@ -124,7 +124,7 @@ function InteractionLayer({ } if (creating) { - activeTool?.handlePointerDown?.(convertEvent(event), activeMark) + activeTool?.handlePointerDown?.(convertEvent(event, canvasRef.current), activeMark) if (activeMark.finished) onFinish(event) return true } @@ -136,7 +136,7 @@ function InteractionLayer({ function onPointerMove(event) { cancelEvent(event) if (creating) { - activeTool?.handlePointerMove?.(convertEvent(event), activeMark) + activeTool?.handlePointerMove?.(convertEvent(event, canvasRef.current), activeMark) } } @@ -149,7 +149,7 @@ function InteractionLayer({ function onPointerUp(event) { cancelEvent(event) if (creating) { - activeTool?.handlePointerUp?.(convertEvent(event), activeMark) + activeTool?.handlePointerUp?.(convertEvent(event, canvasRef.current), activeMark) if (activeMark.finished) onFinish(event) } } @@ -165,7 +165,7 @@ function InteractionLayer({ return ( <> + {zoomControlFn && ( {children} From 4fb445fcb1431248429ba90505c1a92b0efe3e4f Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Sat, 23 Nov 2024 18:42:08 +0000 Subject: [PATCH 2/3] DRY up SVG event-handling functions --- .../InteractionLayer/InteractionLayer.js | 33 +------------------ .../components/draggable/draggable.js | 2 +- 2 files changed, 2 insertions(+), 33 deletions(-) diff --git a/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/InteractionLayer/InteractionLayer.js b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/InteractionLayer/InteractionLayer.js index 5eb561b376..0fbc578b18 100644 --- a/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/InteractionLayer/InteractionLayer.js +++ b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/InteractionLayer/InteractionLayer.js @@ -4,6 +4,7 @@ import { useContext, useRef, useState } from 'react'; import styled, { css } from 'styled-components' import SVGContext from '@plugins/drawingTools/shared/SVGContext' +import { convertEvent } from '@plugins/drawingTools/components/draggable/draggable' import DrawingToolMarks from './components/DrawingToolMarks' import TranscribedLines from './components/TranscribedLines' import SubTaskPopup from './components/SubTaskPopup' @@ -26,38 +27,6 @@ function cancelEvent(event) { event.stopPropagation() } -function createPoint(event) { - const { clientX, clientY } = event - // SVG 2 uses DOMPoint - if (window.DOMPointReadOnly) { - return new DOMPointReadOnly(clientX, clientY) - } - // jsdom doesn't support SVG - return { - x: clientX, - y: clientY - } -} - -function getEventOffset(event, canvas) { - const svgPoint = createPoint(event) - const svgEventOffset = svgPoint.matrixTransform - ? svgPoint.matrixTransform(canvas.getScreenCTM().inverse()) - : svgPoint - return svgEventOffset -} - -function convertEvent(event, canvas) { - const svgEventOffset = getEventOffset(event, canvas) - const svgCoordinateEvent = { - type: event.type, - x: svgEventOffset.x, - y: svgEventOffset.y - } - - return svgCoordinateEvent -} - function InteractionLayer({ activeMark, activeTool, diff --git a/packages/lib-classifier/src/plugins/drawingTools/components/draggable/draggable.js b/packages/lib-classifier/src/plugins/drawingTools/components/draggable/draggable.js index b4b1fda3f4..35e142e7c6 100644 --- a/packages/lib-classifier/src/plugins/drawingTools/components/draggable/draggable.js +++ b/packages/lib-classifier/src/plugins/drawingTools/components/draggable/draggable.js @@ -22,7 +22,7 @@ function getEventOffset(event, canvas) { return svgEventOffset } -function convertEvent(event, canvas) { +export function convertEvent(event, canvas) { const svgEventOffset = getEventOffset(event, canvas) const svgCoordinateEvent = { type: event.type, From 471347d648fdf4c4c3effa14cd74e3156dcff150 Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Fri, 6 Dec 2024 13:00:09 +0000 Subject: [PATCH 3/3] revert changes to SingleImageViewer --- .../components/SingleImageViewer/SingleImageViewer.js | 5 ++++- .../plugins/drawingTools/components/draggable/draggable.js | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/SingleImageViewer/SingleImageViewer.js b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/SingleImageViewer/SingleImageViewer.js index 442e6418e4..5cf940f6af 100644 --- a/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/SingleImageViewer/SingleImageViewer.js +++ b/packages/lib-classifier/src/components/Classifier/components/SubjectViewer/components/SingleImageViewer/SingleImageViewer.js @@ -34,10 +34,12 @@ function SingleImageViewer({ zoomControlFn = null, zooming = false }) { + const canvasLayer = useRef() + const canvas = canvasLayer.current const transform = `rotate(${rotate} ${width / 2} ${height / 2})` return ( - + {zoomControlFn && ( {children} diff --git a/packages/lib-classifier/src/plugins/drawingTools/components/draggable/draggable.js b/packages/lib-classifier/src/plugins/drawingTools/components/draggable/draggable.js index 35e142e7c6..ee56d721af 100644 --- a/packages/lib-classifier/src/plugins/drawingTools/components/draggable/draggable.js +++ b/packages/lib-classifier/src/plugins/drawingTools/components/draggable/draggable.js @@ -16,8 +16,9 @@ function createPoint(event) { function getEventOffset(event, canvas) { const svgPoint = createPoint(event) - const svgEventOffset = svgPoint.matrixTransform - ? svgPoint.matrixTransform(canvas.getScreenCTM().inverse()) + const ctm = canvas?.getScreenCTM() + const svgEventOffset = ctm && svgPoint.matrixTransform + ? svgPoint.matrixTransform(ctm.inverse()) : svgPoint return svgEventOffset }