Skip to content

Commit 55c01ca

Browse files
committed
feat: add thickness selection feature to the sidebar for drawings
1 parent 02e764b commit 55c01ca

File tree

6 files changed

+46
-11
lines changed

6 files changed

+46
-11
lines changed

β€Žsrc/App.tsxβ€Ž

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@ import { getRandomColor } from './utils'
55
// @ts-expect-error -> no support for EaselJS in TypeScript -> https://github.com/CreateJS/EaselJS/issues/796
66
import { Stage, Shape, Ticker } from '@createjs/easeljs'
77
import { Shape as ShapeInterface } from './interfaces'
8-
import { ShapeType } from './utils/constants'
8+
import { defaultPathThickness, ShapeType } from './utils/constants'
99
import Sidebar from './components/Sidebar'
1010
import ThreeJSViewer from './components/ThreeJSViewer'
1111
import { isEmpty } from 'lodash'
1212

1313
const Canvas: React.FC = () => {
14-
const pathThickness = 25
15-
14+
const [pathThickness, setPathThickness] = useState(defaultPathThickness)
1615
const canvasRef = useRef<HTMLCanvasElement>(null)
1716
const stageRef = useRef<Stage | null>(null)
1817
const [shapes, setShapes] = useState<ShapeInterface[]>([])
@@ -49,12 +48,13 @@ const Canvas: React.FC = () => {
4948
if (props.endX === undefined || props.endY === undefined) return
5049

5150
g.beginStroke(props.strokeColor)
51+
.setStrokeStyle(props?.thickness ?? pathThickness)
5252
.moveTo(0, 0)
5353
.lineTo(props.endX - props.x, props.endY - props.y)
5454
break
5555
case 'path':
5656
if (props?.points && props.points?.length > 1) {
57-
g.beginStroke(props.strokeColor).setStrokeStyle(pathThickness)
57+
g.beginStroke(props.strokeColor).setStrokeStyle(props?.thickness ?? pathThickness)
5858
g.moveTo(props.points[0].x, props.points[0].y)
5959

6060
props.points.forEach((point: { x: number; y: number }, index: number) => {
@@ -102,6 +102,7 @@ const Canvas: React.FC = () => {
102102
const g = this.graphics
103103
g.clear()
104104
.beginStroke(props.strokeColor)
105+
.setStrokeStyle(pathThickness)
105106
.moveTo(0, 0)
106107
.lineTo(props.endX - props.x, props.endY - props.y)
107108
} else if (props.type === 'path' && props.points) {
@@ -177,7 +178,7 @@ const Canvas: React.FC = () => {
177178
break
178179
}
179180
case 'line':
180-
g.beginStroke(pathColor.current).moveTo(startX, startY).lineTo(x, y)
181+
g.beginStroke(pathColor.current).setStrokeStyle(pathThickness).moveTo(startX, startY).lineTo(x, y)
181182
break
182183
case 'path': {
183184
const newPoints = g.beginStroke(pathColor.current).setStrokeStyle(pathThickness)
@@ -227,11 +228,12 @@ const Canvas: React.FC = () => {
227228
shapeProps = {
228229
type: 'line',
229230
fillColor: 'transparent',
230-
strokeColor: getRandomColor(),
231+
strokeColor: pathColor.current,
231232
x: startPoint.x,
232233
y: startPoint.y,
233234
endX: x,
234235
endY: y,
236+
thickness: pathThickness,
235237
}
236238
break
237239
default:
@@ -249,12 +251,11 @@ const Canvas: React.FC = () => {
249251
// Mouse event handlers
250252
const handleCanvasMouseDown = useCallback(
251253
(event: React.MouseEvent<HTMLCanvasElement>) => {
254+
if (!isDrawing) pathColor.current = getRandomColor()
252255
const { offsetX, offsetY } = event.nativeEvent
253256

254257
if (shapeType === 'path') {
255258
pathPointsRef.current.push({ x: offsetX, y: offsetY })
256-
} else {
257-
pathColor.current = getRandomColor()
258259
}
259260

260261
const clickedShape = stageRef.current?.getObjectsUnderPoint(offsetX, offsetY, 1)?.[0] as ShapeInterface
@@ -383,6 +384,7 @@ const Canvas: React.FC = () => {
383384
y: 0,
384385
points: pathPointsRef.current,
385386
instance: stageRef.current?.children[stageRef.current?.children.length - 1] as Shape,
387+
thickness: pathThickness,
386388
}
387389

388390
stageRef.current?.removeChild(currentShape)
@@ -422,6 +424,8 @@ const Canvas: React.FC = () => {
422424
shapeType={shapeType}
423425
toggleViewMode={toggleViewMode}
424426
is3DMode={is3DMode}
427+
pathThickness={pathThickness}
428+
setPathThickness={setPathThickness}
425429
/>
426430
<div
427431
id='CanvasContainer'

β€Žsrc/components/Sidebar.tsxβ€Ž

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { importShapes, exportShapes } from '../utils/exportimport'
22
import { isEmpty } from 'lodash'
3-
import { shapeList } from '../utils/constants'
3+
import { defaultPathThickness, shapeList } from '../utils/constants'
44
import { SidebarProps } from '../interfaces'
55
import Button from './ui/Button'
66

@@ -12,11 +12,18 @@ export default function Sidebar({
1212
shapeType,
1313
toggleViewMode,
1414
is3DMode,
15+
pathThickness,
16+
setPathThickness,
1517
}: Readonly<SidebarProps>) {
1618
const importFunc = (event: React.ChangeEvent<HTMLInputElement>) => {
1719
importShapes({ event, createShape, clearShapes })
1820
}
1921

22+
const setThickness = (thickness: number) => {
23+
if (thickness < 1 || isNaN(thickness)) thickness = defaultPathThickness
24+
25+
setPathThickness(thickness)
26+
}
2027
const iconList = ['rectangle', 'circle', 'horizontal_rule', 'route']
2128

2229
return (
@@ -34,6 +41,19 @@ export default function Sidebar({
3441
<span className='material-icons mr-2'>{iconList[index]}</span>
3542
{type.charAt(0).toUpperCase() + type.slice(1)}
3643
</Button>
44+
45+
{['line', 'path'].includes(type) && shapeType === type && (
46+
<div className='w-full my-4 flex items-center justify-between'>
47+
<span className='text-gray-500 text-xs'>Path thickness:</span>
48+
<input
49+
type='number'
50+
value={pathThickness}
51+
onChange={e => setThickness(Number(e.target.value))}
52+
className='p-2 border border-gray-200 rounded-md focus:outline-none focus:shadow-outline w-32'
53+
placeholder='Path thickness'
54+
/>
55+
</div>
56+
)}
3757
</li>
3858
))}
3959
</ul>

β€Žsrc/components/ThreeJSViewer.tsxβ€Ž

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useRef, useEffect } from 'react'
22
import * as THREE from 'three'
33
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
44
import { Shape as ShapeInterface } from '../interfaces'
5+
import { defaultPathThickness } from '../utils/constants'
56

67
interface ThreeJSViewerProps {
78
shapes: ShapeInterface[]
@@ -49,6 +50,8 @@ const ThreeJSViewer: React.FC<ThreeJSViewerProps> = ({ shapes }) => {
4950
let material: THREE.Material
5051
let mesh: THREE.Mesh
5152

53+
const thickness = shape?.thickness ?? shape?.instance?._strokeStyle?.width ?? defaultPathThickness
54+
5255
switch (shape.type) {
5356
case 'rectangle': {
5457
if (shape.width === undefined || shape.height === undefined) {
@@ -75,7 +78,7 @@ const ThreeJSViewer: React.FC<ThreeJSViewerProps> = ({ shapes }) => {
7578
const startPosition = convertTo3DCoords(shape.x, shape.y)
7679
const endPosition = convertTo3DCoords(shape.endX!, shape.endY!)
7780
const lineCurve = new THREE.LineCurve3(startPosition, endPosition)
78-
geometry = new THREE.TubeGeometry(lineCurve, 10, 2, 8, false)
81+
geometry = new THREE.TubeGeometry(lineCurve, 10, thickness, 8, false)
7982
material = new THREE.MeshPhongMaterial({ color: shape?.strokeColor ?? 0x000000 })
8083
mesh = new THREE.Mesh(geometry, material)
8184
break
@@ -84,7 +87,7 @@ const ThreeJSViewer: React.FC<ThreeJSViewerProps> = ({ shapes }) => {
8487
if (shape.points && shape.points.length > 1) {
8588
const pathPoints = shape.points.map(point => convertTo3DCoords(point.x, point.y))
8689
const curve = new THREE.CatmullRomCurve3(pathPoints)
87-
geometry = new THREE.TubeGeometry(curve, 64, 25, 8, true)
90+
geometry = new THREE.TubeGeometry(curve, 64, thickness, 8, false)
8891
material = new THREE.MeshPhongMaterial({ color: shape.strokeColor || 0x000000 })
8992
mesh = new THREE.Mesh(geometry, material)
9093
} else {

β€Žsrc/interfaces/index.tsβ€Ž

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface Shape {
1616
endY?: number
1717
graphics?: any
1818
instance?: any
19+
thickness?: number
1920
}
2021

2122
export interface SidebarProps {
@@ -26,4 +27,6 @@ export interface SidebarProps {
2627
shapeType: string
2728
toggleViewMode: () => void
2829
is3DMode: boolean
30+
pathThickness: number
31+
setPathThickness: (thickness: number) => void
2932
}

β€Žsrc/utils/constants.tsβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export const shapeList = ['rectangle', 'circle', 'line', 'path']
22
export type ShapeType = (typeof shapeList)[number]
3+
export const defaultPathThickness = 25

β€Žsrc/utils/exportimport.tsβ€Ž

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Shape } from '../interfaces'
2+
import { defaultPathThickness } from './constants'
23

34
const getUpdatedShapeData = (shape: Shape) => {
45
const instance = shape.instance
@@ -23,12 +24,15 @@ const getUpdatedShapeData = (shape: Shape) => {
2324
const dy = instance.y - shape.y
2425
updatedShape.endX = (shape.endX ?? 0) + dx
2526
updatedShape.endY = (shape.endY ?? 0) + dy
27+
updatedShape.thickness ??= shape?.instance?._strokeStyle?.width || defaultPathThickness
2628
}
29+
2730
break
2831
case 'path':
2932
if (instance && updatedShape.points) {
3033
updatedShape.x = 0
3134
updatedShape.y = 0
35+
updatedShape.thickness ??= shape?.instance?._strokeStyle?.width || defaultPathThickness
3236

3337
const dx = instance.x - shape.x
3438
const dy = instance.y - shape.y

0 commit comments

Comments
Β (0)