react-three-game 0.0.14 → 0.0.16
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 +50 -66
- package/dist/helpers/index.d.ts +35 -0
- package/dist/helpers/index.js +44 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/shared/GameCanvas.js +13 -13
- package/dist/tools/dragdrop/DragDropLoader.js +1 -0
- package/dist/tools/prefabeditor/EditorTree.js +116 -4
- package/dist/tools/prefabeditor/EditorUI.js +160 -6
- package/dist/tools/prefabeditor/PrefabEditor.js +56 -4
- package/dist/tools/prefabeditor/PrefabRoot.js +1 -10
- package/dist/tools/prefabeditor/types.d.ts +4 -4
- package/dist/tools/prefabeditor/types.js +1 -0
- package/package.json +4 -1
- package/src/helpers/index.ts +95 -0
- package/src/index.ts +3 -0
- package/src/shared/GameCanvas.tsx +5 -2
- package/src/tools/dragdrop/DragDropLoader.tsx +1 -0
- package/src/tools/prefabeditor/EditorTree.tsx +154 -16
- package/src/tools/prefabeditor/EditorUI.tsx +198 -51
- package/src/tools/prefabeditor/PrefabEditor.tsx +67 -7
- package/src/tools/prefabeditor/PrefabRoot.tsx +2 -13
- package/src/tools/prefabeditor/types.ts +5 -5
|
@@ -15,6 +15,95 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
15
15
|
const [collapsedIds, setCollapsedIds] = useState<Set<string>>(new Set());
|
|
16
16
|
const [isTreeCollapsed, setIsTreeCollapsed] = useState(false);
|
|
17
17
|
|
|
18
|
+
const styles: Record<string, React.CSSProperties> = {
|
|
19
|
+
panel: {
|
|
20
|
+
background: "rgba(0,0,0,0.55)",
|
|
21
|
+
color: "rgba(255,255,255,0.9)",
|
|
22
|
+
border: "1px solid rgba(255,255,255,0.12)",
|
|
23
|
+
borderRadius: 6,
|
|
24
|
+
overflow: "hidden",
|
|
25
|
+
maxHeight: "85vh",
|
|
26
|
+
display: "flex",
|
|
27
|
+
flexDirection: "column",
|
|
28
|
+
backdropFilter: "blur(6px)",
|
|
29
|
+
WebkitBackdropFilter: "blur(6px)",
|
|
30
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
|
|
31
|
+
fontSize: 11,
|
|
32
|
+
lineHeight: 1.2,
|
|
33
|
+
userSelect: "none",
|
|
34
|
+
WebkitUserSelect: "none",
|
|
35
|
+
},
|
|
36
|
+
panelHeader: {
|
|
37
|
+
padding: "4px 6px",
|
|
38
|
+
borderBottom: "1px solid rgba(255,255,255,0.10)",
|
|
39
|
+
display: "flex",
|
|
40
|
+
gap: 8,
|
|
41
|
+
alignItems: "center",
|
|
42
|
+
justifyContent: "space-between",
|
|
43
|
+
cursor: "pointer",
|
|
44
|
+
background: "rgba(255,255,255,0.05)",
|
|
45
|
+
textTransform: "uppercase",
|
|
46
|
+
letterSpacing: "0.08em",
|
|
47
|
+
fontSize: 10,
|
|
48
|
+
color: "rgba(255,255,255,0.7)",
|
|
49
|
+
},
|
|
50
|
+
scroll: {
|
|
51
|
+
overflowY: "auto",
|
|
52
|
+
},
|
|
53
|
+
row: {
|
|
54
|
+
display: "flex",
|
|
55
|
+
alignItems: "center",
|
|
56
|
+
padding: "2px 6px",
|
|
57
|
+
borderBottom: "1px solid rgba(255,255,255,0.07)",
|
|
58
|
+
cursor: "pointer",
|
|
59
|
+
whiteSpace: "nowrap",
|
|
60
|
+
},
|
|
61
|
+
rowSelected: {
|
|
62
|
+
background: "rgba(255,255,255,0.10)",
|
|
63
|
+
},
|
|
64
|
+
chevron: {
|
|
65
|
+
width: 12,
|
|
66
|
+
textAlign: "center",
|
|
67
|
+
opacity: 0.55,
|
|
68
|
+
fontSize: 10,
|
|
69
|
+
marginRight: 4,
|
|
70
|
+
cursor: "pointer",
|
|
71
|
+
},
|
|
72
|
+
idText: {
|
|
73
|
+
fontSize: 11,
|
|
74
|
+
overflow: "hidden",
|
|
75
|
+
textOverflow: "ellipsis",
|
|
76
|
+
},
|
|
77
|
+
contextMenu: {
|
|
78
|
+
position: "fixed",
|
|
79
|
+
zIndex: 50,
|
|
80
|
+
minWidth: 120,
|
|
81
|
+
background: "rgba(0,0,0,0.82)",
|
|
82
|
+
border: "1px solid rgba(255,255,255,0.16)",
|
|
83
|
+
borderRadius: 6,
|
|
84
|
+
overflow: "hidden",
|
|
85
|
+
boxShadow: "0 12px 32px rgba(0,0,0,0.45)",
|
|
86
|
+
backdropFilter: "blur(6px)",
|
|
87
|
+
WebkitBackdropFilter: "blur(6px)",
|
|
88
|
+
},
|
|
89
|
+
menuItem: {
|
|
90
|
+
width: "100%",
|
|
91
|
+
textAlign: "left",
|
|
92
|
+
padding: "6px 8px",
|
|
93
|
+
background: "transparent",
|
|
94
|
+
border: "none",
|
|
95
|
+
color: "rgba(255,255,255,0.9)",
|
|
96
|
+
font: "inherit",
|
|
97
|
+
cursor: "pointer",
|
|
98
|
+
},
|
|
99
|
+
menuItemDanger: {
|
|
100
|
+
color: "rgba(255,120,120,0.95)",
|
|
101
|
+
},
|
|
102
|
+
menuDivider: {
|
|
103
|
+
borderTop: "1px solid rgba(255,255,255,0.10)",
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
18
107
|
if (!prefabData || !setPrefabData) return null;
|
|
19
108
|
|
|
20
109
|
const handleContextMenu = (e: MouseEvent, nodeId: string) => {
|
|
@@ -39,8 +128,6 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
39
128
|
const handleAddChild = (parentId: string) => {
|
|
40
129
|
const newNode: GameObject = {
|
|
41
130
|
id: crypto.randomUUID(),
|
|
42
|
-
enabled: true,
|
|
43
|
-
visible: true,
|
|
44
131
|
components: {
|
|
45
132
|
transform: {
|
|
46
133
|
type: "Transform",
|
|
@@ -154,24 +241,42 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
154
241
|
const hasChildren = node.children && node.children.length > 0;
|
|
155
242
|
|
|
156
243
|
return (
|
|
157
|
-
<div key={node.id}
|
|
244
|
+
<div key={node.id}>
|
|
158
245
|
<div
|
|
159
|
-
|
|
160
|
-
|
|
246
|
+
style={{
|
|
247
|
+
...styles.row,
|
|
248
|
+
...(isSelected ? styles.rowSelected : null),
|
|
249
|
+
paddingLeft: `${depth * 10 + 6}px`,
|
|
250
|
+
}}
|
|
161
251
|
onClick={(e) => { e.stopPropagation(); setSelectedId(node.id); }}
|
|
162
252
|
onContextMenu={(e) => handleContextMenu(e, node.id)}
|
|
163
253
|
draggable={node.id !== prefabData.root.id}
|
|
164
254
|
onDragStart={(e) => handleDragStart(e, node.id)}
|
|
165
255
|
onDragOver={(e) => handleDragOver(e, node.id)}
|
|
166
256
|
onDrop={(e) => handleDrop(e, node.id)}
|
|
257
|
+
onPointerEnter={(e) => {
|
|
258
|
+
if (!isSelected) (e.currentTarget as HTMLDivElement).style.background = "rgba(255,255,255,0.06)";
|
|
259
|
+
}}
|
|
260
|
+
onPointerLeave={(e) => {
|
|
261
|
+
if (!isSelected) (e.currentTarget as HTMLDivElement).style.background = "transparent";
|
|
262
|
+
}}
|
|
167
263
|
>
|
|
168
264
|
<span
|
|
169
|
-
|
|
265
|
+
style={{
|
|
266
|
+
...styles.chevron,
|
|
267
|
+
visibility: hasChildren ? 'visible' : 'hidden',
|
|
268
|
+
}}
|
|
170
269
|
onClick={(e) => hasChildren && toggleCollapse(e, node.id)}
|
|
270
|
+
onPointerEnter={(e) => {
|
|
271
|
+
(e.currentTarget as HTMLSpanElement).style.opacity = "0.9";
|
|
272
|
+
}}
|
|
273
|
+
onPointerLeave={(e) => {
|
|
274
|
+
(e.currentTarget as HTMLSpanElement).style.opacity = "0.55";
|
|
275
|
+
}}
|
|
171
276
|
>
|
|
172
277
|
{isCollapsed ? '▶' : '▼'}
|
|
173
278
|
</span>
|
|
174
|
-
<span
|
|
279
|
+
<span style={styles.idText}>
|
|
175
280
|
{node.id}
|
|
176
281
|
</span>
|
|
177
282
|
</div>
|
|
@@ -186,16 +291,28 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
186
291
|
|
|
187
292
|
return (
|
|
188
293
|
<>
|
|
189
|
-
<div
|
|
294
|
+
<div
|
|
295
|
+
style={{
|
|
296
|
+
...styles.panel,
|
|
297
|
+
width: isTreeCollapsed ? 'auto' : '14rem',
|
|
298
|
+
}}
|
|
299
|
+
onClick={closeContextMenu}
|
|
300
|
+
>
|
|
190
301
|
<div
|
|
191
|
-
|
|
302
|
+
style={styles.panelHeader}
|
|
192
303
|
onClick={(e) => { e.stopPropagation(); setIsTreeCollapsed(!isTreeCollapsed); }}
|
|
304
|
+
onPointerEnter={(e) => {
|
|
305
|
+
(e.currentTarget as HTMLDivElement).style.background = "rgba(255,255,255,0.08)";
|
|
306
|
+
}}
|
|
307
|
+
onPointerLeave={(e) => {
|
|
308
|
+
(e.currentTarget as HTMLDivElement).style.background = "rgba(255,255,255,0.05)";
|
|
309
|
+
}}
|
|
193
310
|
>
|
|
194
311
|
<span>Prefab Graph</span>
|
|
195
|
-
<span
|
|
312
|
+
<span style={{ fontSize: 10, opacity: 0.8 }}>{isTreeCollapsed ? '▶' : '◀'}</span>
|
|
196
313
|
</div>
|
|
197
314
|
{!isTreeCollapsed && (
|
|
198
|
-
<div
|
|
315
|
+
<div style={{ ...styles.scroll, padding: 2 }}>
|
|
199
316
|
{renderNode(prefabData.root)}
|
|
200
317
|
</div>
|
|
201
318
|
)}
|
|
@@ -203,28 +320,49 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
203
320
|
|
|
204
321
|
{contextMenu && (
|
|
205
322
|
<div
|
|
206
|
-
|
|
207
|
-
|
|
323
|
+
style={{
|
|
324
|
+
...styles.contextMenu,
|
|
325
|
+
top: contextMenu.y,
|
|
326
|
+
left: contextMenu.x,
|
|
327
|
+
}}
|
|
208
328
|
onClick={(e) => e.stopPropagation()}
|
|
209
329
|
onPointerLeave={closeContextMenu}
|
|
210
330
|
>
|
|
211
331
|
<button
|
|
212
|
-
|
|
332
|
+
style={{ ...styles.menuItem, ...styles.menuDivider }}
|
|
213
333
|
onClick={() => handleAddChild(contextMenu.nodeId)}
|
|
334
|
+
onPointerEnter={(e) => {
|
|
335
|
+
(e.currentTarget as HTMLButtonElement).style.background = "rgba(255,255,255,0.08)";
|
|
336
|
+
}}
|
|
337
|
+
onPointerLeave={(e) => {
|
|
338
|
+
(e.currentTarget as HTMLButtonElement).style.background = "transparent";
|
|
339
|
+
}}
|
|
214
340
|
>
|
|
215
341
|
Add Child
|
|
216
342
|
</button>
|
|
217
343
|
{contextMenu.nodeId !== prefabData.root.id && (
|
|
218
344
|
<>
|
|
219
345
|
<button
|
|
220
|
-
|
|
346
|
+
style={{ ...styles.menuItem, ...styles.menuDivider }}
|
|
221
347
|
onClick={() => handleDuplicate(contextMenu.nodeId)}
|
|
348
|
+
onPointerEnter={(e) => {
|
|
349
|
+
(e.currentTarget as HTMLButtonElement).style.background = "rgba(255,255,255,0.08)";
|
|
350
|
+
}}
|
|
351
|
+
onPointerLeave={(e) => {
|
|
352
|
+
(e.currentTarget as HTMLButtonElement).style.background = "transparent";
|
|
353
|
+
}}
|
|
222
354
|
>
|
|
223
355
|
Duplicate
|
|
224
356
|
</button>
|
|
225
357
|
<button
|
|
226
|
-
|
|
358
|
+
style={{ ...styles.menuItem, ...styles.menuItemDanger }}
|
|
227
359
|
onClick={() => handleDelete(contextMenu.nodeId)}
|
|
360
|
+
onPointerEnter={(e) => {
|
|
361
|
+
(e.currentTarget as HTMLButtonElement).style.background = "rgba(255,255,255,0.08)";
|
|
362
|
+
}}
|
|
363
|
+
onPointerLeave={(e) => {
|
|
364
|
+
(e.currentTarget as HTMLButtonElement).style.background = "transparent";
|
|
365
|
+
}}
|
|
228
366
|
>
|
|
229
367
|
Delete
|
|
230
368
|
</button>
|
|
@@ -15,6 +15,47 @@ function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transf
|
|
|
15
15
|
}) {
|
|
16
16
|
const [isInspectorCollapsed, setIsInspectorCollapsed] = useState(false);
|
|
17
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
|
+
};
|
|
58
|
+
|
|
18
59
|
const updateNode = (updater: (n: GameObjectType) => GameObjectType) => {
|
|
19
60
|
if (!prefabData || !setPrefabData || !selectedId) return;
|
|
20
61
|
setPrefabData(prev => ({
|
|
@@ -40,13 +81,19 @@ function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transf
|
|
|
40
81
|
|
|
41
82
|
// if (!selectedNode) return null;
|
|
42
83
|
return <>
|
|
43
|
-
<div style={
|
|
84
|
+
<div style={ui.panel}>
|
|
44
85
|
<div
|
|
45
|
-
|
|
86
|
+
style={ui.header}
|
|
46
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
|
+
}}
|
|
47
94
|
>
|
|
48
95
|
<span>Inspector</span>
|
|
49
|
-
<span
|
|
96
|
+
<span style={{ fontSize: 10, opacity: 0.8 }}>{isInspectorCollapsed ? '◀' : '▶'}</span>
|
|
50
97
|
</div>
|
|
51
98
|
{!isInspectorCollapsed && selectedNode && (
|
|
52
99
|
<NodeInspector
|
|
@@ -59,7 +106,7 @@ function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transf
|
|
|
59
106
|
/>
|
|
60
107
|
)}
|
|
61
108
|
</div>
|
|
62
|
-
<div style={
|
|
109
|
+
<div style={ui.left}>
|
|
63
110
|
<EditorTree
|
|
64
111
|
prefabData={prefabData}
|
|
65
112
|
setPrefabData={setPrefabData}
|
|
@@ -82,6 +129,103 @@ function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransfo
|
|
|
82
129
|
const allComponentKeys = Object.keys(ALL_COMPONENTS);
|
|
83
130
|
const [addComponentType, setAddComponentType] = useState(allComponentKeys[0]);
|
|
84
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
|
+
};
|
|
228
|
+
|
|
85
229
|
const componentKeys = Object.keys(node.components || {}).join(',');
|
|
86
230
|
useEffect(() => {
|
|
87
231
|
// Components stored on nodes use lowercase keys (e.g. 'geometry'),
|
|
@@ -92,28 +236,44 @@ function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransfo
|
|
|
92
236
|
}
|
|
93
237
|
}, [componentKeys, addComponentType, node.components, allComponentKeys]);
|
|
94
238
|
|
|
95
|
-
return <div
|
|
96
|
-
<div
|
|
239
|
+
return <div style={s.root}>
|
|
240
|
+
<div style={s.section}>
|
|
97
241
|
<input
|
|
98
|
-
|
|
242
|
+
style={s.input}
|
|
99
243
|
value={node.id}
|
|
100
244
|
onChange={e => updateNode(n => ({ ...n, id: e.target.value }))}
|
|
101
245
|
/>
|
|
102
246
|
</div>
|
|
103
247
|
|
|
104
|
-
<div
|
|
105
|
-
<label
|
|
106
|
-
<button
|
|
248
|
+
<div style={{ ...s.row, ...s.section, paddingBottom: 6 }}>
|
|
249
|
+
<label style={{ ...s.label, marginBottom: 0 }}>Components</label>
|
|
250
|
+
<button
|
|
251
|
+
onClick={deleteNode}
|
|
252
|
+
style={s.smallDanger}
|
|
253
|
+
title="Delete node"
|
|
254
|
+
>
|
|
255
|
+
✕
|
|
256
|
+
</button>
|
|
107
257
|
</div>
|
|
108
258
|
|
|
109
|
-
<div
|
|
110
|
-
<label
|
|
111
|
-
<div
|
|
259
|
+
<div style={s.section}>
|
|
260
|
+
<label style={s.label}>Mode</label>
|
|
261
|
+
<div style={{ display: 'flex', gap: 6 }}>
|
|
112
262
|
{["translate", "rotate", "scale"].map(mode => (
|
|
113
263
|
<button
|
|
114
264
|
key={mode}
|
|
115
265
|
onClick={() => setTransformMode(mode as any)}
|
|
116
|
-
|
|
266
|
+
style={{
|
|
267
|
+
...s.button,
|
|
268
|
+
flex: 1,
|
|
269
|
+
...(transformMode === mode ? s.buttonActive : null),
|
|
270
|
+
}}
|
|
271
|
+
onPointerEnter={(e) => {
|
|
272
|
+
if (transformMode !== mode) (e.currentTarget as HTMLButtonElement).style.background = 'rgba(255,255,255,0.08)';
|
|
273
|
+
}}
|
|
274
|
+
onPointerLeave={(e) => {
|
|
275
|
+
if (transformMode !== mode) (e.currentTarget as HTMLButtonElement).style.background = 'transparent';
|
|
276
|
+
}}
|
|
117
277
|
>
|
|
118
278
|
{mode[0].toUpperCase()}
|
|
119
279
|
</button>
|
|
@@ -121,53 +281,29 @@ function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransfo
|
|
|
121
281
|
</div>
|
|
122
282
|
</div>
|
|
123
283
|
|
|
124
|
-
{/* Components */}
|
|
125
|
-
{/* {node.components && Object.entries(node.components).map(([key, comp]: [string, any]) => {
|
|
126
|
-
if (!comp) return null;
|
|
127
|
-
return (
|
|
128
|
-
<div key={key} className="border border-cyan-500/20 mx-1 my-0.5 bg-black/20">
|
|
129
|
-
<div className="flex justify-between items-center px-1 py-0.5 border-b border-cyan-500/20 bg-cyan-500/5">
|
|
130
|
-
<span className="font-mono text-[10px] text-cyan-300 uppercase">{key}</span>
|
|
131
|
-
<button
|
|
132
|
-
onClick={() => updateNode(n => {
|
|
133
|
-
const components = { ...n.components };
|
|
134
|
-
delete components[key as keyof typeof components];
|
|
135
|
-
return { ...n, components };
|
|
136
|
-
})}
|
|
137
|
-
className="text-[9px] text-red-400/60 hover:text-red-400"
|
|
138
|
-
>
|
|
139
|
-
✕
|
|
140
|
-
</button>
|
|
141
|
-
</div>
|
|
142
|
-
<div className="px-1 py-0.5">
|
|
143
|
-
<ComponentEditor component={comp} onChange={(newComp: any) => updateNode(n => ({
|
|
144
|
-
...n,
|
|
145
|
-
components: { ...n.components, [key]: newComp }
|
|
146
|
-
}))} />
|
|
147
|
-
</div>
|
|
148
|
-
</div>
|
|
149
|
-
);
|
|
150
|
-
})} */}
|
|
284
|
+
{/* Components (legacy renderer removed) */}
|
|
151
285
|
|
|
152
286
|
{node.components && Object.entries(node.components).map(([key, comp]: [string, any]) => {
|
|
153
287
|
if (!comp) return null;
|
|
154
288
|
const componentDef = ALL_COMPONENTS[comp.type];
|
|
155
|
-
if (!componentDef) return <div key={key}
|
|
289
|
+
if (!componentDef) return <div key={key} style={{ padding: '4px 0', color: 'rgba(255,120,120,0.95)', fontSize: 11 }}>
|
|
290
|
+
Unknown component type: {comp.type}
|
|
156
291
|
<textarea defaultValue={JSON.stringify(comp)} />
|
|
157
292
|
</div>;
|
|
158
293
|
|
|
159
294
|
const EditorComp = componentDef.Editor;
|
|
160
295
|
return (
|
|
161
|
-
<div key={key}
|
|
162
|
-
<div
|
|
163
|
-
<span
|
|
296
|
+
<div key={key} style={{ padding: '0 2px' }}>
|
|
297
|
+
<div style={s.componentHeader}>
|
|
298
|
+
<span style={s.componentTitle}>{key}</span>
|
|
164
299
|
<button
|
|
165
300
|
onClick={() => updateNode(n => {
|
|
166
301
|
const components = { ...n.components };
|
|
167
302
|
delete components[key as keyof typeof components];
|
|
168
303
|
return { ...n, components };
|
|
169
304
|
})}
|
|
170
|
-
|
|
305
|
+
style={s.smallDanger}
|
|
306
|
+
title="Remove component"
|
|
171
307
|
>
|
|
172
308
|
✕
|
|
173
309
|
</button>
|
|
@@ -193,11 +329,11 @@ function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransfo
|
|
|
193
329
|
})}
|
|
194
330
|
|
|
195
331
|
{/* Add Component */}
|
|
196
|
-
<div
|
|
197
|
-
<label
|
|
198
|
-
<div
|
|
332
|
+
<div style={{ ...s.section, borderBottom: 'none', paddingBottom: 0 }}>
|
|
333
|
+
<label style={s.label}>Add Component</label>
|
|
334
|
+
<div style={{ display: 'flex', gap: 6 }}>
|
|
199
335
|
<select
|
|
200
|
-
|
|
336
|
+
style={s.select}
|
|
201
337
|
value={addComponentType}
|
|
202
338
|
onChange={e => setAddComponentType(e.target.value)}
|
|
203
339
|
>
|
|
@@ -206,7 +342,10 @@ function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransfo
|
|
|
206
342
|
))}
|
|
207
343
|
</select>
|
|
208
344
|
<button
|
|
209
|
-
|
|
345
|
+
style={{
|
|
346
|
+
...s.addButton,
|
|
347
|
+
...(!addComponentType ? s.disabled : null),
|
|
348
|
+
}}
|
|
210
349
|
disabled={!addComponentType}
|
|
211
350
|
onClick={() => {
|
|
212
351
|
if (!addComponentType) return;
|
|
@@ -222,6 +361,14 @@ function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransfo
|
|
|
222
361
|
}));
|
|
223
362
|
}
|
|
224
363
|
}}
|
|
364
|
+
onPointerEnter={(e) => {
|
|
365
|
+
if (!addComponentType) return;
|
|
366
|
+
(e.currentTarget as HTMLButtonElement).style.background = 'rgba(255,255,255,0.12)';
|
|
367
|
+
}}
|
|
368
|
+
onPointerLeave={(e) => {
|
|
369
|
+
if (!addComponentType) return;
|
|
370
|
+
(e.currentTarget as HTMLButtonElement).style.background = 'rgba(255,255,255,0.08)';
|
|
371
|
+
}}
|
|
225
372
|
>
|
|
226
373
|
+
|
|
227
374
|
</button>
|