Skip to content

Commit

Permalink
fix(lib-classifier): canvas context for SVG drawing tools
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
eatyourgreens committed Dec 19, 2024
1 parent 2150c33 commit 05aaf2c
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 47 deletions.
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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,
Expand All @@ -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)
Expand All @@ -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({
Expand All @@ -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)
Expand All @@ -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
}
Expand All @@ -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)
}
}

Expand All @@ -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)
}
}
Expand All @@ -165,7 +165,7 @@ function InteractionLayer({
return (
<>
<DrawingCanvas
ref={canvas}
ref={canvasRef}
disabled={disabled || move}
pointerEvents={move ? 'none' : 'all'}
width={width}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,10 @@ function SingleImageViewer({
zoomControlFn = null,
zooming = false
}) {
const canvasLayer = useRef()
const canvas = canvasLayer.current
const transform = `rotate(${rotate} ${width / 2} ${height / 2})`

return (
<SVGContext.Provider value={{ canvas, viewBox, rotate, width, height }}>
<SVGContext.Provider value={{ viewBox, rotate, width, height }}>
{zoomControlFn && (
<ZoomControlButton
onClick={zoomControlFn}
Expand Down Expand Up @@ -68,7 +66,6 @@ function SingleImageViewer({
transform={transform}
>
<SVGImageCanvas
ref={canvasLayer}
viewBox={viewBox}
>
{children}
Expand Down

0 comments on commit 05aaf2c

Please sign in to comment.