react-three-game 0.0.18 → 0.0.19
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/.github/copilot-instructions.md +54 -183
- package/README.md +69 -214
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -0
- package/dist/tools/prefabeditor/EditorTree.d.ts +2 -4
- package/dist/tools/prefabeditor/EditorTree.js +20 -194
- package/dist/tools/prefabeditor/EditorUI.js +43 -224
- package/dist/tools/prefabeditor/PrefabEditor.js +33 -99
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +0 -1
- package/dist/tools/prefabeditor/PrefabRoot.js +7 -23
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +31 -43
- package/dist/tools/prefabeditor/styles.d.ts +1809 -0
- package/dist/tools/prefabeditor/styles.js +168 -0
- package/dist/tools/prefabeditor/types.d.ts +3 -14
- package/dist/tools/prefabeditor/types.js +0 -1
- package/dist/tools/prefabeditor/utils.d.ts +19 -0
- package/dist/tools/prefabeditor/utils.js +72 -0
- package/package.json +1 -1
- package/src/index.ts +5 -1
- package/src/tools/prefabeditor/EditorTree.tsx +38 -270
- package/src/tools/prefabeditor/EditorUI.tsx +105 -322
- package/src/tools/prefabeditor/PrefabEditor.tsx +40 -151
- package/src/tools/prefabeditor/PrefabRoot.tsx +11 -32
- package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +38 -53
- package/src/tools/prefabeditor/styles.ts +195 -0
- package/src/tools/prefabeditor/types.ts +4 -12
- package/src/tools/prefabeditor/utils.ts +80 -0
|
@@ -2,7 +2,8 @@ import { Dispatch, SetStateAction, useState, useEffect } from 'react';
|
|
|
2
2
|
import { Prefab, GameObject as GameObjectType } from "./types";
|
|
3
3
|
import EditorTree from './EditorTree';
|
|
4
4
|
import { getAllComponents } from './components/ComponentRegistry';
|
|
5
|
-
|
|
5
|
+
import { base, inspector } from './styles';
|
|
6
|
+
import { findNode, updateNode, deleteNode } from './utils';
|
|
6
7
|
|
|
7
8
|
function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transformMode, setTransformMode, basePath }: {
|
|
8
9
|
prefabData?: Prefab;
|
|
@@ -13,100 +14,42 @@ function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transf
|
|
|
13
14
|
setTransformMode: (m: "translate" | "rotate" | "scale") => void;
|
|
14
15
|
basePath?: string;
|
|
15
16
|
}) {
|
|
16
|
-
const [
|
|
17
|
-
|
|
18
|
-
const ui: Record<string, React.CSSProperties> = {
|
|
19
|
-
panel: {
|
|
20
|
-
position: 'absolute',
|
|
21
|
-
top: 8,
|
|
22
|
-
right: 8,
|
|
23
|
-
zIndex: 20,
|
|
24
|
-
width: 260,
|
|
25
|
-
background: 'rgba(0,0,0,0.55)',
|
|
26
|
-
color: 'rgba(255,255,255,0.9)',
|
|
27
|
-
border: '1px solid rgba(255,255,255,0.12)',
|
|
28
|
-
borderRadius: 6,
|
|
29
|
-
overflow: 'hidden',
|
|
30
|
-
backdropFilter: 'blur(6px)',
|
|
31
|
-
WebkitBackdropFilter: 'blur(6px)',
|
|
32
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
|
|
33
|
-
fontSize: 11,
|
|
34
|
-
lineHeight: 1.2,
|
|
35
|
-
},
|
|
36
|
-
header: {
|
|
37
|
-
padding: '4px 6px',
|
|
38
|
-
display: 'flex',
|
|
39
|
-
alignItems: 'center',
|
|
40
|
-
justifyContent: 'space-between',
|
|
41
|
-
cursor: 'pointer',
|
|
42
|
-
background: 'rgba(255,255,255,0.05)',
|
|
43
|
-
borderBottom: '1px solid rgba(255,255,255,0.10)',
|
|
44
|
-
textTransform: 'uppercase',
|
|
45
|
-
letterSpacing: '0.08em',
|
|
46
|
-
fontSize: 10,
|
|
47
|
-
color: 'rgba(255,255,255,0.7)',
|
|
48
|
-
userSelect: 'none',
|
|
49
|
-
WebkitUserSelect: 'none',
|
|
50
|
-
},
|
|
51
|
-
left: {
|
|
52
|
-
position: 'absolute',
|
|
53
|
-
top: 8,
|
|
54
|
-
left: 8,
|
|
55
|
-
zIndex: 20,
|
|
56
|
-
},
|
|
57
|
-
};
|
|
17
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
58
18
|
|
|
59
|
-
const
|
|
19
|
+
const updateNodeHandler = (updater: (n: GameObjectType) => GameObjectType) => {
|
|
60
20
|
if (!prefabData || !setPrefabData || !selectedId) return;
|
|
61
21
|
setPrefabData(prev => ({
|
|
62
22
|
...prev,
|
|
63
|
-
root:
|
|
23
|
+
root: updateNode(prev.root, selectedId, updater)
|
|
64
24
|
}));
|
|
65
25
|
};
|
|
66
26
|
|
|
67
|
-
const
|
|
68
|
-
if (!prefabData || !setPrefabData || !selectedId) return;
|
|
69
|
-
|
|
70
|
-
alert("Cannot delete root node");
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
setPrefabData(prev => {
|
|
74
|
-
const newRoot = deletePrefabNode(prev.root, selectedId);
|
|
75
|
-
return { ...prev, root: newRoot! };
|
|
76
|
-
});
|
|
27
|
+
const deleteNodeHandler = () => {
|
|
28
|
+
if (!prefabData || !setPrefabData || !selectedId || selectedId === prefabData.root.id) return;
|
|
29
|
+
setPrefabData(prev => ({ ...prev, root: deleteNode(prev.root, selectedId)! }));
|
|
77
30
|
setSelectedId(null);
|
|
78
31
|
};
|
|
79
32
|
|
|
80
33
|
const selectedNode = selectedId && prefabData ? findNode(prefabData.root, selectedId) : null;
|
|
81
34
|
|
|
82
|
-
// if (!selectedNode) return null;
|
|
83
35
|
return <>
|
|
84
|
-
<div style={
|
|
85
|
-
<div
|
|
86
|
-
style={ui.header}
|
|
87
|
-
onClick={() => setIsInspectorCollapsed(!isInspectorCollapsed)}
|
|
88
|
-
onPointerEnter={(e) => {
|
|
89
|
-
(e.currentTarget as HTMLDivElement).style.background = 'rgba(255,255,255,0.08)';
|
|
90
|
-
}}
|
|
91
|
-
onPointerLeave={(e) => {
|
|
92
|
-
(e.currentTarget as HTMLDivElement).style.background = 'rgba(255,255,255,0.05)';
|
|
93
|
-
}}
|
|
94
|
-
>
|
|
36
|
+
<div style={inspector.panel}>
|
|
37
|
+
<div style={base.header} onClick={() => setCollapsed(!collapsed)}>
|
|
95
38
|
<span>Inspector</span>
|
|
96
|
-
<span
|
|
39
|
+
<span>{collapsed ? '◀' : '▼'}</span>
|
|
97
40
|
</div>
|
|
98
|
-
{!
|
|
41
|
+
{!collapsed && selectedNode && (
|
|
99
42
|
<NodeInspector
|
|
100
43
|
node={selectedNode}
|
|
101
|
-
updateNode={
|
|
102
|
-
deleteNode={
|
|
44
|
+
updateNode={updateNodeHandler}
|
|
45
|
+
deleteNode={deleteNodeHandler}
|
|
103
46
|
transformMode={transformMode}
|
|
104
47
|
setTransformMode={setTransformMode}
|
|
105
48
|
basePath={basePath}
|
|
106
49
|
/>
|
|
107
50
|
)}
|
|
108
51
|
</div>
|
|
109
|
-
<div style={
|
|
52
|
+
<div style={{ position: 'absolute', top: 8, left: 8, zIndex: 20 }}>
|
|
110
53
|
<EditorTree
|
|
111
54
|
prefabData={prefabData}
|
|
112
55
|
setPrefabData={setPrefabData}
|
|
@@ -117,6 +60,7 @@ function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transf
|
|
|
117
60
|
</>;
|
|
118
61
|
}
|
|
119
62
|
|
|
63
|
+
|
|
120
64
|
function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransformMode, basePath }: {
|
|
121
65
|
node: GameObjectType;
|
|
122
66
|
updateNode: (updater: (n: GameObjectType) => GameObjectType) => void;
|
|
@@ -126,270 +70,109 @@ function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransfo
|
|
|
126
70
|
basePath?: string;
|
|
127
71
|
}) {
|
|
128
72
|
const ALL_COMPONENTS = getAllComponents();
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
const s: Record<string, React.CSSProperties> = {
|
|
133
|
-
root: {
|
|
134
|
-
display: 'flex',
|
|
135
|
-
flexDirection: 'column',
|
|
136
|
-
gap: 6,
|
|
137
|
-
padding: 6,
|
|
138
|
-
maxHeight: '80vh',
|
|
139
|
-
overflowY: 'auto',
|
|
140
|
-
},
|
|
141
|
-
section: {
|
|
142
|
-
paddingBottom: 6,
|
|
143
|
-
borderBottom: '1px solid rgba(255,255,255,0.10)',
|
|
144
|
-
},
|
|
145
|
-
label: {
|
|
146
|
-
display: 'block',
|
|
147
|
-
fontSize: 10,
|
|
148
|
-
opacity: 0.7,
|
|
149
|
-
textTransform: 'uppercase',
|
|
150
|
-
letterSpacing: '0.08em',
|
|
151
|
-
marginBottom: 4,
|
|
152
|
-
},
|
|
153
|
-
input: {
|
|
154
|
-
width: '100%',
|
|
155
|
-
background: 'rgba(255,255,255,0.06)',
|
|
156
|
-
border: '1px solid rgba(255,255,255,0.14)',
|
|
157
|
-
borderRadius: 4,
|
|
158
|
-
padding: '4px 6px',
|
|
159
|
-
color: 'rgba(255,255,255,0.92)',
|
|
160
|
-
font: 'inherit',
|
|
161
|
-
outline: 'none',
|
|
162
|
-
},
|
|
163
|
-
row: {
|
|
164
|
-
display: 'flex',
|
|
165
|
-
alignItems: 'center',
|
|
166
|
-
justifyContent: 'space-between',
|
|
167
|
-
gap: 8,
|
|
168
|
-
},
|
|
169
|
-
button: {
|
|
170
|
-
padding: '2px 6px',
|
|
171
|
-
background: 'transparent',
|
|
172
|
-
color: 'rgba(255,255,255,0.9)',
|
|
173
|
-
border: '1px solid rgba(255,255,255,0.14)',
|
|
174
|
-
borderRadius: 4,
|
|
175
|
-
cursor: 'pointer',
|
|
176
|
-
font: 'inherit',
|
|
177
|
-
},
|
|
178
|
-
buttonActive: {
|
|
179
|
-
background: 'rgba(255,255,255,0.10)',
|
|
180
|
-
},
|
|
181
|
-
smallDanger: {
|
|
182
|
-
background: 'transparent',
|
|
183
|
-
border: 'none',
|
|
184
|
-
cursor: 'pointer',
|
|
185
|
-
color: 'rgba(255,120,120,0.95)',
|
|
186
|
-
font: 'inherit',
|
|
187
|
-
padding: '2px 4px',
|
|
188
|
-
},
|
|
189
|
-
componentHeader: {
|
|
190
|
-
display: 'flex',
|
|
191
|
-
alignItems: 'center',
|
|
192
|
-
justifyContent: 'space-between',
|
|
193
|
-
padding: '4px 0',
|
|
194
|
-
borderBottom: '1px solid rgba(255,255,255,0.08)',
|
|
195
|
-
marginBottom: 4,
|
|
196
|
-
},
|
|
197
|
-
componentTitle: {
|
|
198
|
-
fontSize: 10,
|
|
199
|
-
textTransform: 'uppercase',
|
|
200
|
-
letterSpacing: '0.08em',
|
|
201
|
-
opacity: 0.8,
|
|
202
|
-
},
|
|
203
|
-
select: {
|
|
204
|
-
flex: 1,
|
|
205
|
-
background: 'rgba(255,255,255,0.06)',
|
|
206
|
-
border: '1px solid rgba(255,255,255,0.14)',
|
|
207
|
-
borderRadius: 4,
|
|
208
|
-
padding: '4px 6px',
|
|
209
|
-
color: 'rgba(255,255,255,0.92)',
|
|
210
|
-
font: 'inherit',
|
|
211
|
-
outline: 'none',
|
|
212
|
-
},
|
|
213
|
-
addButton: {
|
|
214
|
-
width: 28,
|
|
215
|
-
padding: '4px 0',
|
|
216
|
-
background: 'rgba(255,255,255,0.08)',
|
|
217
|
-
color: 'rgba(255,255,255,0.92)',
|
|
218
|
-
border: '1px solid rgba(255,255,255,0.14)',
|
|
219
|
-
borderRadius: 4,
|
|
220
|
-
cursor: 'pointer',
|
|
221
|
-
font: 'inherit',
|
|
222
|
-
},
|
|
223
|
-
disabled: {
|
|
224
|
-
opacity: 0.35,
|
|
225
|
-
cursor: 'not-allowed',
|
|
226
|
-
},
|
|
227
|
-
};
|
|
73
|
+
const allKeys = Object.keys(ALL_COMPONENTS);
|
|
74
|
+
const available = allKeys.filter(k => !node.components?.[k.toLowerCase()]);
|
|
75
|
+
const [addType, setAddType] = useState(available[0] || "");
|
|
228
76
|
|
|
229
|
-
const componentKeys = Object.keys(node.components || {}).join(',');
|
|
230
77
|
useEffect(() => {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
return <div style={s.root}>
|
|
240
|
-
<div style={s.section}>
|
|
78
|
+
const newAvailable = allKeys.filter(k => !node.components?.[k.toLowerCase()]);
|
|
79
|
+
if (!newAvailable.includes(addType)) setAddType(newAvailable[0] || "");
|
|
80
|
+
}, [Object.keys(node.components || {}).join(',')]);
|
|
81
|
+
|
|
82
|
+
return <div style={inspector.content}>
|
|
83
|
+
{/* Node ID */}
|
|
84
|
+
<div style={base.section}>
|
|
85
|
+
<div style={base.label}>Node ID</div>
|
|
241
86
|
<input
|
|
242
|
-
style={
|
|
87
|
+
style={base.input}
|
|
243
88
|
value={node.id}
|
|
244
89
|
onChange={e => updateNode(n => ({ ...n, id: e.target.value }))}
|
|
245
90
|
/>
|
|
246
91
|
</div>
|
|
247
92
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
<
|
|
251
|
-
|
|
252
|
-
style={
|
|
253
|
-
|
|
254
|
-
>
|
|
255
|
-
✕
|
|
256
|
-
</button>
|
|
257
|
-
</div>
|
|
258
|
-
|
|
259
|
-
{node.components && Object.entries(node.components).map(([key, comp]: [string, any]) => {
|
|
260
|
-
if (!comp) return null;
|
|
261
|
-
const componentDef = ALL_COMPONENTS[comp.type];
|
|
262
|
-
if (!componentDef) return <div key={key} style={{ padding: '4px 0', color: 'rgba(255,120,120,0.95)', fontSize: 11 }}>
|
|
263
|
-
Unknown component type: {comp.type}
|
|
264
|
-
<textarea defaultValue={JSON.stringify(comp)} />
|
|
265
|
-
</div>;
|
|
93
|
+
{/* Components */}
|
|
94
|
+
<div style={base.section}>
|
|
95
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
|
|
96
|
+
<div style={base.label}>Components</div>
|
|
97
|
+
<button style={{ ...base.btn, ...base.btnDanger }} onClick={deleteNode}>Delete Node</button>
|
|
98
|
+
</div>
|
|
266
99
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
100
|
+
{node.components && Object.entries(node.components).map(([key, comp]: [string, any]) => {
|
|
101
|
+
if (!comp) return null;
|
|
102
|
+
const def = ALL_COMPONENTS[comp.type];
|
|
103
|
+
if (!def) return <div key={key} style={{ color: '#ff8888', fontSize: 11 }}>
|
|
104
|
+
Unknown: {comp.type}
|
|
105
|
+
</div>;
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<div key={key} style={{ marginBottom: 8 }}>
|
|
109
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }}>
|
|
110
|
+
<div style={{ fontSize: 11, fontWeight: 500 }}>{key}</div>
|
|
111
|
+
<button
|
|
112
|
+
style={{ ...base.btn, padding: '2px 6px' }}
|
|
113
|
+
onClick={() => updateNode(n => {
|
|
114
|
+
const { [key]: _, ...rest } = n.components || {};
|
|
115
|
+
return { ...n, components: rest };
|
|
116
|
+
})}
|
|
117
|
+
>
|
|
118
|
+
✕
|
|
119
|
+
</button>
|
|
120
|
+
</div>
|
|
121
|
+
{def.Editor && (
|
|
122
|
+
<def.Editor
|
|
123
|
+
component={comp}
|
|
124
|
+
onUpdate={(newProps: any) => updateNode(n => ({
|
|
125
|
+
...n,
|
|
126
|
+
components: {
|
|
127
|
+
...n.components,
|
|
128
|
+
[key]: { ...comp, properties: { ...comp.properties, ...newProps } }
|
|
294
129
|
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
)
|
|
304
|
-
|
|
130
|
+
}))}
|
|
131
|
+
basePath={basePath}
|
|
132
|
+
transformMode={transformMode}
|
|
133
|
+
setTransformMode={setTransformMode}
|
|
134
|
+
/>
|
|
135
|
+
)}
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
})}
|
|
139
|
+
</div>
|
|
305
140
|
|
|
306
141
|
{/* Add Component */}
|
|
307
|
-
|
|
308
|
-
<
|
|
309
|
-
|
|
310
|
-
<
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
<option key={k} value={k}>{k}</option>
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}}
|
|
339
|
-
onPointerEnter={(e) => {
|
|
340
|
-
if (!addComponentType) return;
|
|
341
|
-
(e.currentTarget as HTMLButtonElement).style.background = 'rgba(255,255,255,0.12)';
|
|
342
|
-
}}
|
|
343
|
-
onPointerLeave={(e) => {
|
|
344
|
-
if (!addComponentType) return;
|
|
345
|
-
(e.currentTarget as HTMLButtonElement).style.background = 'rgba(255,255,255,0.08)';
|
|
346
|
-
}}
|
|
347
|
-
>
|
|
348
|
-
+
|
|
349
|
-
</button>
|
|
142
|
+
{available.length > 0 && (
|
|
143
|
+
<div>
|
|
144
|
+
<div style={base.label}>Add Component</div>
|
|
145
|
+
<div style={base.row}>
|
|
146
|
+
<select
|
|
147
|
+
style={{ ...base.input, flex: 1 }}
|
|
148
|
+
value={addType}
|
|
149
|
+
onChange={e => setAddType(e.target.value)}
|
|
150
|
+
>
|
|
151
|
+
{available.map(k => <option key={k} value={k}>{k}</option>)}
|
|
152
|
+
</select>
|
|
153
|
+
<button
|
|
154
|
+
style={base.btn}
|
|
155
|
+
disabled={!addType}
|
|
156
|
+
onClick={() => {
|
|
157
|
+
if (!addType) return;
|
|
158
|
+
const def = ALL_COMPONENTS[addType];
|
|
159
|
+
if (def) {
|
|
160
|
+
updateNode(n => ({
|
|
161
|
+
...n,
|
|
162
|
+
components: {
|
|
163
|
+
...n.components,
|
|
164
|
+
[addType.toLowerCase()]: { type: def.name, properties: def.defaultProperties }
|
|
165
|
+
}
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
168
|
+
}}
|
|
169
|
+
>
|
|
170
|
+
+
|
|
171
|
+
</button>
|
|
172
|
+
</div>
|
|
350
173
|
</div>
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
174
|
+
)}
|
|
354
175
|
</div>
|
|
355
176
|
}
|
|
356
177
|
|
|
357
|
-
function findNode(root: GameObjectType, id: string): GameObjectType | null {
|
|
358
|
-
if (root.id === id) return root;
|
|
359
|
-
if (root.children) {
|
|
360
|
-
for (const child of root.children) {
|
|
361
|
-
const found = findNode(child, id);
|
|
362
|
-
if (found) return found;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
return null;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function updatePrefabNode(root: GameObjectType, id: string, update: (node: GameObjectType) => GameObjectType): GameObjectType {
|
|
369
|
-
if (root.id === id) {
|
|
370
|
-
return update(root);
|
|
371
|
-
}
|
|
372
|
-
if (root.children) {
|
|
373
|
-
return {
|
|
374
|
-
...root,
|
|
375
|
-
children: root.children.map(child => updatePrefabNode(child, id, update))
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
return root;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function deletePrefabNode(root: GameObjectType, id: string): GameObjectType | null {
|
|
382
|
-
if (root.id === id) return null;
|
|
383
|
-
|
|
384
|
-
if (root.children) {
|
|
385
|
-
return {
|
|
386
|
-
...root,
|
|
387
|
-
children: root.children
|
|
388
|
-
.map(child => deletePrefabNode(child, id))
|
|
389
|
-
.filter((child): child is GameObjectType => child !== null)
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
return root;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
178
|
export default EditorUI;
|