@@ -80,15 +80,17 @@ export function ScreenshotEditorFactory({
80
80
const canvasContainerRef = hooks . useRef < HTMLDivElement > ( null ) ;
81
81
const cropContainerRef = hooks . useRef < HTMLDivElement > ( null ) ;
82
82
const croppingRef = hooks . useRef < HTMLCanvasElement > ( null ) ;
83
+ const annotatingRef = hooks . useRef < HTMLCanvasElement > ( null ) ;
83
84
const [ croppingRect , setCroppingRect ] = hooks . useState < Box > ( { startX : 0 , startY : 0 , endX : 0 , endY : 0 } ) ;
84
85
const [ confirmCrop , setConfirmCrop ] = hooks . useState ( false ) ;
85
86
const [ isResizing , setIsResizing ] = hooks . useState ( false ) ;
87
+ const [ isAnnotating , setIsAnnotating ] = hooks . useState ( false ) ;
86
88
87
89
hooks . useEffect ( ( ) => {
88
- WINDOW . addEventListener ( 'resize' , resizeCropper , false ) ;
90
+ WINDOW . addEventListener ( 'resize' , resize , false ) ;
89
91
} , [ ] ) ;
90
92
91
- function resizeCropper ( ) : void {
93
+ function resize ( ) : void {
92
94
const cropper = croppingRef . current ;
93
95
const imageDimensions = constructRect ( getContainedSize ( imageBuffer ) ) ;
94
96
if ( cropper ) {
@@ -102,13 +104,25 @@ export function ScreenshotEditorFactory({
102
104
}
103
105
}
104
106
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` ;
109
111
}
110
112
111
113
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
+ }
112
126
}
113
127
114
128
hooks . useEffect ( ( ) => {
@@ -141,6 +155,7 @@ export function ScreenshotEditorFactory({
141
155
} , [ croppingRect ] ) ;
142
156
143
157
function onGrabButton ( e : Event , corner : string ) : void {
158
+ setIsAnnotating ( false ) ;
144
159
setConfirmCrop ( false ) ;
145
160
setIsResizing ( true ) ;
146
161
const handleMouseMove = makeHandleMouseMove ( corner ) ;
@@ -247,7 +262,48 @@ export function ScreenshotEditorFactory({
247
262
DOCUMENT . addEventListener ( 'mouseup' , handleMouseUp ) ;
248
263
}
249
264
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 {
251
307
const cutoutCanvas = DOCUMENT . createElement ( 'canvas' ) ;
252
308
const imageBox = constructRect ( getContainedSize ( imageBuffer ) ) ;
253
309
const croppingBox = constructRect ( croppingRect ) ;
@@ -277,7 +333,32 @@ export function ScreenshotEditorFactory({
277
333
imageBuffer . style . width = `${ croppingBox . width } px` ;
278
334
imageBuffer . style . height = `${ croppingBox . height } px` ;
279
335
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
+ }
281
362
}
282
363
}
283
364
@@ -303,7 +384,7 @@ export function ScreenshotEditorFactory({
303
384
( dialog . el as HTMLElement ) . style . display = 'block' ;
304
385
const container = canvasContainerRef . current ;
305
386
container ?. appendChild ( imageBuffer ) ;
306
- resizeCropper ( ) ;
387
+ resize ( ) ;
307
388
} , [ ] ) ,
308
389
onError : hooks . useCallback ( error => {
309
390
( dialog . el as HTMLElement ) . style . display = 'block' ;
@@ -314,8 +395,20 @@ export function ScreenshotEditorFactory({
314
395
return (
315
396
< div class = "editor" >
316
397
< 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 >
317
406
< 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
+ >
319
412
< canvas
320
413
onMouseDown = { onDragStart }
321
414
style = { { position : 'absolute' , cursor : confirmCrop ? 'move' : 'auto' } }
@@ -373,7 +466,7 @@ export function ScreenshotEditorFactory({
373
466
< button
374
467
onClick = { e => {
375
468
e . preventDefault ( ) ;
376
- submit ( ) ;
469
+ submitCrop ( ) ;
377
470
setConfirmCrop ( false ) ;
378
471
} }
379
472
class = "btn btn--primary"
@@ -382,6 +475,11 @@ export function ScreenshotEditorFactory({
382
475
</ button >
383
476
</ div >
384
477
</ div >
478
+ < canvas
479
+ onMouseDown = { onAnnotateStart }
480
+ style = { { position : 'absolute' , zIndex : isAnnotating ? '2' : '1' } }
481
+ ref = { annotatingRef }
482
+ > </ canvas >
385
483
</ div >
386
484
</ div >
387
485
) ;
0 commit comments