Skip to content

Commit 02e764b

Browse files
committed
feat: enhance path drawing with flexible handling and live updates
1 parent 5969d1c commit 02e764b

File tree

2 files changed

+57
-77
lines changed

2 files changed

+57
-77
lines changed

src/App.tsx

Lines changed: 52 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Shape as ShapeInterface } from './interfaces'
88
import { ShapeType } from './utils/constants'
99
import Sidebar from './components/Sidebar'
1010
import ThreeJSViewer from './components/ThreeJSViewer'
11+
import { isEmpty } from 'lodash'
1112

1213
const Canvas: React.FC = () => {
1314
const pathThickness = 25
@@ -17,15 +18,14 @@ const Canvas: React.FC = () => {
1718
const [shapes, setShapes] = useState<ShapeInterface[]>([])
1819
const [selectedShape, setSelectedShape] = useState<ShapeInterface | null>(null)
1920
const [shapeType, setShapeType] = useState<ShapeType>('rectangle')
20-
const [currentPath, setCurrentPath] = useState<Shape | null>(null)
21-
const [pathPoints, setPathPoints] = useState<{ x: number; y: number }[]>([])
2221
const [isDrawing, setIsDrawing] = useState(false)
2322
const [startPoint, setStartPoint] = useState<{ x: number; y: number } | null>(null)
2423
const [currentShape, setCurrentShape] = useState<Shape | null>(null)
2524
const [is3DMode, setIs3DMode] = useState(false)
2625
const pathColor = useRef(getRandomColor())
2726

2827
const selectedIdRef = useRef<number | undefined>(undefined)
28+
const pathPointsRef = useRef<{ x: number; y: number }[]>([])
2929

3030
const toggleViewMode = () => setIs3DMode(!is3DMode)
3131

@@ -163,20 +163,33 @@ const Canvas: React.FC = () => {
163163
const g = currentShape.graphics
164164
g.clear()
165165

166+
const { x: startX, y: startY } = startPoint
167+
166168
switch (shapeType) {
167169
case 'rectangle':
168170
g.beginFill(pathColor.current)
169171
.beginStroke(pathColor.current)
170-
.drawRect(startPoint.x, startPoint.y, x - startPoint.x, y - startPoint.y)
172+
.drawRect(startX, startY, x - startX, y - startY)
171173
break
172174
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)
175+
const radius = Math.sqrt(Math.pow(x - startX, 2) + Math.pow(y - startY, 2))
176+
g.beginFill(pathColor.current).beginStroke(pathColor.current).drawCircle(startX, startY, radius)
175177
break
176178
}
177179
case 'line':
178-
g.beginStroke(pathColor.current).moveTo(startPoint.x, startPoint.y).lineTo(x, y)
180+
g.beginStroke(pathColor.current).moveTo(startX, startY).lineTo(x, y)
179181
break
182+
case 'path': {
183+
const newPoints = g.beginStroke(pathColor.current).setStrokeStyle(pathThickness)
184+
185+
if (isEmpty(pathPointsRef.current)) {
186+
newPoints.moveTo(startX, startY)
187+
} else {
188+
pathPointsRef.current.forEach(point => newPoints.lineTo(point.x, point.y))
189+
}
190+
191+
newPoints.lineTo(x, y)
192+
}
180193
}
181194

182195
stageRef.current?.update()
@@ -233,57 +246,22 @@ const Canvas: React.FC = () => {
233246
stageRef.current?.update()
234247
}
235248

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-
268249
// Mouse event handlers
269250
const handleCanvasMouseDown = useCallback(
270251
(event: React.MouseEvent<HTMLCanvasElement>) => {
271-
pathColor.current = getRandomColor()
272-
273252
const { offsetX, offsetY } = event.nativeEvent
253+
254+
if (shapeType === 'path') {
255+
pathPointsRef.current.push({ x: offsetX, y: offsetY })
256+
} else {
257+
pathColor.current = getRandomColor()
258+
}
259+
274260
const clickedShape = stageRef.current?.getObjectsUnderPoint(offsetX, offsetY, 1)?.[0] as ShapeInterface
275261

276262
if (clickedShape) {
277263
selectedIdRef.current = clickedShape?.id as number
278-
279264
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-
}
287265
} else {
288266
startDrawing(offsetX, offsetY)
289267
}
@@ -296,11 +274,7 @@ const Canvas: React.FC = () => {
296274
const { offsetX, offsetY } = event.nativeEvent
297275

298276
if (isDrawing) {
299-
if (shapeType === 'path') {
300-
updatePath(offsetX, offsetY)
301-
} else {
302-
draw(offsetX, offsetY)
303-
}
277+
draw(offsetX, offsetY)
304278
}
305279
},
306280
[isDrawing, shapeType]
@@ -312,7 +286,7 @@ const Canvas: React.FC = () => {
312286

313287
if (isDrawing) {
314288
if (shapeType === 'path') {
315-
endPath(offsetX, offsetY)
289+
pathPointsRef.current = [...pathPointsRef.current, { x: offsetX, y: offsetY }]
316290
} else {
317291
endDrawing(offsetX, offsetY)
318292
}
@@ -339,16 +313,6 @@ const Canvas: React.FC = () => {
339313
[isDrawing, shapeType]
340314
)
341315

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-
352316
// Initialize canvas and attach event handlers
353317
useEffect(() => {
354318
if (canvasRef.current) {
@@ -380,9 +344,8 @@ const Canvas: React.FC = () => {
380344

381345
setShapes([])
382346
setSelectedShape(null)
383-
setCurrentPath(null)
384-
setPathPoints([])
385347
setIsDrawing(false)
348+
pathPointsRef.current = []
386349
}, [])
387350

388351
// Keyboard delete, backspace handlers
@@ -411,28 +374,41 @@ const Canvas: React.FC = () => {
411374
event.preventDefault()
412375

413376
if (isDrawing) {
377+
if (shapeType === 'path' && Array.isArray(pathPointsRef.current) && pathPointsRef.current.length > 1) {
378+
const current = {
379+
type: 'path',
380+
fillColor: pathColor.current,
381+
strokeColor: pathColor.current,
382+
x: 0,
383+
y: 0,
384+
points: pathPointsRef.current,
385+
instance: stageRef.current?.children[stageRef.current?.children.length - 1] as Shape,
386+
}
387+
388+
stageRef.current?.removeChild(currentShape)
389+
stageRef.current?.update()
390+
391+
createShape({
392+
...current,
393+
})
394+
395+
pathPointsRef.current = []
396+
setStartPoint(null)
397+
}
398+
414399
setIsDrawing(false)
415-
setCurrentPath(null)
416-
setPathPoints([])
417400
}
418401
}
419402

420403
window.addEventListener('contextmenu', handleRightClick)
421404

422-
if (!isDrawing) {
423-
setCurrentPath(null)
424-
setPathPoints([])
425-
}
426-
427405
return () => {
428406
window.removeEventListener('contextmenu', handleRightClick)
429407
}
430408
}, [isDrawing])
431409

432410
useEffect(() => {
433-
if (shapeType !== 'path') {
434-
setIsDrawing(false)
435-
}
411+
setIsDrawing(false)
436412
}, [shapeType])
437413

438414
return (

src/utils/exportimport.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@ const getUpdatedShapeData = (shape: Shape) => {
2727
break
2828
case 'path':
2929
if (instance && updatedShape.points) {
30+
updatedShape.x = 0
31+
updatedShape.y = 0
32+
3033
const dx = instance.x - shape.x
3134
const dy = instance.y - shape.y
35+
3236
updatedShape.points = updatedShape.points.map(point => ({
3337
x: point.x + dx,
3438
y: point.y + dy,
@@ -42,7 +46,7 @@ const getUpdatedShapeData = (shape: Shape) => {
4246

4347
export const exportShapes = (shapes: Shape[]) => {
4448
// Only export active shapes (not deleted)
45-
const activeShapes = shapes.filter(shape => shape.instance && !shape.instance?.isDeleted)
49+
const activeShapes = shapes.filter(shape => !shape.instance?.isDeleted)
4650
const exportedData = activeShapes.map(getUpdatedShapeData)
4751

4852
try {

0 commit comments

Comments
 (0)