castle-web-cli 0.4.75 → 0.4.76
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/dist/agent-prompts.d.ts +4 -0
- package/dist/agent-prompts.js +20 -8
- package/dist/agent.js +301 -28
- package/dist/castle-host/host.js +59 -5
- package/dist/commonInstructions.d.ts +1 -1
- package/dist/commonInstructions.js +4 -0
- package/dist/ide.d.ts +1 -0
- package/dist/ide.js +250 -1
- package/dist/init.js +46 -4
- package/dist/save-deck.js +8 -1
- package/dist/serve.js +27 -1
- package/dist/shell/assets/index-DNWEQd4R.js +141 -0
- package/dist/shell/assets/{index-WNbOHPBj.css → index-DuKq-Grp.css} +1 -1
- package/dist/shell/index.html +2 -2
- package/kits/basic-2d/CLAUDE.md +13 -5
- package/kits/basic-2d/castle.json +15 -0
- package/kits/basic-2d/drawings/pig.pxart +22 -55
- package/kits/basic-2d/editors/PxArtEditor.jsx +237 -4
- package/kits/basic-2d/editors/SingleEditor.jsx +6 -51
- package/kits/basic-2d/editors/pixelEditorChrome.jsx +17 -11
- package/kits/basic-2d/editors/pixelGeometry.js +95 -0
- package/kits/basic-2d/editors/pixelInspector.jsx +229 -51
- package/kits/basic-2d/editors/pxArtTools.js +109 -1
- package/kits/basic-2d/engine/ScenePlayer.jsx +11 -160
- package/kits/basic-2d/engine/ui.jsx +15 -0
- package/kits/basic-2d/engine/ui.module.css +44 -117
- package/kits/basic-2d/main.jsx +6 -10
- package/package.json +10 -1
- package/dist/shell/assets/index-Dfn29Bkt.js +0 -108
- package/kits/basic-2d/editors/App.jsx +0 -226
- package/kits/basic-2d/editors/CodeEditor.jsx +0 -79
- package/kits/basic-2d/editors/FileBrowser.jsx +0 -349
- package/kits/basic-2d/editors/codeTheme.js +0 -135
- package/kits/basic-2d/engine/playConsole.js +0 -66
- package/kits/basic-3d/.prettierrc +0 -8
- package/kits/basic-3d/CLAUDE.md +0 -162
- package/kits/basic-3d/behaviors/Camera.jsx +0 -56
- package/kits/basic-3d/behaviors/Collider.jsx +0 -78
- package/kits/basic-3d/behaviors/Mesh.jsx +0 -82
- package/kits/basic-3d/behaviors/Model.jsx +0 -61
- package/kits/basic-3d/behaviors/Transform.jsx +0 -35
- package/kits/basic-3d/editors/App.jsx +0 -147
- package/kits/basic-3d/editors/CodeEditor.jsx +0 -112
- package/kits/basic-3d/editors/FileBrowser.jsx +0 -143
- package/kits/basic-3d/editors/ModelEditor.jsx +0 -400
- package/kits/basic-3d/editors/PlayOnly.jsx +0 -22
- package/kits/basic-3d/editors/SceneEditor.jsx +0 -1081
- package/kits/basic-3d/editors/behaviorRegistry.js +0 -24
- package/kits/basic-3d/editors/editorHistory.js +0 -52
- package/kits/basic-3d/editors/viewportRig.js +0 -90
- package/kits/basic-3d/engine/ScenePlayer.jsx +0 -58
- package/kits/basic-3d/engine/SceneUI.jsx +0 -67
- package/kits/basic-3d/engine/SceneViewport.jsx +0 -102
- package/kits/basic-3d/engine/autoInspector.jsx +0 -51
- package/kits/basic-3d/engine/files.js +0 -73
- package/kits/basic-3d/engine/scene.js +0 -502
- package/kits/basic-3d/engine/threeUtil.js +0 -260
- package/kits/basic-3d/engine/ui.jsx +0 -352
- package/kits/basic-3d/engine/ui.module.css +0 -944
- package/kits/basic-3d/eslint.config.js +0 -51
- package/kits/basic-3d/index.html +0 -11
- package/kits/basic-3d/main.jsx +0 -10
- package/kits/basic-3d/models/block.model +0 -14
- package/kits/basic-3d/package-lock.json +0 -2713
- package/kits/basic-3d/package.json +0 -41
- package/kits/basic-3d/pnpm-lock.yaml +0 -1769
- package/kits/basic-3d/scenes/main.scene +0 -76
- package/kits/basic-3d/vite.config.js +0 -1
|
@@ -1,400 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
import * as THREE from 'three';
|
|
3
|
-
import { basename, formatJson, parseJsonFile } from '../engine/files';
|
|
4
|
-
import {
|
|
5
|
-
buildModelGroup,
|
|
6
|
-
defaultModelPart,
|
|
7
|
-
disposeObject3D,
|
|
8
|
-
fitRendererToCanvas,
|
|
9
|
-
geometryKinds,
|
|
10
|
-
radiansToDegrees,
|
|
11
|
-
} from '../engine/threeUtil';
|
|
12
|
-
import { roundUnits } from '../engine/scene';
|
|
13
|
-
import { ThreeCanvas } from '../engine/SceneViewport';
|
|
14
|
-
import {
|
|
15
|
-
cx,
|
|
16
|
-
EditorBody,
|
|
17
|
-
EditorHeader,
|
|
18
|
-
IconButton,
|
|
19
|
-
Panel,
|
|
20
|
-
SelectField,
|
|
21
|
-
SheetGrabHandle,
|
|
22
|
-
styles,
|
|
23
|
-
useMobileSheet,
|
|
24
|
-
} from '../engine/ui';
|
|
25
|
-
import { AutoFields } from '../engine/autoInspector';
|
|
26
|
-
import { useEditHistory } from './editorHistory';
|
|
27
|
-
import { createPreviewScene, createViewportRig, GIZMO_MODES } from './viewportRig';
|
|
28
|
-
|
|
29
|
-
const CLICK_THRESHOLD_PX = 5;
|
|
30
|
-
|
|
31
|
-
// Authoring editor for `.model` files -- a list of primitive parts, the 3d
|
|
32
|
-
// analog of the 2d kit's pixel DrawingEditor. Left-drag orbits, click selects
|
|
33
|
-
// a part, the gizmo moves/rotates it, exact values live in the inspector.
|
|
34
|
-
export function ModelEditor({ path, text, onChange, onToggleFiles, filesOpen }) {
|
|
35
|
-
const [selectedPartIndex, setSelectedPartIndex] = useState(-1);
|
|
36
|
-
const [gizmoMode, setGizmoMode] = useState('translate');
|
|
37
|
-
const [inspectorOpen, setInspectorOpen] = useState(true);
|
|
38
|
-
const history = useEditHistory(text, onChange);
|
|
39
|
-
const inspectorSheet = useInspectorSheet(inspectorOpen);
|
|
40
|
-
const { value: model, error } = parseJsonFile(path, text);
|
|
41
|
-
const parts = model?.parts ?? [];
|
|
42
|
-
const viewport = useModelViewport({
|
|
43
|
-
model,
|
|
44
|
-
text,
|
|
45
|
-
selectedPartIndex,
|
|
46
|
-
gizmoMode,
|
|
47
|
-
applyModel: (next) => onChange(formatJson(next)),
|
|
48
|
-
recordSnapshot: history.recordSnapshot,
|
|
49
|
-
});
|
|
50
|
-
const clickSelect = usePartClickSelect(viewport, setSelectedPartIndex);
|
|
51
|
-
function commitModel(nextModel) {
|
|
52
|
-
history.commit(formatJson(nextModel));
|
|
53
|
-
}
|
|
54
|
-
function updatePart(index, nextProps) {
|
|
55
|
-
if (!model || !model.parts?.[index]) return;
|
|
56
|
-
const next = structuredClone(model);
|
|
57
|
-
next.parts[index] = { ...next.parts[index], ...nextProps };
|
|
58
|
-
commitModel(next);
|
|
59
|
-
}
|
|
60
|
-
function addPart(geometry) {
|
|
61
|
-
if (!model) return;
|
|
62
|
-
const next = structuredClone(model);
|
|
63
|
-
next.parts = next.parts ?? [];
|
|
64
|
-
next.parts.push({ ...defaultModelPart, geometry });
|
|
65
|
-
commitModel(next);
|
|
66
|
-
setSelectedPartIndex(next.parts.length - 1);
|
|
67
|
-
}
|
|
68
|
-
function duplicatePart(index) {
|
|
69
|
-
if (!model || !model.parts?.[index]) return;
|
|
70
|
-
const next = structuredClone(model);
|
|
71
|
-
const copy = structuredClone(next.parts[index]);
|
|
72
|
-
copy.x = roundUnits((copy.x ?? 0) + 0.5);
|
|
73
|
-
copy.z = roundUnits((copy.z ?? 0) + 0.5);
|
|
74
|
-
next.parts.splice(index + 1, 0, copy);
|
|
75
|
-
commitModel(next);
|
|
76
|
-
setSelectedPartIndex(index + 1);
|
|
77
|
-
}
|
|
78
|
-
function removePart(index) {
|
|
79
|
-
if (!model || !model.parts?.[index]) return;
|
|
80
|
-
const next = structuredClone(model);
|
|
81
|
-
next.parts.splice(index, 1);
|
|
82
|
-
commitModel(next);
|
|
83
|
-
setSelectedPartIndex(-1);
|
|
84
|
-
}
|
|
85
|
-
const sharedActionButtons = (
|
|
86
|
-
<>
|
|
87
|
-
<IconButton icon="undo" label="Undo" onClick={history.undo} disabled={!history.canUndo} />
|
|
88
|
-
<IconButton icon="redo" label="Redo" onClick={history.redo} disabled={!history.canRedo} />
|
|
89
|
-
</>
|
|
90
|
-
);
|
|
91
|
-
const gizmoButtons = (
|
|
92
|
-
<>
|
|
93
|
-
{GIZMO_MODES.filter((mode) => mode.key !== 'scale').map((mode) => (
|
|
94
|
-
<IconButton
|
|
95
|
-
key={mode.key}
|
|
96
|
-
icon={mode.icon}
|
|
97
|
-
label={mode.label}
|
|
98
|
-
active={gizmoMode === mode.key}
|
|
99
|
-
onClick={() => setGizmoMode(mode.key)}
|
|
100
|
-
/>
|
|
101
|
-
))}
|
|
102
|
-
</>
|
|
103
|
-
);
|
|
104
|
-
return (
|
|
105
|
-
<>
|
|
106
|
-
<EditorHeader
|
|
107
|
-
title={basename(path)}
|
|
108
|
-
subtitle={model ? `${parts.length} part${parts.length === 1 ? '' : 's'}` : error}
|
|
109
|
-
right={
|
|
110
|
-
<span className={styles.mobileOnly}>
|
|
111
|
-
{sharedActionButtons}
|
|
112
|
-
<IconButton
|
|
113
|
-
icon="palette"
|
|
114
|
-
label="Tools"
|
|
115
|
-
active={inspectorOpen}
|
|
116
|
-
onClick={() => setInspectorOpen((previous) => !previous)}
|
|
117
|
-
/>
|
|
118
|
-
</span>
|
|
119
|
-
}
|
|
120
|
-
onToggleFiles={onToggleFiles}
|
|
121
|
-
filesOpen={filesOpen}
|
|
122
|
-
/>
|
|
123
|
-
<EditorBody>
|
|
124
|
-
<div className={cx(styles.sceneWorkspace)}>
|
|
125
|
-
<div className={styles.sceneTools}>
|
|
126
|
-
<div className={styles.sceneToolsGroup}>{gizmoButtons}</div>
|
|
127
|
-
<div className={styles.sceneToolsGroup}>{sharedActionButtons}</div>
|
|
128
|
-
<span aria-hidden="true" />
|
|
129
|
-
</div>
|
|
130
|
-
<div className={cx(styles.stageWrap, styles.stageWrapFull)}>
|
|
131
|
-
<div className={styles.stageCard}>
|
|
132
|
-
<ThreeCanvas
|
|
133
|
-
onSetup={viewport.onSetup}
|
|
134
|
-
onFrame={viewport.onFrame}
|
|
135
|
-
className={styles.stageCanvas}
|
|
136
|
-
onContextMenu={(event) => event.preventDefault()}
|
|
137
|
-
onPointerDown={clickSelect.onPointerDown}
|
|
138
|
-
onPointerUp={clickSelect.onPointerUp}
|
|
139
|
-
/>
|
|
140
|
-
</div>
|
|
141
|
-
</div>
|
|
142
|
-
<ModelInspector
|
|
143
|
-
sheet={inspectorSheet}
|
|
144
|
-
parts={parts}
|
|
145
|
-
selectedPartIndex={selectedPartIndex}
|
|
146
|
-
onSelectPart={setSelectedPartIndex}
|
|
147
|
-
onAddPart={addPart}
|
|
148
|
-
onDuplicatePart={duplicatePart}
|
|
149
|
-
onRemovePart={removePart}
|
|
150
|
-
onUpdatePart={updatePart}
|
|
151
|
-
/>
|
|
152
|
-
</div>
|
|
153
|
-
</EditorBody>
|
|
154
|
-
</>
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Preview scene + camera rig + gizmo lifecycle for the model being edited.
|
|
159
|
-
function useModelViewport(args) {
|
|
160
|
-
const argsRef = useRef(args);
|
|
161
|
-
argsRef.current = args;
|
|
162
|
-
const sceneRef = useRef(null);
|
|
163
|
-
const groupRef = useRef(null);
|
|
164
|
-
const rigRef = useRef(null);
|
|
165
|
-
const gizmoDraggingRef = useRef(false);
|
|
166
|
-
const gizmoRecordedRef = useRef(false);
|
|
167
|
-
if (!sceneRef.current) sceneRef.current = createPreviewScene();
|
|
168
|
-
const rebuildGroup = useCallback(() => {
|
|
169
|
-
const scene = sceneRef.current;
|
|
170
|
-
if (groupRef.current) {
|
|
171
|
-
scene.remove(groupRef.current);
|
|
172
|
-
disposeObject3D(groupRef.current);
|
|
173
|
-
groupRef.current = null;
|
|
174
|
-
}
|
|
175
|
-
const model = argsRef.current.model;
|
|
176
|
-
if (model) {
|
|
177
|
-
groupRef.current = buildModelGroup(model, null);
|
|
178
|
-
scene.add(groupRef.current);
|
|
179
|
-
}
|
|
180
|
-
}, []);
|
|
181
|
-
// Rebuild the preview on text changes -- except mid-gizmo-drag, where the
|
|
182
|
-
// attached mesh is the live source of truth and a rebuild would orphan it.
|
|
183
|
-
useEffect(() => {
|
|
184
|
-
if (gizmoDraggingRef.current) return;
|
|
185
|
-
rebuildGroup();
|
|
186
|
-
attachGizmoToSelection(rigRef.current, groupRef.current, argsRef.current);
|
|
187
|
-
}, [args.text, rebuildGroup]);
|
|
188
|
-
useEffect(() => {
|
|
189
|
-
attachGizmoToSelection(rigRef.current, groupRef.current, argsRef.current);
|
|
190
|
-
}, [args.selectedPartIndex, args.gizmoMode]);
|
|
191
|
-
useEffect(() => {
|
|
192
|
-
return () => {
|
|
193
|
-
if (groupRef.current) disposeObject3D(groupRef.current);
|
|
194
|
-
};
|
|
195
|
-
}, []);
|
|
196
|
-
const onSetup = ({ canvas }) => {
|
|
197
|
-
const rig = createViewportRig({
|
|
198
|
-
canvas,
|
|
199
|
-
scene: sceneRef.current,
|
|
200
|
-
onGizmoChange: (gizmo) => writeGizmoPart(gizmo, argsRef, gizmoRecordedRef),
|
|
201
|
-
onGizmoDraggingChanged: (dragging) => {
|
|
202
|
-
gizmoDraggingRef.current = dragging;
|
|
203
|
-
if (dragging) {
|
|
204
|
-
gizmoRecordedRef.current = false;
|
|
205
|
-
} else {
|
|
206
|
-
rebuildGroup();
|
|
207
|
-
attachGizmoToSelection(rigRef.current, groupRef.current, argsRef.current);
|
|
208
|
-
}
|
|
209
|
-
},
|
|
210
|
-
});
|
|
211
|
-
rig.setNavMode(true);
|
|
212
|
-
rigRef.current = rig;
|
|
213
|
-
attachGizmoToSelection(rig, groupRef.current, argsRef.current);
|
|
214
|
-
return () => {
|
|
215
|
-
rig.dispose();
|
|
216
|
-
rigRef.current = null;
|
|
217
|
-
};
|
|
218
|
-
};
|
|
219
|
-
const onFrame = ({ canvas, renderer }) => {
|
|
220
|
-
const rig = rigRef.current;
|
|
221
|
-
if (!rig) return;
|
|
222
|
-
if (!fitRendererToCanvas(renderer, rig.camera, canvas)) return;
|
|
223
|
-
rig.orbit.update();
|
|
224
|
-
renderer.render(sceneRef.current, rig.camera);
|
|
225
|
-
};
|
|
226
|
-
return { onSetup, onFrame, sceneRef, groupRef, rigRef };
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function attachGizmoToSelection(rig, group, args) {
|
|
230
|
-
if (!rig) return;
|
|
231
|
-
const mesh = group?.children.find((child) => child.userData.partIndex === args.selectedPartIndex);
|
|
232
|
-
if (mesh) {
|
|
233
|
-
rig.gizmo.attach(mesh);
|
|
234
|
-
rig.gizmo.setMode(args.gizmoMode);
|
|
235
|
-
} else {
|
|
236
|
-
rig.gizmo.detach();
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Gizmo drag write-back for the selected part (position + rotation; size is
|
|
241
|
-
// numeric-only in the inspector).
|
|
242
|
-
function writeGizmoPart(gizmo, argsRef, gizmoRecordedRef) {
|
|
243
|
-
const current = argsRef.current;
|
|
244
|
-
const mesh = gizmo.object;
|
|
245
|
-
const index = mesh?.userData.partIndex;
|
|
246
|
-
if (!mesh || index == null || !current.model?.parts?.[index]) return;
|
|
247
|
-
if (!gizmoRecordedRef.current) {
|
|
248
|
-
current.recordSnapshot();
|
|
249
|
-
gizmoRecordedRef.current = true;
|
|
250
|
-
}
|
|
251
|
-
const next = structuredClone(current.model);
|
|
252
|
-
next.parts[index] = {
|
|
253
|
-
...next.parts[index],
|
|
254
|
-
x: roundUnits(mesh.position.x),
|
|
255
|
-
y: roundUnits(mesh.position.y),
|
|
256
|
-
z: roundUnits(mesh.position.z),
|
|
257
|
-
rotationX: roundUnits(radiansToDegrees(mesh.rotation.x)),
|
|
258
|
-
rotationY: roundUnits(radiansToDegrees(mesh.rotation.y)),
|
|
259
|
-
rotationZ: roundUnits(radiansToDegrees(mesh.rotation.z)),
|
|
260
|
-
};
|
|
261
|
-
current.applyModel(next);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Click (press + release without drag) selects the part under the pointer;
|
|
265
|
-
// empty space deselects. Orbit owns actual drags.
|
|
266
|
-
function usePartClickSelect(viewport, setSelectedPartIndex) {
|
|
267
|
-
const downRef = useRef(null);
|
|
268
|
-
const onPointerDown = useCallback(
|
|
269
|
-
(event) => {
|
|
270
|
-
if (event.button !== 0 || viewport.rigRef.current?.gizmoBusy()) return;
|
|
271
|
-
downRef.current = { x: event.clientX, y: event.clientY };
|
|
272
|
-
},
|
|
273
|
-
[viewport.rigRef]
|
|
274
|
-
);
|
|
275
|
-
const onPointerUp = useCallback(
|
|
276
|
-
(event) => {
|
|
277
|
-
const down = downRef.current;
|
|
278
|
-
downRef.current = null;
|
|
279
|
-
if (!down || viewport.rigRef.current?.gizmoBusy()) return;
|
|
280
|
-
if (Math.hypot(event.clientX - down.x, event.clientY - down.y) > CLICK_THRESHOLD_PX) return;
|
|
281
|
-
const rig = viewport.rigRef.current;
|
|
282
|
-
const group = viewport.groupRef.current;
|
|
283
|
-
if (!rig || !group) return;
|
|
284
|
-
const index = raycastPartIndex(event, rig.camera, group);
|
|
285
|
-
setSelectedPartIndex(index);
|
|
286
|
-
},
|
|
287
|
-
[viewport, setSelectedPartIndex]
|
|
288
|
-
);
|
|
289
|
-
return { onPointerDown, onPointerUp };
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function raycastPartIndex(event, camera, group) {
|
|
293
|
-
const canvas = event.currentTarget;
|
|
294
|
-
const rect = canvas.getBoundingClientRect();
|
|
295
|
-
const ndc = {
|
|
296
|
-
x: ((event.clientX - rect.left) / rect.width) * 2 - 1,
|
|
297
|
-
y: -(((event.clientY - rect.top) / rect.height) * 2 - 1),
|
|
298
|
-
};
|
|
299
|
-
const raycaster = new THREE.Raycaster();
|
|
300
|
-
raycaster.setFromCamera(ndc, camera);
|
|
301
|
-
const hits = raycaster.intersectObjects(group.children, false);
|
|
302
|
-
return hits.length ? (hits[0].object.userData.partIndex ?? -1) : -1;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function useInspectorSheet(inspectorOpen) {
|
|
306
|
-
const [snap, setSnap] = useState('high');
|
|
307
|
-
useEffect(() => {
|
|
308
|
-
if (inspectorOpen) setSnap('high');
|
|
309
|
-
}, [inspectorOpen]);
|
|
310
|
-
const effectiveSnap = inspectorOpen ? snap : 'hidden';
|
|
311
|
-
return useMobileSheet({
|
|
312
|
-
snap: effectiveSnap,
|
|
313
|
-
baseClassName: styles.inspector,
|
|
314
|
-
onTransition: (direction) => {
|
|
315
|
-
if (!inspectorOpen) return;
|
|
316
|
-
if (direction === 'tap') setSnap((previous) => (previous === 'high' ? 'low' : 'high'));
|
|
317
|
-
else if (direction === 'down') setSnap('low');
|
|
318
|
-
else if (direction === 'up') setSnap('high');
|
|
319
|
-
},
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function ModelInspector({
|
|
324
|
-
sheet,
|
|
325
|
-
parts,
|
|
326
|
-
selectedPartIndex,
|
|
327
|
-
onSelectPart,
|
|
328
|
-
onAddPart,
|
|
329
|
-
onDuplicatePart,
|
|
330
|
-
onRemovePart,
|
|
331
|
-
onUpdatePart,
|
|
332
|
-
}) {
|
|
333
|
-
const selectedPart = parts[selectedPartIndex];
|
|
334
|
-
return (
|
|
335
|
-
<aside {...sheet.rootProps}>
|
|
336
|
-
<div {...sheet.grabProps}>
|
|
337
|
-
<SheetGrabHandle
|
|
338
|
-
label="Inspector"
|
|
339
|
-
hint={selectedPart ? `part ${selectedPartIndex}` : `${parts.length} parts`}
|
|
340
|
-
/>
|
|
341
|
-
</div>
|
|
342
|
-
<div className={cx(styles.sheetBody, styles.inspectorBody)}>
|
|
343
|
-
<Panel title="Parts">
|
|
344
|
-
<div className={styles.actorList}>
|
|
345
|
-
{parts.map((part, index) => (
|
|
346
|
-
<button
|
|
347
|
-
key={index}
|
|
348
|
-
type="button"
|
|
349
|
-
className={cx(
|
|
350
|
-
styles.actorRow,
|
|
351
|
-
index === selectedPartIndex && styles.actorRowSelected
|
|
352
|
-
)}
|
|
353
|
-
onClick={() => onSelectPart(index === selectedPartIndex ? -1 : index)}>
|
|
354
|
-
{index}: {part.geometry ?? 'box'}
|
|
355
|
-
</button>
|
|
356
|
-
))}
|
|
357
|
-
</div>
|
|
358
|
-
<div style={{ paddingTop: 8 }}>
|
|
359
|
-
<SelectField
|
|
360
|
-
label="Add part"
|
|
361
|
-
value=""
|
|
362
|
-
onChange={(geometry) => {
|
|
363
|
-
if (geometry) onAddPart(geometry);
|
|
364
|
-
}}
|
|
365
|
-
options={['', ...geometryKinds]}
|
|
366
|
-
/>
|
|
367
|
-
</div>
|
|
368
|
-
</Panel>
|
|
369
|
-
{selectedPart ? (
|
|
370
|
-
<Panel title={`Part ${selectedPartIndex}`}>
|
|
371
|
-
<SelectField
|
|
372
|
-
label="Geometry"
|
|
373
|
-
value={selectedPart.geometry ?? 'box'}
|
|
374
|
-
onChange={(geometry) => onUpdatePart(selectedPartIndex, { geometry })}
|
|
375
|
-
options={geometryKinds}
|
|
376
|
-
/>
|
|
377
|
-
<AutoFields
|
|
378
|
-
defaultProps={defaultModelPart}
|
|
379
|
-
component={selectedPart}
|
|
380
|
-
setComponent={(nextProps) => onUpdatePart(selectedPartIndex, nextProps)}
|
|
381
|
-
exclude={['geometry']}
|
|
382
|
-
/>
|
|
383
|
-
<div style={{ display: 'flex', gap: 8, paddingBottom: 12 }}>
|
|
384
|
-
<IconButton
|
|
385
|
-
icon="clone"
|
|
386
|
-
label="Duplicate part"
|
|
387
|
-
onClick={() => onDuplicatePart(selectedPartIndex)}
|
|
388
|
-
/>
|
|
389
|
-
<IconButton
|
|
390
|
-
icon="trash"
|
|
391
|
-
label="Remove part"
|
|
392
|
-
onClick={() => onRemovePart(selectedPartIndex)}
|
|
393
|
-
/>
|
|
394
|
-
</div>
|
|
395
|
-
</Panel>
|
|
396
|
-
) : null}
|
|
397
|
-
</div>
|
|
398
|
-
</aside>
|
|
399
|
-
);
|
|
400
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Lifecycle } from 'castle-web-sdk';
|
|
3
|
-
import { initialFiles, parseJsonFile, parseModels } from '../engine/files';
|
|
4
|
-
import { ScenePlayer } from '../engine/ScenePlayer';
|
|
5
|
-
import { behaviorClasses } from './behaviorRegistry';
|
|
6
|
-
// Play-mode entry point. Intentionally thin: it locates the start scene and
|
|
7
|
-
// hands it to the engine's `ScenePlayer`, which owns the runtime and input.
|
|
8
|
-
// Deck/game logic belongs in `scenes/` and `behaviors/`, not here.
|
|
9
|
-
export function PlayOnly() {
|
|
10
|
-
const sceneText = initialFiles['scenes/main.scene'] ?? '';
|
|
11
|
-
const { value: sceneData } = parseJsonFile('scenes/main.scene', sceneText);
|
|
12
|
-
if (!sceneData) return null;
|
|
13
|
-
const models = parseModels(initialFiles);
|
|
14
|
-
return (
|
|
15
|
-
<ScenePlayer
|
|
16
|
-
sceneData={sceneData}
|
|
17
|
-
models={models}
|
|
18
|
-
behaviorClasses={behaviorClasses}
|
|
19
|
-
onFirstFrame={Lifecycle.ready}
|
|
20
|
-
/>
|
|
21
|
-
);
|
|
22
|
-
}
|