17 | {props.colors.map((color) => {
18 | return (
19 |
34 | >
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/packages/dev/src/components/colors/index.ts:
--------------------------------------------------------------------------------
1 | export * from './colors'
2 |
--------------------------------------------------------------------------------
/packages/dev/src/components/controls/controls.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | position: fixed;
3 | width: min(100%, 320px);
4 | top: 48px;
5 | left: 0px;
6 | padding: 12px;
7 | background-color: rgba(255, 255, 255, 0.95);
8 | border-bottom-right-radius: 16px;
9 | box-shadow: 0px 0px 8px -4px rgba(0, 0, 0, 0.16),
10 | 0px 0px 36px 0px rgba(0, 0, 0, 0.08);
11 | z-index: 100;
12 | transform: translate(-100%, 0px);
13 | }
14 |
15 | .open {
16 | transform: translate(0%, 0px);
17 | transition: transform 0.2s;
18 | }
19 |
20 | .inputs {
21 | display: grid;
22 | grid-template-columns: 96px 1fr auto;
23 | grid-auto-rows: 32px;
24 | align-items: center;
25 | column-gap: 16px;
26 | }
27 |
28 | .colors {
29 | padding: 12px 0;
30 | }
31 |
32 | hr {
33 | grid-column: 1 / span 3;
34 | height: 1px;
35 | border: none;
36 | background-color: gainsboro;
37 | width: calc(100% + 24px);
38 | margin-left: -12px;
39 | }
40 |
41 | .buttonsRow {
42 | display: flex;
43 | height: 40px;
44 | }
45 |
46 | .rowButton {
47 | cursor: pointer;
48 | flex-grow: 2;
49 | font-family: 'Recursive', sans-serif;
50 | border: none;
51 | background-color: transparent;
52 | border: 2px solid transparent;
53 | }
54 |
55 | .rowButton:hover {
56 | color: dodgerblue;
57 | }
58 |
59 | .rowButton:focus {
60 | outline: none;
61 | border: 2px solid dodgerblue;
62 | }
63 |
--------------------------------------------------------------------------------
/packages/dev/src/components/controls/controls.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Colors } from 'components/colors'
3 | import { Checkbox } from 'components/checkbox'
4 | import { Select } from 'components/select'
5 | import { Slider } from 'components/slider'
6 | import styles from './controls.module.css'
7 | import { app, useAppState } from 'state'
8 | import type { Easing, State } from 'types'
9 |
10 | const COLORS = [
11 | '#000000',
12 | '#ffc107',
13 | '#ff5722',
14 | '#e91e63',
15 | '#673ab7',
16 | '#00bcd4',
17 | '#efefef',
18 | ]
19 |
20 | const EASINGS = [
21 | 'linear',
22 | 'easeInQuad',
23 | 'easeOutQuad',
24 | 'easeInOutQuad',
25 | 'easeInCubic',
26 | 'easeOutCubic',
27 | 'easeInOutCubic',
28 | 'easeInQuart',
29 | 'easeOutQuart',
30 | 'easeInOutQuart',
31 | 'easeInQuint',
32 | 'easeOutQuint',
33 | 'easeInOutQuint',
34 | 'easeInSine',
35 | 'easeOutSine',
36 | 'easeInOutSine',
37 | 'easeInExpo',
38 | 'easeOutExpo',
39 | 'easeInOutExpo',
40 | ]
41 |
42 | const appStateSelector = (s: State) => s.appState
43 |
44 | export function Controls() {
45 | const appState = useAppState(appStateSelector)
46 | const { style } = appState
47 |
48 | const handleSizeChangeStart = React.useCallback(() => {
49 | app.setSnapshot()
50 | }, [])
51 |
52 | const handleSizeChange = React.useCallback((v: number[]) => {
53 | app.patchStyleForAllShapes({ size: v[0] })
54 | }, [])
55 |
56 | const handleStrokeWidthChangeStart = React.useCallback(() => {
57 | app.setSnapshot()
58 | }, [])
59 |
60 | const handleStrokeWidthChange = React.useCallback((v: number[]) => {
61 | app.patchStyleForAllShapes({ strokeWidth: v[0] })
62 | }, [])
63 |
64 | const handleThinningChangeStart = React.useCallback(() => {
65 | app.setSnapshot()
66 | }, [])
67 |
68 | const handleThinningChange = React.useCallback((v: number[]) => {
69 | app.patchStyleForAllShapes({ thinning: v[0] })
70 | }, [])
71 |
72 | const handleStreamlineChangeStart = React.useCallback(() => {
73 | app.setSnapshot()
74 | }, [])
75 |
76 | const handleStreamlineChange = React.useCallback((v: number[]) => {
77 | app.patchStyleForAllShapes({ streamline: v[0] })
78 | }, [])
79 |
80 | const handleSmoothingChangeStart = React.useCallback(() => {
81 | app.setSnapshot()
82 | }, [])
83 |
84 | const handleSmoothingChange = React.useCallback((v: number[]) => {
85 | app.patchStyleForAllShapes({ smoothing: v[0] })
86 | }, [])
87 |
88 | const handleEasingChange = React.useCallback((easing: string) => {
89 | app.patchStyleForAllShapes({ easing: easing as Easing })
90 | }, [])
91 |
92 | const handleCapStartChange = React.useCallback(
93 | (v: boolean | 'indeterminate') => {
94 | app.setNextStyleForAllShapes({ capStart: !!v })
95 | },
96 | []
97 | )
98 |
99 | const handleTaperStartChangeStart = React.useCallback(() => {
100 | app.setSnapshot()
101 | }, [])
102 |
103 | const handleTaperStartChange = React.useCallback((v: number[]) => {
104 | app.patchStyleForAllShapes({ taperStart: v[0] === 100 ? true : v[0] })
105 | }, [])
106 |
107 | const handleEasingStartChange = React.useCallback((easing: string) => {
108 | app.patchStyleForAllShapes({ easingStart: easing as Easing })
109 | }, [])
110 |
111 | const handleCapEndChange = React.useCallback(
112 | (v: boolean | 'indeterminate') => {
113 | app.setNextStyleForAllShapes({ capEnd: !!v })
114 | },
115 | []
116 | )
117 |
118 | const handleTaperEndChangeStart = React.useCallback(() => {
119 | app.setSnapshot()
120 | }, [])
121 |
122 | const handleTaperEndChange = React.useCallback((v: number[]) => {
123 | app.patchStyleForAllShapes({ taperEnd: v[0] === 100 ? true : v[0] })
124 | }, [])
125 |
126 | const handleEasingEndChange = React.useCallback((easing: string) => {
127 | app.patchStyleForAllShapes({ easingEnd: easing as Easing })
128 | }, [])
129 |
130 | const handleIsFilledChange = React.useCallback(
131 | (v: boolean | 'indeterminate') => {
132 | app.setNextStyleForAllShapes({ isFilled: !!v })
133 | },
134 | []
135 | )
136 |
137 | const handleStyleChangeComplete = React.useCallback(() => {
138 | app.finishStyleUpdate()
139 | }, [])
140 |
141 | const handleStrokeColorChange = React.useCallback((color: string) => {
142 | app.patchStyle({ stroke: color })
143 | }, [])
144 |
145 | const handleFillColorChange = React.useCallback((color: string) => {
146 | app.patchStyle({ fill: color })
147 | }, [])
148 |
149 | // Resets
150 |
151 | const handleResetSize = React.useCallback(() => {
152 | app.resetStyle('size')
153 | }, [])
154 |
155 | const handleResetThinning = React.useCallback(() => {
156 | app.resetStyle('thinning')
157 | }, [])
158 |
159 | const handleResetStreamline = React.useCallback(() => {
160 | app.resetStyle('streamline')
161 | }, [])
162 |
163 | const handleResetSmoothing = React.useCallback(() => {
164 | app.resetStyle('smoothing')
165 | }, [])
166 |
167 | const handleResetEasing = React.useCallback(() => {
168 | app.resetStyle('easing')
169 | }, [])
170 |
171 | const handleResetTaperStart = React.useCallback(() => {
172 | app.resetStyle('taperStart')
173 | }, [])
174 |
175 | const handleResetEasingStart = React.useCallback(() => {
176 | app.resetStyle('easingStart')
177 | }, [])
178 |
179 | const handleResetTaperEnd = React.useCallback(() => {
180 | app.resetStyle('taperEnd')
181 | }, [])
182 |
183 | const handleResetEasingEnd = React.useCallback(() => {
184 | app.resetStyle('easingEnd')
185 | }, [])
186 |
187 | const handleResetStrokeWidth = React.useCallback(() => {
188 | app.resetStyle('strokeWidth')
189 | }, [])
190 |
191 | return (
192 |
198 |
199 |
210 |
221 |
232 |
243 |
255 |
256 |
284 | {style.taperStart <= 0 && (
285 | 0}
288 | checked={style.taperStart === 0 && style.capStart}
289 | onCheckedChange={handleCapStartChange}
290 | />
291 | )}
292 | {style.taperStart > 0 && (
293 |
305 | )}
306 |
307 |
335 | {style.taperEnd <= 0 && (
336 | 0}
339 | checked={style.taperEnd === 0 && style.capEnd}
340 | onCheckedChange={handleCapEndChange}
341 | />
342 | )}
343 | {style.taperEnd > 0 && (
344 |
356 | )}
357 |
358 |
363 | {style.isFilled && (
364 |
370 | )}
371 |
382 | {style.strokeWidth > 0 && (
383 |
389 | )}
390 |
391 |
392 |
393 |
396 |
399 |
400 |
401 |
402 |
405 |
406 |
407 | )
408 | }
409 |
--------------------------------------------------------------------------------
/packages/dev/src/components/controls/index.ts:
--------------------------------------------------------------------------------
1 | export * from './controls'
2 |
--------------------------------------------------------------------------------
/packages/dev/src/components/editor/editor.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | position: fixed;
3 | top: 0;
4 | left: 0;
5 | width: 100%;
6 | height: 100%;
7 | z-index: 1;
8 | }
9 |
--------------------------------------------------------------------------------
/packages/dev/src/components/editor/editor.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Renderer } from '@tldraw/core'
3 | import { app, useAppState } from 'state'
4 | import styles from './editor.module.css'
5 |
6 | export function Editor(): JSX.Element {
7 | const {
8 | onPinch,
9 | onPinchStart,
10 | onPinchEnd,
11 | onPan,
12 | onPointerDown,
13 | onPointerMove,
14 | onPointerUp,
15 | shapeUtils,
16 | } = app
17 | const { page, pageState } = useAppState()
18 |
19 | React.useEffect(() => {
20 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
21 | // @ts-ignore
22 | window.freehand = app
23 | }, [])
24 |
25 | return (
26 |