@@ -5,30 +5,31 @@ import { getRandomColor } from './utils'
55// @ts -expect-error -> no support for EaselJS in TypeScript -> https://github.com/CreateJS/EaselJS/issues/796
66import { Stage , Shape , Ticker } from '@createjs/easeljs'
77import { Shape as ShapeInterface } from './interfaces'
8- import { ShapeType } from './utils/constants'
8+ import { defaultPathThickness , ShapeType } from './utils/constants'
99import Sidebar from './components/Sidebar'
1010import ThreeJSViewer from './components/ThreeJSViewer'
11+ import { isEmpty } from 'lodash'
1112
1213const Canvas : React . FC = ( ) => {
13- const pathThickness = 25
14-
14+ const [ pathThickness , setPathThickness ] = useState ( defaultPathThickness )
1515 const canvasRef = useRef < HTMLCanvasElement > ( null )
1616 const stageRef = useRef < Stage | null > ( null )
1717 const [ shapes , setShapes ] = useState < ShapeInterface [ ] > ( [ ] )
1818 const [ selectedShape , setSelectedShape ] = useState < ShapeInterface | null > ( null )
1919 const [ shapeType , setShapeType ] = useState < ShapeType > ( 'rectangle' )
20- const [ currentPath , setCurrentPath ] = useState < Shape | null > ( null )
21- const [ pathPoints , setPathPoints ] = useState < { x : number ; y : number } [ ] > ( [ ] )
2220 const [ isDrawing , setIsDrawing ] = useState ( false )
2321 const [ startPoint , setStartPoint ] = useState < { x : number ; y : number } | null > ( null )
2422 const [ currentShape , setCurrentShape ] = useState < Shape | null > ( null )
2523 const [ is3DMode , setIs3DMode ] = useState ( false )
2624 const pathColor = useRef ( getRandomColor ( ) )
2725
2826 const selectedIdRef = useRef < number | undefined > ( undefined )
27+ const pathPointsRef = useRef < { x : number ; y : number } [ ] > ( [ ] )
2928
3029 const toggleViewMode = ( ) => setIs3DMode ( ! is3DMode )
3130
31+ const getStrokeThickness = ( thickness ?: number ) => thickness ?? pathThickness
32+
3233 // Function to handle shape creation
3334 const createShape = useCallback (
3435 ( props : ShapeInterface ) => {
@@ -49,12 +50,13 @@ const Canvas: React.FC = () => {
4950 if ( props . endX === undefined || props . endY === undefined ) return
5051
5152 g . beginStroke ( props . strokeColor )
53+ . setStrokeStyle ( getStrokeThickness ( props ?. thickness ) )
5254 . moveTo ( 0 , 0 )
5355 . lineTo ( props . endX - props . x , props . endY - props . y )
5456 break
5557 case 'path' :
5658 if ( props ?. points && props . points ?. length > 1 ) {
57- g . beginStroke ( props . strokeColor ) . setStrokeStyle ( pathThickness )
59+ g . beginStroke ( props . strokeColor ) . setStrokeStyle ( getStrokeThickness ( props ?. thickness ) )
5860 g . moveTo ( props . points [ 0 ] . x , props . points [ 0 ] . y )
5961
6062 props . points . forEach ( ( point : { x : number ; y : number } , index : number ) => {
@@ -102,6 +104,7 @@ const Canvas: React.FC = () => {
102104 const g = this . graphics
103105 g . clear ( )
104106 . beginStroke ( props . strokeColor )
107+ . setStrokeStyle ( getStrokeThickness ( props ?. thickness ) )
105108 . moveTo ( 0 , 0 )
106109 . lineTo ( props . endX - props . x , props . endY - props . y )
107110 } else if ( props . type === 'path' && props . points ) {
@@ -163,20 +166,38 @@ const Canvas: React.FC = () => {
163166 const g = currentShape . graphics
164167 g . clear ( )
165168
169+ const { x : startX , y : startY } = startPoint
170+ let thickness = pathThickness
171+
172+ if ( [ 'line' , 'path' ] . includes ( shapeType ) && currentShape ?. graphics ?. _strokeStyle ?. width ) {
173+ thickness = currentShape ?. graphics ?. _strokeStyle ?. width
174+ }
175+
166176 switch ( shapeType ) {
167177 case 'rectangle' :
168178 g . beginFill ( pathColor . current )
169179 . beginStroke ( pathColor . current )
170- . drawRect ( startPoint . x , startPoint . y , x - startPoint . x , y - startPoint . y )
180+ . drawRect ( startX , startY , x - startX , y - startY )
171181 break
172182 case 'circle' : {
173- const radius = Math . sqrt ( Math . pow ( x - startPoint . x , 2 ) + Math . pow ( y - startPoint . y , 2 ) )
174- g . beginFill ( pathColor . current ) . beginStroke ( pathColor . current ) . drawCircle ( startPoint . x , startPoint . y , radius )
183+ const radius = Math . sqrt ( Math . pow ( x - startX , 2 ) + Math . pow ( y - startY , 2 ) )
184+ g . beginFill ( pathColor . current ) . beginStroke ( pathColor . current ) . drawCircle ( startX , startY , radius )
175185 break
176186 }
177187 case 'line' :
178- g . beginStroke ( pathColor . current ) . moveTo ( startPoint . x , startPoint . y ) . lineTo ( x , y )
188+ g . beginStroke ( pathColor . current ) . setStrokeStyle ( thickness ) . moveTo ( startX , startY ) . lineTo ( x , y )
179189 break
190+ case 'path' : {
191+ const newPoints = g . beginStroke ( pathColor . current ) . setStrokeStyle ( thickness )
192+
193+ if ( isEmpty ( pathPointsRef . current ) ) {
194+ newPoints . moveTo ( startX , startY )
195+ } else {
196+ pathPointsRef . current . forEach ( point => newPoints . lineTo ( point . x , point . y ) )
197+ }
198+
199+ newPoints . lineTo ( x , y )
200+ }
180201 }
181202
182203 stageRef . current ?. update ( )
@@ -214,11 +235,12 @@ const Canvas: React.FC = () => {
214235 shapeProps = {
215236 type : 'line' ,
216237 fillColor : 'transparent' ,
217- strokeColor : getRandomColor ( ) ,
238+ strokeColor : pathColor . current ,
218239 x : startPoint . x ,
219240 y : startPoint . y ,
220241 endX : x ,
221242 endY : y ,
243+ thickness : pathThickness ,
222244 }
223245 break
224246 default :
@@ -233,57 +255,21 @@ const Canvas: React.FC = () => {
233255 stageRef . current ?. update ( )
234256 }
235257
236- const updatePath = ( x : number , y : number ) => {
237- if ( currentPath && pathPoints . length > 0 ) {
238- const g = currentPath . graphics
239- g . clear ( ) . beginStroke ( pathColor . current ) . setStrokeStyle ( pathThickness )
240- g . moveTo ( pathPoints [ 0 ] . x , pathPoints [ 0 ] . y )
241- g . lineTo ( x , y ) // Draw the latest line to the current mouse point
242-
243- stageRef . current ?. update ( ) // Refresh stage
244- }
245- }
246-
247- const endPath = ( x : number , y : number ) => {
248- if ( currentPath && pathPoints . length > 0 ) {
249- const newPoints = [ ...pathPoints , { x, y } ]
250-
251- createShape ( {
252- type : 'path' ,
253- fillColor : 'transparent' ,
254- strokeColor : pathColor . current ,
255- x : 0 ,
256- y : 0 ,
257- points : newPoints ,
258- } )
259-
260- stageRef . current ?. removeChild ( currentPath )
261- setPathPoints ( [ ] )
262- setCurrentPath ( null )
263- setIsDrawing ( false )
264- stageRef . current ?. update ( )
265- }
266- }
267-
268258 // Mouse event handlers
269259 const handleCanvasMouseDown = useCallback (
270260 ( event : React . MouseEvent < HTMLCanvasElement > ) => {
271- pathColor . current = getRandomColor ( )
272-
261+ if ( ! isDrawing ) pathColor . current = getRandomColor ( )
273262 const { offsetX, offsetY } = event . nativeEvent
263+
264+ if ( shapeType === 'path' ) {
265+ pathPointsRef . current . push ( { x : offsetX , y : offsetY } )
266+ }
267+
274268 const clickedShape = stageRef . current ?. getObjectsUnderPoint ( offsetX , offsetY , 1 ) ?. [ 0 ] as ShapeInterface
275269
276270 if ( clickedShape ) {
277271 selectedIdRef . current = clickedShape ?. id as number
278-
279272 setSelectedShape ( clickedShape )
280- } else if ( shapeType === 'path' ) {
281- if ( ! isDrawing ) {
282- startPath ( offsetX , offsetY )
283- } else {
284- endPath ( offsetX , offsetY )
285- startPath ( offsetX , offsetY )
286- }
287273 } else {
288274 startDrawing ( offsetX , offsetY )
289275 }
@@ -296,11 +282,7 @@ const Canvas: React.FC = () => {
296282 const { offsetX, offsetY } = event . nativeEvent
297283
298284 if ( isDrawing ) {
299- if ( shapeType === 'path' ) {
300- updatePath ( offsetX , offsetY )
301- } else {
302- draw ( offsetX , offsetY )
303- }
285+ draw ( offsetX , offsetY )
304286 }
305287 } ,
306288 [ isDrawing , shapeType ]
@@ -312,7 +294,7 @@ const Canvas: React.FC = () => {
312294
313295 if ( isDrawing ) {
314296 if ( shapeType === 'path' ) {
315- endPath ( offsetX , offsetY )
297+ pathPointsRef . current = [ ... pathPointsRef . current , { x : offsetX , y : offsetY } ]
316298 } else {
317299 endDrawing ( offsetX , offsetY )
318300 }
@@ -339,16 +321,6 @@ const Canvas: React.FC = () => {
339321 [ isDrawing , shapeType ]
340322 )
341323
342- const startPath = ( x : number , y : number ) => {
343- const newPath = new Shape ( )
344- newPath . graphics . beginStroke ( pathColor . current ) . setStrokeStyle ( pathThickness )
345- stageRef . current ?. addChild ( newPath )
346-
347- setCurrentPath ( newPath )
348- setPathPoints ( [ { x, y } ] )
349- setIsDrawing ( true ) // Ensure we are in drawing mode
350- }
351-
352324 // Initialize canvas and attach event handlers
353325 useEffect ( ( ) => {
354326 if ( canvasRef . current ) {
@@ -380,9 +352,8 @@ const Canvas: React.FC = () => {
380352
381353 setShapes ( [ ] )
382354 setSelectedShape ( null )
383- setCurrentPath ( null )
384- setPathPoints ( [ ] )
385355 setIsDrawing ( false )
356+ pathPointsRef . current = [ ]
386357 } , [ ] )
387358
388359 // Keyboard delete, backspace handlers
@@ -411,28 +382,42 @@ const Canvas: React.FC = () => {
411382 event . preventDefault ( )
412383
413384 if ( isDrawing ) {
385+ if ( shapeType === 'path' && Array . isArray ( pathPointsRef . current ) && pathPointsRef . current . length > 1 ) {
386+ const current = {
387+ type : 'path' ,
388+ fillColor : pathColor . current ,
389+ strokeColor : pathColor . current ,
390+ x : 0 ,
391+ y : 0 ,
392+ points : pathPointsRef . current ,
393+ instance : stageRef . current ?. children [ stageRef . current ?. children . length - 1 ] as Shape ,
394+ thickness : pathThickness ,
395+ }
396+
397+ stageRef . current ?. removeChild ( currentShape )
398+ stageRef . current ?. update ( )
399+
400+ createShape ( {
401+ ...current ,
402+ } )
403+
404+ pathPointsRef . current = [ ]
405+ setStartPoint ( null )
406+ }
407+
414408 setIsDrawing ( false )
415- setCurrentPath ( null )
416- setPathPoints ( [ ] )
417409 }
418410 }
419411
420412 window . addEventListener ( 'contextmenu' , handleRightClick )
421413
422- if ( ! isDrawing ) {
423- setCurrentPath ( null )
424- setPathPoints ( [ ] )
425- }
426-
427414 return ( ) => {
428415 window . removeEventListener ( 'contextmenu' , handleRightClick )
429416 }
430417 } , [ isDrawing ] )
431418
432419 useEffect ( ( ) => {
433- if ( shapeType !== 'path' ) {
434- setIsDrawing ( false )
435- }
420+ setIsDrawing ( false )
436421 } , [ shapeType ] )
437422
438423 return (
@@ -446,6 +431,8 @@ const Canvas: React.FC = () => {
446431 shapeType = { shapeType }
447432 toggleViewMode = { toggleViewMode }
448433 is3DMode = { is3DMode }
434+ pathThickness = { pathThickness }
435+ setPathThickness = { setPathThickness }
449436 />
450437 < div
451438 id = 'CanvasContainer'
0 commit comments