1- // eslint-disable max-lines
1+ /* eslint-disable max-lines */
22import type { ComponentType , VNode , h as hType } from 'preact' ;
33// biome-ignore lint: needed for preact
44import { h } from 'preact' ; // eslint-disable-line @typescript-eslint/no-unused-vars
@@ -8,6 +8,10 @@ import type { Dialog } from '../../types';
88import { createScreenshotInputStyles } from './ScreenshotInput.css' ;
99import { useTakeScreenshot } from './useTakeScreenshot' ;
1010
11+ const CROP_BUTTON_SIZE = 30 ;
12+ const CROP_BUTTON_BORDER = 3 ;
13+ const CROP_BUTTON_OFFSET = CROP_BUTTON_SIZE + CROP_BUTTON_BORDER ;
14+
1115interface FactoryParams {
1216 h : typeof hType ;
1317 imageBuffer : HTMLCanvasElement ;
@@ -19,10 +23,10 @@ interface Props {
1923}
2024
2125interface Box {
22- startx : number ;
23- starty : number ;
24- endx : number ;
25- endy : number ;
26+ startX : number ;
27+ startY : number ;
28+ endX : number ;
29+ endY : number ;
2630}
2731
2832interface Rect {
@@ -34,10 +38,10 @@ interface Rect {
3438
3539const constructRect = ( box : Box ) : Rect => {
3640 return {
37- x : Math . min ( box . startx , box . endx ) ,
38- y : Math . min ( box . starty , box . endy ) ,
39- width : Math . abs ( box . startx - box . endx ) ,
40- height : Math . abs ( box . starty - box . endy ) ,
41+ x : Math . min ( box . startX , box . endX ) ,
42+ y : Math . min ( box . startY , box . endY ) ,
43+ width : Math . abs ( box . startX - box . endX ) ,
44+ height : Math . abs ( box . startY - box . endY ) ,
4145 } ;
4246} ;
4347
@@ -51,7 +55,7 @@ const getContainedSize = (img: HTMLCanvasElement): Box => {
5155 }
5256 const x = ( img . clientWidth - width ) / 2 ;
5357 const y = ( img . clientHeight - height ) / 2 ;
54- return { startx : x , starty : y , endx : width + x , endy : height + y } ;
58+ return { startX : x , startY : y , endX : width + x , endY : height + y } ;
5559} ;
5660
5761// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -62,7 +66,7 @@ export function makeScreenshotEditorComponent({ h, imageBuffer, dialog }: Factor
6266 const canvasContainerRef = useRef < HTMLDivElement > ( null ) ;
6367 const cropContainerRef = useRef < HTMLDivElement > ( null ) ;
6468 const croppingRef = useRef < HTMLCanvasElement > ( null ) ;
65- const [ croppingRect , setCroppingRect ] = useState < Box > ( { startx : 0 , starty : 0 , endx : 0 , endy : 0 } ) ;
69+ const [ croppingRect , setCroppingRect ] = useState < Box > ( { startX : 0 , startY : 0 , endX : 0 , endY : 0 } ) ;
6670 const [ confirmCrop , setConfirmCrop ] = useState ( false ) ;
6771
6872 useEffect ( ( ) => {
@@ -85,7 +89,7 @@ export function makeScreenshotEditorComponent({ h, imageBuffer, dialog }: Factor
8589 cropButton . style . top = `${ imageDimensions . y } px` ;
8690 }
8791
88- setCroppingRect ( { startx : 0 , starty : 0 , endx : imageDimensions . width , endy : imageDimensions . height } ) ;
92+ setCroppingRect ( { startX : 0 , startY : 0 , endX : imageDimensions . width , endY : imageDimensions . height } ) ;
8993 }
9094
9195 useEffect ( ( ) => {
@@ -118,44 +122,51 @@ export function makeScreenshotEditorComponent({ h, imageBuffer, dialog }: Factor
118122 setConfirmCrop ( false ) ;
119123 const handleMouseMove = makeHandleMouseMove ( corner ) ;
120124 const handleMouseUp = ( ) : void => {
121- croppingRef . current && croppingRef . current . removeEventListener ( 'mousemove' , handleMouseMove ) ;
125+ DOCUMENT . removeEventListener ( 'mousemove' , handleMouseMove ) ;
122126 DOCUMENT . removeEventListener ( 'mouseup' , handleMouseUp ) ;
123127 setConfirmCrop ( true ) ;
124128 } ;
125129
126130 DOCUMENT . addEventListener ( 'mouseup' , handleMouseUp ) ;
127- croppingRef . current && croppingRef . current . addEventListener ( 'mousemove' , handleMouseMove ) ;
131+ DOCUMENT . addEventListener ( 'mousemove' , handleMouseMove ) ;
128132 }
129133
130134 const makeHandleMouseMove = useCallback ( ( corner : string ) => {
131135 return function ( e : MouseEvent ) {
136+ if ( ! croppingRef . current ) {
137+ return ;
138+ }
139+ const cropCanvas = croppingRef . current ;
140+ const cropBoundingRect = cropCanvas . getBoundingClientRect ( ) ;
141+ const mouseX = e . clientX - cropBoundingRect . x ;
142+ const mouseY = e . clientY - cropBoundingRect . y ;
132143 switch ( corner ) {
133144 case 'topleft' :
134145 setCroppingRect ( prev => ( {
135146 ...prev ,
136- startx : Math . min ( e . offsetX , prev . endx - 30 ) ,
137- starty : Math . min ( e . offsetY , prev . endy - 30 ) ,
147+ startX : Math . min ( Math . max ( 0 , mouseX ) , prev . endX - CROP_BUTTON_OFFSET ) ,
148+ startY : Math . min ( Math . max ( 0 , mouseY ) , prev . endY - CROP_BUTTON_OFFSET ) ,
138149 } ) ) ;
139150 break ;
140151 case 'topright' :
141152 setCroppingRect ( prev => ( {
142153 ...prev ,
143- endx : Math . max ( e . offsetX , prev . startx + 30 ) ,
144- starty : Math . min ( e . offsetY , prev . endy - 30 ) ,
154+ endX : Math . max ( Math . min ( mouseX , cropCanvas . width ) , prev . startX + CROP_BUTTON_OFFSET ) ,
155+ startY : Math . min ( Math . max ( 0 , mouseY ) , prev . endY - CROP_BUTTON_OFFSET ) ,
145156 } ) ) ;
146157 break ;
147158 case 'bottomleft' :
148159 setCroppingRect ( prev => ( {
149160 ...prev ,
150- startx : Math . min ( e . offsetX , prev . endx - 30 ) ,
151- endy : Math . max ( e . offsetY , prev . starty + 30 ) ,
161+ startX : Math . min ( Math . max ( 0 , mouseX ) , prev . endX - CROP_BUTTON_OFFSET ) ,
162+ endY : Math . max ( Math . min ( mouseY , cropCanvas . height ) , prev . startY + CROP_BUTTON_OFFSET ) ,
152163 } ) ) ;
153164 break ;
154165 case 'bottomright' :
155166 setCroppingRect ( prev => ( {
156167 ...prev ,
157- endx : Math . max ( e . offsetX , prev . startx + 30 ) ,
158- endy : Math . max ( e . offsetY , prev . starty + 30 ) ,
168+ endX : Math . max ( Math . min ( mouseX , cropCanvas . width ) , prev . startX + CROP_BUTTON_OFFSET ) ,
169+ endY : Math . max ( Math . min ( mouseY , cropCanvas . height ) , prev . startY + CROP_BUTTON_OFFSET ) ,
159170 } ) ) ;
160171 break ;
161172 }
@@ -229,33 +240,33 @@ export function makeScreenshotEditorComponent({ h, imageBuffer, dialog }: Factor
229240 < div class = "cropButtonContainer" style = { { position : 'absolute' } } ref = { cropContainerRef } >
230241 < canvas style = { { position : 'absolute' } } ref = { croppingRef } > </ canvas >
231242 < CropCorner
232- left = { croppingRect . startx }
233- top = { croppingRect . starty }
243+ left = { croppingRect . startX }
244+ top = { croppingRect . startY }
234245 onGrabButton = { onGrabButton }
235246 corner = "topleft"
236247 > </ CropCorner >
237248 < CropCorner
238- left = { croppingRect . endx - 30 }
239- top = { croppingRect . starty }
249+ left = { croppingRect . endX - CROP_BUTTON_SIZE }
250+ top = { croppingRect . startY }
240251 onGrabButton = { onGrabButton }
241252 corner = "topright"
242253 > </ CropCorner >
243254 < CropCorner
244- left = { croppingRect . startx }
245- top = { croppingRect . endy - 30 }
255+ left = { croppingRect . startX }
256+ top = { croppingRect . endY - CROP_BUTTON_SIZE }
246257 onGrabButton = { onGrabButton }
247258 corner = "bottomleft"
248259 > </ CropCorner >
249260 < CropCorner
250- left = { croppingRect . endx - 30 }
251- top = { croppingRect . endy - 30 }
261+ left = { croppingRect . endX - CROP_BUTTON_SIZE }
262+ top = { croppingRect . endY - CROP_BUTTON_SIZE }
252263 onGrabButton = { onGrabButton }
253264 corner = "bottomright"
254265 > </ CropCorner >
255266 < div
256267 style = { {
257- left : Math . max ( 0 , croppingRect . endx - 191 ) ,
258- top : Math . max ( 0 , croppingRect . endy + 8 ) ,
268+ left : Math . max ( 0 , croppingRect . endX - 191 ) ,
269+ top : Math . max ( 0 , croppingRect . endY + 8 ) ,
259270 display : confirmCrop ? 'flex' : 'none' ,
260271 } }
261272 class = "crop-btn-group"
@@ -265,10 +276,10 @@ export function makeScreenshotEditorComponent({ h, imageBuffer, dialog }: Factor
265276 e . preventDefault ( ) ;
266277 if ( croppingRef . current ) {
267278 setCroppingRect ( {
268- startx : 0 ,
269- starty : 0 ,
270- endx : croppingRef . current . width ,
271- endy : croppingRef . current . height ,
279+ startX : 0 ,
280+ startY : 0 ,
281+ endX : croppingRef . current . width ,
282+ endY : croppingRef . current . height ,
272283 } ) ;
273284 }
274285 setConfirmCrop ( false ) ;
@@ -316,7 +327,8 @@ function CropCorner({
316327 borderLeft : corner === 'topleft' || corner === 'bottomleft' ? 'solid purple' : 'none' ,
317328 borderRight : corner === 'topright' || corner === 'bottomright' ? 'solid purple' : 'none' ,
318329 borderBottom : corner === 'bottomleft' || corner === 'bottomright' ? 'solid purple' : 'none' ,
319- borderWidth : '3px' ,
330+ borderWidth : `${ CROP_BUTTON_BORDER } px` ,
331+ cursor : corner === 'topleft' || corner === 'bottomright' ? 'nwse-resize' : 'nesw-resize' ,
320332 } }
321333 onMouseDown = { e => {
322334 e . preventDefault ( ) ;
0 commit comments