From e95810990284f194b191480d728ae67502ee6da2 Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Sat, 23 Nov 2024 18:16:18 +0000 Subject: [PATCH] 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 1ec9ee978b7..5eb561b3766 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}