From 17b1ec9b7e193a582b62a0e5a0632173ab6f8754 Mon Sep 17 00:00:00 2001 From: Catherine Lee <55311782+c298lee@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:26:38 -0500 Subject: [PATCH 1/8] UF annotation: adds draw tool --- .../components/ScreenshotEditor.tsx | 120 ++++++++++++++++-- .../components/ScreenshotInput.css.ts | 4 + 2 files changed, 113 insertions(+), 11 deletions(-) diff --git a/packages/feedback/src/screenshot/components/ScreenshotEditor.tsx b/packages/feedback/src/screenshot/components/ScreenshotEditor.tsx index ef33e1b611b0..cddb29b3129f 100644 --- a/packages/feedback/src/screenshot/components/ScreenshotEditor.tsx +++ b/packages/feedback/src/screenshot/components/ScreenshotEditor.tsx @@ -80,15 +80,17 @@ export function ScreenshotEditorFactory({ const canvasContainerRef = hooks.useRef(null); const cropContainerRef = hooks.useRef(null); const croppingRef = hooks.useRef(null); + const annotatingRef = hooks.useRef(null); const [croppingRect, setCroppingRect] = hooks.useState({ startX: 0, startY: 0, endX: 0, endY: 0 }); const [confirmCrop, setConfirmCrop] = hooks.useState(false); const [isResizing, setIsResizing] = hooks.useState(false); + const [isAnnotating, setIsAnnotating] = hooks.useState(false); hooks.useEffect(() => { - WINDOW.addEventListener('resize', resizeCropper, false); + WINDOW.addEventListener('resize', resize, false); }, []); - function resizeCropper(): void { + function resize(): void { const cropper = croppingRef.current; const imageDimensions = constructRect(getContainedSize(imageBuffer)); if (cropper) { @@ -102,13 +104,25 @@ export function ScreenshotEditorFactory({ } } - const cropButton = cropContainerRef.current; - if (cropButton) { - cropButton.style.width = `${imageDimensions.width}px`; - cropButton.style.height = `${imageDimensions.height}px`; + const cropContainer = cropContainerRef.current; + if (cropContainer) { + cropContainer.style.width = `${imageDimensions.width}px`; + cropContainer.style.height = `${imageDimensions.height}px`; } setCroppingRect({ startX: 0, startY: 0, endX: imageDimensions.width, endY: imageDimensions.height }); + + const annotater = annotatingRef.current; + if (annotater) { + annotater.width = imageDimensions.width * DPI; + annotater.height = imageDimensions.height * DPI; + annotater.style.width = `${imageDimensions.width}px`; + annotater.style.height = `${imageDimensions.height}px`; + const ctx = annotater.getContext('2d'); + if (ctx) { + ctx.scale(DPI, DPI); + } + } } hooks.useEffect(() => { @@ -141,6 +155,7 @@ export function ScreenshotEditorFactory({ }, [croppingRect]); function onGrabButton(e: Event, corner: string): void { + setIsAnnotating(false); setConfirmCrop(false); setIsResizing(true); const handleMouseMove = makeHandleMouseMove(corner); @@ -247,7 +262,48 @@ export function ScreenshotEditorFactory({ DOCUMENT.addEventListener('mouseup', handleMouseUp); } - function submit(): void { + function onAnnotateStart(e: MouseEvent): void { + if (!isAnnotating) return; + + const handleMouseMove = (moveEvent: MouseEvent): void => { + console.log(moveEvent.clientX, moveEvent.clientY); + const annotateCanvas = annotatingRef.current; + if (annotateCanvas) { + const rect = annotateCanvas.getBoundingClientRect(); + + const x = moveEvent.clientX - rect.x; + const y = moveEvent.clientY - rect.y; + + const ctx = annotateCanvas.getContext('2d'); + if (ctx) { + ctx.lineTo(x, y); + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(x, y); + } + } + }; + + const handleMouseUp = (): void => { + const ctx = annotatingRef.current?.getContext('2d'); + // starts a new path so on next mouse down, the lines won't connect + if (ctx) { + ctx.beginPath(); + } + + // draws the annotation onto the image buffer + // TODO: move this to a better place + submitAnnotate(); + + DOCUMENT.removeEventListener('mousemove', handleMouseMove); + DOCUMENT.removeEventListener('mouseup', handleMouseUp); + }; + + DOCUMENT.addEventListener('mousemove', handleMouseMove); + DOCUMENT.addEventListener('mouseup', handleMouseUp); + } + + function submitCrop(): void { const cutoutCanvas = DOCUMENT.createElement('canvas'); const imageBox = constructRect(getContainedSize(imageBuffer)); const croppingBox = constructRect(croppingRect); @@ -277,7 +333,32 @@ export function ScreenshotEditorFactory({ imageBuffer.style.width = `${croppingBox.width}px`; imageBuffer.style.height = `${croppingBox.height}px`; ctx.drawImage(cutoutCanvas, 0, 0); - resizeCropper(); + resize(); + } + } + + function submitAnnotate(): void { + // draw the annotations onto the image (ie "squash" the canvases) + const imageCtx = imageBuffer.getContext('2d'); + const annotateCanvas = annotatingRef.current; + if (imageCtx && annotateCanvas) { + imageCtx.drawImage( + annotateCanvas, + 0, + 0, + annotateCanvas.width, + annotateCanvas.height, + 0, + 0, + imageBuffer.width, + imageBuffer.height, + ); + + // clear the annotation canvas + const annotateCtx = annotateCanvas.getContext('2d'); + if (annotateCtx) { + annotateCtx.clearRect(0, 0, annotateCanvas.width, annotateCanvas.height); + } } } @@ -303,7 +384,7 @@ export function ScreenshotEditorFactory({ (dialog.el as HTMLElement).style.display = 'block'; const container = canvasContainerRef.current; container?.appendChild(imageBuffer); - resizeCropper(); + resize(); }, []), onError: hooks.useCallback(error => { (dialog.el as HTMLElement).style.display = 'block'; @@ -314,8 +395,20 @@ export function ScreenshotEditorFactory({ return (