Skip to content

Commit 17b1ec9

Browse files
committed
UF annotation: adds draw tool
1 parent 4b0d06f commit 17b1ec9

File tree

2 files changed

+113
-11
lines changed

2 files changed

+113
-11
lines changed

packages/feedback/src/screenshot/components/ScreenshotEditor.tsx

Lines changed: 109 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,17 @@ export function ScreenshotEditorFactory({
8080
const canvasContainerRef = hooks.useRef<HTMLDivElement>(null);
8181
const cropContainerRef = hooks.useRef<HTMLDivElement>(null);
8282
const croppingRef = hooks.useRef<HTMLCanvasElement>(null);
83+
const annotatingRef = hooks.useRef<HTMLCanvasElement>(null);
8384
const [croppingRect, setCroppingRect] = hooks.useState<Box>({ startX: 0, startY: 0, endX: 0, endY: 0 });
8485
const [confirmCrop, setConfirmCrop] = hooks.useState(false);
8586
const [isResizing, setIsResizing] = hooks.useState(false);
87+
const [isAnnotating, setIsAnnotating] = hooks.useState(false);
8688

8789
hooks.useEffect(() => {
88-
WINDOW.addEventListener('resize', resizeCropper, false);
90+
WINDOW.addEventListener('resize', resize, false);
8991
}, []);
9092

91-
function resizeCropper(): void {
93+
function resize(): void {
9294
const cropper = croppingRef.current;
9395
const imageDimensions = constructRect(getContainedSize(imageBuffer));
9496
if (cropper) {
@@ -102,13 +104,25 @@ export function ScreenshotEditorFactory({
102104
}
103105
}
104106

105-
const cropButton = cropContainerRef.current;
106-
if (cropButton) {
107-
cropButton.style.width = `${imageDimensions.width}px`;
108-
cropButton.style.height = `${imageDimensions.height}px`;
107+
const cropContainer = cropContainerRef.current;
108+
if (cropContainer) {
109+
cropContainer.style.width = `${imageDimensions.width}px`;
110+
cropContainer.style.height = `${imageDimensions.height}px`;
109111
}
110112

111113
setCroppingRect({ startX: 0, startY: 0, endX: imageDimensions.width, endY: imageDimensions.height });
114+
115+
const annotater = annotatingRef.current;
116+
if (annotater) {
117+
annotater.width = imageDimensions.width * DPI;
118+
annotater.height = imageDimensions.height * DPI;
119+
annotater.style.width = `${imageDimensions.width}px`;
120+
annotater.style.height = `${imageDimensions.height}px`;
121+
const ctx = annotater.getContext('2d');
122+
if (ctx) {
123+
ctx.scale(DPI, DPI);
124+
}
125+
}
112126
}
113127

114128
hooks.useEffect(() => {
@@ -141,6 +155,7 @@ export function ScreenshotEditorFactory({
141155
}, [croppingRect]);
142156

143157
function onGrabButton(e: Event, corner: string): void {
158+
setIsAnnotating(false);
144159
setConfirmCrop(false);
145160
setIsResizing(true);
146161
const handleMouseMove = makeHandleMouseMove(corner);
@@ -247,7 +262,48 @@ export function ScreenshotEditorFactory({
247262
DOCUMENT.addEventListener('mouseup', handleMouseUp);
248263
}
249264

250-
function submit(): void {
265+
function onAnnotateStart(e: MouseEvent): void {
266+
if (!isAnnotating) return;
267+
268+
const handleMouseMove = (moveEvent: MouseEvent): void => {
269+
console.log(moveEvent.clientX, moveEvent.clientY);
270+
const annotateCanvas = annotatingRef.current;
271+
if (annotateCanvas) {
272+
const rect = annotateCanvas.getBoundingClientRect();
273+
274+
const x = moveEvent.clientX - rect.x;
275+
const y = moveEvent.clientY - rect.y;
276+
277+
const ctx = annotateCanvas.getContext('2d');
278+
if (ctx) {
279+
ctx.lineTo(x, y);
280+
ctx.stroke();
281+
ctx.beginPath();
282+
ctx.moveTo(x, y);
283+
}
284+
}
285+
};
286+
287+
const handleMouseUp = (): void => {
288+
const ctx = annotatingRef.current?.getContext('2d');
289+
// starts a new path so on next mouse down, the lines won't connect
290+
if (ctx) {
291+
ctx.beginPath();
292+
}
293+
294+
// draws the annotation onto the image buffer
295+
// TODO: move this to a better place
296+
submitAnnotate();
297+
298+
DOCUMENT.removeEventListener('mousemove', handleMouseMove);
299+
DOCUMENT.removeEventListener('mouseup', handleMouseUp);
300+
};
301+
302+
DOCUMENT.addEventListener('mousemove', handleMouseMove);
303+
DOCUMENT.addEventListener('mouseup', handleMouseUp);
304+
}
305+
306+
function submitCrop(): void {
251307
const cutoutCanvas = DOCUMENT.createElement('canvas');
252308
const imageBox = constructRect(getContainedSize(imageBuffer));
253309
const croppingBox = constructRect(croppingRect);
@@ -277,7 +333,32 @@ export function ScreenshotEditorFactory({
277333
imageBuffer.style.width = `${croppingBox.width}px`;
278334
imageBuffer.style.height = `${croppingBox.height}px`;
279335
ctx.drawImage(cutoutCanvas, 0, 0);
280-
resizeCropper();
336+
resize();
337+
}
338+
}
339+
340+
function submitAnnotate(): void {
341+
// draw the annotations onto the image (ie "squash" the canvases)
342+
const imageCtx = imageBuffer.getContext('2d');
343+
const annotateCanvas = annotatingRef.current;
344+
if (imageCtx && annotateCanvas) {
345+
imageCtx.drawImage(
346+
annotateCanvas,
347+
0,
348+
0,
349+
annotateCanvas.width,
350+
annotateCanvas.height,
351+
0,
352+
0,
353+
imageBuffer.width,
354+
imageBuffer.height,
355+
);
356+
357+
// clear the annotation canvas
358+
const annotateCtx = annotateCanvas.getContext('2d');
359+
if (annotateCtx) {
360+
annotateCtx.clearRect(0, 0, annotateCanvas.width, annotateCanvas.height);
361+
}
281362
}
282363
}
283364

@@ -303,7 +384,7 @@ export function ScreenshotEditorFactory({
303384
(dialog.el as HTMLElement).style.display = 'block';
304385
const container = canvasContainerRef.current;
305386
container?.appendChild(imageBuffer);
306-
resizeCropper();
387+
resize();
307388
}, []),
308389
onError: hooks.useCallback(error => {
309390
(dialog.el as HTMLElement).style.display = 'block';
@@ -314,8 +395,20 @@ export function ScreenshotEditorFactory({
314395
return (
315396
<div class="editor">
316397
<style nonce={options.styleNonce} dangerouslySetInnerHTML={styles} />
398+
<button
399+
class="editor__pen-tool"
400+
style={{ background: isAnnotating ? 'red' : 'white' }}
401+
onClick={e => {
402+
e.preventDefault();
403+
setIsAnnotating(!isAnnotating);
404+
}}
405+
></button>
317406
<div class="editor__canvas-container" ref={canvasContainerRef}>
318-
<div class="editor__crop-container" style={{ position: 'absolute', zIndex: 1 }} ref={cropContainerRef}>
407+
<div
408+
class="editor__crop-container"
409+
style={{ position: 'absolute', zIndex: isAnnotating ? 1 : 2 }}
410+
ref={cropContainerRef}
411+
>
319412
<canvas
320413
onMouseDown={onDragStart}
321414
style={{ position: 'absolute', cursor: confirmCrop ? 'move' : 'auto' }}
@@ -373,7 +466,7 @@ export function ScreenshotEditorFactory({
373466
<button
374467
onClick={e => {
375468
e.preventDefault();
376-
submit();
469+
submitCrop();
377470
setConfirmCrop(false);
378471
}}
379472
class="btn btn--primary"
@@ -382,6 +475,11 @@ export function ScreenshotEditorFactory({
382475
</button>
383476
</div>
384477
</div>
478+
<canvas
479+
onMouseDown={onAnnotateStart}
480+
style={{ position: 'absolute', zIndex: isAnnotating ? '2' : '1' }}
481+
ref={annotatingRef}
482+
></canvas>
385483
</div>
386484
</div>
387485
);

packages/feedback/src/screenshot/components/ScreenshotInput.css.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ export function createScreenshotInputStyles(styleNonce?: string): HTMLStyleEleme
8484
border-left: none;
8585
border-top: none;
8686
}
87+
.editor__pen-tool {
88+
width: 30px;
89+
height: 30px;
90+
}
8791
`;
8892

8993
if (styleNonce) {

0 commit comments

Comments
 (0)