react-three-game 0.0.56 → 0.0.58
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 +16 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/shared/GameCanvas.js +1 -3
- package/dist/tools/assetviewer/page.js +35 -14
- package/dist/tools/prefabeditor/Dropdown.d.ts +15 -0
- package/dist/tools/prefabeditor/Dropdown.js +82 -0
- package/dist/tools/prefabeditor/EditorContext.d.ts +5 -0
- package/dist/tools/prefabeditor/EditorTree.js +149 -91
- package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +33 -0
- package/dist/tools/prefabeditor/EditorTreeMenus.js +136 -0
- package/dist/tools/prefabeditor/EditorUI.js +1 -1
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +1 -0
- package/dist/tools/prefabeditor/PrefabEditor.js +13 -2
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +1 -0
- package/dist/tools/prefabeditor/PrefabRoot.js +120 -34
- package/dist/tools/prefabeditor/components/AmbientLightComponent.js +3 -7
- package/dist/tools/prefabeditor/components/CameraComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/CameraComponent.js +45 -0
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +50 -24
- package/dist/tools/prefabeditor/components/EnvironmentComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/EnvironmentComponent.js +15 -0
- package/dist/tools/prefabeditor/components/GeometryComponent.js +46 -46
- package/dist/tools/prefabeditor/components/Input.d.ts +51 -1
- package/dist/tools/prefabeditor/components/Input.js +73 -21
- package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +16 -2
- package/dist/tools/prefabeditor/components/MaterialComponent.js +129 -15
- package/dist/tools/prefabeditor/components/ModelComponent.js +44 -3
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +16 -81
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +36 -23
- package/dist/tools/prefabeditor/components/TextComponent.js +7 -53
- package/dist/tools/prefabeditor/components/TransformComponent.js +18 -8
- package/dist/tools/prefabeditor/components/index.js +5 -1
- package/dist/tools/prefabeditor/styles.d.ts +5 -2
- package/dist/tools/prefabeditor/styles.js +7 -3
- package/dist/tools/prefabeditor/utils.d.ts +4 -3
- package/dist/tools/prefabeditor/utils.js +53 -5
- package/package.json +1 -1
- package/react-three-game-skill/react-three-game/SKILL.md +4 -1
- package/src/index.ts +7 -0
- package/src/shared/GameCanvas.tsx +0 -3
- package/src/tools/assetviewer/page.tsx +77 -45
- package/src/tools/prefabeditor/Dropdown.tsx +112 -0
- package/src/tools/prefabeditor/EditorContext.tsx +5 -0
- package/src/tools/prefabeditor/EditorTree.tsx +242 -178
- package/src/tools/prefabeditor/EditorTreeMenus.tsx +307 -0
- package/src/tools/prefabeditor/EditorUI.tsx +1 -1
- package/src/tools/prefabeditor/PrefabEditor.tsx +17 -4
- package/src/tools/prefabeditor/PrefabRoot.tsx +208 -58
- package/src/tools/prefabeditor/components/AmbientLightComponent.tsx +5 -11
- package/src/tools/prefabeditor/components/CameraComponent.tsx +117 -0
- package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +61 -30
- package/src/tools/prefabeditor/components/EnvironmentComponent.tsx +47 -0
- package/src/tools/prefabeditor/components/GeometryComponent.tsx +69 -63
- package/src/tools/prefabeditor/components/Input.tsx +220 -27
- package/src/tools/prefabeditor/components/MaterialComponent.tsx +189 -18
- package/src/tools/prefabeditor/components/ModelComponent.tsx +51 -4
- package/src/tools/prefabeditor/components/PhysicsComponent.tsx +44 -85
- package/src/tools/prefabeditor/components/SpotLightComponent.tsx +52 -27
- package/src/tools/prefabeditor/components/TextComponent.tsx +58 -57
- package/src/tools/prefabeditor/components/TransformComponent.tsx +61 -9
- package/src/tools/prefabeditor/components/index.ts +5 -1
- package/src/tools/prefabeditor/styles.ts +7 -3
- package/src/tools/prefabeditor/utils.ts +55 -4
|
@@ -58,11 +58,12 @@ export declare function Input({ value, onChange, step, min, max, style, label }:
|
|
|
58
58
|
export declare function Label({ children }: {
|
|
59
59
|
children: React.ReactNode;
|
|
60
60
|
}): import("react/jsx-runtime").JSX.Element;
|
|
61
|
-
export declare function Vector3Input({ label, value, onChange, snap }: {
|
|
61
|
+
export declare function Vector3Input({ label, value, onChange, snap, labelExtra }: {
|
|
62
62
|
label: string;
|
|
63
63
|
value: [number, number, number];
|
|
64
64
|
onChange: (v: [number, number, number]) => void;
|
|
65
65
|
snap?: number;
|
|
66
|
+
labelExtra?: React.ReactNode;
|
|
66
67
|
}): import("react/jsx-runtime").JSX.Element;
|
|
67
68
|
export declare function ColorInput({ label, value, onChange }: {
|
|
68
69
|
label?: string;
|
|
@@ -89,6 +90,55 @@ export declare function SelectInput({ label, value, onChange, options }: {
|
|
|
89
90
|
label: string;
|
|
90
91
|
}[];
|
|
91
92
|
}): import("react/jsx-runtime").JSX.Element;
|
|
93
|
+
interface BoundFieldProps {
|
|
94
|
+
name: string;
|
|
95
|
+
values: Record<string, any>;
|
|
96
|
+
onChange: (values: Record<string, any>) => void;
|
|
97
|
+
}
|
|
98
|
+
interface BoundNumberFieldProps extends BoundFieldProps {
|
|
99
|
+
label: string;
|
|
100
|
+
fallback?: number;
|
|
101
|
+
step?: string | number;
|
|
102
|
+
min?: number;
|
|
103
|
+
max?: number;
|
|
104
|
+
style?: React.CSSProperties;
|
|
105
|
+
}
|
|
106
|
+
interface BoundStringFieldProps extends BoundFieldProps {
|
|
107
|
+
label: string;
|
|
108
|
+
fallback?: string;
|
|
109
|
+
placeholder?: string;
|
|
110
|
+
}
|
|
111
|
+
interface BoundColorFieldProps extends BoundFieldProps {
|
|
112
|
+
label: string;
|
|
113
|
+
fallback?: string;
|
|
114
|
+
}
|
|
115
|
+
interface BoundBooleanFieldProps extends BoundFieldProps {
|
|
116
|
+
label: string;
|
|
117
|
+
fallback?: boolean;
|
|
118
|
+
}
|
|
119
|
+
interface BoundSelectFieldProps extends BoundFieldProps {
|
|
120
|
+
label: string;
|
|
121
|
+
fallback?: string;
|
|
122
|
+
options: {
|
|
123
|
+
value: string;
|
|
124
|
+
label: string;
|
|
125
|
+
}[];
|
|
126
|
+
}
|
|
127
|
+
interface BoundVector3FieldProps extends BoundFieldProps {
|
|
128
|
+
label: string;
|
|
129
|
+
fallback?: [number, number, number];
|
|
130
|
+
snap?: number;
|
|
131
|
+
labelExtra?: React.ReactNode;
|
|
132
|
+
}
|
|
133
|
+
export declare function FieldGroup({ children }: {
|
|
134
|
+
children: React.ReactNode;
|
|
135
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
136
|
+
export declare function NumberField({ name, label, values, onChange, fallback, step, min, max, style, }: BoundNumberFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
137
|
+
export declare function StringField({ name, label, values, onChange, fallback, placeholder, }: BoundStringFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
138
|
+
export declare function ColorField({ name, label, values, onChange, fallback, }: BoundColorFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
139
|
+
export declare function BooleanField({ name, label, values, onChange, fallback, }: BoundBooleanFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
140
|
+
export declare function SelectField({ name, label, values, onChange, fallback, options, }: BoundSelectFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
141
|
+
export declare function Vector3Field({ name, label, values, onChange, fallback, snap, labelExtra, }: BoundVector3FieldProps): import("react/jsx-runtime").JSX.Element;
|
|
92
142
|
interface FieldRendererProps {
|
|
93
143
|
fields: FieldDefinition[];
|
|
94
144
|
values: Record<string, any>;
|
|
@@ -27,6 +27,28 @@ const styles = {
|
|
|
27
27
|
fontWeight: 500,
|
|
28
28
|
},
|
|
29
29
|
};
|
|
30
|
+
function getNumericStep(step, fallback) {
|
|
31
|
+
if (typeof step === 'number' && Number.isFinite(step) && step > 0)
|
|
32
|
+
return step;
|
|
33
|
+
if (typeof step === 'string') {
|
|
34
|
+
const parsed = parseFloat(step);
|
|
35
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
return fallback;
|
|
39
|
+
}
|
|
40
|
+
function getStepPrecision(step) {
|
|
41
|
+
var _a;
|
|
42
|
+
if (!Number.isFinite(step) || step <= 0)
|
|
43
|
+
return 3;
|
|
44
|
+
const stepString = step.toString();
|
|
45
|
+
if (stepString.includes('e-')) {
|
|
46
|
+
const exponent = stepString.split('e-')[1];
|
|
47
|
+
return exponent ? parseInt(exponent, 10) : 3;
|
|
48
|
+
}
|
|
49
|
+
const decimal = stepString.split('.')[1];
|
|
50
|
+
return (_a = decimal === null || decimal === void 0 ? void 0 : decimal.length) !== null && _a !== void 0 ? _a : 0;
|
|
51
|
+
}
|
|
30
52
|
export function Input({ value, onChange, step, min, max, style, label }) {
|
|
31
53
|
const [draft, setDraft] = useState(() => value.toString());
|
|
32
54
|
useEffect(() => {
|
|
@@ -48,14 +70,11 @@ export function Input({ value, onChange, step, min, max, style, label }) {
|
|
|
48
70
|
};
|
|
49
71
|
const dragState = useRef(null);
|
|
50
72
|
const startScrub = (e) => {
|
|
51
|
-
if (!label)
|
|
52
|
-
return;
|
|
53
|
-
e.preventDefault();
|
|
54
73
|
dragState.current = {
|
|
55
74
|
startX: e.clientX,
|
|
56
75
|
startValue: value
|
|
57
76
|
};
|
|
58
|
-
e.
|
|
77
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
59
78
|
document.body.style.cursor = "ew-resize";
|
|
60
79
|
};
|
|
61
80
|
const onScrubMove = (e) => {
|
|
@@ -63,18 +82,21 @@ export function Input({ value, onChange, step, min, max, style, label }) {
|
|
|
63
82
|
return;
|
|
64
83
|
const { startX, startValue } = dragState.current;
|
|
65
84
|
const dx = e.clientX - startX;
|
|
66
|
-
|
|
85
|
+
const baseStep = getNumericStep(step, 0.1);
|
|
86
|
+
let scrubStep = baseStep;
|
|
67
87
|
if (e.shiftKey)
|
|
68
|
-
|
|
88
|
+
scrubStep /= 10;
|
|
69
89
|
if (e.altKey)
|
|
70
|
-
|
|
71
|
-
|
|
90
|
+
scrubStep *= 10;
|
|
91
|
+
const precision = getStepPrecision(scrubStep);
|
|
92
|
+
const deltaSteps = Math.round(dx / 8);
|
|
93
|
+
let nextValue = startValue + deltaSteps * scrubStep;
|
|
72
94
|
// Apply min/max constraints
|
|
73
95
|
if (min !== undefined && nextValue < min)
|
|
74
96
|
nextValue = min;
|
|
75
97
|
if (max !== undefined && nextValue > max)
|
|
76
98
|
nextValue = max;
|
|
77
|
-
setDraft(nextValue.toFixed(
|
|
99
|
+
setDraft(nextValue.toFixed(precision));
|
|
78
100
|
onChange(nextValue);
|
|
79
101
|
};
|
|
80
102
|
const endScrub = (e) => {
|
|
@@ -82,29 +104,29 @@ export function Input({ value, onChange, step, min, max, style, label }) {
|
|
|
82
104
|
return;
|
|
83
105
|
dragState.current = null;
|
|
84
106
|
document.body.style.cursor = "";
|
|
85
|
-
e.
|
|
107
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
86
108
|
};
|
|
87
109
|
if (label) {
|
|
88
110
|
return (_jsxs("div", { style: {
|
|
89
111
|
display: 'flex',
|
|
90
112
|
alignItems: 'center',
|
|
91
113
|
justifyContent: 'space-between',
|
|
92
|
-
}, children: [_jsx("span", { style: Object.assign(Object.assign({}, styles.label), { marginBottom: 0,
|
|
114
|
+
}, children: [_jsx("span", { style: Object.assign(Object.assign({}, styles.label), { marginBottom: 0, userSelect: 'none', flex: '0 0 auto', minWidth: 20 }), children: label }), _jsx("input", { type: "text", value: draft, onChange: handleChange, onBlur: handleBlur, onKeyDown: e => {
|
|
93
115
|
if (e.key === 'Enter') {
|
|
94
116
|
e.target.blur();
|
|
95
117
|
}
|
|
96
|
-
}, step: step, min: min, max: max, style: Object.assign(Object.assign({}, styles.input), style) })] }));
|
|
118
|
+
}, step: step, min: min, max: max, style: Object.assign(Object.assign(Object.assign({}, styles.input), { cursor: 'ew-resize' }), style), onPointerDown: startScrub, onPointerMove: onScrubMove, onPointerUp: endScrub })] }));
|
|
97
119
|
}
|
|
98
120
|
return (_jsx("input", { type: "text", value: draft, onChange: handleChange, onBlur: handleBlur, onKeyDown: e => {
|
|
99
121
|
if (e.key === 'Enter') {
|
|
100
122
|
e.target.blur();
|
|
101
123
|
}
|
|
102
|
-
}, step: step, min: min, max: max, style: Object.assign(Object.assign({}, styles.input), style) }));
|
|
124
|
+
}, step: step, min: min, max: max, style: Object.assign(Object.assign(Object.assign({}, styles.input), { cursor: 'ew-resize' }), style), onPointerDown: startScrub, onPointerMove: onScrubMove, onPointerUp: endScrub }));
|
|
103
125
|
}
|
|
104
126
|
export function Label({ children }) {
|
|
105
127
|
return _jsx("label", { style: styles.label, children: children });
|
|
106
128
|
}
|
|
107
|
-
export function Vector3Input({ label, value, onChange, snap }) {
|
|
129
|
+
export function Vector3Input({ label, value, onChange, snap, labelExtra }) {
|
|
108
130
|
const snapValue = (num) => {
|
|
109
131
|
if (!snap)
|
|
110
132
|
return num;
|
|
@@ -125,13 +147,12 @@ export function Vector3Input({ label, value, onChange, snap }) {
|
|
|
125
147
|
}
|
|
126
148
|
};
|
|
127
149
|
const startScrub = (e, index) => {
|
|
128
|
-
e.preventDefault();
|
|
129
150
|
dragState.current = {
|
|
130
151
|
index,
|
|
131
152
|
startX: e.clientX,
|
|
132
153
|
startValue: value[index]
|
|
133
154
|
};
|
|
134
|
-
e.
|
|
155
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
135
156
|
document.body.style.cursor = "ew-resize";
|
|
136
157
|
};
|
|
137
158
|
const onScrubMove = (e) => {
|
|
@@ -160,14 +181,14 @@ export function Vector3Input({ label, value, onChange, snap }) {
|
|
|
160
181
|
return;
|
|
161
182
|
dragState.current = null;
|
|
162
183
|
document.body.style.cursor = "";
|
|
163
|
-
e.
|
|
184
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
164
185
|
};
|
|
165
186
|
const axes = [
|
|
166
187
|
{ key: "x", color: '#e06c75', index: 0 },
|
|
167
188
|
{ key: "y", color: '#98c379', index: 1 },
|
|
168
189
|
{ key: "z", color: '#61afef', index: 2 }
|
|
169
190
|
];
|
|
170
|
-
return (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsx("label", { style: Object.assign(Object.assign({}, styles.label), { marginBottom:
|
|
191
|
+
return (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4 }, children: [_jsx("label", { style: Object.assign(Object.assign({}, styles.label), { marginBottom: 0 }), children: label }), labelExtra] }), _jsx("div", { style: { display: 'flex', gap: 4 }, children: axes.map(({ key, color, index }) => (_jsxs("div", { style: {
|
|
171
192
|
flex: 1,
|
|
172
193
|
display: 'flex',
|
|
173
194
|
alignItems: 'center',
|
|
@@ -177,14 +198,14 @@ export function Vector3Input({ label, value, onChange, snap }) {
|
|
|
177
198
|
borderRadius: 3,
|
|
178
199
|
padding: '4px 6px',
|
|
179
200
|
minHeight: 28,
|
|
180
|
-
|
|
201
|
+
cursor: 'ew-resize',
|
|
202
|
+
}, onPointerDown: e => startScrub(e, index), onPointerMove: onScrubMove, onPointerUp: endScrub, children: [_jsx("span", { style: {
|
|
181
203
|
fontSize: 11,
|
|
182
204
|
fontWeight: 600,
|
|
183
205
|
color,
|
|
184
206
|
width: 12,
|
|
185
|
-
cursor: 'ew-resize',
|
|
186
207
|
userSelect: 'none',
|
|
187
|
-
},
|
|
208
|
+
}, children: key.toUpperCase() }), _jsx("input", { style: {
|
|
188
209
|
flex: 1,
|
|
189
210
|
backgroundColor: 'transparent',
|
|
190
211
|
border: 'none',
|
|
@@ -194,6 +215,7 @@ export function Vector3Input({ label, value, onChange, snap }) {
|
|
|
194
215
|
outline: 'none',
|
|
195
216
|
width: '100%',
|
|
196
217
|
minWidth: 0,
|
|
218
|
+
cursor: 'inherit',
|
|
197
219
|
}, type: "text", value: draft[index], onChange: e => {
|
|
198
220
|
const next = [...draft];
|
|
199
221
|
next[index] = e.target.value;
|
|
@@ -233,6 +255,36 @@ export function BooleanInput({ label, value, onChange }) {
|
|
|
233
255
|
export function SelectInput({ label, value, onChange, options }) {
|
|
234
256
|
return (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, 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))) })] }));
|
|
235
257
|
}
|
|
258
|
+
function bindFieldChange(name, onChange) {
|
|
259
|
+
return (value) => onChange({ [name]: value });
|
|
260
|
+
}
|
|
261
|
+
export function FieldGroup({ children }) {
|
|
262
|
+
return _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: children });
|
|
263
|
+
}
|
|
264
|
+
export function NumberField({ name, label, values, onChange, fallback = 0, step, min, max, style, }) {
|
|
265
|
+
var _a;
|
|
266
|
+
return (_jsx(Input, { label: label, value: (_a = values[name]) !== null && _a !== void 0 ? _a : fallback, onChange: bindFieldChange(name, onChange), step: step, min: min, max: max, style: style }));
|
|
267
|
+
}
|
|
268
|
+
export function StringField({ name, label, values, onChange, fallback = '', placeholder, }) {
|
|
269
|
+
var _a;
|
|
270
|
+
return (_jsx(StringInput, { label: label, value: (_a = values[name]) !== null && _a !== void 0 ? _a : fallback, onChange: bindFieldChange(name, onChange), placeholder: placeholder }));
|
|
271
|
+
}
|
|
272
|
+
export function ColorField({ name, label, values, onChange, fallback = '#ffffff', }) {
|
|
273
|
+
var _a;
|
|
274
|
+
return (_jsx(ColorInput, { label: label, value: (_a = values[name]) !== null && _a !== void 0 ? _a : fallback, onChange: bindFieldChange(name, onChange) }));
|
|
275
|
+
}
|
|
276
|
+
export function BooleanField({ name, label, values, onChange, fallback = false, }) {
|
|
277
|
+
var _a;
|
|
278
|
+
return (_jsx(BooleanInput, { label: label, value: (_a = values[name]) !== null && _a !== void 0 ? _a : fallback, onChange: bindFieldChange(name, onChange) }));
|
|
279
|
+
}
|
|
280
|
+
export function SelectField({ name, label, values, onChange, fallback, options, }) {
|
|
281
|
+
var _a, _b, _c, _d;
|
|
282
|
+
return (_jsx(SelectInput, { label: label, value: (_d = (_b = (_a = values[name]) !== null && _a !== void 0 ? _a : fallback) !== null && _b !== void 0 ? _b : (_c = options[0]) === null || _c === void 0 ? void 0 : _c.value) !== null && _d !== void 0 ? _d : '', onChange: bindFieldChange(name, onChange), options: options }));
|
|
283
|
+
}
|
|
284
|
+
export function Vector3Field({ name, label, values, onChange, fallback = [0, 0, 0], snap, labelExtra, }) {
|
|
285
|
+
var _a;
|
|
286
|
+
return (_jsx(Vector3Input, { label: label, value: (_a = values[name]) !== null && _a !== void 0 ? _a : fallback, onChange: bindFieldChange(name, onChange), snap: snap, labelExtra: labelExtra }));
|
|
287
|
+
}
|
|
236
288
|
export function FieldRenderer({ fields, values, onChange }) {
|
|
237
289
|
const updateField = (name, value) => {
|
|
238
290
|
onChange({ [name]: value });
|
|
@@ -1,12 +1,26 @@
|
|
|
1
|
+
import type { ThreeElement } from '@react-three/fiber';
|
|
1
2
|
import { Component } from './ComponentRegistry';
|
|
2
|
-
import {
|
|
3
|
-
|
|
3
|
+
import { MeshBasicNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
4
|
+
import { MeshBasicMaterialProperties, MeshStandardMaterialProperties } from 'three';
|
|
5
|
+
declare module '@react-three/fiber' {
|
|
6
|
+
interface ThreeElements {
|
|
7
|
+
meshBasicNodeMaterial: ThreeElement<typeof MeshBasicNodeMaterial>;
|
|
8
|
+
meshStandardNodeMaterial: ThreeElement<typeof MeshStandardNodeMaterial>;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export interface MaterialProps extends Omit<MeshStandardMaterialProperties & MeshBasicMaterialProperties, 'args' | 'normalScale'> {
|
|
12
|
+
materialType?: 'standard' | 'basic';
|
|
13
|
+
transmission?: number;
|
|
14
|
+
thickness?: number;
|
|
15
|
+
ior?: number;
|
|
4
16
|
texture?: string;
|
|
5
17
|
repeat?: boolean;
|
|
6
18
|
repeatCount?: [number, number];
|
|
7
19
|
generateMipmaps?: boolean;
|
|
8
20
|
minFilter?: string;
|
|
9
21
|
magFilter?: string;
|
|
22
|
+
normalMapTexture?: string;
|
|
23
|
+
normalScale?: [number, number];
|
|
10
24
|
}
|
|
11
25
|
declare const MaterialComponent: Component;
|
|
12
26
|
export default MaterialComponent;
|
|
@@ -11,46 +11,118 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
13
|
import { SingleTextureViewer, TextureListViewer } from '../../assetviewer/page';
|
|
14
|
-
import {
|
|
14
|
+
import { extend } from '@react-three/fiber';
|
|
15
|
+
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
16
|
+
import { createPortal } from 'react-dom';
|
|
15
17
|
import { FieldRenderer, Input } from './Input';
|
|
16
18
|
import { colors } from '../styles';
|
|
17
19
|
import { useMemo } from 'react';
|
|
18
|
-
import {
|
|
20
|
+
import { MeshBasicNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
21
|
+
import { RepeatWrapping, ClampToEdgeWrapping, SRGBColorSpace, LinearSRGBColorSpace, Vector2, NearestFilter, LinearFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter, FrontSide, BackSide, DoubleSide, } from 'three';
|
|
22
|
+
const PICKER_POPUP_WIDTH = 260;
|
|
23
|
+
const PICKER_POPUP_HEIGHT = 360;
|
|
24
|
+
extend({
|
|
25
|
+
MeshBasicNodeMaterial,
|
|
26
|
+
MeshStandardNodeMaterial,
|
|
27
|
+
});
|
|
19
28
|
function TexturePicker({ value, onChange, basePath }) {
|
|
20
29
|
const [textureFiles, setTextureFiles] = useState([]);
|
|
21
30
|
const [showPicker, setShowPicker] = useState(false);
|
|
31
|
+
const [popupStyle, setPopupStyle] = useState(null);
|
|
32
|
+
const triggerRef = useRef(null);
|
|
22
33
|
useEffect(() => {
|
|
23
34
|
fetch(`${basePath}/textures/manifest.json`)
|
|
24
35
|
.then(r => r.json())
|
|
25
36
|
.then(data => setTextureFiles(Array.isArray(data) ? data : data.files || []))
|
|
26
37
|
.catch(console.error);
|
|
27
38
|
}, [basePath]);
|
|
39
|
+
useLayoutEffect(() => {
|
|
40
|
+
if (!showPicker || !triggerRef.current || typeof window === 'undefined')
|
|
41
|
+
return;
|
|
42
|
+
const updatePosition = () => {
|
|
43
|
+
var _a;
|
|
44
|
+
const rect = (_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
|
|
45
|
+
if (!rect)
|
|
46
|
+
return;
|
|
47
|
+
const preferredLeft = rect.left - PICKER_POPUP_WIDTH - 8;
|
|
48
|
+
const fallbackLeft = rect.right + 8;
|
|
49
|
+
const fitsLeft = preferredLeft >= 8;
|
|
50
|
+
const left = fitsLeft ? preferredLeft : Math.min(fallbackLeft, window.innerWidth - PICKER_POPUP_WIDTH - 8);
|
|
51
|
+
const top = Math.min(Math.max(8, rect.top), window.innerHeight - PICKER_POPUP_HEIGHT - 8);
|
|
52
|
+
setPopupStyle({
|
|
53
|
+
position: 'fixed',
|
|
54
|
+
left,
|
|
55
|
+
top,
|
|
56
|
+
background: colors.bg,
|
|
57
|
+
padding: 12,
|
|
58
|
+
border: `1px solid ${colors.border}`,
|
|
59
|
+
borderRadius: 6,
|
|
60
|
+
width: PICKER_POPUP_WIDTH,
|
|
61
|
+
height: PICKER_POPUP_HEIGHT,
|
|
62
|
+
overflow: 'hidden',
|
|
63
|
+
zIndex: 1000,
|
|
64
|
+
boxShadow: '0 4px 16px rgba(0,0,0,0.6)',
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
updatePosition();
|
|
68
|
+
window.addEventListener('resize', updatePosition);
|
|
69
|
+
window.addEventListener('scroll', updatePosition, true);
|
|
70
|
+
return () => {
|
|
71
|
+
window.removeEventListener('resize', updatePosition);
|
|
72
|
+
window.removeEventListener('scroll', updatePosition, true);
|
|
73
|
+
};
|
|
74
|
+
}, [showPicker]);
|
|
28
75
|
// Only show 3D preview for server-hosted textures (starting with / or http)
|
|
29
76
|
const canPreview = value && (value.startsWith('/') || value.startsWith('http'));
|
|
30
77
|
return (_jsxs("div", { style: { maxHeight: 128, overflow: 'visible', position: 'relative', display: 'flex', alignItems: 'center' }, children: [canPreview
|
|
31
78
|
? _jsx(SingleTextureViewer, { file: value, basePath: basePath })
|
|
32
79
|
: value
|
|
33
80
|
? _jsx("span", { style: { fontSize: 10, opacity: 0.6, maxWidth: 100, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: value })
|
|
34
|
-
: null, _jsx("button", { onClick: () => setShowPicker(!showPicker), style: { padding: '4px 8px', backgroundColor: colors.bgLight, color: 'inherit', fontSize: 10, cursor: 'pointer', border: `1px solid ${colors.border}`, borderRadius: 3, marginTop: 4 }, children: showPicker ? 'Cancel' : 'Change' }), _jsx("button", { onClick: () => {
|
|
81
|
+
: null, _jsx("button", { ref: triggerRef, onClick: () => setShowPicker(!showPicker), style: { padding: '4px 8px', backgroundColor: colors.bgLight, color: 'inherit', fontSize: 10, cursor: 'pointer', border: `1px solid ${colors.border}`, borderRadius: 3, marginTop: 4 }, children: showPicker ? 'Cancel' : 'Change' }), _jsx("button", { onClick: () => {
|
|
35
82
|
onChange(undefined);
|
|
36
|
-
}, style: { padding: '4px 8px', backgroundColor: colors.bgLight, color: 'inherit', fontSize: 10, cursor: 'pointer', border: `1px solid ${colors.border}`, borderRadius: 3, marginTop: 4, marginLeft: 4 }, children: "Clear" }), showPicker &&
|
|
83
|
+
}, style: { padding: '4px 8px', backgroundColor: colors.bgLight, color: 'inherit', fontSize: 10, cursor: 'pointer', border: `1px solid ${colors.border}`, borderRadius: 3, marginTop: 4, marginLeft: 4 }, children: "Clear" }), showPicker && popupStyle && typeof document !== 'undefined' && createPortal(_jsx("div", { style: popupStyle, onMouseLeave: () => setShowPicker(false), children: _jsx(TextureListViewer, { files: textureFiles, selected: value || undefined, onSelect: (file) => {
|
|
37
84
|
onChange(file);
|
|
38
85
|
setShowPicker(false);
|
|
39
|
-
}, basePath: basePath }) }))] }));
|
|
86
|
+
}, basePath: basePath }) }), document.body)] }));
|
|
40
87
|
}
|
|
41
88
|
function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
89
|
+
var _a;
|
|
90
|
+
const materialType = (_a = component.properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
|
|
42
91
|
const hasTexture = !!component.properties.texture;
|
|
43
92
|
const hasRepeat = component.properties.repeat;
|
|
93
|
+
const isStandardMaterial = materialType === 'standard';
|
|
44
94
|
const fields = [
|
|
95
|
+
{
|
|
96
|
+
name: 'materialType',
|
|
97
|
+
type: 'select',
|
|
98
|
+
label: 'Material Type',
|
|
99
|
+
options: [
|
|
100
|
+
{ value: 'standard', label: 'Standard' },
|
|
101
|
+
{ value: 'basic', label: 'Basic' },
|
|
102
|
+
],
|
|
103
|
+
},
|
|
45
104
|
{ name: 'color', type: 'color', label: 'Color' },
|
|
105
|
+
{ name: 'toneMapped', type: 'boolean', label: 'Tone Mapped' },
|
|
46
106
|
{ name: 'wireframe', type: 'boolean', label: 'Wireframe' },
|
|
47
107
|
{ name: 'transparent', type: 'boolean', label: 'Transparent' },
|
|
48
108
|
{ name: 'opacity', type: 'number', label: 'Opacity', min: 0, max: 1, step: 0.01 },
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
109
|
+
...(isStandardMaterial ? [
|
|
110
|
+
{ name: 'metalness', type: 'number', label: 'Metalness', min: 0, max: 1, step: 0.01 },
|
|
111
|
+
{ name: 'roughness', type: 'number', label: 'Roughness', min: 0, max: 1, step: 0.01 },
|
|
112
|
+
{ name: 'transmission', type: 'number', label: 'Transmission', min: 0, max: 1, step: 0.01 },
|
|
113
|
+
{ name: 'thickness', type: 'number', label: 'Thickness', min: 0, step: 0.1 },
|
|
114
|
+
{ name: 'ior', type: 'number', label: 'IOR (Index of Refraction)', min: 1, max: 2.333, step: 0.01 },
|
|
115
|
+
] : []),
|
|
116
|
+
{
|
|
117
|
+
name: 'side',
|
|
118
|
+
type: 'select',
|
|
119
|
+
label: 'Side',
|
|
120
|
+
options: [
|
|
121
|
+
{ value: 'FrontSide', label: 'Front' },
|
|
122
|
+
{ value: 'BackSide', label: 'Back' },
|
|
123
|
+
{ value: 'DoubleSide', label: 'Double' },
|
|
124
|
+
],
|
|
125
|
+
},
|
|
54
126
|
{
|
|
55
127
|
name: 'texture',
|
|
56
128
|
type: 'custom',
|
|
@@ -69,6 +141,21 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
69
141
|
return (_jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx(Input, { label: "X", value: (_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 1, onChange: v => { var _a; return onChange([v, (_a = value === null || value === void 0 ? void 0 : value[1]) !== null && _a !== void 0 ? _a : 1]); }, min: 0.01, max: 100, step: 0.1 }), _jsx(Input, { label: "Y", value: (_b = value === null || value === void 0 ? void 0 : value[1]) !== null && _b !== void 0 ? _b : 1, onChange: v => { var _a; return onChange([(_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 1, v]); }, min: 0.01, max: 100, step: 0.1 })] }));
|
|
70
142
|
},
|
|
71
143
|
}] : []),
|
|
144
|
+
{
|
|
145
|
+
name: 'normalMapTexture',
|
|
146
|
+
type: 'custom',
|
|
147
|
+
label: 'Normal Map',
|
|
148
|
+
render: ({ value, onChange }) => (_jsx(TexturePicker, { value: value, onChange: onChange, basePath: basePath })),
|
|
149
|
+
},
|
|
150
|
+
...(component.properties.normalMapTexture ? [{
|
|
151
|
+
name: 'normalScale',
|
|
152
|
+
type: 'custom',
|
|
153
|
+
label: 'Normal Scale (X, Y)',
|
|
154
|
+
render: ({ value, onChange }) => {
|
|
155
|
+
var _a, _b;
|
|
156
|
+
return (_jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx(Input, { label: "X", value: (_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 1, onChange: v => { var _a; return onChange([v, (_a = value === null || value === void 0 ? void 0 : value[1]) !== null && _a !== void 0 ? _a : 1]); }, min: 0, max: 5, step: 0.01 }), _jsx(Input, { label: "Y", value: (_b = value === null || value === void 0 ? void 0 : value[1]) !== null && _b !== void 0 ? _b : 1, onChange: v => { var _a; return onChange([(_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 1, v]); }, min: 0, max: 5, step: 0.01 })] }));
|
|
157
|
+
},
|
|
158
|
+
}] : []),
|
|
72
159
|
{ name: 'generateMipmaps', type: 'boolean', label: 'Generate Mipmaps' },
|
|
73
160
|
{
|
|
74
161
|
name: 'minFilter',
|
|
@@ -98,7 +185,8 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
98
185
|
}
|
|
99
186
|
// View for Material component
|
|
100
187
|
function MaterialComponentView({ properties, loadedTextures }) {
|
|
101
|
-
var _a;
|
|
188
|
+
var _a, _b, _c;
|
|
189
|
+
const materialType = (_a = properties === null || properties === void 0 ? void 0 : properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
|
|
102
190
|
const textureName = properties === null || properties === void 0 ? void 0 : properties.texture;
|
|
103
191
|
const repeat = properties === null || properties === void 0 ? void 0 : properties.repeat;
|
|
104
192
|
const repeatCount = properties === null || properties === void 0 ? void 0 : properties.repeatCount;
|
|
@@ -106,9 +194,14 @@ function MaterialComponentView({ properties, loadedTextures }) {
|
|
|
106
194
|
const minFilter = (properties === null || properties === void 0 ? void 0 : properties.minFilter) || 'LinearMipmapLinearFilter';
|
|
107
195
|
const magFilter = (properties === null || properties === void 0 ? void 0 : properties.magFilter) || 'LinearFilter';
|
|
108
196
|
const texture = textureName && loadedTextures ? loadedTextures[textureName] : undefined;
|
|
197
|
+
const normalMapTextureName = properties === null || properties === void 0 ? void 0 : properties.normalMapTexture;
|
|
198
|
+
const normalScaleProp = properties === null || properties === void 0 ? void 0 : properties.normalScale;
|
|
199
|
+
const normalMapTexture = normalMapTextureName && loadedTextures ? loadedTextures[normalMapTextureName] : undefined;
|
|
200
|
+
const materialSource = properties !== null && properties !== void 0 ? properties : {};
|
|
109
201
|
// Destructure all material props and separate custom texture handling props
|
|
110
|
-
const
|
|
111
|
-
|
|
202
|
+
const { texture: _texture, repeat: _repeat, repeatCount: _repeatCount, generateMipmaps: _generateMipmaps, minFilter: _minFilter, magFilter: _magFilter, map: _map, materialType: _materialType, normalMapTexture: _normalMapTexture, normalScale: _normalScale, normalMap: _normalMap, side: sideProp } = materialSource, materialProps = __rest(materialSource, ["texture", "repeat", "repeatCount", "generateMipmaps", "minFilter", "magFilter", "map", "materialType", "normalMapTexture", "normalScale", "normalMap", "side"]);
|
|
203
|
+
const sideMap = { FrontSide, BackSide, DoubleSide };
|
|
204
|
+
const resolvedSide = sideProp ? ((_b = sideMap[sideProp]) !== null && _b !== void 0 ? _b : FrontSide) : FrontSide;
|
|
112
205
|
const minFilterMap = {
|
|
113
206
|
NearestFilter,
|
|
114
207
|
LinearFilter,
|
|
@@ -142,10 +235,29 @@ function MaterialComponentView({ properties, loadedTextures }) {
|
|
|
142
235
|
t.needsUpdate = true;
|
|
143
236
|
return t;
|
|
144
237
|
}, [texture, repeat, repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0], repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1], generateMipmaps, minFilter, magFilter]);
|
|
238
|
+
const finalNormalMap = useMemo(() => {
|
|
239
|
+
if (!normalMapTexture)
|
|
240
|
+
return undefined;
|
|
241
|
+
const t = normalMapTexture.clone();
|
|
242
|
+
t.colorSpace = LinearSRGBColorSpace;
|
|
243
|
+
t.needsUpdate = true;
|
|
244
|
+
return t;
|
|
245
|
+
}, [normalMapTexture]);
|
|
246
|
+
const normalScaleVec = useMemo(() => {
|
|
247
|
+
var _a, _b;
|
|
248
|
+
if (!finalNormalMap)
|
|
249
|
+
return undefined;
|
|
250
|
+
return new Vector2((_a = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[0]) !== null && _a !== void 0 ? _a : 1, (_b = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[1]) !== null && _b !== void 0 ? _b : 1);
|
|
251
|
+
}, [finalNormalMap, normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[0], normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[1]]);
|
|
145
252
|
if (!properties) {
|
|
146
|
-
return _jsx("
|
|
253
|
+
return _jsx("meshStandardNodeMaterial", { color: "red", wireframe: true });
|
|
254
|
+
}
|
|
255
|
+
const materialKey = `${(_c = finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.uuid) !== null && _c !== void 0 ? _c : 'no-texture'}:${materialProps.transparent ? 'transparent' : 'opaque'}`;
|
|
256
|
+
const sharedProps = Object.assign({ map: finalTexture, side: resolvedSide }, materialProps);
|
|
257
|
+
if (materialType === 'basic') {
|
|
258
|
+
return _jsx("meshBasicNodeMaterial", Object.assign({}, sharedProps), materialKey);
|
|
147
259
|
}
|
|
148
|
-
return (_jsx("
|
|
260
|
+
return (_jsx("meshStandardNodeMaterial", Object.assign({}, sharedProps, { normalMap: finalNormalMap, normalScale: normalScaleVec }), materialKey));
|
|
149
261
|
}
|
|
150
262
|
const MaterialComponent = {
|
|
151
263
|
name: 'Material',
|
|
@@ -153,7 +265,9 @@ const MaterialComponent = {
|
|
|
153
265
|
View: MaterialComponentView,
|
|
154
266
|
nonComposable: true,
|
|
155
267
|
defaultProperties: {
|
|
268
|
+
materialType: 'standard',
|
|
156
269
|
color: '#ffffff',
|
|
270
|
+
toneMapped: true,
|
|
157
271
|
wireframe: false,
|
|
158
272
|
transparent: false,
|
|
159
273
|
opacity: 1,
|
|
@@ -1,24 +1,65 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { ModelListViewer, SingleModelViewer } from '../../assetviewer/page';
|
|
3
|
-
import { useEffect, useState, useMemo } from 'react';
|
|
3
|
+
import { useEffect, useLayoutEffect, useState, useMemo, useRef } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
4
5
|
import { FieldRenderer } from './Input';
|
|
6
|
+
const PICKER_POPUP_WIDTH = 260;
|
|
7
|
+
const PICKER_POPUP_HEIGHT = 360;
|
|
5
8
|
function ModelPicker({ value, onChange, basePath, nodeId }) {
|
|
6
9
|
const [modelFiles, setModelFiles] = useState([]);
|
|
7
10
|
const [showPicker, setShowPicker] = useState(false);
|
|
11
|
+
const [popupStyle, setPopupStyle] = useState(null);
|
|
12
|
+
const triggerRef = useRef(null);
|
|
8
13
|
useEffect(() => {
|
|
9
14
|
fetch(`${basePath}/models/manifest.json`)
|
|
10
15
|
.then(r => r.json())
|
|
11
16
|
.then(data => setModelFiles(Array.isArray(data) ? data : data.files || []))
|
|
12
17
|
.catch(console.error);
|
|
13
18
|
}, [basePath]);
|
|
19
|
+
useLayoutEffect(() => {
|
|
20
|
+
if (!showPicker || !triggerRef.current || typeof window === 'undefined')
|
|
21
|
+
return;
|
|
22
|
+
const updatePosition = () => {
|
|
23
|
+
var _a;
|
|
24
|
+
const rect = (_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
|
|
25
|
+
if (!rect)
|
|
26
|
+
return;
|
|
27
|
+
const preferredLeft = rect.left - PICKER_POPUP_WIDTH - 8;
|
|
28
|
+
const fallbackLeft = rect.right + 8;
|
|
29
|
+
const fitsLeft = preferredLeft >= 8;
|
|
30
|
+
const left = fitsLeft ? preferredLeft : Math.min(fallbackLeft, window.innerWidth - PICKER_POPUP_WIDTH - 8);
|
|
31
|
+
const top = Math.min(Math.max(8, rect.top), window.innerHeight - PICKER_POPUP_HEIGHT - 8);
|
|
32
|
+
setPopupStyle({
|
|
33
|
+
position: 'fixed',
|
|
34
|
+
left,
|
|
35
|
+
top,
|
|
36
|
+
background: 'rgba(0,0,0,0.9)',
|
|
37
|
+
padding: 12,
|
|
38
|
+
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
39
|
+
borderRadius: 6,
|
|
40
|
+
width: PICKER_POPUP_WIDTH,
|
|
41
|
+
height: PICKER_POPUP_HEIGHT,
|
|
42
|
+
overflow: 'hidden',
|
|
43
|
+
zIndex: 1000,
|
|
44
|
+
boxShadow: '0 4px 16px rgba(0,0,0,0.6)',
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
updatePosition();
|
|
48
|
+
window.addEventListener('resize', updatePosition);
|
|
49
|
+
window.addEventListener('scroll', updatePosition, true);
|
|
50
|
+
return () => {
|
|
51
|
+
window.removeEventListener('resize', updatePosition);
|
|
52
|
+
window.removeEventListener('scroll', updatePosition, true);
|
|
53
|
+
};
|
|
54
|
+
}, [showPicker]);
|
|
14
55
|
const handleModelSelect = (file) => {
|
|
15
56
|
const filename = file.startsWith('/') ? file.slice(1) : file;
|
|
16
57
|
onChange(filename);
|
|
17
58
|
setShowPicker(false);
|
|
18
59
|
};
|
|
19
|
-
return (_jsxs("div", { style: { maxHeight: 128, overflow: 'visible', position: 'relative', display: 'flex', alignItems: 'center' }, children: [_jsx(SingleModelViewer, { file: value ? `/${value}` : undefined, basePath: basePath }), _jsx("button", { onClick: () => setShowPicker(!showPicker), style: { padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(34, 211, 238, 0.3)', marginTop: 4 }, children: showPicker ? 'Cancel' : 'Change' }), _jsx("button", { onClick: () => {
|
|
60
|
+
return (_jsxs("div", { style: { maxHeight: 128, overflow: 'visible', position: 'relative', display: 'flex', alignItems: 'center' }, children: [_jsx(SingleModelViewer, { file: value ? `/${value}` : undefined, basePath: basePath }), _jsx("button", { ref: triggerRef, onClick: () => setShowPicker(!showPicker), style: { padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(34, 211, 238, 0.3)', marginTop: 4 }, children: showPicker ? 'Cancel' : 'Change' }), _jsx("button", { onClick: () => {
|
|
20
61
|
onChange(undefined);
|
|
21
|
-
}, style: { padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(34, 211, 238, 0.3)', marginTop: 4, marginLeft: 4 }, children: "Clear" }), showPicker &&
|
|
62
|
+
}, style: { padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(34, 211, 238, 0.3)', marginTop: 4, marginLeft: 4 }, children: "Clear" }), showPicker && popupStyle && typeof document !== 'undefined' && createPortal(_jsx("div", { style: popupStyle, onMouseLeave: () => setShowPicker(false), children: _jsx(ModelListViewer, { files: modelFiles, selected: value ? `/${value}` : undefined, onSelect: handleModelSelect, basePath: basePath }, nodeId) }), document.body)] }));
|
|
22
63
|
}
|
|
23
64
|
function ModelComponentEditor({ component, node, onUpdate, basePath = "" }) {
|
|
24
65
|
const fields = [
|