Pārlūkot izejas kodu

Updates code editor

main
Steve Ruiz 4 gadus atpakaļ
vecāks
revīzija
abd310aa2e

+ 21
- 131
components/code-panel/code-panel.tsx Parādīt failu

7
 import { CodeFile } from "types"
7
 import { CodeFile } from "types"
8
 import CodeDocs from "./code-docs"
8
 import CodeDocs from "./code-docs"
9
 import CodeEditor from "./code-editor"
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
 import {
13
 import {
12
   X,
14
   X,
13
   Code,
15
   Code,
51
     states: {
53
     states: {
52
       editingCode: {
54
       editingCode: {
53
         on: {
55
         on: {
54
-          RAN_CODE: "runCode",
55
-          SAVED_CODE: ["runCode", "saveCode"],
56
+          RAN_CODE: ["saveCode", "runCode"],
57
+          SAVED_CODE: ["saveCode", "runCode"],
56
           CHANGED_CODE: { secretlyDo: "setCode" },
58
           CHANGED_CODE: { secretlyDo: "setCode" },
57
           CLEARED_ERROR: { if: "hasError", do: "clearError" },
59
           CLEARED_ERROR: { if: "hasError", do: "clearError" },
58
           TOGGLED_DOCS: { to: "viewingDocs" },
60
           TOGGLED_DOCS: { to: "viewingDocs" },
80
         let error = null
82
         let error = null
81
 
83
 
82
         try {
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
         } catch (e) {
87
         } catch (e) {
86
           console.error(e)
88
           console.error(e)
87
           error = { message: e.message, ...getErrorLineAndColumn(e) }
89
           error = { message: e.message, ...getErrorLineAndColumn(e) }
113
   const { error } = local.data
115
   const { error } = local.data
114
 
116
 
115
   return (
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
       {isOpen ? (
119
       {isOpen ? (
123
-        <Content>
124
-          <Header>
120
+        <Panel.Layout>
121
+          <Panel.Header>
125
             <IconButton onClick={() => state.send("CLOSED_CODE_PANEL")}>
122
             <IconButton onClick={() => state.send("CLOSED_CODE_PANEL")}>
126
               <X />
123
               <X />
127
             </IconButton>
124
             </IconButton>
151
                 <PlayCircle />
148
                 <PlayCircle />
152
               </IconButton>
149
               </IconButton>
153
             </ButtonsGroup>
150
             </ButtonsGroup>
154
-          </Header>
155
-          <EditorContainer>
151
+          </Panel.Header>
152
+          <Panel.Content>
156
             <CodeEditor
153
             <CodeEditor
157
               fontSize={fontSize}
154
               fontSize={fontSize}
158
               readOnly={isReadOnly}
155
               readOnly={isReadOnly}
163
               onKey={() => local.send("CLEARED_ERROR")}
160
               onKey={() => local.send("CLEARED_ERROR")}
164
             />
161
             />
165
             <CodeDocs isHidden={!local.isIn("viewingDocs")} />
162
             <CodeDocs isHidden={!local.isIn("viewingDocs")} />
166
-          </EditorContainer>
167
-          <ErrorContainer>
163
+          </Panel.Content>
164
+          <Panel.Footer>
168
             {error &&
165
             {error &&
169
               (error.line
166
               (error.line
170
                 ? `(${Number(error.line) - 2}:${error.column}) ${error.message}`
167
                 ? `(${Number(error.line) - 2}:${error.column}) ${error.message}`
171
                 : error.message)}
168
                 : error.message)}
172
-          </ErrorContainer>
173
-        </Content>
169
+          </Panel.Footer>
170
+        </Panel.Layout>
174
       ) : (
171
       ) : (
175
         <IconButton onClick={() => state.send("OPENED_CODE_PANEL")}>
172
         <IconButton onClick={() => state.send("OPENED_CODE_PANEL")}>
176
           <Code />
173
           <Code />
177
         </IconButton>
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
 const ButtonsGroup = styled("div", {
180
 const ButtonsGroup = styled("div", {
276
   gridRow: "1",
181
   gridRow: "1",
277
   gridColumn: "3",
182
   gridColumn: "3",
278
   display: "flex",
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
 const FontSizeButtons = styled("div", {
186
 const FontSizeButtons = styled("div", {
297
   paddingRight: 4,
187
   paddingRight: 4,
188
+  display: "flex",
189
+  flexDirection: "column",
298
 
190
 
299
   "& > button": {
191
   "& > button": {
300
     height: "50%",
192
     height: "50%",
301
-    width: "100%",
302
-
303
     "&:nth-of-type(1)": {
193
     "&:nth-of-type(1)": {
304
-      paddingTop: 4,
194
+      alignItems: "flex-end",
305
     },
195
     },
306
 
196
 
307
     "&:nth-of-type(2)": {
197
     "&:nth-of-type(2)": {
308
-      paddingBottom: 4,
198
+      alignItems: "flex-start",
309
     },
199
     },
310
 
200
 
311
     "& svg": {
201
     "& svg": {

+ 145
- 0
components/controls-panel/control.tsx Parādīt failu

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 Parādīt failu

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 Parādīt failu

4
 import StatusBar from "./status-bar"
4
 import StatusBar from "./status-bar"
5
 import Toolbar from "./toolbar"
5
 import Toolbar from "./toolbar"
6
 import CodePanel from "./code-panel/code-panel"
6
 import CodePanel from "./code-panel/code-panel"
7
+import ControlsPanel from "./controls-panel/controls-panel"
8
+import styled from "styles"
7
 
9
 
8
 export default function Editor() {
10
 export default function Editor() {
9
   useKeyboardEvents()
11
   useKeyboardEvents()
10
   useLoadOnMount()
12
   useLoadOnMount()
11
 
13
 
12
   return (
14
   return (
13
-    <>
15
+    <Layout>
14
       <Canvas />
16
       <Canvas />
15
       <StatusBar />
17
       <StatusBar />
16
       <Toolbar />
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 Parādīt failu

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 Parādīt failu

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 Parādīt failu

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

+ 11
- 0
lib/code/circle.ts Parādīt failu

1
 import CodeShape from "./index"
1
 import CodeShape from "./index"
2
 import { v4 as uuid } from "uuid"
2
 import { v4 as uuid } from "uuid"
3
 import { CircleShape, ShapeType } from "types"
3
 import { CircleShape, ShapeType } from "types"
4
+import { vectorToPoint } from "utils/utils"
4
 
5
 
5
 export default class Circle extends CodeShape<CircleShape> {
6
 export default class Circle extends CodeShape<CircleShape> {
6
   constructor(props = {} as Partial<CircleShape>) {
7
   constructor(props = {} as Partial<CircleShape>) {
8
+    props.point = vectorToPoint(props.point)
9
+
7
     super({
10
     super({
8
       id: uuid(),
11
       id: uuid(),
9
       type: ShapeType.Circle,
12
       type: ShapeType.Circle,
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
   get radius() {
37
   get radius() {
27
     return this.shape.radius
38
     return this.shape.radius
28
   }
39
   }

+ 57
- 0
lib/code/control.ts Parādīt failu

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 Parādīt failu

1
 import CodeShape from "./index"
1
 import CodeShape from "./index"
2
 import { v4 as uuid } from "uuid"
2
 import { v4 as uuid } from "uuid"
3
 import { DotShape, ShapeType } from "types"
3
 import { DotShape, ShapeType } from "types"
4
+import { vectorToPoint } from "utils/utils"
4
 
5
 
5
 export default class Dot extends CodeShape<DotShape> {
6
 export default class Dot extends CodeShape<DotShape> {
6
   constructor(props = {} as Partial<DotShape>) {
7
   constructor(props = {} as Partial<DotShape>) {
8
+    props.point = vectorToPoint(props.point)
9
+
7
     super({
10
     super({
8
       id: uuid(),
11
       id: uuid(),
9
       type: ShapeType.Dot,
12
       type: ShapeType.Dot,
21
       ...props,
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 Parādīt failu

1
 import CodeShape from "./index"
1
 import CodeShape from "./index"
2
 import { v4 as uuid } from "uuid"
2
 import { v4 as uuid } from "uuid"
3
 import { EllipseShape, ShapeType } from "types"
3
 import { EllipseShape, ShapeType } from "types"
4
+import { vectorToPoint } from "utils/utils"
4
 
5
 
5
 export default class Ellipse extends CodeShape<EllipseShape> {
6
 export default class Ellipse extends CodeShape<EllipseShape> {
6
   constructor(props = {} as Partial<EllipseShape>) {
7
   constructor(props = {} as Partial<EllipseShape>) {
8
+    props.point = vectorToPoint(props.point)
9
+
7
     super({
10
     super({
8
       id: uuid(),
11
       id: uuid(),
9
       type: ShapeType.Ellipse,
12
       type: ShapeType.Ellipse,
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 Parādīt failu

7
 import Line from "./line"
7
 import Line from "./line"
8
 import Vector from "./vector"
8
 import Vector from "./vector"
9
 import Utils from "./utils"
9
 import Utils from "./utils"
10
+import { NumberControl, VectorControl, codeControls, controls } from "./control"
10
 import { codeShapes } from "./index"
11
 import { codeShapes } from "./index"
12
+import { CodeControl } from "types"
11
 
13
 
12
-const scope = {
14
+const baseScope = {
13
   Dot,
15
   Dot,
14
   Circle,
16
   Circle,
15
   Ellipse,
17
   Ellipse,
19
   Rectangle,
21
   Rectangle,
20
   Vector,
22
   Vector,
21
   Utils,
23
   Utils,
24
+  VectorControl,
25
+  NumberControl,
22
 }
26
 }
23
 
27
 
24
 /**
28
 /**
26
  * collected shapes as an array.
30
  * collected shapes as an array.
27
  * @param code
31
  * @param code
28
  */
32
  */
29
-export function getShapesFromCode(code: string) {
33
+export function generateFromCode(code: string) {
34
+  codeControls.clear()
30
   codeShapes.clear()
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
   new Function(...Object.keys(scope), `${code}`)(...Object.values(scope))
74
   new Function(...Object.keys(scope), `${code}`)(...Object.values(scope))
33
 
75
 
36
     return instance.shape
78
     return instance.shape
37
   })
79
   })
38
 
80
 
39
-  return generatedShapes
81
+  return { shapes: generatedShapes }
40
 }
82
 }

+ 13
- 7
lib/code/index.ts Parādīt failu

1
 import { Shape } from "types"
1
 import { Shape } from "types"
2
-import * as vec from "utils/vec"
3
 import { getShapeUtils } from "lib/shapes"
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
 export const codeShapes = new Set<CodeShape<Shape>>([])
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
  * A base class for code shapes. Note that creating a shape adds it to the
14
  * A base class for code shapes. Note that creating a shape adds it to the
9
  * shape map, while deleting it removes it from the collected shapes set
15
  * shape map, while deleting it removes it from the collected shapes set
20
     codeShapes.delete(this)
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
   rotate(rotation: number) {
37
   rotate(rotation: number) {
40
     return getShapeUtils(this.shape).getBounds(this.shape)
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
   get shape() {
53
   get shape() {

+ 17
- 0
lib/code/line.ts Parādīt failu

1
 import CodeShape from "./index"
1
 import CodeShape from "./index"
2
 import { v4 as uuid } from "uuid"
2
 import { v4 as uuid } from "uuid"
3
 import { LineShape, ShapeType } from "types"
3
 import { LineShape, ShapeType } from "types"
4
+import { vectorToPoint } from "utils/utils"
4
 
5
 
5
 export default class Line extends CodeShape<LineShape> {
6
 export default class Line extends CodeShape<LineShape> {
6
   constructor(props = {} as Partial<LineShape>) {
7
   constructor(props = {} as Partial<LineShape>) {
8
+    props.point = vectorToPoint(props.point)
9
+    props.direction = vectorToPoint(props.direction)
10
+
7
     super({
11
     super({
8
       id: uuid(),
12
       id: uuid(),
9
       type: ShapeType.Line,
13
       type: ShapeType.Line,
22
       ...props,
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 Parādīt failu

1
 import CodeShape from "./index"
1
 import CodeShape from "./index"
2
 import { v4 as uuid } from "uuid"
2
 import { v4 as uuid } from "uuid"
3
 import { PolylineShape, ShapeType } from "types"
3
 import { PolylineShape, ShapeType } from "types"
4
+import { vectorToPoint } from "utils/utils"
4
 
5
 
5
 export default class Polyline extends CodeShape<PolylineShape> {
6
 export default class Polyline extends CodeShape<PolylineShape> {
6
   constructor(props = {} as Partial<PolylineShape>) {
7
   constructor(props = {} as Partial<PolylineShape>) {
8
+    props.point = vectorToPoint(props.point)
9
+    props.points = props.points.map(vectorToPoint)
10
+
7
     super({
11
     super({
8
       id: uuid(),
12
       id: uuid(),
9
       type: ShapeType.Polyline,
13
       type: ShapeType.Polyline,
22
       ...props,
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 Parādīt failu

1
 import CodeShape from "./index"
1
 import CodeShape from "./index"
2
 import { v4 as uuid } from "uuid"
2
 import { v4 as uuid } from "uuid"
3
 import { RayShape, ShapeType } from "types"
3
 import { RayShape, ShapeType } from "types"
4
+import { vectorToPoint } from "utils/utils"
4
 
5
 
5
 export default class Ray extends CodeShape<RayShape> {
6
 export default class Ray extends CodeShape<RayShape> {
6
   constructor(props = {} as Partial<RayShape>) {
7
   constructor(props = {} as Partial<RayShape>) {
8
+    props.point = vectorToPoint(props.point)
9
+    props.direction = vectorToPoint(props.direction)
10
+
7
     super({
11
     super({
8
       id: uuid(),
12
       id: uuid(),
9
       type: ShapeType.Ray,
13
       type: ShapeType.Ray,
22
       ...props,
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 Parādīt failu

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

state/commands/generate-shapes.ts → state/commands/generate.ts Parādīt failu

1
 import Command from "./command"
1
 import Command from "./command"
2
 import history from "../history"
2
 import history from "../history"
3
-import { Data, Shape } from "types"
3
+import { CodeControl, Data, Shape } from "types"
4
 import { current } from "immer"
4
 import { current } from "immer"
5
 
5
 
6
 export default function setGeneratedShapes(
6
 export default function setGeneratedShapes(
8
   currentPageId: string,
8
   currentPageId: string,
9
   generatedShapes: Shape[]
9
   generatedShapes: Shape[]
10
 ) {
10
 ) {
11
+  const cData = current(data)
12
+
11
   const prevGeneratedShapes = Object.values(
13
   const prevGeneratedShapes = Object.values(
12
-    current(data).document.pages[currentPageId].shapes
14
+    cData.document.pages[currentPageId].shapes
13
   ).filter((shape) => shape.isGenerated)
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
   for (let shape of generatedShapes) {
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
   history.execute(
31
   history.execute(

+ 2
- 2
state/commands/index.ts Parādīt failu

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

+ 8
- 8
state/data.ts Parādīt failu

121
       name: "index.ts",
121
       name: "index.ts",
122
       code: `
122
       code: `
123
 new Dot({
123
 new Dot({
124
-  point: [0, 0],
124
+  point: new Vector(0, 0),
125
 })
125
 })
126
 
126
 
127
 new Circle({
127
 new Circle({
128
-  point: [200, 0],
128
+  point: new Vector(200, 0),
129
   radius: 50,
129
   radius: 50,
130
 })
130
 })
131
 
131
 
132
 new Ellipse({
132
 new Ellipse({
133
-  point: [400, 0],
133
+  point: new Vector(400, 0),
134
   radiusX: 50,
134
   radiusX: 50,
135
   radiusY: 75
135
   radiusY: 75
136
 })
136
 })
137
 
137
 
138
 new Rectangle({
138
 new Rectangle({
139
-  point: [0, 300],
139
+  point: new Vector(0, 300),
140
 })
140
 })
141
 
141
 
142
 new Line({
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
 new Polyline({
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 Parādīt failu

9
   Shapes,
9
   Shapes,
10
   TransformCorner,
10
   TransformCorner,
11
   TransformEdge,
11
   TransformEdge,
12
+  CodeControl,
12
 } from "types"
13
 } from "types"
13
 import { defaultDocument } from "./data"
14
 import { defaultDocument } from "./data"
14
 import shapeUtilityMap, { getShapeUtils } from "lib/shapes"
15
 import shapeUtilityMap, { getShapeUtils } from "lib/shapes"
15
 import history from "state/history"
16
 import history from "state/history"
16
 import * as Sessions from "./sessions"
17
 import * as Sessions from "./sessions"
17
 import commands from "./commands"
18
 import commands from "./commands"
19
+import { controls } from "lib/code/control"
20
+import { generateFromCode, updateFromCode } from "lib/code/generate"
18
 
21
 
19
 const initialData: Data = {
22
 const initialData: Data = {
20
   isReadOnly: false,
23
   isReadOnly: false,
32
   selectedIds: new Set([]),
35
   selectedIds: new Set([]),
33
   currentPageId: "page0",
36
   currentPageId: "page0",
34
   currentCodeFileId: "file0",
37
   currentCodeFileId: "file0",
38
+  codeControls: {},
35
   document: defaultDocument,
39
   document: defaultDocument,
36
 }
40
 }
37
 
41
 
52
     SELECTED_LINE_TOOL: { unless: "isReadOnly", to: "line" },
56
     SELECTED_LINE_TOOL: { unless: "isReadOnly", to: "line" },
53
     SELECTED_POLYLINE_TOOL: { unless: "isReadOnly", to: "polyline" },
57
     SELECTED_POLYLINE_TOOL: { unless: "isReadOnly", to: "polyline" },
54
     SELECTED_RECTANGLE_TOOL: { unless: "isReadOnly", to: "rectangle" },
58
     SELECTED_RECTANGLE_TOOL: { unless: "isReadOnly", to: "rectangle" },
59
+    RESET_CAMERA: "resetCamera",
55
   },
60
   },
56
   initial: "loading",
61
   initial: "loading",
57
   states: {
62
   states: {
70
         CANCELLED: { do: "clearSelectedIds" },
75
         CANCELLED: { do: "clearSelectedIds" },
71
         DELETED: { do: "deleteSelectedIds" },
76
         DELETED: { do: "deleteSelectedIds" },
72
         SAVED_CODE: "saveCode",
77
         SAVED_CODE: "saveCode",
73
-        GENERATED_SHAPES_FROM_CODE: "setGeneratedShapes",
78
+        GENERATED_FROM_CODE: ["setCodeControls", "setGeneratedShapes"],
74
         INCREASED_CODE_FONT_SIZE: "increaseCodeFontSize",
79
         INCREASED_CODE_FONT_SIZE: "increaseCodeFontSize",
75
         DECREASED_CODE_FONT_SIZE: "decreaseCodeFontSize",
80
         DECREASED_CODE_FONT_SIZE: "decreaseCodeFontSize",
81
+        CHANGED_CODE_CONTROL: "updateControls",
76
       },
82
       },
77
       initial: "notPointing",
83
       initial: "notPointing",
78
       states: {
84
       states: {
437
       data.selectedIds.add(data.pointedId)
443
       data.selectedIds.add(data.pointedId)
438
     },
444
     },
439
     // Camera
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
     zoomCamera(data, payload: { delta: number; point: number[] }) {
452
     zoomCamera(data, payload: { delta: number; point: number[] }) {
441
       const { camera } = data
453
       const { camera } = data
442
       const p0 = screenToWorld(payload.point, data)
454
       const p0 = screenToWorld(payload.point, data)
493
     },
505
     },
494
 
506
 
495
     // Code
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
     increaseCodeFontSize(data) {
519
     increaseCodeFontSize(data) {
500
       data.settings.fontSize++
520
       data.settings.fontSize++
502
     decreaseCodeFontSize(data) {
522
     decreaseCodeFontSize(data) {
503
       data.settings.fontSize--
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
     // Data
548
     // Data
507
     saveCode(data, payload: { code: string }) {
549
     saveCode(data, payload: { code: string }) {
524
         document: { pages },
566
         document: { pages },
525
       } = data
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
       if (selectedIds.size === 0) return null
573
       if (selectedIds.size === 0) return null
532
 
574
 

+ 3
- 1
styles/stitches.config.ts Parādīt failu

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

+ 107
- 40
types.ts Parādīt failu

2
 
2
 
3
 import React from "react"
3
 import React from "react"
4
 
4
 
5
+/* -------------------------------------------------- */
6
+/*                    Client State                    */
7
+/* -------------------------------------------------- */
8
+
5
 export interface Data {
9
 export interface Data {
6
   isReadOnly: boolean
10
   isReadOnly: boolean
7
   settings: {
11
   settings: {
18
   hoveredId?: string
22
   hoveredId?: string
19
   currentPageId: string
23
   currentPageId: string
20
   currentCodeFileId: string
24
   currentCodeFileId: string
25
+  codeControls: Record<string, CodeControl>
21
   document: {
26
   document: {
22
     pages: Record<string, Page>
27
     pages: Record<string, Page>
23
     code: Record<string, CodeFile>
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
 export interface Page {
36
 export interface Page {
34
   id: string
37
   id: string
46
   Ray = "ray",
49
   Ray = "ray",
47
   Polyline = "polyline",
50
   Polyline = "polyline",
48
   Rectangle = "rectangle",
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
 export interface BaseShape {
60
 export interface BaseShape {
56
   id: string
61
   id: string
57
   type: ShapeType
62
   type: ShapeType
108
   | PolylineShape
113
   | PolylineShape
109
   | RectangleShape
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
 export interface Bounds {
161
 export interface Bounds {
112
   minX: number
162
   minX: number
113
   minY: number
163
   minY: number
133
   nh: number
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
 export type Difference<A, B> = A extends B ? never : A
186
 export type Difference<A, B> = A extends B ? never : A
147
 
187
 
148
 export type ShapeSpecificProps<T extends Shape> = Pick<
188
 export type ShapeSpecificProps<T extends Shape> = Pick<
163
   stretch(shape: K, scaleX: number, scaleY: number): K
203
   stretch(shape: K, scaleX: number, scaleY: number): K
164
   render(shape: K): JSX.Element
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 Parādīt failu

1
+import Vector from "lib/code/vector"
1
 import React from "react"
2
 import React from "react"
2
 import { Data, Bounds, TransformEdge, TransformCorner } from "types"
3
 import { Data, Bounds, TransformEdge, TransformCorner } from "types"
3
 import * as svg from "./svg"
4
 import * as svg from "./svg"
801
 ) {
802
 ) {
802
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
803
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
803
   let inThrottle: boolean, lastFn: any, lastTime: number
804
   let inThrottle: boolean, lastFn: any, lastTime: number
804
-  return function(...args: P) {
805
+  return function (...args: P) {
805
     if (preventDefault) args[0].preventDefault()
806
     if (preventDefault) args[0].preventDefault()
806
     // eslint-disable-next-line @typescript-eslint/no-this-alias
807
     // eslint-disable-next-line @typescript-eslint/no-this-alias
807
     const context = this
808
     const context = this
811
       inThrottle = true
812
       inThrottle = true
812
     } else {
813
     } else {
813
       clearTimeout(lastFn)
814
       clearTimeout(lastFn)
814
-      lastFn = setTimeout(function() {
815
+      lastFn = setTimeout(function () {
815
         if (Date.now() - lastTime >= wait) {
816
         if (Date.now() - lastTime >= wait) {
816
           fn.apply(context, args)
817
           fn.apply(context, args)
817
           lastTime = Date.now()
818
           lastTime = Date.now()
950
 
951
 
951
   return anchor
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
+}

Notiek ielāde…
Atcelt
Saglabāt