create-substrate 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/dist/index.js +1 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +68 -4
- package/dist/scaffold.js.map +1 -1
- package/dist/surfaces/3d-scene.d.ts.map +1 -1
- package/dist/surfaces/3d-scene.js +414 -67
- package/dist/surfaces/3d-scene.js.map +1 -1
- package/dist/surfaces/animation.d.ts.map +1 -1
- package/dist/surfaces/animation.js +95 -103
- package/dist/surfaces/animation.js.map +1 -1
- package/dist/surfaces/blank.d.ts.map +1 -1
- package/dist/surfaces/blank.js +28 -16
- package/dist/surfaces/blank.js.map +1 -1
- package/dist/surfaces/canvas-2d.d.ts.map +1 -1
- package/dist/surfaces/canvas-2d.js +369 -92
- package/dist/surfaces/canvas-2d.js.map +1 -1
- package/dist/surfaces/data-vis.d.ts.map +1 -1
- package/dist/surfaces/data-vis.js +262 -98
- package/dist/surfaces/data-vis.js.map +1 -1
- package/dist/surfaces/image-gen.d.ts.map +1 -1
- package/dist/surfaces/image-gen.js +354 -92
- package/dist/surfaces/image-gen.js.map +1 -1
- package/dist/surfaces/node-editor.d.ts.map +1 -1
- package/dist/surfaces/node-editor.js +559 -114
- package/dist/surfaces/node-editor.js.map +1 -1
- package/package.json +4 -2
- package/skills/3d-scene/SKILL.md +6 -0
- package/skills/animation/SKILL.md +6 -0
- package/skills/canvas-2d/SKILL.md +7 -1
- package/skills/composing-panels/SKILL.md +14 -8
- package/skills/create-custom-tool/SKILL.md +6 -0
- package/skills/data-visualisation/SKILL.md +6 -0
- package/skills/image-generation/SKILL.md +6 -0
- package/skills/install-components/SKILL.md +199 -0
- package/skills/scaffold-playground/SKILL.md +12 -6
- package/skills/substrate-controls/SKILL.md +1 -3
- package/skills/theming/SKILL.md +6 -0
- package/skills/wire-interactions/SKILL.md +6 -0
|
@@ -3,6 +3,7 @@ export const scene3d = {
|
|
|
3
3
|
"toolbar",
|
|
4
4
|
"panel",
|
|
5
5
|
"pane",
|
|
6
|
+
"action",
|
|
6
7
|
"slider",
|
|
7
8
|
"number-input",
|
|
8
9
|
"colour-picker",
|
|
@@ -16,9 +17,10 @@ export const scene3d = {
|
|
|
16
17
|
extraDevDeps: {
|
|
17
18
|
"@types/three": "^0.172.0",
|
|
18
19
|
},
|
|
19
|
-
app: () => `import {
|
|
20
|
+
app: () => `import { useEffect, useRef, useState } from "react"
|
|
21
|
+
import type { Mesh } from "three"
|
|
20
22
|
import { Canvas } from "@react-three/fiber"
|
|
21
|
-
import {
|
|
23
|
+
import { Grid, OrbitControls, TransformControls } from "@react-three/drei"
|
|
22
24
|
import {
|
|
23
25
|
Toolbar,
|
|
24
26
|
ToolbarToggleGroup,
|
|
@@ -30,150 +32,495 @@ import {
|
|
|
30
32
|
Pane,
|
|
31
33
|
PaneHeader,
|
|
32
34
|
PaneLabel,
|
|
35
|
+
PaneAction,
|
|
33
36
|
PaneContent,
|
|
34
37
|
} from "@/components/substrate/pane"
|
|
38
|
+
import { Action, ActionLabel, ActionControls } from "@/components/substrate/action"
|
|
35
39
|
import { NumberInput } from "@/components/substrate/number-input"
|
|
36
40
|
import { Slider } from "@/components/substrate/slider"
|
|
37
41
|
import { ColourPicker } from "@/components/substrate/colour-picker"
|
|
38
|
-
import { StatusBar, StatusBarSection, StatusBarItem } from "@/components/substrate/status-bar"
|
|
39
42
|
import {
|
|
43
|
+
StatusBar,
|
|
44
|
+
StatusBarSection,
|
|
45
|
+
StatusBarItem,
|
|
46
|
+
StatusBarSeparator,
|
|
47
|
+
} from "@/components/substrate/status-bar"
|
|
48
|
+
import {
|
|
49
|
+
RiBox3Line,
|
|
40
50
|
RiCursorLine,
|
|
51
|
+
RiDeleteBinLine,
|
|
41
52
|
RiDragMoveLine,
|
|
42
|
-
RiRefreshLine,
|
|
43
53
|
RiExpandDiagonal2Line,
|
|
54
|
+
RiGlobeLine,
|
|
55
|
+
RiRefreshLine,
|
|
44
56
|
} from "@remixicon/react"
|
|
45
57
|
|
|
58
|
+
type Vec3 = [number, number, number]
|
|
59
|
+
type Mode = "select" | "translate" | "rotate" | "scale"
|
|
60
|
+
type Shape = "box" | "sphere"
|
|
61
|
+
|
|
46
62
|
interface SceneObject {
|
|
47
63
|
id: string
|
|
48
|
-
|
|
49
|
-
|
|
64
|
+
name: string
|
|
65
|
+
shape: Shape
|
|
66
|
+
position: Vec3
|
|
67
|
+
rotation: Vec3
|
|
68
|
+
scale: Vec3
|
|
50
69
|
colour: string
|
|
51
70
|
roughness: number
|
|
71
|
+
metalness: number
|
|
52
72
|
}
|
|
53
73
|
|
|
74
|
+
const MODES: Mode[] = ["select", "translate", "rotate", "scale"]
|
|
75
|
+
const MODE_KEYS: Record<string, Mode> = {
|
|
76
|
+
v: "select",
|
|
77
|
+
g: "translate",
|
|
78
|
+
r: "rotate",
|
|
79
|
+
s: "scale",
|
|
80
|
+
}
|
|
81
|
+
const AXES = ["X", "Y", "Z"] as const
|
|
82
|
+
const COLOURS = ["#6366f1", "#f43f5e", "#10b981", "#f59e0b", "#0ea5e9"]
|
|
83
|
+
|
|
54
84
|
const INITIAL_OBJECTS: SceneObject[] = [
|
|
55
|
-
{
|
|
56
|
-
|
|
85
|
+
{
|
|
86
|
+
id: "box-1",
|
|
87
|
+
name: "Box 1",
|
|
88
|
+
shape: "box",
|
|
89
|
+
position: [-1.5, 0.5, 0],
|
|
90
|
+
rotation: [0, 0, 0],
|
|
91
|
+
scale: [1, 1, 1],
|
|
92
|
+
colour: "#6366f1",
|
|
93
|
+
roughness: 0.4,
|
|
94
|
+
metalness: 0.1,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "sphere-1",
|
|
98
|
+
name: "Sphere 1",
|
|
99
|
+
shape: "sphere",
|
|
100
|
+
position: [1.5, 0.6, 0],
|
|
101
|
+
rotation: [0, 0, 0],
|
|
102
|
+
scale: [1, 1, 1],
|
|
103
|
+
colour: "#f43f5e",
|
|
104
|
+
roughness: 0.2,
|
|
105
|
+
metalness: 0.1,
|
|
106
|
+
},
|
|
57
107
|
]
|
|
58
108
|
|
|
109
|
+
const toolbarButton =
|
|
110
|
+
"inline-flex h-7 min-w-7 items-center justify-center rounded-md px-2 outline-none transition-all hover:bg-muted hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50"
|
|
111
|
+
|
|
112
|
+
function isMode(value: string): value is Mode {
|
|
113
|
+
return MODES.some((mode) => mode === value)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function toDegrees(radians: number) {
|
|
117
|
+
return (radians * 180) / Math.PI
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function toRadians(degrees: number) {
|
|
121
|
+
return (degrees * Math.PI) / 180
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function withAxis(vector: Vec3, axis: number, value: number): Vec3 {
|
|
125
|
+
const next: Vec3 = [vector[0], vector[1], vector[2]]
|
|
126
|
+
next[axis] = value
|
|
127
|
+
return next
|
|
128
|
+
}
|
|
129
|
+
|
|
59
130
|
function SceneMesh({
|
|
60
131
|
object,
|
|
61
132
|
selected,
|
|
133
|
+
mode,
|
|
62
134
|
onSelect,
|
|
135
|
+
onTransform,
|
|
63
136
|
}: {
|
|
64
137
|
object: SceneObject
|
|
65
138
|
selected: boolean
|
|
139
|
+
mode: Mode
|
|
66
140
|
onSelect: () => void
|
|
141
|
+
onTransform: (patch: Pick<SceneObject, "position" | "rotation" | "scale">) => void
|
|
67
142
|
}) {
|
|
143
|
+
const [mesh, setMesh] = useState<Mesh | null>(null)
|
|
144
|
+
|
|
68
145
|
return (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
146
|
+
<>
|
|
147
|
+
<mesh
|
|
148
|
+
ref={setMesh}
|
|
149
|
+
position={object.position}
|
|
150
|
+
rotation={object.rotation}
|
|
151
|
+
scale={object.scale}
|
|
152
|
+
onClick={(event) => {
|
|
153
|
+
event.stopPropagation()
|
|
154
|
+
onSelect()
|
|
155
|
+
}}
|
|
156
|
+
>
|
|
157
|
+
{object.shape === "box" ? (
|
|
158
|
+
<boxGeometry args={[1, 1, 1]} />
|
|
159
|
+
) : (
|
|
160
|
+
<sphereGeometry args={[0.6, 32, 32]} />
|
|
161
|
+
)}
|
|
162
|
+
<meshStandardMaterial
|
|
163
|
+
color={object.colour}
|
|
164
|
+
roughness={object.roughness}
|
|
165
|
+
metalness={object.metalness}
|
|
166
|
+
emissive={selected ? "#ffffff" : "#000000"}
|
|
167
|
+
emissiveIntensity={selected ? 0.08 : 0}
|
|
168
|
+
/>
|
|
169
|
+
</mesh>
|
|
170
|
+
{mesh && selected && mode !== "select" && (
|
|
171
|
+
<TransformControls
|
|
172
|
+
object={mesh}
|
|
173
|
+
mode={mode}
|
|
174
|
+
onObjectChange={() =>
|
|
175
|
+
onTransform({
|
|
176
|
+
position: [mesh.position.x, mesh.position.y, mesh.position.z],
|
|
177
|
+
rotation: [mesh.rotation.x, mesh.rotation.y, mesh.rotation.z],
|
|
178
|
+
scale: [mesh.scale.x, mesh.scale.y, mesh.scale.z],
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
/>
|
|
74
182
|
)}
|
|
75
|
-
|
|
76
|
-
color={object.colour}
|
|
77
|
-
roughness={object.roughness}
|
|
78
|
-
emissive={selected ? "#ffffff" : "#000000"}
|
|
79
|
-
emissiveIntensity={selected ? 0.05 : 0}
|
|
80
|
-
/>
|
|
81
|
-
</mesh>
|
|
183
|
+
</>
|
|
82
184
|
)
|
|
83
185
|
}
|
|
84
186
|
|
|
85
187
|
export default function App() {
|
|
86
|
-
const [mode, setMode] = useState("select")
|
|
188
|
+
const [mode, setMode] = useState<Mode>("select")
|
|
87
189
|
const [objects, setObjects] = useState(INITIAL_OBJECTS)
|
|
88
|
-
const [selectedId, setSelectedId] = useState<string | null>(
|
|
190
|
+
const [selectedId, setSelectedId] = useState<string | null>("box-1")
|
|
191
|
+
const [lightIntensity, setLightIntensity] = useState(1.5)
|
|
192
|
+
const nextIndex = useRef(INITIAL_OBJECTS.length + 1)
|
|
89
193
|
|
|
90
|
-
const selected = objects.find((
|
|
194
|
+
const selected = objects.find((object) => object.id === selectedId) ?? null
|
|
91
195
|
|
|
92
|
-
function
|
|
93
|
-
if (!selectedId) return
|
|
196
|
+
function updateObject(id: string, patch: Partial<SceneObject>) {
|
|
94
197
|
setObjects((prev) =>
|
|
95
|
-
prev.map((
|
|
198
|
+
prev.map((object) => (object.id === id ? { ...object, ...patch } : object)),
|
|
96
199
|
)
|
|
97
200
|
}
|
|
98
201
|
|
|
202
|
+
function addObject(shape: Shape) {
|
|
203
|
+
const index = nextIndex.current++
|
|
204
|
+
const object: SceneObject = {
|
|
205
|
+
id: \`\${shape}-\${index}\`,
|
|
206
|
+
name: \`\${shape === "box" ? "Box" : "Sphere"} \${index}\`,
|
|
207
|
+
shape,
|
|
208
|
+
position: [(index % 5) - 2, shape === "box" ? 0.5 : 0.6, 1.5],
|
|
209
|
+
rotation: [0, 0, 0],
|
|
210
|
+
scale: [1, 1, 1],
|
|
211
|
+
colour: COLOURS[index % COLOURS.length],
|
|
212
|
+
roughness: 0.4,
|
|
213
|
+
metalness: 0.1,
|
|
214
|
+
}
|
|
215
|
+
setObjects((prev) => [...prev, object])
|
|
216
|
+
setSelectedId(object.id)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function removeObject(id: string) {
|
|
220
|
+
setObjects((prev) => prev.filter((object) => object.id !== id))
|
|
221
|
+
setSelectedId((current) => (current === id ? null : current))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
226
|
+
const target = event.target
|
|
227
|
+
if (
|
|
228
|
+
target instanceof HTMLElement &&
|
|
229
|
+
(target.isContentEditable || target.tagName === "INPUT" || target.tagName === "TEXTAREA")
|
|
230
|
+
) {
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.altKey) return
|
|
234
|
+
|
|
235
|
+
const nextMode = MODE_KEYS[event.key.toLowerCase()]
|
|
236
|
+
if (nextMode) {
|
|
237
|
+
setMode(nextMode)
|
|
238
|
+
} else if (event.key === "Escape") {
|
|
239
|
+
setSelectedId(null)
|
|
240
|
+
} else if ((event.key === "Delete" || event.key === "Backspace") && selectedId) {
|
|
241
|
+
removeObject(selectedId)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
window.addEventListener("keydown", handleKeyDown)
|
|
246
|
+
return () => window.removeEventListener("keydown", handleKeyDown)
|
|
247
|
+
}, [selectedId])
|
|
248
|
+
|
|
99
249
|
return (
|
|
100
250
|
<div className="flex h-screen flex-col bg-background text-foreground">
|
|
101
|
-
<div className="
|
|
102
|
-
<div className="flex-1 bg-canvas">
|
|
103
|
-
<Canvas
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
251
|
+
<div className="flex min-h-0 flex-1">
|
|
252
|
+
<div className="relative min-w-0 flex-1 bg-canvas">
|
|
253
|
+
<Canvas
|
|
254
|
+
camera={{ position: [4, 3, 5], fov: 50 }}
|
|
255
|
+
onPointerMissed={() => {
|
|
256
|
+
if (mode === "select") setSelectedId(null)
|
|
257
|
+
}}
|
|
258
|
+
>
|
|
259
|
+
<ambientLight intensity={0.6} />
|
|
260
|
+
<hemisphereLight args={["#ffffff", "#444444", 0.6]} />
|
|
261
|
+
<directionalLight position={[5, 8, 5]} intensity={lightIntensity} />
|
|
262
|
+
<Grid infiniteGrid fadeDistance={25} cellColor="#333333" sectionColor="#555555" />
|
|
108
263
|
<OrbitControls makeDefault />
|
|
109
|
-
{objects.map((
|
|
264
|
+
{objects.map((object) => (
|
|
110
265
|
<SceneMesh
|
|
111
|
-
key={
|
|
112
|
-
object={
|
|
113
|
-
selected={
|
|
114
|
-
|
|
266
|
+
key={object.id}
|
|
267
|
+
object={object}
|
|
268
|
+
selected={object.id === selectedId}
|
|
269
|
+
mode={mode}
|
|
270
|
+
onSelect={() => setSelectedId(object.id)}
|
|
271
|
+
onTransform={(patch) => updateObject(object.id, patch)}
|
|
115
272
|
/>
|
|
116
273
|
))}
|
|
117
274
|
</Canvas>
|
|
275
|
+
|
|
276
|
+
<Toolbar className="absolute bottom-4">
|
|
277
|
+
<ToolbarToggleGroup
|
|
278
|
+
value={mode}
|
|
279
|
+
onValueChange={(value) => {
|
|
280
|
+
if (isMode(value)) setMode(value)
|
|
281
|
+
}}
|
|
282
|
+
>
|
|
283
|
+
<ToolbarToggle value="select" tooltip="Select" shortcut="V">
|
|
284
|
+
<RiCursorLine className="size-4" />
|
|
285
|
+
</ToolbarToggle>
|
|
286
|
+
<ToolbarSeparator />
|
|
287
|
+
<ToolbarToggle value="translate" tooltip="Move" shortcut="G">
|
|
288
|
+
<RiDragMoveLine className="size-4" />
|
|
289
|
+
</ToolbarToggle>
|
|
290
|
+
<ToolbarToggle value="rotate" tooltip="Rotate" shortcut="R">
|
|
291
|
+
<RiRefreshLine className="size-4" />
|
|
292
|
+
</ToolbarToggle>
|
|
293
|
+
<ToolbarToggle value="scale" tooltip="Scale" shortcut="S">
|
|
294
|
+
<RiExpandDiagonal2Line className="size-4" />
|
|
295
|
+
</ToolbarToggle>
|
|
296
|
+
</ToolbarToggleGroup>
|
|
297
|
+
<ToolbarSeparator />
|
|
298
|
+
<div className="flex items-center gap-1">
|
|
299
|
+
<button
|
|
300
|
+
type="button"
|
|
301
|
+
aria-label="Add box"
|
|
302
|
+
title="Add box"
|
|
303
|
+
className={toolbarButton}
|
|
304
|
+
onClick={() => addObject("box")}
|
|
305
|
+
>
|
|
306
|
+
<RiBox3Line className="size-4" />
|
|
307
|
+
</button>
|
|
308
|
+
<button
|
|
309
|
+
type="button"
|
|
310
|
+
aria-label="Add sphere"
|
|
311
|
+
title="Add sphere"
|
|
312
|
+
className={toolbarButton}
|
|
313
|
+
onClick={() => addObject("sphere")}
|
|
314
|
+
>
|
|
315
|
+
<RiGlobeLine className="size-4" />
|
|
316
|
+
</button>
|
|
317
|
+
<button
|
|
318
|
+
type="button"
|
|
319
|
+
aria-label="Delete selected"
|
|
320
|
+
title="Delete selected"
|
|
321
|
+
className={toolbarButton}
|
|
322
|
+
disabled={!selected}
|
|
323
|
+
onClick={() => {
|
|
324
|
+
if (selected) removeObject(selected.id)
|
|
325
|
+
}}
|
|
326
|
+
>
|
|
327
|
+
<RiDeleteBinLine className="size-4" />
|
|
328
|
+
</button>
|
|
329
|
+
</div>
|
|
330
|
+
</Toolbar>
|
|
118
331
|
</div>
|
|
119
332
|
|
|
120
333
|
<Panel position="right">
|
|
334
|
+
<Pane>
|
|
335
|
+
<PaneHeader>
|
|
336
|
+
<PaneLabel>Scene</PaneLabel>
|
|
337
|
+
</PaneHeader>
|
|
338
|
+
<PaneContent className="space-y-1">
|
|
339
|
+
{objects.length === 0 && (
|
|
340
|
+
<p className="text-xs text-muted-foreground">
|
|
341
|
+
No objects. Add one from the toolbar.
|
|
342
|
+
</p>
|
|
343
|
+
)}
|
|
344
|
+
{objects.map((object) => (
|
|
345
|
+
<button
|
|
346
|
+
key={object.id}
|
|
347
|
+
type="button"
|
|
348
|
+
onClick={() => setSelectedId(object.id)}
|
|
349
|
+
className={
|
|
350
|
+
object.id === selectedId
|
|
351
|
+
? "flex h-7 w-full items-center gap-2 rounded-md bg-accent px-2 text-xs text-accent-foreground"
|
|
352
|
+
: "flex h-7 w-full items-center gap-2 rounded-md px-2 text-xs hover:bg-accent/50"
|
|
353
|
+
}
|
|
354
|
+
>
|
|
355
|
+
<span
|
|
356
|
+
className="size-2.5 rounded-full"
|
|
357
|
+
style={{ backgroundColor: object.colour }}
|
|
358
|
+
/>
|
|
359
|
+
{object.name}
|
|
360
|
+
</button>
|
|
361
|
+
))}
|
|
362
|
+
</PaneContent>
|
|
363
|
+
</Pane>
|
|
364
|
+
|
|
365
|
+
<Pane>
|
|
366
|
+
<PaneHeader>
|
|
367
|
+
<PaneLabel>Lighting</PaneLabel>
|
|
368
|
+
</PaneHeader>
|
|
369
|
+
<PaneContent>
|
|
370
|
+
<Action>
|
|
371
|
+
<ActionLabel>Intensity</ActionLabel>
|
|
372
|
+
<ActionControls>
|
|
373
|
+
<Slider
|
|
374
|
+
min={0}
|
|
375
|
+
max={4}
|
|
376
|
+
step={0.05}
|
|
377
|
+
value={[lightIntensity]}
|
|
378
|
+
onValueChange={([value]) => setLightIntensity(value)}
|
|
379
|
+
/>
|
|
380
|
+
</ActionControls>
|
|
381
|
+
</Action>
|
|
382
|
+
</PaneContent>
|
|
383
|
+
</Pane>
|
|
384
|
+
|
|
121
385
|
{selected ? (
|
|
122
386
|
<>
|
|
123
387
|
<Pane>
|
|
124
388
|
<PaneHeader>
|
|
125
|
-
<PaneLabel>
|
|
389
|
+
<PaneLabel>Transform</PaneLabel>
|
|
390
|
+
<PaneAction
|
|
391
|
+
aria-label="Reset transform"
|
|
392
|
+
title="Reset transform"
|
|
393
|
+
onClick={() =>
|
|
394
|
+
updateObject(selected.id, { rotation: [0, 0, 0], scale: [1, 1, 1] })
|
|
395
|
+
}
|
|
396
|
+
>
|
|
397
|
+
<RiRefreshLine />
|
|
398
|
+
</PaneAction>
|
|
126
399
|
</PaneHeader>
|
|
127
|
-
<PaneContent>
|
|
400
|
+
<PaneContent className="space-y-2">
|
|
401
|
+
<div className="text-xs text-muted-foreground">Position</div>
|
|
402
|
+
<div className="grid grid-cols-3 gap-2">
|
|
403
|
+
{AXES.map((axis, index) => (
|
|
404
|
+
<NumberInput
|
|
405
|
+
key={axis}
|
|
406
|
+
label={axis}
|
|
407
|
+
step={0.1}
|
|
408
|
+
value={selected.position[index]}
|
|
409
|
+
onChange={(value) =>
|
|
410
|
+
updateObject(selected.id, {
|
|
411
|
+
position: withAxis(selected.position, index, value),
|
|
412
|
+
})
|
|
413
|
+
}
|
|
414
|
+
/>
|
|
415
|
+
))}
|
|
416
|
+
</div>
|
|
417
|
+
<div className="text-xs text-muted-foreground">Rotation</div>
|
|
418
|
+
<div className="grid grid-cols-3 gap-2">
|
|
419
|
+
{AXES.map((axis, index) => (
|
|
420
|
+
<NumberInput
|
|
421
|
+
key={axis}
|
|
422
|
+
label={axis}
|
|
423
|
+
step={1}
|
|
424
|
+
precision={1}
|
|
425
|
+
value={toDegrees(selected.rotation[index])}
|
|
426
|
+
onChange={(value) =>
|
|
427
|
+
updateObject(selected.id, {
|
|
428
|
+
rotation: withAxis(selected.rotation, index, toRadians(value)),
|
|
429
|
+
})
|
|
430
|
+
}
|
|
431
|
+
/>
|
|
432
|
+
))}
|
|
433
|
+
</div>
|
|
434
|
+
<div className="text-xs text-muted-foreground">Scale</div>
|
|
128
435
|
<div className="grid grid-cols-3 gap-2">
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
436
|
+
{AXES.map((axis, index) => (
|
|
437
|
+
<NumberInput
|
|
438
|
+
key={axis}
|
|
439
|
+
label={axis}
|
|
440
|
+
step={0.1}
|
|
441
|
+
min={0.01}
|
|
442
|
+
value={selected.scale[index]}
|
|
443
|
+
onChange={(value) =>
|
|
444
|
+
updateObject(selected.id, {
|
|
445
|
+
scale: withAxis(selected.scale, index, value),
|
|
446
|
+
})
|
|
447
|
+
}
|
|
448
|
+
/>
|
|
449
|
+
))}
|
|
132
450
|
</div>
|
|
133
451
|
</PaneContent>
|
|
134
452
|
</Pane>
|
|
453
|
+
|
|
135
454
|
<Pane>
|
|
136
455
|
<PaneHeader>
|
|
137
456
|
<PaneLabel>Material</PaneLabel>
|
|
138
457
|
</PaneHeader>
|
|
139
|
-
<PaneContent className="space-y-
|
|
140
|
-
<
|
|
141
|
-
|
|
458
|
+
<PaneContent className="space-y-2">
|
|
459
|
+
<Action>
|
|
460
|
+
<ActionLabel>Colour</ActionLabel>
|
|
461
|
+
<ActionControls>
|
|
462
|
+
<ColourPicker
|
|
463
|
+
value={selected.colour}
|
|
464
|
+
onChange={(colour) => updateObject(selected.id, { colour })}
|
|
465
|
+
/>
|
|
466
|
+
<span className="font-mono text-xs text-muted-foreground">
|
|
467
|
+
{selected.colour}
|
|
468
|
+
</span>
|
|
469
|
+
</ActionControls>
|
|
470
|
+
</Action>
|
|
471
|
+
<Action>
|
|
472
|
+
<ActionLabel>Roughness</ActionLabel>
|
|
473
|
+
<ActionControls>
|
|
474
|
+
<Slider
|
|
475
|
+
min={0}
|
|
476
|
+
max={1}
|
|
477
|
+
step={0.01}
|
|
478
|
+
value={[selected.roughness]}
|
|
479
|
+
onValueChange={([value]) =>
|
|
480
|
+
updateObject(selected.id, { roughness: value })
|
|
481
|
+
}
|
|
482
|
+
/>
|
|
483
|
+
</ActionControls>
|
|
484
|
+
</Action>
|
|
485
|
+
<Action>
|
|
486
|
+
<ActionLabel>Metalness</ActionLabel>
|
|
487
|
+
<ActionControls>
|
|
488
|
+
<Slider
|
|
489
|
+
min={0}
|
|
490
|
+
max={1}
|
|
491
|
+
step={0.01}
|
|
492
|
+
value={[selected.metalness]}
|
|
493
|
+
onValueChange={([value]) =>
|
|
494
|
+
updateObject(selected.id, { metalness: value })
|
|
495
|
+
}
|
|
496
|
+
/>
|
|
497
|
+
</ActionControls>
|
|
498
|
+
</Action>
|
|
142
499
|
</PaneContent>
|
|
143
500
|
</Pane>
|
|
144
501
|
</>
|
|
145
502
|
) : (
|
|
146
503
|
<Pane>
|
|
147
504
|
<PaneContent>
|
|
148
|
-
<p className="text-
|
|
505
|
+
<p className="text-xs text-muted-foreground">
|
|
506
|
+
Click an object to inspect it.
|
|
507
|
+
</p>
|
|
149
508
|
</PaneContent>
|
|
150
509
|
</Pane>
|
|
151
510
|
)}
|
|
152
511
|
</Panel>
|
|
153
512
|
</div>
|
|
154
513
|
|
|
155
|
-
<Toolbar>
|
|
156
|
-
<ToolbarToggleGroup value={mode} onValueChange={setMode}>
|
|
157
|
-
<ToolbarToggle value="select" tooltip="Select" shortcut="V">
|
|
158
|
-
<RiCursorLine className="size-4" />
|
|
159
|
-
</ToolbarToggle>
|
|
160
|
-
<ToolbarSeparator />
|
|
161
|
-
<ToolbarToggle value="translate" tooltip="Translate" shortcut="G">
|
|
162
|
-
<RiDragMoveLine className="size-4" />
|
|
163
|
-
</ToolbarToggle>
|
|
164
|
-
<ToolbarToggle value="rotate" tooltip="Rotate" shortcut="R">
|
|
165
|
-
<RiRefreshLine className="size-4" />
|
|
166
|
-
</ToolbarToggle>
|
|
167
|
-
<ToolbarToggle value="scale" tooltip="Scale" shortcut="S">
|
|
168
|
-
<RiExpandDiagonal2Line className="size-4" />
|
|
169
|
-
</ToolbarToggle>
|
|
170
|
-
</ToolbarToggleGroup>
|
|
171
|
-
</Toolbar>
|
|
172
|
-
|
|
173
514
|
<StatusBar>
|
|
174
|
-
<StatusBarSection>
|
|
515
|
+
<StatusBarSection align="left">
|
|
175
516
|
<StatusBarItem>Mode: {mode}</StatusBarItem>
|
|
176
|
-
|
|
517
|
+
<StatusBarSeparator />
|
|
518
|
+
<StatusBarItem>
|
|
519
|
+
{objects.length} {objects.length === 1 ? "object" : "objects"}
|
|
520
|
+
</StatusBarItem>
|
|
521
|
+
</StatusBarSection>
|
|
522
|
+
<StatusBarSection align="right">
|
|
523
|
+
<StatusBarItem>{selected ? selected.name : "Nothing selected"}</StatusBarItem>
|
|
177
524
|
</StatusBarSection>
|
|
178
525
|
</StatusBar>
|
|
179
526
|
</div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"3d-scene.js","sourceRoot":"","sources":["../../src/surfaces/3d-scene.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,OAAO,GAAkB;IACpC,UAAU,EAAE;QACV,SAAS;QACT,OAAO;QACP,MAAM;QACN,QAAQ;QACR,cAAc;QACd,eAAe;QACf,YAAY;KACb;IACD,SAAS,EAAE;QACT,KAAK,EAAE,UAAU;QACjB,oBAAoB,EAAE,QAAQ;QAC9B,mBAAmB,EAAE,SAAS;KAC/B;IACD,YAAY,EAAE;QACZ,cAAc,EAAE,UAAU;KAC3B;IACD,GAAG,EAAE,GAAG,EAAE,CAAC
|
|
1
|
+
{"version":3,"file":"3d-scene.js","sourceRoot":"","sources":["../../src/surfaces/3d-scene.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,OAAO,GAAkB;IACpC,UAAU,EAAE;QACV,SAAS;QACT,OAAO;QACP,MAAM;QACN,QAAQ;QACR,QAAQ;QACR,cAAc;QACd,eAAe;QACf,YAAY;KACb;IACD,SAAS,EAAE;QACT,KAAK,EAAE,UAAU;QACjB,oBAAoB,EAAE,QAAQ;QAC9B,mBAAmB,EAAE,SAAS;KAC/B;IACD,YAAY,EAAE;QACZ,cAAc,EAAE,UAAU;KAC3B;IACD,GAAG,EAAE,GAAG,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6fZ;CACA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"animation.d.ts","sourceRoot":"","sources":["../../src/surfaces/animation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,eAAO,MAAM,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"animation.d.ts","sourceRoot":"","sources":["../../src/surfaces/animation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,eAAO,MAAM,SAAS,EAAE,aA4MvB,CAAA"}
|