react-three-game 0.0.44 → 0.0.46
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 +38 -4
- package/assets/architecture.png +0 -0
- package/dist/index.d.ts +7 -6
- package/dist/index.js +9 -6
- package/dist/tools/assetviewer/page.js +1 -1
- package/dist/tools/prefabeditor/EditorUI.js +3 -5
- package/dist/tools/prefabeditor/PrefabRoot.js +8 -3
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +2 -2
- package/dist/tools/prefabeditor/components/ComponentRegistry.js +5 -0
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +27 -27
- package/dist/tools/prefabeditor/components/GeometryComponent.js +41 -21
- package/dist/tools/prefabeditor/components/Input.d.ts +78 -1
- package/dist/tools/prefabeditor/components/Input.js +65 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.js +60 -28
- package/dist/tools/prefabeditor/components/ModelComponent.js +18 -8
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +26 -14
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +10 -21
- package/dist/tools/prefabeditor/components/TextComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/TextComponent.js +103 -0
- package/dist/tools/prefabeditor/components/TransformComponent.js +28 -19
- package/dist/tools/prefabeditor/components/index.js +3 -1
- package/dist/tools/prefabeditor/page.js +1 -1
- package/package.json +3 -2
- package/skill/SKILL.md +491 -0
- package/skill/package.json +17 -0
- package/src/index.ts +28 -10
- package/src/tools/assetviewer/page.tsx +2 -0
- package/src/tools/prefabeditor/EditorUI.tsx +0 -10
- package/src/tools/prefabeditor/PrefabRoot.tsx +11 -2
- package/src/tools/prefabeditor/components/ComponentRegistry.ts +11 -5
- package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +72 -76
- package/src/tools/prefabeditor/components/GeometryComponent.tsx +56 -38
- package/src/tools/prefabeditor/components/Input.tsx +299 -0
- package/src/tools/prefabeditor/components/MaterialComponent.tsx +98 -142
- package/src/tools/prefabeditor/components/ModelComponent.tsx +63 -41
- package/src/tools/prefabeditor/components/PhysicsComponent.tsx +30 -33
- package/src/tools/prefabeditor/components/SpotLightComponent.tsx +17 -65
- package/src/tools/prefabeditor/components/TextComponent.tsx +136 -0
- package/src/tools/prefabeditor/components/TransformComponent.tsx +84 -56
- package/src/tools/prefabeditor/components/index.ts +3 -1
- package/src/tools/prefabeditor/page.tsx +1 -1
package/README.md
CHANGED
|
@@ -72,21 +72,29 @@ interface GameObject {
|
|
|
72
72
|
## Custom Components
|
|
73
73
|
|
|
74
74
|
```tsx
|
|
75
|
-
import { Component, registerComponent } from 'react-three-game';
|
|
75
|
+
import { Component, registerComponent, FieldRenderer, FieldDefinition } from 'react-three-game';
|
|
76
76
|
import { useFrame } from '@react-three/fiber';
|
|
77
77
|
|
|
78
|
+
const rotatorFields: FieldDefinition[] = [
|
|
79
|
+
{ name: 'speed', type: 'number', label: 'Speed', step: 0.1 },
|
|
80
|
+
{ name: 'axis', type: 'select', label: 'Axis', options: [
|
|
81
|
+
{ value: 'x', label: 'X' },
|
|
82
|
+
{ value: 'y', label: 'Y' },
|
|
83
|
+
{ value: 'z', label: 'Z' },
|
|
84
|
+
]},
|
|
85
|
+
];
|
|
86
|
+
|
|
78
87
|
const Rotator: Component = {
|
|
79
88
|
name: 'Rotator',
|
|
80
89
|
Editor: ({ component, onUpdate }) => (
|
|
81
|
-
<
|
|
82
|
-
onChange={e => onUpdate({ speed: +e.target.value })} />
|
|
90
|
+
<FieldRenderer fields={rotatorFields} values={component.properties} onChange={onUpdate} />
|
|
83
91
|
),
|
|
84
92
|
View: ({ properties, children }) => {
|
|
85
93
|
const ref = useRef<Group>(null);
|
|
86
94
|
useFrame((_, dt) => { ref.current!.rotation.y += dt * properties.speed });
|
|
87
95
|
return <group ref={ref}>{children}</group>;
|
|
88
96
|
},
|
|
89
|
-
defaultProperties: { speed: 1 }
|
|
97
|
+
defaultProperties: { speed: 1, axis: 'y' }
|
|
90
98
|
};
|
|
91
99
|
|
|
92
100
|
registerComponent(Rotator); // before rendering PrefabEditor
|
|
@@ -94,6 +102,32 @@ registerComponent(Rotator); // before rendering PrefabEditor
|
|
|
94
102
|
|
|
95
103
|
**Wrapper** components accept `children` (animations, controllers). **Leaf** components don't (lights, particles).
|
|
96
104
|
|
|
105
|
+
### Schema-Driven Field Types
|
|
106
|
+
|
|
107
|
+
The `FieldRenderer` component auto-generates editor UI from a field schema:
|
|
108
|
+
|
|
109
|
+
| Type | Description | Options |
|
|
110
|
+
|------|-------------|---------|
|
|
111
|
+
| `vector3` | X/Y/Z inputs with drag-to-scrub | `snap?: number` |
|
|
112
|
+
| `number` | Numeric input | `min?`, `max?`, `step?` |
|
|
113
|
+
| `string` | Text input | `placeholder?` |
|
|
114
|
+
| `color` | Color picker + hex input | — |
|
|
115
|
+
| `boolean` | Checkbox | — |
|
|
116
|
+
| `select` | Dropdown | `options: { value, label }[]` |
|
|
117
|
+
| `custom` | Render function for one-off UI | `render: (props) => ReactNode` |
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
// Custom field example for complex one-off UI
|
|
121
|
+
{
|
|
122
|
+
name: 'gradient',
|
|
123
|
+
type: 'custom',
|
|
124
|
+
label: 'Gradient',
|
|
125
|
+
render: ({ value, onChange, values, onChangeMultiple }) => (
|
|
126
|
+
<GradientPicker value={value} onChange={onChange} />
|
|
127
|
+
),
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
97
131
|
## Visual Editor
|
|
98
132
|
|
|
99
133
|
```jsx
|
package/assets/architecture.png
CHANGED
|
Binary file
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
export { default as GameCanvas } from './shared/GameCanvas';
|
|
2
|
+
export * from './helpers';
|
|
3
|
+
export { sound as soundManager } from './helpers/SoundManager';
|
|
2
4
|
export { default as PrefabEditor } from './tools/prefabeditor/PrefabEditor';
|
|
3
|
-
export type { PrefabEditorRef } from './tools/prefabeditor/PrefabEditor';
|
|
4
5
|
export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
|
|
5
|
-
export type { PrefabRootRef } from './tools/prefabeditor/PrefabRoot';
|
|
6
6
|
export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
|
|
7
|
+
export { FieldRenderer, Input, Label, Vector3Input, ColorInput, StringInput, BooleanInput, SelectInput, } from './tools/prefabeditor/components/Input';
|
|
8
|
+
export * from './tools/prefabeditor/utils';
|
|
9
|
+
export type { PrefabEditorRef } from './tools/prefabeditor/PrefabEditor';
|
|
10
|
+
export type { PrefabRootRef } from './tools/prefabeditor/PrefabRoot';
|
|
7
11
|
export type { Component } from './tools/prefabeditor/components/ComponentRegistry';
|
|
12
|
+
export type { FieldDefinition, FieldType } from './tools/prefabeditor/components/Input';
|
|
8
13
|
export type { Prefab, GameObject, ComponentData } from './tools/prefabeditor/types';
|
|
9
|
-
export * as editorStyles from './tools/prefabeditor/styles';
|
|
10
|
-
export * from './tools/prefabeditor/utils';
|
|
11
14
|
export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
|
|
12
15
|
export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
|
|
13
|
-
export { sound as soundManager } from './helpers/SoundManager';
|
|
14
|
-
export * from './helpers';
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
// Core
|
|
1
|
+
// Core
|
|
2
2
|
export { default as GameCanvas } from './shared/GameCanvas';
|
|
3
|
-
//
|
|
3
|
+
// Helpers
|
|
4
|
+
export * from './helpers';
|
|
5
|
+
export { sound as soundManager } from './helpers/SoundManager';
|
|
6
|
+
// Prefab Editor - Components
|
|
4
7
|
export { default as PrefabEditor } from './tools/prefabeditor/PrefabEditor';
|
|
5
8
|
export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
|
|
9
|
+
// Prefab Editor - Component Registry
|
|
6
10
|
export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
|
|
7
|
-
|
|
11
|
+
// Prefab Editor - Input Components
|
|
12
|
+
export { FieldRenderer, Input, Label, Vector3Input, ColorInput, StringInput, BooleanInput, SelectInput, } from './tools/prefabeditor/components/Input';
|
|
13
|
+
// Prefab Editor - Styles & Utils
|
|
8
14
|
export * from './tools/prefabeditor/utils';
|
|
9
15
|
// Asset Tools
|
|
10
16
|
export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
|
|
11
17
|
export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
|
|
12
|
-
export { sound as soundManager } from './helpers/SoundManager';
|
|
13
|
-
// Helpers
|
|
14
|
-
export * from './helpers';
|
|
@@ -103,7 +103,7 @@ function ModelCard({ file, onSelect, basePath = "" }) {
|
|
|
103
103
|
if (error) {
|
|
104
104
|
return (_jsx("div", { ref: ref, style: { aspectRatio: '1 / 1', backgroundColor: '#374151', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }, onClick: () => onSelect(file), children: _jsx("div", { style: styles.errorIcon, children: "\u2717" }) }));
|
|
105
105
|
}
|
|
106
|
-
return (_jsxs("div", { ref: ref, style: { maxWidth: 60, aspectRatio: '1 / 1', backgroundColor: '#111827', cursor: 'pointer', display: 'flex', flexDirection: 'column' }, onClick: () => onSelect(file), children: [_jsx("div", { style: styles.flexFillRelative, children: isInView ? (_jsxs(View, { style: { width: '100%', height: '100%' }, children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 1, 3], fov: 50 }), _jsxs(Suspense, { fallback: null, children: [_jsx(ModelPreview, { url: fullPath, onError: () => setError(true) }), _jsx(OrbitControls, { enableZoom: false })] })] })) : null }), _jsx("div", { style: { backgroundColor: 'rgba(0,0,0,0.6)', fontSize: 10, padding: '0 4px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center' }, children: file.split('/').pop() })] }));
|
|
106
|
+
return (_jsxs("div", { ref: ref, style: { maxWidth: 60, aspectRatio: '1 / 1', backgroundColor: '#111827', cursor: 'pointer', display: 'flex', flexDirection: 'column' }, onClick: () => onSelect(file), children: [_jsx("div", { style: styles.flexFillRelative, children: isInView ? (_jsxs(View, { style: { width: '100%', height: '100%' }, children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 1, 3], fov: 50 }), _jsxs(Suspense, { fallback: null, children: [_jsx("ambientLight", { intensity: 1 }), _jsx("pointLight", { position: [5, 5, 5], intensity: 0.5 }), _jsx(ModelPreview, { url: fullPath, onError: () => setError(true) }), _jsx(OrbitControls, { enableZoom: false })] })] })) : null }), _jsx("div", { style: { backgroundColor: 'rgba(0,0,0,0.6)', fontSize: 10, padding: '0 4px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center' }, children: file.split('/').pop() })] }));
|
|
107
107
|
}
|
|
108
108
|
function ModelPreview({ url, onError }) {
|
|
109
109
|
const [model, setModel] = useState(null);
|
|
@@ -15,10 +15,8 @@ import EditorTree from './EditorTree';
|
|
|
15
15
|
import { getAllComponents } from './components/ComponentRegistry';
|
|
16
16
|
import { base, inspector } from './styles';
|
|
17
17
|
import { findNode, updateNode, deleteNode } from './utils';
|
|
18
|
-
import { useEditorContext } from './EditorContext';
|
|
19
18
|
function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, basePath, onUndo, onRedo, canUndo, canRedo }) {
|
|
20
19
|
const [collapsed, setCollapsed] = useState(false);
|
|
21
|
-
const { transformMode, setTransformMode } = useEditorContext();
|
|
22
20
|
const updateNodeHandler = (updater) => {
|
|
23
21
|
if (!prefabData || !setPrefabData || !selectedId)
|
|
24
22
|
return;
|
|
@@ -36,9 +34,9 @@ function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, basePa
|
|
|
36
34
|
.prefab-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
37
35
|
.prefab-scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.06); border-radius: 8px; }
|
|
38
36
|
.prefab-scroll { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.06) transparent; }
|
|
39
|
-
` }), _jsxs("div", { style: inspector.panel, children: [_jsxs("div", { style: base.header, onClick: () => setCollapsed(!collapsed), children: [_jsx("span", { children: "Inspector" }), _jsx("span", { children: collapsed ? '◀' : '▼' })] }), !collapsed && selectedNode && (_jsx(NodeInspector, { node: selectedNode, updateNode: updateNodeHandler, deleteNode: deleteNodeHandler,
|
|
37
|
+
` }), _jsxs("div", { style: inspector.panel, children: [_jsxs("div", { style: base.header, onClick: () => setCollapsed(!collapsed), children: [_jsx("span", { children: "Inspector" }), _jsx("span", { children: collapsed ? '◀' : '▼' })] }), !collapsed && selectedNode && (_jsx(NodeInspector, { node: selectedNode, updateNode: updateNodeHandler, deleteNode: deleteNodeHandler, basePath: basePath }))] }), _jsx("div", { style: { position: 'absolute', top: 8, left: 8, zIndex: 20 }, children: _jsx(EditorTree, { prefabData: prefabData, setPrefabData: setPrefabData, selectedId: selectedId, setSelectedId: setSelectedId, onUndo: onUndo, onRedo: onRedo, canUndo: canUndo, canRedo: canRedo }) })] });
|
|
40
38
|
}
|
|
41
|
-
function NodeInspector({ node, updateNode, deleteNode,
|
|
39
|
+
function NodeInspector({ node, updateNode, deleteNode, basePath }) {
|
|
42
40
|
var _a;
|
|
43
41
|
const ALL_COMPONENTS = getAllComponents();
|
|
44
42
|
const allKeys = Object.keys(ALL_COMPONENTS);
|
|
@@ -58,7 +56,7 @@ function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransfo
|
|
|
58
56
|
return (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }, children: [_jsx("div", { style: { fontSize: 11, fontWeight: 500 }, children: key }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px' }), title: "Remove Component", onClick: () => updateNode(n => {
|
|
59
57
|
const _a = n.components || {}, _b = key, _ = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]);
|
|
60
58
|
return Object.assign(Object.assign({}, n), { components: rest });
|
|
61
|
-
}), children: "\u2715" })] }), def.Editor && (_jsx(def.Editor, { component: comp, node: node, onUpdate: (newProps) => updateNode(n => (Object.assign(Object.assign({}, n), { components: Object.assign(Object.assign({}, n.components), { [key]: Object.assign(Object.assign({}, comp), { properties: Object.assign(Object.assign({}, comp.properties), newProps) }) }) }))), basePath: basePath
|
|
59
|
+
}), children: "\u2715" })] }), def.Editor && (_jsx(def.Editor, { component: comp, node: node, onUpdate: (newProps) => updateNode(n => (Object.assign(Object.assign({}, n), { components: Object.assign(Object.assign({}, n.components), { [key]: Object.assign(Object.assign({}, comp), { properties: Object.assign(Object.assign({}, comp.properties), newProps) }) }) }))), basePath: basePath }))] }, key));
|
|
62
60
|
})] }), available.length > 0 && (_jsx("div", { children: _jsxs("div", { style: base.row, children: [_jsx("select", { style: Object.assign(Object.assign({}, base.input), { flex: 1 }), value: addType, onChange: e => setAddType(e.target.value), children: available.map(k => _jsx("option", { value: k, children: k }, k)) }), _jsx("button", { style: base.btn, disabled: !addType, onClick: () => {
|
|
63
61
|
if (!addType)
|
|
64
62
|
return;
|
|
@@ -11,7 +11,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
11
11
|
import { MapControls, TransformControls, useHelper } from "@react-three/drei";
|
|
12
12
|
import { forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
13
13
|
import { BoxHelper, Euler, Matrix4, Quaternion, SRGBColorSpace, TextureLoader, Vector3, } from "three";
|
|
14
|
-
import { getComponent, registerComponent } from "./components/ComponentRegistry";
|
|
14
|
+
import { getComponent, registerComponent, getNonComposableKeys } from "./components/ComponentRegistry";
|
|
15
15
|
import components from "./components";
|
|
16
16
|
import { loadModel } from "../dragdrop/modelLoader";
|
|
17
17
|
import { GameInstance, GameInstanceProvider, useInstanceCheck } from "./InstanceProvider";
|
|
@@ -245,13 +245,15 @@ function computeParentWorldMatrix(root, targetId) {
|
|
|
245
245
|
return result !== null && result !== void 0 ? result : IDENTITY;
|
|
246
246
|
}
|
|
247
247
|
function renderCoreNode(gameObject, ctx, parentMatrix) {
|
|
248
|
-
var _a, _b, _c;
|
|
248
|
+
var _a, _b, _c, _d;
|
|
249
249
|
const geometry = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.geometry;
|
|
250
250
|
const material = (_b = gameObject.components) === null || _b === void 0 ? void 0 : _b.material;
|
|
251
251
|
const model = (_c = gameObject.components) === null || _c === void 0 ? void 0 : _c.model;
|
|
252
|
+
const text = (_d = gameObject.components) === null || _d === void 0 ? void 0 : _d.text;
|
|
252
253
|
const geometryDef = geometry && getComponent("Geometry");
|
|
253
254
|
const materialDef = material && getComponent("Material");
|
|
254
255
|
const modelDef = model && getComponent("Model");
|
|
256
|
+
const textDef = text && getComponent("Text");
|
|
255
257
|
const contextProps = {
|
|
256
258
|
loadedModels: ctx.loadedModels,
|
|
257
259
|
loadedTextures: ctx.loadedTextures,
|
|
@@ -263,7 +265,7 @@ function renderCoreNode(gameObject, ctx, parentMatrix) {
|
|
|
263
265
|
const leaves = [];
|
|
264
266
|
if (gameObject.components) {
|
|
265
267
|
Object.entries(gameObject.components)
|
|
266
|
-
.filter(([k]) => !
|
|
268
|
+
.filter(([k]) => !getNonComposableKeys().includes(k))
|
|
267
269
|
.forEach(([key, comp]) => {
|
|
268
270
|
if (!(comp === null || comp === void 0 ? void 0 : comp.type))
|
|
269
271
|
return;
|
|
@@ -285,6 +287,9 @@ function renderCoreNode(gameObject, ctx, parentMatrix) {
|
|
|
285
287
|
else if (geometry && (geometryDef === null || geometryDef === void 0 ? void 0 : geometryDef.View)) {
|
|
286
288
|
core = (_jsxs("mesh", { castShadow: true, receiveShadow: true, children: [_jsx(geometryDef.View, Object.assign({ properties: geometry.properties }, contextProps)), material && (materialDef === null || materialDef === void 0 ? void 0 : materialDef.View) && (_jsx(materialDef.View, Object.assign({ properties: material.properties }, contextProps), "material")), leaves] }));
|
|
287
289
|
}
|
|
290
|
+
else if (text && (textDef === null || textDef === void 0 ? void 0 : textDef.View)) {
|
|
291
|
+
core = (_jsxs(_Fragment, { children: [_jsx(textDef.View, Object.assign({ properties: text.properties }, contextProps)), leaves] }));
|
|
292
|
+
}
|
|
288
293
|
else {
|
|
289
294
|
core = _jsx(_Fragment, { children: leaves });
|
|
290
295
|
}
|
|
@@ -7,12 +7,12 @@ export interface Component {
|
|
|
7
7
|
component: ComponentData;
|
|
8
8
|
onUpdate: (newComp: any) => void;
|
|
9
9
|
basePath?: string;
|
|
10
|
-
transformMode?: "translate" | "rotate" | "scale";
|
|
11
|
-
setTransformMode?: (m: "translate" | "rotate" | "scale") => void;
|
|
12
10
|
}>;
|
|
13
11
|
defaultProperties: any;
|
|
14
12
|
View?: FC<any>;
|
|
13
|
+
nonComposable?: boolean;
|
|
15
14
|
}
|
|
16
15
|
export declare function registerComponent(component: Component): void;
|
|
17
16
|
export declare function getComponent(name: string): Component | undefined;
|
|
18
17
|
export declare function getAllComponents(): Record<string, Component>;
|
|
18
|
+
export declare function getNonComposableKeys(): string[];
|
|
@@ -11,3 +11,8 @@ export function getComponent(name) {
|
|
|
11
11
|
export function getAllComponents() {
|
|
12
12
|
return Object.assign({}, REGISTRY);
|
|
13
13
|
}
|
|
14
|
+
export function getNonComposableKeys() {
|
|
15
|
+
return Object.values(REGISTRY)
|
|
16
|
+
.filter(c => c.nonComposable)
|
|
17
|
+
.map(c => c.name.toLowerCase());
|
|
18
|
+
}
|
|
@@ -2,34 +2,34 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useRef, useEffect } from "react";
|
|
3
3
|
import { useFrame } from "@react-three/fiber";
|
|
4
4
|
import { Vector3 } from "three";
|
|
5
|
-
import {
|
|
5
|
+
import { FieldRenderer, Input } from "./Input";
|
|
6
|
+
const smallLabel = { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 };
|
|
7
|
+
const directionalLightFields = [
|
|
8
|
+
{ name: 'color', type: 'color', label: 'Color' },
|
|
9
|
+
{ name: 'intensity', type: 'number', label: 'Intensity', step: 0.1, min: 0 },
|
|
10
|
+
{ name: 'castShadow', type: 'boolean', label: 'Cast Shadow' },
|
|
11
|
+
{ name: 'shadowMapSize', type: 'number', label: 'Shadow Map Size', step: 256, min: 256 },
|
|
12
|
+
{
|
|
13
|
+
name: '_shadowCamera',
|
|
14
|
+
type: 'custom',
|
|
15
|
+
label: 'Shadow Camera',
|
|
16
|
+
render: ({ values, onChangeMultiple }) => {
|
|
17
|
+
var _a, _b, _c, _d, _e, _f;
|
|
18
|
+
return (_jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4 }, children: [_jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Near" }), _jsx(Input, { step: 0.1, value: (_a = values.shadowCameraNear) !== null && _a !== void 0 ? _a : 0.1, onChange: v => onChangeMultiple({ shadowCameraNear: v }) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Far" }), _jsx(Input, { step: 1, value: (_b = values.shadowCameraFar) !== null && _b !== void 0 ? _b : 100, onChange: v => onChangeMultiple({ shadowCameraFar: v }) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Top" }), _jsx(Input, { step: 1, value: (_c = values.shadowCameraTop) !== null && _c !== void 0 ? _c : 30, onChange: v => onChangeMultiple({ shadowCameraTop: v }) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Bottom" }), _jsx(Input, { step: 1, value: (_d = values.shadowCameraBottom) !== null && _d !== void 0 ? _d : -30, onChange: v => onChangeMultiple({ shadowCameraBottom: v }) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Left" }), _jsx(Input, { step: 1, value: (_e = values.shadowCameraLeft) !== null && _e !== void 0 ? _e : -30, onChange: v => onChangeMultiple({ shadowCameraLeft: v }) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Right" }), _jsx(Input, { step: 1, value: (_f = values.shadowCameraRight) !== null && _f !== void 0 ? _f : 30, onChange: v => onChangeMultiple({ shadowCameraRight: v }) })] })] }));
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'targetOffset',
|
|
23
|
+
type: 'custom',
|
|
24
|
+
label: 'Target Offset',
|
|
25
|
+
render: ({ value, onChange }) => {
|
|
26
|
+
const offset = value !== null && value !== void 0 ? value : [0, -5, 0];
|
|
27
|
+
return (_jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 4 }, children: [_jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "X" }), _jsx(Input, { step: 0.5, value: offset[0], onChange: v => onChange([v, offset[1], offset[2]]) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Y" }), _jsx(Input, { step: 0.5, value: offset[1], onChange: v => onChange([offset[0], v, offset[2]]) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Z" }), _jsx(Input, { step: 0.5, value: offset[2], onChange: v => onChange([offset[0], offset[1], v]) })] })] }));
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
];
|
|
6
31
|
function DirectionalLightComponentEditor({ component, onUpdate }) {
|
|
7
|
-
|
|
8
|
-
const props = {
|
|
9
|
-
color: (_a = component.properties.color) !== null && _a !== void 0 ? _a : '#ffffff',
|
|
10
|
-
intensity: (_b = component.properties.intensity) !== null && _b !== void 0 ? _b : 1.0,
|
|
11
|
-
castShadow: (_c = component.properties.castShadow) !== null && _c !== void 0 ? _c : true,
|
|
12
|
-
shadowMapSize: (_d = component.properties.shadowMapSize) !== null && _d !== void 0 ? _d : 1024,
|
|
13
|
-
shadowCameraNear: (_e = component.properties.shadowCameraNear) !== null && _e !== void 0 ? _e : 0.1,
|
|
14
|
-
shadowCameraFar: (_f = component.properties.shadowCameraFar) !== null && _f !== void 0 ? _f : 100,
|
|
15
|
-
shadowCameraTop: (_g = component.properties.shadowCameraTop) !== null && _g !== void 0 ? _g : 30,
|
|
16
|
-
shadowCameraBottom: (_h = component.properties.shadowCameraBottom) !== null && _h !== void 0 ? _h : -30,
|
|
17
|
-
shadowCameraLeft: (_j = component.properties.shadowCameraLeft) !== null && _j !== void 0 ? _j : -30,
|
|
18
|
-
shadowCameraRight: (_k = component.properties.shadowCameraRight) !== null && _k !== void 0 ? _k : 30,
|
|
19
|
-
targetOffset: (_l = component.properties.targetOffset) !== null && _l !== void 0 ? _l : [0, -5, 0]
|
|
20
|
-
};
|
|
21
|
-
const textInputStyle = {
|
|
22
|
-
flex: 1,
|
|
23
|
-
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
24
|
-
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
25
|
-
padding: '2px 4px',
|
|
26
|
-
fontSize: '10px',
|
|
27
|
-
color: 'rgba(165, 243, 252, 1)',
|
|
28
|
-
fontFamily: 'monospace',
|
|
29
|
-
outline: 'none',
|
|
30
|
-
};
|
|
31
|
-
const smallLabel = { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 };
|
|
32
|
-
return _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Color" }), _jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx("input", { type: "color", style: { height: 20, width: 20, backgroundColor: 'transparent', border: 'none', cursor: 'pointer' }, value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { color: e.target.value })) }), _jsx("input", { type: "text", style: textInputStyle, value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { color: e.target.value })) })] })] }), _jsxs("div", { children: [_jsx(Label, { children: "Intensity" }), _jsx(Input, { step: "0.1", value: props.intensity, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { intensity: value })) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Cast Shadow" }), _jsx("input", { type: "checkbox", style: { height: 16, width: 16, backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', cursor: 'pointer' }, checked: props.castShadow, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { castShadow: e.target.checked })) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Shadow Map Size" }), _jsx(Input, { step: "256", value: props.shadowMapSize, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowMapSize: value })) })] }), _jsxs("div", { style: { borderTop: '1px solid rgba(34, 211, 238, 0.2)', paddingTop: 8, marginTop: 8 }, children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }, children: "Shadow Camera" }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4 }, children: [_jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Near" }), _jsx(Input, { step: "0.1", value: props.shadowCameraNear, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraNear: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Far" }), _jsx(Input, { step: "1", value: props.shadowCameraFar, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraFar: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Top" }), _jsx(Input, { step: "1", value: props.shadowCameraTop, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraTop: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Bottom" }), _jsx(Input, { step: "1", value: props.shadowCameraBottom, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraBottom: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Left" }), _jsx(Input, { step: "1", value: props.shadowCameraLeft, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraLeft: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Right" }), _jsx(Input, { step: "1", value: props.shadowCameraRight, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraRight: value })) })] })] })] }), _jsxs("div", { style: { borderTop: '1px solid rgba(34, 211, 238, 0.2)', paddingTop: 8, marginTop: 8 }, children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }, children: "Target Offset" }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 4 }, children: [_jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "X" }), _jsx(Input, { step: "0.5", value: props.targetOffset[0], onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { targetOffset: [value, props.targetOffset[1], props.targetOffset[2]] })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Y" }), _jsx(Input, { step: "0.5", value: props.targetOffset[1], onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { targetOffset: [props.targetOffset[0], value, props.targetOffset[2]] })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Z" }), _jsx(Input, { step: "0.5", value: props.targetOffset[2], onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { targetOffset: [props.targetOffset[0], props.targetOffset[1], value] })) })] })] })] })] });
|
|
32
|
+
return (_jsx(FieldRenderer, { fields: directionalLightFields, values: component.properties, onChange: onUpdate }));
|
|
33
33
|
}
|
|
34
34
|
function DirectionalLightView({ properties, editMode }) {
|
|
35
35
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Input, Label } from "./Input";
|
|
2
|
+
import { FieldRenderer, Input, Label } from "./Input";
|
|
3
3
|
const GEOMETRY_ARGS = {
|
|
4
4
|
box: {
|
|
5
5
|
labels: ["Width", "Height", "Depth"],
|
|
@@ -17,27 +17,46 @@ const GEOMETRY_ARGS = {
|
|
|
17
17
|
function GeometryComponentEditor({ component, onUpdate, }) {
|
|
18
18
|
const { geometryType, args = [] } = component.properties;
|
|
19
19
|
const schema = GEOMETRY_ARGS[geometryType];
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
const fields = [
|
|
21
|
+
{
|
|
22
|
+
name: 'geometryType',
|
|
23
|
+
type: 'select',
|
|
24
|
+
label: 'Type',
|
|
25
|
+
options: [
|
|
26
|
+
{ value: 'box', label: 'Box' },
|
|
27
|
+
{ value: 'sphere', label: 'Sphere' },
|
|
28
|
+
{ value: 'plane', label: 'Plane' },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'args',
|
|
33
|
+
type: 'custom',
|
|
34
|
+
label: '',
|
|
35
|
+
render: ({ values, onChangeMultiple }) => {
|
|
36
|
+
const currentType = values.geometryType;
|
|
37
|
+
const currentSchema = GEOMETRY_ARGS[currentType];
|
|
38
|
+
const currentArgs = values.args || currentSchema.defaults;
|
|
39
|
+
return (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: currentSchema.labels.map((label, i) => {
|
|
40
|
+
var _a;
|
|
41
|
+
return (_jsxs("div", { children: [_jsx(Label, { children: label }), _jsx(Input, { value: (_a = currentArgs[i]) !== null && _a !== void 0 ? _a : currentSchema.defaults[i], step: 0.1, onChange: value => {
|
|
42
|
+
const next = [...currentArgs];
|
|
43
|
+
next[i] = value;
|
|
44
|
+
onChangeMultiple({ args: next });
|
|
45
|
+
} })] }, label));
|
|
46
|
+
}) }));
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
// Handle geometry type change to reset args
|
|
51
|
+
const handleChange = (newValues) => {
|
|
52
|
+
if ('geometryType' in newValues && newValues.geometryType !== geometryType) {
|
|
53
|
+
onUpdate({ geometryType: newValues.geometryType, args: GEOMETRY_ARGS[newValues.geometryType].defaults });
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
onUpdate(newValues);
|
|
57
|
+
}
|
|
29
58
|
};
|
|
30
|
-
return (
|
|
31
|
-
const type = e.target.value;
|
|
32
|
-
onUpdate({ geometryType: type, args: GEOMETRY_ARGS[type].defaults });
|
|
33
|
-
}, children: [_jsx("option", { value: "box", children: "Box" }), _jsx("option", { value: "sphere", children: "Sphere" }), _jsx("option", { value: "plane", children: "Plane" })] })] }), schema.labels.map((label, i) => {
|
|
34
|
-
var _a;
|
|
35
|
-
return (_jsxs("div", { children: [_jsx(Label, { children: label }), _jsx(Input, { value: (_a = args[i]) !== null && _a !== void 0 ? _a : schema.defaults[i], step: "0.1", onChange: value => {
|
|
36
|
-
const next = [...args];
|
|
37
|
-
next[i] = value;
|
|
38
|
-
onUpdate({ args: next });
|
|
39
|
-
} })] }, label));
|
|
40
|
-
})] }));
|
|
59
|
+
return (_jsx(FieldRenderer, { fields: fields, values: component.properties, onChange: handleChange }));
|
|
41
60
|
}
|
|
42
61
|
// View for Geometry component
|
|
43
62
|
function GeometryComponentView({ properties, children }) {
|
|
@@ -58,6 +77,7 @@ const GeometryComponent = {
|
|
|
58
77
|
name: 'Geometry',
|
|
59
78
|
Editor: GeometryComponentEditor,
|
|
60
79
|
View: GeometryComponentView,
|
|
80
|
+
nonComposable: true,
|
|
61
81
|
defaultProperties: {
|
|
62
82
|
geometryType: 'box',
|
|
63
83
|
args: GEOMETRY_ARGS.box.defaults,
|
|
@@ -1,4 +1,50 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
export type FieldType = 'vector3' | 'number' | 'string' | 'color' | 'boolean' | 'select';
|
|
3
|
+
interface BaseFieldDefinition {
|
|
4
|
+
name: string;
|
|
5
|
+
label: string;
|
|
6
|
+
}
|
|
7
|
+
interface Vector3FieldDefinition extends BaseFieldDefinition {
|
|
8
|
+
type: 'vector3';
|
|
9
|
+
snap?: number;
|
|
10
|
+
}
|
|
11
|
+
interface NumberFieldDefinition extends BaseFieldDefinition {
|
|
12
|
+
type: 'number';
|
|
13
|
+
min?: number;
|
|
14
|
+
max?: number;
|
|
15
|
+
step?: number;
|
|
16
|
+
}
|
|
17
|
+
interface StringFieldDefinition extends BaseFieldDefinition {
|
|
18
|
+
type: 'string';
|
|
19
|
+
placeholder?: string;
|
|
20
|
+
}
|
|
21
|
+
interface ColorFieldDefinition extends BaseFieldDefinition {
|
|
22
|
+
type: 'color';
|
|
23
|
+
}
|
|
24
|
+
interface BooleanFieldDefinition extends BaseFieldDefinition {
|
|
25
|
+
type: 'boolean';
|
|
26
|
+
}
|
|
27
|
+
interface SelectFieldDefinition extends BaseFieldDefinition {
|
|
28
|
+
type: 'select';
|
|
29
|
+
options: {
|
|
30
|
+
value: string;
|
|
31
|
+
label: string;
|
|
32
|
+
}[];
|
|
33
|
+
}
|
|
34
|
+
interface CustomFieldDefinition extends BaseFieldDefinition {
|
|
35
|
+
type: 'custom';
|
|
36
|
+
render: (props: {
|
|
37
|
+
value: any;
|
|
38
|
+
onChange: (value: any) => void;
|
|
39
|
+
values: Record<string, any>;
|
|
40
|
+
onChangeMultiple: (values: Record<string, any>) => void;
|
|
41
|
+
}) => React.ReactNode;
|
|
42
|
+
}
|
|
43
|
+
export type FieldDefinition = Vector3FieldDefinition | NumberFieldDefinition | StringFieldDefinition | ColorFieldDefinition | BooleanFieldDefinition | SelectFieldDefinition | CustomFieldDefinition;
|
|
44
|
+
declare const styles: {
|
|
45
|
+
input: React.CSSProperties;
|
|
46
|
+
label: React.CSSProperties;
|
|
47
|
+
};
|
|
2
48
|
interface InputProps {
|
|
3
49
|
value: number;
|
|
4
50
|
onChange: (value: number) => void;
|
|
@@ -17,4 +63,35 @@ export declare function Vector3Input({ label, value, onChange, snap }: {
|
|
|
17
63
|
onChange: (v: [number, number, number]) => void;
|
|
18
64
|
snap?: number;
|
|
19
65
|
}): import("react/jsx-runtime").JSX.Element;
|
|
20
|
-
export {}
|
|
66
|
+
export declare function ColorInput({ label, value, onChange }: {
|
|
67
|
+
label?: string;
|
|
68
|
+
value: string;
|
|
69
|
+
onChange: (value: string) => void;
|
|
70
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
71
|
+
export declare function StringInput({ label, value, onChange, placeholder }: {
|
|
72
|
+
label?: string;
|
|
73
|
+
value: string;
|
|
74
|
+
onChange: (value: string) => void;
|
|
75
|
+
placeholder?: string;
|
|
76
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
77
|
+
export declare function BooleanInput({ label, value, onChange }: {
|
|
78
|
+
label?: string;
|
|
79
|
+
value: boolean;
|
|
80
|
+
onChange: (value: boolean) => void;
|
|
81
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
82
|
+
export declare function SelectInput({ label, value, onChange, options }: {
|
|
83
|
+
label?: string;
|
|
84
|
+
value: string;
|
|
85
|
+
onChange: (value: string) => void;
|
|
86
|
+
options: {
|
|
87
|
+
value: string;
|
|
88
|
+
label: string;
|
|
89
|
+
}[];
|
|
90
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
91
|
+
interface FieldRendererProps {
|
|
92
|
+
fields: FieldDefinition[];
|
|
93
|
+
values: Record<string, any>;
|
|
94
|
+
onChange: (values: Record<string, any>) => void;
|
|
95
|
+
}
|
|
96
|
+
export declare function FieldRenderer({ fields, values, onChange }: FieldRendererProps): import("react/jsx-runtime").JSX.Element;
|
|
97
|
+
export { styles };
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Shared Styles
|
|
5
|
+
// ============================================================================
|
|
3
6
|
// Shared styles
|
|
4
7
|
const styles = {
|
|
5
8
|
input: {
|
|
@@ -127,3 +130,65 @@ export function Vector3Input({ label, value, onChange, snap }) {
|
|
|
127
130
|
}
|
|
128
131
|
} })] }, key))) })] }));
|
|
129
132
|
}
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// Additional Input Components
|
|
135
|
+
// ============================================================================
|
|
136
|
+
export function ColorInput({ label, value, onChange }) {
|
|
137
|
+
return (_jsxs("div", { children: [label && _jsx(Label, { children: label }), _jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx("input", { type: "color", style: {
|
|
138
|
+
height: 20,
|
|
139
|
+
width: 20,
|
|
140
|
+
backgroundColor: 'transparent',
|
|
141
|
+
border: 'none',
|
|
142
|
+
cursor: 'pointer',
|
|
143
|
+
padding: 0,
|
|
144
|
+
}, value: value, onChange: e => onChange(e.target.value) }), _jsx("input", { type: "text", style: Object.assign(Object.assign({}, styles.input), { flex: 1 }), value: value, onChange: e => onChange(e.target.value) })] })] }));
|
|
145
|
+
}
|
|
146
|
+
export function StringInput({ label, value, onChange, placeholder }) {
|
|
147
|
+
return (_jsxs("div", { children: [label && _jsx(Label, { children: label }), _jsx("input", { type: "text", style: styles.input, value: value, onChange: e => onChange(e.target.value), placeholder: placeholder })] }));
|
|
148
|
+
}
|
|
149
|
+
export function BooleanInput({ label, value, onChange }) {
|
|
150
|
+
return (_jsxs("div", { children: [label && _jsx(Label, { children: label }), _jsx("input", { type: "checkbox", style: {
|
|
151
|
+
height: 16,
|
|
152
|
+
width: 16,
|
|
153
|
+
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
154
|
+
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
155
|
+
cursor: 'pointer',
|
|
156
|
+
}, checked: value, onChange: e => onChange(e.target.checked) })] }));
|
|
157
|
+
}
|
|
158
|
+
export function SelectInput({ label, value, onChange, options }) {
|
|
159
|
+
return (_jsxs("div", { children: [label && _jsx(Label, { children: label }), _jsx("select", { style: styles.input, value: value, onChange: e => onChange(e.target.value), children: options.map(opt => (_jsx("option", { value: opt.value, children: opt.label }, opt.value))) })] }));
|
|
160
|
+
}
|
|
161
|
+
export function FieldRenderer({ fields, values, onChange }) {
|
|
162
|
+
const updateField = (name, value) => {
|
|
163
|
+
onChange({ [name]: value });
|
|
164
|
+
};
|
|
165
|
+
return (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: fields.map(field => {
|
|
166
|
+
var _a, _b;
|
|
167
|
+
const value = values[field.name];
|
|
168
|
+
switch (field.type) {
|
|
169
|
+
case 'vector3':
|
|
170
|
+
return (_jsx(Vector3Input, { label: field.label, value: value !== null && value !== void 0 ? value : [0, 0, 0], onChange: v => updateField(field.name, v), snap: field.snap }, field.name));
|
|
171
|
+
case 'number':
|
|
172
|
+
return (_jsxs("div", { children: [_jsx(Label, { children: field.label }), _jsx(Input, { value: value !== null && value !== void 0 ? value : 0, onChange: v => updateField(field.name, v), min: field.min, max: field.max, step: field.step })] }, field.name));
|
|
173
|
+
case 'string':
|
|
174
|
+
return (_jsx(StringInput, { label: field.label, value: value !== null && value !== void 0 ? value : '', onChange: v => updateField(field.name, v), placeholder: field.placeholder }, field.name));
|
|
175
|
+
case 'color':
|
|
176
|
+
return (_jsx(ColorInput, { label: field.label, value: value !== null && value !== void 0 ? value : '#ffffff', onChange: v => updateField(field.name, v) }, field.name));
|
|
177
|
+
case 'boolean':
|
|
178
|
+
return (_jsx(BooleanInput, { label: field.label, value: value !== null && value !== void 0 ? value : false, onChange: v => updateField(field.name, v) }, field.name));
|
|
179
|
+
case 'select':
|
|
180
|
+
return (_jsx(SelectInput, { label: field.label, value: (_b = value !== null && value !== void 0 ? value : (_a = field.options[0]) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '', onChange: v => updateField(field.name, v), options: field.options }, field.name));
|
|
181
|
+
case 'custom':
|
|
182
|
+
return (_jsxs("div", { children: [field.label && _jsx(Label, { children: field.label }), field.render({
|
|
183
|
+
value,
|
|
184
|
+
onChange: v => updateField(field.name, v),
|
|
185
|
+
values,
|
|
186
|
+
onChangeMultiple: onChange,
|
|
187
|
+
})] }, field.name));
|
|
188
|
+
default:
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}) }));
|
|
192
|
+
}
|
|
193
|
+
// Export styles for use in custom field renderers
|
|
194
|
+
export { styles };
|