Procházet zdrojové kódy

Updates code editor

main
Steve Ruiz před 4 roky
rodič
revize
abd310aa2e

+ 21
- 131
components/code-panel/code-panel.tsx Zobrazit soubor

@@ -7,7 +7,9 @@ import state, { useSelector } from "state"
7 7
 import { CodeFile } from "types"
8 8
 import CodeDocs from "./code-docs"
9 9
 import CodeEditor from "./code-editor"
10
-import { getShapesFromCode } from "lib/code/generate"
10
+import { generateFromCode } from "lib/code/generate"
11
+import * as Panel from "../panel"
12
+import { IconButton } from "../shared"
11 13
 import {
12 14
   X,
13 15
   Code,
@@ -51,8 +53,8 @@ export default function CodePanel() {
51 53
     states: {
52 54
       editingCode: {
53 55
         on: {
54
-          RAN_CODE: "runCode",
55
-          SAVED_CODE: ["runCode", "saveCode"],
56
+          RAN_CODE: ["saveCode", "runCode"],
57
+          SAVED_CODE: ["saveCode", "runCode"],
56 58
           CHANGED_CODE: { secretlyDo: "setCode" },
57 59
           CLEARED_ERROR: { if: "hasError", do: "clearError" },
58 60
           TOGGLED_DOCS: { to: "viewingDocs" },
@@ -80,8 +82,8 @@ export default function CodePanel() {
80 82
         let error = null
81 83
 
82 84
         try {
83
-          const shapes = getShapesFromCode(data.code)
84
-          state.send("GENERATED_SHAPES_FROM_CODE", { shapes })
85
+          const { shapes, controls } = generateFromCode(data.code)
86
+          state.send("GENERATED_FROM_CODE", { shapes, controls })
85 87
         } catch (e) {
86 88
           console.error(e)
87 89
           error = { message: e.message, ...getErrorLineAndColumn(e) }
@@ -113,15 +115,10 @@ export default function CodePanel() {
113 115
   const { error } = local.data
114 116
 
115 117
   return (
116
-    <PanelContainer
117
-      data-bp-desktop
118
-      ref={rContainer}
119
-      dragMomentum={false}
120
-      isCollapsed={!isOpen}
121
-    >
118
+    <Panel.Root data-bp-desktop ref={rContainer} isCollapsed={!isOpen}>
122 119
       {isOpen ? (
123
-        <Content>
124
-          <Header>
120
+        <Panel.Layout>
121
+          <Panel.Header>
125 122
             <IconButton onClick={() => state.send("CLOSED_CODE_PANEL")}>
126 123
               <X />
127 124
             </IconButton>
@@ -151,8 +148,8 @@ export default function CodePanel() {
151 148
                 <PlayCircle />
152 149
               </IconButton>
153 150
             </ButtonsGroup>
154
-          </Header>
155
-          <EditorContainer>
151
+          </Panel.Header>
152
+          <Panel.Content>
156 153
             <CodeEditor
157 154
               fontSize={fontSize}
158 155
               readOnly={isReadOnly}
@@ -163,149 +160,42 @@ export default function CodePanel() {
163 160
               onKey={() => local.send("CLEARED_ERROR")}
164 161
             />
165 162
             <CodeDocs isHidden={!local.isIn("viewingDocs")} />
166
-          </EditorContainer>
167
-          <ErrorContainer>
163
+          </Panel.Content>
164
+          <Panel.Footer>
168 165
             {error &&
169 166
               (error.line
170 167
                 ? `(${Number(error.line) - 2}:${error.column}) ${error.message}`
171 168
                 : error.message)}
172
-          </ErrorContainer>
173
-        </Content>
169
+          </Panel.Footer>
170
+        </Panel.Layout>
174 171
       ) : (
175 172
         <IconButton onClick={() => state.send("OPENED_CODE_PANEL")}>
176 173
           <Code />
177 174
         </IconButton>
178 175
       )}
179
-    </PanelContainer>
176
+    </Panel.Root>
180 177
   )
181 178
 }
182 179
 
183
-const PanelContainer = styled(motion.div, {
184
-  position: "absolute",
185
-  top: "48px",
186
-  right: "8px",
187
-  bottom: "48px",
188
-  backgroundColor: "$panel",
189
-  borderRadius: "4px",
190
-  overflow: "hidden",
191
-  border: "1px solid $border",
192
-  pointerEvents: "all",
193
-  userSelect: "none",
194
-  zIndex: 200,
195
-
196
-  button: {
197
-    border: "none",
198
-  },
199
-
200
-  variants: {
201
-    isCollapsed: {
202
-      true: {},
203
-      false: {},
204
-    },
205
-  },
206
-})
207
-
208
-const IconButton = styled("button", {
209
-  height: "40px",
210
-  width: "40px",
211
-  backgroundColor: "$panel",
212
-  borderRadius: "4px",
213
-  border: "1px solid $border",
214
-  padding: "0",
215
-  margin: "0",
216
-  display: "flex",
217
-  alignItems: "center",
218
-  justifyContent: "center",
219
-  outline: "none",
220
-  pointerEvents: "all",
221
-  cursor: "pointer",
222
-
223
-  "&:hover:not(:disabled)": {
224
-    backgroundColor: "$panel",
225
-  },
226
-
227
-  "&:disabled": {
228
-    opacity: "0.5",
229
-  },
230
-
231
-  svg: {
232
-    height: "20px",
233
-    width: "20px",
234
-    strokeWidth: "2px",
235
-    stroke: "$text",
236
-  },
237
-})
238
-
239
-const Content = styled("div", {
240
-  display: "grid",
241
-  gridTemplateColumns: "1fr",
242
-  gridTemplateRows: "auto 1fr 28px",
243
-  height: "100%",
244
-  width: 560,
245
-  minWidth: "100%",
246
-  maxWidth: 560,
247
-  overflow: "hidden",
248
-  userSelect: "none",
249
-  pointerEvents: "all",
250
-})
251
-
252
-const Header = styled("div", {
253
-  pointerEvents: "all",
254
-  display: "grid",
255
-  gridTemplateColumns: "auto 1fr",
256
-  alignItems: "center",
257
-  justifyContent: "center",
258
-  borderBottom: "1px solid $border",
259
-
260
-  "& button": {
261
-    gridColumn: "1",
262
-    gridRow: "1",
263
-  },
264
-
265
-  "& h3": {
266
-    gridColumn: "1 / span 3",
267
-    gridRow: "1",
268
-    textAlign: "center",
269
-    margin: "0",
270
-    padding: "0",
271
-    fontSize: "16px",
272
-  },
273
-})
274
-
275 180
 const ButtonsGroup = styled("div", {
276 181
   gridRow: "1",
277 182
   gridColumn: "3",
278 183
   display: "flex",
279 184
 })
280 185
 
281
-const EditorContainer = styled("div", {
282
-  position: "relative",
283
-  pointerEvents: "all",
284
-  overflowY: "scroll",
285
-})
286
-
287
-const ErrorContainer = styled("div", {
288
-  overflowX: "scroll",
289
-  color: "$text",
290
-  font: "$debug",
291
-  padding: "0 12px",
292
-  display: "flex",
293
-  alignItems: "center",
294
-})
295
-
296 186
 const FontSizeButtons = styled("div", {
297 187
   paddingRight: 4,
188
+  display: "flex",
189
+  flexDirection: "column",
298 190
 
299 191
   "& > button": {
300 192
     height: "50%",
301
-    width: "100%",
302
-
303 193
     "&:nth-of-type(1)": {
304
-      paddingTop: 4,
194
+      alignItems: "flex-end",
305 195
     },
306 196
 
307 197
     "&:nth-of-type(2)": {
308
-      paddingBottom: 4,
198
+      alignItems: "flex-start",
309 199
     },
310 200
 
311 201
     "& svg": {

+ 145
- 0
components/controls-panel/control.tsx Zobrazit soubor

@@ -0,0 +1,145 @@
1
+import state, { useSelector } from "state"
2
+import styled from "styles"
3
+import {
4
+  ControlType,
5
+  NumberCodeControl,
6
+  SelectCodeControl,
7
+  TextCodeControl,
8
+  VectorCodeControl,
9
+} from "types"
10
+
11
+export default function Control({ id }: { id: string }) {
12
+  const control = useSelector((s) => s.data.codeControls[id])
13
+
14
+  if (!control) return null
15
+
16
+  return (
17
+    <>
18
+      <label>{control.label}</label>
19
+      {control.type === ControlType.Number ? (
20
+        <NumberControl {...control} />
21
+      ) : control.type === ControlType.Vector ? (
22
+        <VectorControl {...control} />
23
+      ) : control.type === ControlType.Text ? (
24
+        <TextControl {...control} />
25
+      ) : control.type === ControlType.Select ? (
26
+        <SelectControl {...control} />
27
+      ) : null}
28
+    </>
29
+  )
30
+}
31
+
32
+function NumberControl({ id, min, max, step, value }: NumberCodeControl) {
33
+  return (
34
+    <Inputs>
35
+      <input
36
+        type="range"
37
+        min={min}
38
+        max={max}
39
+        step={step}
40
+        value={value}
41
+        onChange={(e) =>
42
+          state.send("CHANGED_CODE_CONTROL", {
43
+            [id]: Number(e.currentTarget.value),
44
+          })
45
+        }
46
+      />
47
+      <input
48
+        type="number"
49
+        min={min}
50
+        max={max}
51
+        step={step}
52
+        value={value}
53
+        onChange={(e) =>
54
+          state.send("CHANGED_CODE_CONTROL", {
55
+            [id]: Number(e.currentTarget.value),
56
+          })
57
+        }
58
+      />
59
+    </Inputs>
60
+  )
61
+}
62
+
63
+function VectorControl({ id, value, isNormalized }: VectorCodeControl) {
64
+  return (
65
+    <Inputs>
66
+      <input
67
+        type="range"
68
+        min={isNormalized ? -1 : -Infinity}
69
+        max={isNormalized ? 1 : Infinity}
70
+        step={0.01}
71
+        value={value[0]}
72
+        onChange={(e) =>
73
+          state.send("CHANGED_CODE_CONTROL", {
74
+            [id]: [Number(e.currentTarget.value), value[1]],
75
+          })
76
+        }
77
+      />
78
+      <input
79
+        type="number"
80
+        min={isNormalized ? -1 : -Infinity}
81
+        max={isNormalized ? 1 : Infinity}
82
+        step={0.01}
83
+        value={value[0]}
84
+        onChange={(e) =>
85
+          state.send("CHANGED_CODE_CONTROL", {
86
+            [id]: [Number(e.currentTarget.value), value[1]],
87
+          })
88
+        }
89
+      />
90
+      <input
91
+        type="range"
92
+        min={isNormalized ? -1 : -Infinity}
93
+        max={isNormalized ? 1 : Infinity}
94
+        step={0.01}
95
+        value={value[1]}
96
+        onChange={(e) =>
97
+          state.send("CHANGED_CODE_CONTROL", {
98
+            [id]: [value[0], Number(e.currentTarget.value)],
99
+          })
100
+        }
101
+      />
102
+      <input
103
+        type="number"
104
+        min={isNormalized ? -1 : -Infinity}
105
+        max={isNormalized ? 1 : Infinity}
106
+        step={0.01}
107
+        value={value[1]}
108
+        onChange={(e) =>
109
+          state.send("CHANGED_CODE_CONTROL", {
110
+            [id]: [value[0], Number(e.currentTarget.value)],
111
+          })
112
+        }
113
+      />
114
+    </Inputs>
115
+  )
116
+}
117
+
118
+function TextControl({}: TextCodeControl) {
119
+  return <></>
120
+}
121
+
122
+function SelectControl({}: SelectCodeControl) {
123
+  return <></>
124
+}
125
+
126
+const Inputs = styled("div", {
127
+  display: "flex",
128
+  gap: "8px",
129
+  height: "100%",
130
+
131
+  "& input": {
132
+    font: "$ui",
133
+    width: "64px",
134
+    fontSize: "$1",
135
+    border: "1px solid $inputBorder",
136
+    backgroundColor: "$input",
137
+    color: "$text",
138
+    height: "100%",
139
+    padding: "0px 6px",
140
+  },
141
+  "& input[type='range']": {
142
+    padding: 0,
143
+    flexGrow: 2,
144
+  },
145
+})

+ 62
- 0
components/controls-panel/controls-panel.tsx Zobrazit soubor

@@ -0,0 +1,62 @@
1
+/* eslint-disable @typescript-eslint/ban-ts-comment */
2
+import styled from "styles"
3
+import React, { useEffect, useRef } from "react"
4
+import state, { useSelector } from "state"
5
+import { X, Code, PlayCircle } from "react-feather"
6
+import { IconButton } from "components/shared"
7
+import * as Panel from "../panel"
8
+import Control from "./control"
9
+import { deepCompareArrays } from "utils/utils"
10
+
11
+export default function ControlPanel() {
12
+  const rContainer = useRef<HTMLDivElement>(null)
13
+  const codeControls = useSelector(
14
+    (state) => Object.keys(state.data.codeControls),
15
+    deepCompareArrays
16
+  )
17
+  const isOpen = true
18
+
19
+  return (
20
+    <Panel.Root data-bp-desktop ref={rContainer} isCollapsed={!isOpen}>
21
+      {isOpen ? (
22
+        <Panel.Layout>
23
+          <Panel.Header>
24
+            <IconButton onClick={() => state.send("CLOSED_CODE_PANEL")}>
25
+              <X />
26
+            </IconButton>
27
+            <h3>Controls</h3>
28
+          </Panel.Header>
29
+          <ControlsList>
30
+            {codeControls.map((id) => (
31
+              <Control key={id} id={id} />
32
+            ))}
33
+          </ControlsList>
34
+        </Panel.Layout>
35
+      ) : (
36
+        <IconButton onClick={() => state.send("OPENED_CODE_PANEL")}>
37
+          <Code />
38
+        </IconButton>
39
+      )}
40
+    </Panel.Root>
41
+  )
42
+}
43
+
44
+const ControlsList = styled(Panel.Content, {
45
+  padding: 12,
46
+  display: "grid",
47
+  gridTemplateColumns: "1fr 4fr",
48
+  gridAutoRows: "24px",
49
+  alignItems: "center",
50
+  gridColumnGap: "8px",
51
+  gridRowGap: "8px",
52
+
53
+  "& input": {
54
+    font: "$ui",
55
+    fontSize: "$1",
56
+    border: "1px solid $inputBorder",
57
+    backgroundColor: "$input",
58
+    color: "$text",
59
+    height: "100%",
60
+    padding: "0px 6px",
61
+  },
62
+})

+ 32
- 3
components/editor.tsx Zobrazit soubor

@@ -4,17 +4,46 @@ import Canvas from "./canvas/canvas"
4 4
 import StatusBar from "./status-bar"
5 5
 import Toolbar from "./toolbar"
6 6
 import CodePanel from "./code-panel/code-panel"
7
+import ControlsPanel from "./controls-panel/controls-panel"
8
+import styled from "styles"
7 9
 
8 10
 export default function Editor() {
9 11
   useKeyboardEvents()
10 12
   useLoadOnMount()
11 13
 
12 14
   return (
13
-    <>
15
+    <Layout>
14 16
       <Canvas />
15 17
       <StatusBar />
16 18
       <Toolbar />
17
-      <CodePanel />
18
-    </>
19
+      <LeftPanels>
20
+        <CodePanel />
21
+        <ControlsPanel />
22
+      </LeftPanels>
23
+    </Layout>
19 24
   )
20 25
 }
26
+
27
+const Layout = styled("div", {
28
+  position: "fixed",
29
+  top: 0,
30
+  left: 0,
31
+  bottom: 0,
32
+  right: 0,
33
+  display: "grid",
34
+  gridTemplateRows: "40px 1fr 40px",
35
+  gridTemplateColumns: "auto 1fr",
36
+  gridTemplateAreas: `
37
+    "toolbar toolbar"
38
+    "leftPanels main"
39
+    "statusbar statusbar"
40
+  `,
41
+})
42
+
43
+const LeftPanels = styled("main", {
44
+  display: "grid",
45
+  gridArea: "leftPanels",
46
+  gridTemplateRows: "1fr auto",
47
+  padding: 8,
48
+  gap: 8,
49
+})

+ 79
- 0
components/panel.tsx Zobrazit soubor

@@ -0,0 +1,79 @@
1
+import styled from "styles"
2
+
3
+export const Root = styled("div", {
4
+  position: "relative",
5
+  backgroundColor: "$panel",
6
+  borderRadius: "4px",
7
+  overflow: "hidden",
8
+  border: "1px solid $border",
9
+  pointerEvents: "all",
10
+  userSelect: "none",
11
+  zIndex: 200,
12
+  boxShadow: "0px 2px 25px rgba(0,0,0,.16)",
13
+
14
+  variants: {
15
+    isCollapsed: {
16
+      true: {},
17
+      false: {},
18
+    },
19
+  },
20
+})
21
+
22
+export const Layout = styled("div", {
23
+  display: "grid",
24
+  gridTemplateColumns: "1fr",
25
+  gridTemplateRows: "auto 1fr",
26
+  gridAutoRows: "28px",
27
+  height: "100%",
28
+  width: 560,
29
+  minWidth: "100%",
30
+  maxWidth: 560,
31
+  overflow: "hidden",
32
+  userSelect: "none",
33
+  pointerEvents: "all",
34
+})
35
+
36
+export const Header = styled("div", {
37
+  pointerEvents: "all",
38
+  display: "grid",
39
+  gridTemplateColumns: "auto 1fr auto",
40
+  alignItems: "center",
41
+  justifyContent: "center",
42
+  borderBottom: "1px solid $border",
43
+
44
+  "& button": {
45
+    gridColumn: "1",
46
+    gridRow: "1",
47
+  },
48
+
49
+  "& h3": {
50
+    gridColumn: "1 / span 3",
51
+    gridRow: "1",
52
+    textAlign: "center",
53
+    margin: "0",
54
+    padding: "0",
55
+    fontSize: "13px",
56
+  },
57
+})
58
+
59
+export const ButtonsGroup = styled("div", {
60
+  gridRow: "1",
61
+  gridColumn: "3",
62
+  display: "flex",
63
+})
64
+
65
+export const Content = styled("div", {
66
+  position: "relative",
67
+  pointerEvents: "all",
68
+  overflowY: "scroll",
69
+})
70
+
71
+export const Footer = styled("div", {
72
+  overflowX: "scroll",
73
+  color: "$text",
74
+  font: "$debug",
75
+  padding: "0 12px",
76
+  display: "flex",
77
+  alignItems: "center",
78
+  borderTop: "1px solid $border",
79
+})

+ 32
- 0
components/shared.tsx Zobrazit soubor

@@ -0,0 +1,32 @@
1
+import styled from "styles"
2
+
3
+export const IconButton = styled("button", {
4
+  height: "32px",
5
+  width: "32px",
6
+  backgroundColor: "$panel",
7
+  borderRadius: "4px",
8
+  padding: "0",
9
+  margin: "0",
10
+  display: "flex",
11
+  alignItems: "center",
12
+  justifyContent: "center",
13
+  outline: "none",
14
+  border: "none",
15
+  pointerEvents: "all",
16
+  cursor: "pointer",
17
+
18
+  "&:hover:not(:disabled)": {
19
+    backgroundColor: "$panel",
20
+  },
21
+
22
+  "&:disabled": {
23
+    opacity: "0.5",
24
+  },
25
+
26
+  svg: {
27
+    height: "16px",
28
+    width: "16px",
29
+    strokeWidth: "2px",
30
+    stroke: "$text",
31
+  },
32
+})

+ 2
- 6
components/toolbar.tsx Zobrazit soubor

@@ -70,20 +70,16 @@ export default function Toolbar() {
70 70
         >
71 71
           Rectangle
72 72
         </Button>
73
+        <Button onClick={() => state.send("RESET_CAMERA")}>Reset Camera</Button>
73 74
       </Section>
74 75
     </ToolbarContainer>
75 76
   )
76 77
 }
77 78
 
78 79
 const ToolbarContainer = styled("div", {
79
-  position: "absolute",
80
-  top: 0,
81
-  left: 0,
82
-  width: "100%",
83
-  height: 40,
80
+  gridArea: "toolbar",
84 81
   userSelect: "none",
85 82
   borderBottom: "1px solid black",
86
-  gridArea: "status",
87 83
   display: "grid",
88 84
   gridTemplateColumns: "auto 1fr auto",
89 85
   alignItems: "center",

+ 11
- 0
lib/code/circle.ts Zobrazit soubor

@@ -1,9 +1,12 @@
1 1
 import CodeShape from "./index"
2 2
 import { v4 as uuid } from "uuid"
3 3
 import { CircleShape, ShapeType } from "types"
4
+import { vectorToPoint } from "utils/utils"
4 5
 
5 6
 export default class Circle extends CodeShape<CircleShape> {
6 7
   constructor(props = {} as Partial<CircleShape>) {
8
+    props.point = vectorToPoint(props.point)
9
+
7 10
     super({
8 11
       id: uuid(),
9 12
       type: ShapeType.Circle,
@@ -23,6 +26,14 @@ export default class Circle extends CodeShape<CircleShape> {
23 26
     })
24 27
   }
25 28
 
29
+  export() {
30
+    const shape = { ...this.shape }
31
+
32
+    shape.point = vectorToPoint(shape.point)
33
+
34
+    return shape
35
+  }
36
+
26 37
   get radius() {
27 38
     return this.shape.radius
28 39
   }

+ 57
- 0
lib/code/control.ts Zobrazit soubor

@@ -0,0 +1,57 @@
1
+import {
2
+  CodeControl,
3
+  ControlType,
4
+  NumberCodeControl,
5
+  VectorCodeControl,
6
+} from "types"
7
+import { v4 as uuid } from "uuid"
8
+import Vector from "./vector"
9
+
10
+export const controls: Record<string, any> = {}
11
+
12
+export const codeControls = new Set<CodeControl>([])
13
+
14
+export class Control<T extends CodeControl> {
15
+  control: T
16
+
17
+  constructor(control: Omit<T, "id">) {
18
+    this.control = { ...control, id: uuid() } as T
19
+    codeControls.add(this.control)
20
+
21
+    // Could there be a better way to prevent this?
22
+    // When updating, constructor should just bind to
23
+    // the existing control rather than creating a new one?
24
+    if (!(window as any).isUpdatingCode) {
25
+      controls[this.control.label] = this.control.value
26
+    }
27
+  }
28
+
29
+  destroy() {
30
+    codeControls.delete(this.control)
31
+    delete controls[this.control.label]
32
+  }
33
+}
34
+
35
+export class NumberControl extends Control<NumberCodeControl> {
36
+  constructor(options: Omit<NumberCodeControl, "id" | "type">) {
37
+    const { value = 0, step = 1 } = options
38
+    super({
39
+      type: ControlType.Number,
40
+      ...options,
41
+      value,
42
+      step,
43
+    })
44
+  }
45
+}
46
+
47
+export class VectorControl extends Control<VectorCodeControl> {
48
+  constructor(options: Omit<VectorCodeControl, "id" | "type">) {
49
+    const { value = [0, 0], isNormalized = false } = options
50
+    super({
51
+      type: ControlType.Vector,
52
+      ...options,
53
+      value,
54
+      isNormalized,
55
+    })
56
+  }
57
+}

+ 11
- 0
lib/code/dot.ts Zobrazit soubor

@@ -1,9 +1,12 @@
1 1
 import CodeShape from "./index"
2 2
 import { v4 as uuid } from "uuid"
3 3
 import { DotShape, ShapeType } from "types"
4
+import { vectorToPoint } from "utils/utils"
4 5
 
5 6
 export default class Dot extends CodeShape<DotShape> {
6 7
   constructor(props = {} as Partial<DotShape>) {
8
+    props.point = vectorToPoint(props.point)
9
+
7 10
     super({
8 11
       id: uuid(),
9 12
       type: ShapeType.Dot,
@@ -21,4 +24,12 @@ export default class Dot extends CodeShape<DotShape> {
21 24
       ...props,
22 25
     })
23 26
   }
27
+
28
+  export() {
29
+    const shape = { ...this.shape }
30
+
31
+    shape.point = vectorToPoint(shape.point)
32
+
33
+    return shape
34
+  }
24 35
 }

+ 17
- 2
lib/code/ellipse.ts Zobrazit soubor

@@ -1,9 +1,12 @@
1 1
 import CodeShape from "./index"
2 2
 import { v4 as uuid } from "uuid"
3 3
 import { EllipseShape, ShapeType } from "types"
4
+import { vectorToPoint } from "utils/utils"
4 5
 
5 6
 export default class Ellipse extends CodeShape<EllipseShape> {
6 7
   constructor(props = {} as Partial<EllipseShape>) {
8
+    props.point = vectorToPoint(props.point)
9
+
7 10
     super({
8 11
       id: uuid(),
9 12
       type: ShapeType.Ellipse,
@@ -24,7 +27,19 @@ export default class Ellipse extends CodeShape<EllipseShape> {
24 27
     })
25 28
   }
26 29
 
27
-  get radius() {
28
-    return this.shape.radius
30
+  export() {
31
+    const shape = { ...this.shape }
32
+
33
+    shape.point = vectorToPoint(shape.point)
34
+
35
+    return shape
36
+  }
37
+
38
+  get radiusX() {
39
+    return this.shape.radiusX
40
+  }
41
+
42
+  get radiusY() {
43
+    return this.shape.radiusY
29 44
   }
30 45
 }

+ 45
- 3
lib/code/generate.ts Zobrazit soubor

@@ -7,9 +7,11 @@ import Ray from "./ray"
7 7
 import Line from "./line"
8 8
 import Vector from "./vector"
9 9
 import Utils from "./utils"
10
+import { NumberControl, VectorControl, codeControls, controls } from "./control"
10 11
 import { codeShapes } from "./index"
12
+import { CodeControl } from "types"
11 13
 
12
-const scope = {
14
+const baseScope = {
13 15
   Dot,
14 16
   Circle,
15 17
   Ellipse,
@@ -19,6 +21,8 @@ const scope = {
19 21
   Rectangle,
20 22
   Vector,
21 23
   Utils,
24
+  VectorControl,
25
+  NumberControl,
22 26
 }
23 27
 
24 28
 /**
@@ -26,8 +30,46 @@ const scope = {
26 30
  * collected shapes as an array.
27 31
  * @param code
28 32
  */
29
-export function getShapesFromCode(code: string) {
33
+export function generateFromCode(code: string) {
34
+  codeControls.clear()
30 35
   codeShapes.clear()
36
+  ;(window as any).isUpdatingCode = false
37
+
38
+  const scope = { ...baseScope, controls }
39
+
40
+  new Function(...Object.keys(scope), `${code}`)(...Object.values(scope))
41
+
42
+  const generatedShapes = Array.from(codeShapes.values()).map((instance) => {
43
+    instance.shape.isGenerated = true
44
+    return instance.shape
45
+  })
46
+
47
+  const generatedControls = Array.from(codeControls.values())
48
+
49
+  return { shapes: generatedShapes, controls: generatedControls }
50
+}
51
+
52
+/**
53
+ * Evaluate code, collecting generated shapes in the shape set. Return the
54
+ * collected shapes as an array.
55
+ * @param code
56
+ */
57
+export function updateFromCode(
58
+  code: string,
59
+  controls: Record<string, CodeControl>
60
+) {
61
+  codeShapes.clear()
62
+  ;(window as any).isUpdatingCode = true
63
+
64
+  const scope = {
65
+    ...baseScope,
66
+    controls: Object.fromEntries(
67
+      Object.entries(controls).map(([id, control]) => [
68
+        control.label,
69
+        control.value,
70
+      ])
71
+    ),
72
+  }
31 73
 
32 74
   new Function(...Object.keys(scope), `${code}`)(...Object.values(scope))
33 75
 
@@ -36,5 +78,5 @@ export function getShapesFromCode(code: string) {
36 78
     return instance.shape
37 79
   })
38 80
 
39
-  return generatedShapes
81
+  return { shapes: generatedShapes }
40 82
 }

+ 13
- 7
lib/code/index.ts Zobrazit soubor

@@ -1,9 +1,15 @@
1 1
 import { Shape } from "types"
2
-import * as vec from "utils/vec"
3 2
 import { getShapeUtils } from "lib/shapes"
3
+import * as vec from "utils/vec"
4
+import Vector from "./vector"
5
+import { vectorToPoint } from "utils/utils"
4 6
 
5 7
 export const codeShapes = new Set<CodeShape<Shape>>([])
6 8
 
9
+type WithVectors<T extends Shape> = {
10
+  [key in keyof T]: number[] extends T[key] ? Vector : T[key]
11
+}
12
+
7 13
 /**
8 14
  * A base class for code shapes. Note that creating a shape adds it to the
9 15
  * shape map, while deleting it removes it from the collected shapes set
@@ -20,12 +26,12 @@ export default class CodeShape<T extends Shape> {
20 26
     codeShapes.delete(this)
21 27
   }
22 28
 
23
-  moveTo(point: number[]) {
24
-    this.shape.point = point
29
+  moveTo(point: Vector) {
30
+    this.shape.point = vectorToPoint(point)
25 31
   }
26 32
 
27
-  translate(delta: number[]) {
28
-    this.shape.point = vec.add(this._shape.point, delta)
33
+  translate(delta: Vector) {
34
+    this.shape.point = vec.add(this._shape.point, vectorToPoint(delta))
29 35
   }
30 36
 
31 37
   rotate(rotation: number) {
@@ -40,8 +46,8 @@ export default class CodeShape<T extends Shape> {
40 46
     return getShapeUtils(this.shape).getBounds(this.shape)
41 47
   }
42 48
 
43
-  hitTest(point: number[]) {
44
-    return getShapeUtils(this.shape).hitTest(this.shape, point)
49
+  hitTest(point: Vector) {
50
+    return getShapeUtils(this.shape).hitTest(this.shape, vectorToPoint(point))
45 51
   }
46 52
 
47 53
   get shape() {

+ 17
- 0
lib/code/line.ts Zobrazit soubor

@@ -1,9 +1,13 @@
1 1
 import CodeShape from "./index"
2 2
 import { v4 as uuid } from "uuid"
3 3
 import { LineShape, ShapeType } from "types"
4
+import { vectorToPoint } from "utils/utils"
4 5
 
5 6
 export default class Line extends CodeShape<LineShape> {
6 7
   constructor(props = {} as Partial<LineShape>) {
8
+    props.point = vectorToPoint(props.point)
9
+    props.direction = vectorToPoint(props.direction)
10
+
7 11
     super({
8 12
       id: uuid(),
9 13
       type: ShapeType.Line,
@@ -22,4 +26,17 @@ export default class Line extends CodeShape<LineShape> {
22 26
       ...props,
23 27
     })
24 28
   }
29
+
30
+  export() {
31
+    const shape = { ...this.shape }
32
+
33
+    shape.point = vectorToPoint(shape.point)
34
+    shape.direction = vectorToPoint(shape.direction)
35
+
36
+    return shape
37
+  }
38
+
39
+  get direction() {
40
+    return this.shape.direction
41
+  }
25 42
 }

+ 17
- 0
lib/code/polyline.ts Zobrazit soubor

@@ -1,9 +1,13 @@
1 1
 import CodeShape from "./index"
2 2
 import { v4 as uuid } from "uuid"
3 3
 import { PolylineShape, ShapeType } from "types"
4
+import { vectorToPoint } from "utils/utils"
4 5
 
5 6
 export default class Polyline extends CodeShape<PolylineShape> {
6 7
   constructor(props = {} as Partial<PolylineShape>) {
8
+    props.point = vectorToPoint(props.point)
9
+    props.points = props.points.map(vectorToPoint)
10
+
7 11
     super({
8 12
       id: uuid(),
9 13
       type: ShapeType.Polyline,
@@ -22,4 +26,17 @@ export default class Polyline extends CodeShape<PolylineShape> {
22 26
       ...props,
23 27
     })
24 28
   }
29
+
30
+  export() {
31
+    const shape = { ...this.shape }
32
+
33
+    shape.point = vectorToPoint(shape.point)
34
+    shape.points = shape.points.map(vectorToPoint)
35
+
36
+    return shape
37
+  }
38
+
39
+  get points() {
40
+    return this.shape.points
41
+  }
25 42
 }

+ 17
- 0
lib/code/ray.ts Zobrazit soubor

@@ -1,9 +1,13 @@
1 1
 import CodeShape from "./index"
2 2
 import { v4 as uuid } from "uuid"
3 3
 import { RayShape, ShapeType } from "types"
4
+import { vectorToPoint } from "utils/utils"
4 5
 
5 6
 export default class Ray extends CodeShape<RayShape> {
6 7
   constructor(props = {} as Partial<RayShape>) {
8
+    props.point = vectorToPoint(props.point)
9
+    props.direction = vectorToPoint(props.direction)
10
+
7 11
     super({
8 12
       id: uuid(),
9 13
       type: ShapeType.Ray,
@@ -22,4 +26,17 @@ export default class Ray extends CodeShape<RayShape> {
22 26
       ...props,
23 27
     })
24 28
   }
29
+
30
+  export() {
31
+    const shape = { ...this.shape }
32
+
33
+    shape.point = vectorToPoint(shape.point)
34
+    shape.direction = vectorToPoint(shape.direction)
35
+
36
+    return shape
37
+  }
38
+
39
+  get direction() {
40
+    return this.shape.direction
41
+  }
25 42
 }

+ 4
- 0
lib/code/rectangle.ts Zobrazit soubor

@@ -1,9 +1,13 @@
1 1
 import CodeShape from "./index"
2 2
 import { v4 as uuid } from "uuid"
3 3
 import { RectangleShape, ShapeType } from "types"
4
+import { vectorToPoint } from "utils/utils"
4 5
 
5 6
 export default class Rectangle extends CodeShape<RectangleShape> {
6 7
   constructor(props = {} as Partial<RectangleShape>) {
8
+    props.point = vectorToPoint(props.point)
9
+    props.size = vectorToPoint(props.size)
10
+
7 11
     super({
8 12
       id: uuid(),
9 13
       type: ShapeType.Rectangle,

state/commands/generate-shapes.ts → state/commands/generate.ts Zobrazit soubor

@@ -1,6 +1,6 @@
1 1
 import Command from "./command"
2 2
 import history from "../history"
3
-import { Data, Shape } from "types"
3
+import { CodeControl, Data, Shape } from "types"
4 4
 import { current } from "immer"
5 5
 
6 6
 export default function setGeneratedShapes(
@@ -8,12 +8,24 @@ export default function setGeneratedShapes(
8 8
   currentPageId: string,
9 9
   generatedShapes: Shape[]
10 10
 ) {
11
+  const cData = current(data)
12
+
11 13
   const prevGeneratedShapes = Object.values(
12
-    current(data).document.pages[currentPageId].shapes
14
+    cData.document.pages[currentPageId].shapes
13 15
   ).filter((shape) => shape.isGenerated)
14 16
 
17
+  const currentShapes = data.document.pages[currentPageId].shapes
18
+
19
+  // Remove previous generated shapes
20
+  for (let id in currentShapes) {
21
+    if (currentShapes[id].isGenerated) {
22
+      delete currentShapes[id]
23
+    }
24
+  }
25
+
26
+  // Add new ones
15 27
   for (let shape of generatedShapes) {
16
-    data.document.pages[currentPageId].shapes[shape.id] = shape
28
+    currentShapes[shape.id] = shape
17 29
   }
18 30
 
19 31
   history.execute(

+ 2
- 2
state/commands/index.ts Zobrazit soubor

@@ -1,13 +1,13 @@
1 1
 import translate from "./translate"
2 2
 import transform from "./transform"
3
-import generateShapes from "./generate-shapes"
3
+import generate from "./generate"
4 4
 import createShape from "./create-shape"
5 5
 import direction from "./direction"
6 6
 
7 7
 const commands = {
8 8
   translate,
9 9
   transform,
10
-  generateShapes,
10
+  generate,
11 11
   createShape,
12 12
   direction,
13 13
 }

+ 8
- 8
state/data.ts Zobrazit soubor

@@ -121,32 +121,32 @@ export const defaultDocument: Data["document"] = {
121 121
       name: "index.ts",
122 122
       code: `
123 123
 new Dot({
124
-  point: [0, 0],
124
+  point: new Vector(0, 0),
125 125
 })
126 126
 
127 127
 new Circle({
128
-  point: [200, 0],
128
+  point: new Vector(200, 0),
129 129
   radius: 50,
130 130
 })
131 131
 
132 132
 new Ellipse({
133
-  point: [400, 0],
133
+  point: new Vector(400, 0),
134 134
   radiusX: 50,
135 135
   radiusY: 75
136 136
 })
137 137
 
138 138
 new Rectangle({
139
-  point: [0, 300],
139
+  point: new Vector(0, 300),
140 140
 })
141 141
 
142 142
 new Line({
143
-  point: [200, 300],
144
-  direction: [1,0.2]
143
+  point: new Vector(200, 300),
144
+  direction: new Vector(1,0.2)
145 145
 })
146 146
 
147 147
 new Polyline({
148
-  point: [400, 300],
149
-  points: [[0, 200], [0,0], [200, 200], [200, 0]],
148
+  point: new Vector(400, 300),
149
+  points: [new Vector(0, 200), new Vector(0,0), new Vector(200, 200), new Vector(200, 0)],
150 150
 })
151 151
 `,
152 152
     },

+ 48
- 6
state/state.ts Zobrazit soubor

@@ -9,12 +9,15 @@ import {
9 9
   Shapes,
10 10
   TransformCorner,
11 11
   TransformEdge,
12
+  CodeControl,
12 13
 } from "types"
13 14
 import { defaultDocument } from "./data"
14 15
 import shapeUtilityMap, { getShapeUtils } from "lib/shapes"
15 16
 import history from "state/history"
16 17
 import * as Sessions from "./sessions"
17 18
 import commands from "./commands"
19
+import { controls } from "lib/code/control"
20
+import { generateFromCode, updateFromCode } from "lib/code/generate"
18 21
 
19 22
 const initialData: Data = {
20 23
   isReadOnly: false,
@@ -32,6 +35,7 @@ const initialData: Data = {
32 35
   selectedIds: new Set([]),
33 36
   currentPageId: "page0",
34 37
   currentCodeFileId: "file0",
38
+  codeControls: {},
35 39
   document: defaultDocument,
36 40
 }
37 41
 
@@ -52,6 +56,7 @@ const state = createState({
52 56
     SELECTED_LINE_TOOL: { unless: "isReadOnly", to: "line" },
53 57
     SELECTED_POLYLINE_TOOL: { unless: "isReadOnly", to: "polyline" },
54 58
     SELECTED_RECTANGLE_TOOL: { unless: "isReadOnly", to: "rectangle" },
59
+    RESET_CAMERA: "resetCamera",
55 60
   },
56 61
   initial: "loading",
57 62
   states: {
@@ -70,9 +75,10 @@ const state = createState({
70 75
         CANCELLED: { do: "clearSelectedIds" },
71 76
         DELETED: { do: "deleteSelectedIds" },
72 77
         SAVED_CODE: "saveCode",
73
-        GENERATED_SHAPES_FROM_CODE: "setGeneratedShapes",
78
+        GENERATED_FROM_CODE: ["setCodeControls", "setGeneratedShapes"],
74 79
         INCREASED_CODE_FONT_SIZE: "increaseCodeFontSize",
75 80
         DECREASED_CODE_FONT_SIZE: "decreaseCodeFontSize",
81
+        CHANGED_CODE_CONTROL: "updateControls",
76 82
       },
77 83
       initial: "notPointing",
78 84
       states: {
@@ -437,6 +443,12 @@ const state = createState({
437 443
       data.selectedIds.add(data.pointedId)
438 444
     },
439 445
     // Camera
446
+    resetCamera(data) {
447
+      data.camera.zoom = 1
448
+      data.camera.point = [window.innerWidth / 2, window.innerHeight / 2]
449
+
450
+      document.documentElement.style.setProperty("--camera-zoom", "1")
451
+    },
440 452
     zoomCamera(data, payload: { delta: number; point: number[] }) {
441 453
       const { camera } = data
442 454
       const p0 = screenToWorld(payload.point, data)
@@ -493,8 +505,16 @@ const state = createState({
493 505
     },
494 506
 
495 507
     // Code
496
-    setGeneratedShapes(data, payload: { shapes: Shape[] }) {
497
-      commands.generateShapes(data, data.currentPageId, payload.shapes)
508
+    setGeneratedShapes(
509
+      data,
510
+      payload: { shapes: Shape[]; controls: CodeControl[] }
511
+    ) {
512
+      commands.generate(data, data.currentPageId, payload.shapes)
513
+    },
514
+    setCodeControls(data, payload: { controls: CodeControl[] }) {
515
+      data.codeControls = Object.fromEntries(
516
+        payload.controls.map((control) => [control.id, control])
517
+      )
498 518
     },
499 519
     increaseCodeFontSize(data) {
500 520
       data.settings.fontSize++
@@ -502,6 +522,28 @@ const state = createState({
502 522
     decreaseCodeFontSize(data) {
503 523
       data.settings.fontSize--
504 524
     },
525
+    updateControls(data, payload: { [key: string]: any }) {
526
+      for (let key in payload) {
527
+        data.codeControls[key].value = payload[key]
528
+      }
529
+
530
+      history.disable()
531
+
532
+      data.selectedIds.clear()
533
+
534
+      try {
535
+        const { shapes } = updateFromCode(
536
+          data.document.code[data.currentCodeFileId].code,
537
+          data.codeControls
538
+        )
539
+
540
+        commands.generate(data, data.currentPageId, shapes)
541
+      } catch (e) {
542
+        console.error(e)
543
+      }
544
+
545
+      history.enable()
546
+    },
505 547
 
506 548
     // Data
507 549
     saveCode(data, payload: { code: string }) {
@@ -524,9 +566,9 @@ const state = createState({
524 566
         document: { pages },
525 567
       } = data
526 568
 
527
-      const shapes = Array.from(selectedIds.values()).map(
528
-        (id) => pages[currentPageId].shapes[id]
529
-      )
569
+      const shapes = Array.from(selectedIds.values())
570
+        .map((id) => pages[currentPageId].shapes[id])
571
+        .filter(Boolean)
530 572
 
531 573
       if (selectedIds.size === 0) return null
532 574
 

+ 3
- 1
styles/stitches.config.ts Zobrazit soubor

@@ -15,6 +15,8 @@ const { styled, global, css, theme, getCssString } = createCss({
15 15
       border: "#aaa",
16 16
       panel: "#fefefe",
17 17
       text: "#000",
18
+      input: "#f3f3f3",
19
+      inputBorder: "#ddd",
18 20
     },
19 21
     space: {},
20 22
     fontSizes: {
@@ -47,7 +49,7 @@ const { styled, global, css, theme, getCssString } = createCss({
47 49
             min !== undefined && max !== undefined
48 50
               ? `clamp(${min}, ${val} / var(--camera-zoom), ${max})`
49 51
               : min !== undefined
50
-              ? `max(${min}, ${val} / var(--camera-zoom))`
52
+              ? `min(${min}, ${val} / var(--camera-zoom))`
51 53
               : `calc(${val} / var(--camera-zoom))`,
52 54
         }
53 55
       }

+ 107
- 40
types.ts Zobrazit soubor

@@ -2,6 +2,10 @@ import * as monaco from "monaco-editor/esm/vs/editor/editor.api"
2 2
 
3 3
 import React from "react"
4 4
 
5
+/* -------------------------------------------------- */
6
+/*                    Client State                    */
7
+/* -------------------------------------------------- */
8
+
5 9
 export interface Data {
6 10
   isReadOnly: boolean
7 11
   settings: {
@@ -18,17 +22,16 @@ export interface Data {
18 22
   hoveredId?: string
19 23
   currentPageId: string
20 24
   currentCodeFileId: string
25
+  codeControls: Record<string, CodeControl>
21 26
   document: {
22 27
     pages: Record<string, Page>
23 28
     code: Record<string, CodeFile>
24 29
   }
25 30
 }
26 31
 
27
-export interface CodeFile {
28
-  id: string
29
-  name: string
30
-  code: string
31
-}
32
+/* -------------------------------------------------- */
33
+/*                      Document                      */
34
+/* -------------------------------------------------- */
32 35
 
33 36
 export interface Page {
34 37
   id: string
@@ -46,12 +49,14 @@ export enum ShapeType {
46 49
   Ray = "ray",
47 50
   Polyline = "polyline",
48 51
   Rectangle = "rectangle",
49
-  // Glob = "glob",
50
-  // Spline = "spline",
51
-  // Cubic = "cubic",
52
-  // Conic = "conic",
53 52
 }
54 53
 
54
+// Consider:
55
+// Glob = "glob",
56
+// Spline = "spline",
57
+// Cubic = "cubic",
58
+// Conic = "conic",
59
+
55 60
 export interface BaseShape {
56 61
   id: string
57 62
   type: ShapeType
@@ -108,6 +113,51 @@ export type Shape =
108 113
   | PolylineShape
109 114
   | RectangleShape
110 115
 
116
+export interface Shapes extends Record<ShapeType, Shape> {
117
+  [ShapeType.Dot]: DotShape
118
+  [ShapeType.Circle]: CircleShape
119
+  [ShapeType.Ellipse]: EllipseShape
120
+  [ShapeType.Line]: LineShape
121
+  [ShapeType.Ray]: RayShape
122
+  [ShapeType.Polyline]: PolylineShape
123
+  [ShapeType.Rectangle]: RectangleShape
124
+}
125
+
126
+export interface CodeFile {
127
+  id: string
128
+  name: string
129
+  code: string
130
+}
131
+
132
+/* -------------------------------------------------- */
133
+/*                      Editor UI                     */
134
+/* -------------------------------------------------- */
135
+
136
+export interface PointerInfo {
137
+  target: string
138
+  pointerId: number
139
+  origin: number[]
140
+  point: number[]
141
+  shiftKey: boolean
142
+  ctrlKey: boolean
143
+  metaKey: boolean
144
+  altKey: boolean
145
+}
146
+
147
+export enum TransformEdge {
148
+  Top = "top_edge",
149
+  Right = "right_edge",
150
+  Bottom = "bottom_edge",
151
+  Left = "left_edge",
152
+}
153
+
154
+export enum TransformCorner {
155
+  TopLeft = "top_left_corner",
156
+  TopRight = "top_right_corner",
157
+  BottomRight = "bottom_right_corner",
158
+  BottomLeft = "bottom_left_corner",
159
+}
160
+
111 161
 export interface Bounds {
112 162
   minX: number
113 163
   minY: number
@@ -133,16 +183,6 @@ export interface BoundsSnapshot extends PointSnapshot {
133 183
   nh: number
134 184
 }
135 185
 
136
-export interface Shapes extends Record<ShapeType, Shape> {
137
-  [ShapeType.Dot]: DotShape
138
-  [ShapeType.Circle]: CircleShape
139
-  [ShapeType.Ellipse]: EllipseShape
140
-  [ShapeType.Line]: LineShape
141
-  [ShapeType.Ray]: RayShape
142
-  [ShapeType.Polyline]: PolylineShape
143
-  [ShapeType.Rectangle]: RectangleShape
144
-}
145
-
146 186
 export type Difference<A, B> = A extends B ? never : A
147 187
 
148 188
 export type ShapeSpecificProps<T extends Shape> = Pick<
@@ -163,32 +203,59 @@ export type ShapeUtil<K extends Shape> = {
163 203
   stretch(shape: K, scaleX: number, scaleY: number): K
164 204
   render(shape: K): JSX.Element
165 205
 }
206
+/* -------------------------------------------------- */
207
+/*                     Code Editor                    */
208
+/* -------------------------------------------------- */
166 209
 
167
-export interface PointerInfo {
168
-  target: string
169
-  pointerId: number
170
-  origin: number[]
171
-  point: number[]
172
-  shiftKey: boolean
173
-  ctrlKey: boolean
174
-  metaKey: boolean
175
-  altKey: boolean
210
+export type IMonaco = typeof monaco
211
+
212
+export type IMonacoEditor = monaco.editor.IStandaloneCodeEditor
213
+
214
+export enum ControlType {
215
+  Number = "number",
216
+  Vector = "vector",
217
+  Text = "text",
218
+  Select = "select",
176 219
 }
177 220
 
178
-export enum TransformEdge {
179
-  Top = "top_edge",
180
-  Right = "right_edge",
181
-  Bottom = "bottom_edge",
182
-  Left = "left_edge",
221
+export interface BaseCodeControl {
222
+  id: string
223
+  type: ControlType
224
+  label: string
183 225
 }
184 226
 
185
-export enum TransformCorner {
186
-  TopLeft = "top_left_corner",
187
-  TopRight = "top_right_corner",
188
-  BottomRight = "bottom_right_corner",
189
-  BottomLeft = "bottom_left_corner",
227
+export interface NumberCodeControl extends BaseCodeControl {
228
+  type: ControlType.Number
229
+  min?: number
230
+  max?: number
231
+  value: number
232
+  step: number
233
+  format?: (value: number) => number
190 234
 }
191 235
 
192
-export type IMonaco = typeof monaco
236
+export interface VectorCodeControl extends BaseCodeControl {
237
+  type: ControlType.Vector
238
+  value: number[]
239
+  isNormalized: boolean
240
+  format?: (value: number[]) => number[]
241
+}
193 242
 
194
-export type IMonacoEditor = monaco.editor.IStandaloneCodeEditor
243
+export interface TextCodeControl extends BaseCodeControl {
244
+  type: ControlType.Text
245
+  value: string
246
+  format?: (value: string) => string
247
+}
248
+
249
+export interface SelectCodeControl<T extends string = "">
250
+  extends BaseCodeControl {
251
+  type: ControlType.Select
252
+  value: T
253
+  options: T[]
254
+  format?: (string: T) => string
255
+}
256
+
257
+export type CodeControl =
258
+  | NumberCodeControl
259
+  | VectorCodeControl
260
+  | TextCodeControl
261
+  | SelectCodeControl

+ 14
- 2
utils/utils.ts Zobrazit soubor

@@ -1,3 +1,4 @@
1
+import Vector from "lib/code/vector"
1 2
 import React from "react"
2 3
 import { Data, Bounds, TransformEdge, TransformCorner } from "types"
3 4
 import * as svg from "./svg"
@@ -801,7 +802,7 @@ export function throttle<P extends any[], T extends (...args: P) => any>(
801 802
 ) {
802 803
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
803 804
   let inThrottle: boolean, lastFn: any, lastTime: number
804
-  return function(...args: P) {
805
+  return function (...args: P) {
805 806
     if (preventDefault) args[0].preventDefault()
806 807
     // eslint-disable-next-line @typescript-eslint/no-this-alias
807 808
     const context = this
@@ -811,7 +812,7 @@ export function throttle<P extends any[], T extends (...args: P) => any>(
811 812
       inThrottle = true
812 813
     } else {
813 814
       clearTimeout(lastFn)
814
-      lastFn = setTimeout(function() {
815
+      lastFn = setTimeout(function () {
815 816
         if (Date.now() - lastTime >= wait) {
816 817
           fn.apply(context, args)
817 818
           lastTime = Date.now()
@@ -950,3 +951,14 @@ export function getTransformAnchor(
950 951
 
951 952
   return anchor
952 953
 }
954
+
955
+export function vectorToPoint(point: number[] | Vector | undefined) {
956
+  if (typeof point === "undefined") {
957
+    return [0, 0]
958
+  }
959
+
960
+  if (point instanceof Vector) {
961
+    return [point.x, point.y]
962
+  }
963
+  return point
964
+}

Načítá se…
Zrušit
Uložit