react-three-game 0.0.18 → 0.0.20
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/assetviewer/page.js +24 -13
- 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 +8 -3
- package/src/index.ts +5 -1
- package/src/tools/assetviewer/page.tsx +22 -12
- 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
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { Canvas, useLoader } from "@react-three/fiber";
|
|
4
|
-
import { OrbitControls,
|
|
4
|
+
import { OrbitControls, Stage, View, PerspectiveCamera } from "@react-three/drei";
|
|
5
5
|
import { Suspense, useEffect, useState, useRef } from "react";
|
|
6
6
|
import { TextureLoader } from "three";
|
|
7
|
+
import { loadModel } from "../dragdrop/modelLoader";
|
|
7
8
|
// view models and textures in manifest, onselect callback
|
|
8
9
|
function getItemsInPath(files, currentPath) {
|
|
9
10
|
// Remove the leading category folder (e.g., /textures/, /models/, /sounds/)
|
|
@@ -100,18 +101,28 @@ function ModelCard({ file, onSelect, basePath = "" }) {
|
|
|
100
101
|
return (_jsxs("div", { ref: ref, className: "aspect-square bg-gray-900 cursor-pointer hover:bg-gray-800 flex flex-col", onClick: () => onSelect(file), children: [_jsx("div", { className: "flex-1 relative", children: isInView ? (_jsxs(View, { className: "w-full h-full", children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 1, 3], fov: 50 }), _jsxs(Suspense, { fallback: null, children: [_jsx(Stage, { intensity: 0.5, environment: "city", children: _jsx(ModelPreview, { url: fullPath, onError: () => setError(true) }) }), _jsx(OrbitControls, { enableZoom: false })] })] })) : null }), _jsx("div", { className: "bg-black/60 text-[10px] px-1 truncate text-center", children: file.split('/').pop() })] }));
|
|
101
102
|
}
|
|
102
103
|
function ModelPreview({ url, onError }) {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
104
|
+
const [model, setModel] = useState(null);
|
|
105
|
+
const onErrorRef = useRef(onError);
|
|
106
|
+
onErrorRef.current = onError;
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
let cancelled = false;
|
|
109
|
+
setModel(null);
|
|
110
|
+
loadModel(url).then((result) => {
|
|
111
|
+
var _a;
|
|
112
|
+
if (cancelled)
|
|
113
|
+
return;
|
|
114
|
+
if (result.success && result.model) {
|
|
115
|
+
setModel(result.model);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
(_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
return () => { cancelled = true; };
|
|
122
|
+
}, [url]);
|
|
123
|
+
if (!model)
|
|
124
|
+
return null;
|
|
125
|
+
return _jsx("primitive", { object: model });
|
|
115
126
|
}
|
|
116
127
|
export function SoundListViewer({ files, selected, onSelect, basePath = "" }) {
|
|
117
128
|
return (_jsx(AssetListViewer, { files: files, selected: selected, onSelect: onSelect, renderCard: (file, onSelectHandler) => (_jsx(SoundCard, { file: file, basePath: basePath, onSelect: onSelectHandler })) }));
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { Dispatch, SetStateAction } from 'react';
|
|
2
2
|
import { Prefab } from "./types";
|
|
3
|
-
|
|
3
|
+
export default function EditorTree({ prefabData, setPrefabData, selectedId, setSelectedId }: {
|
|
4
4
|
prefabData?: Prefab;
|
|
5
5
|
setPrefabData?: Dispatch<SetStateAction<Prefab>>;
|
|
6
6
|
selectedId: string | null;
|
|
7
7
|
setSelectedId: Dispatch<SetStateAction<string | null>>;
|
|
8
|
-
}
|
|
9
|
-
export default function EditorTree({ prefabData, setPrefabData, selectedId, setSelectedId }: EditorTreeProps): import("react/jsx-runtime").JSX.Element | null;
|
|
10
|
-
export {};
|
|
8
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -1,110 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { getComponent } from './components/ComponentRegistry';
|
|
4
|
+
import { base, tree, menu } from './styles';
|
|
5
|
+
import { findNode, findParent, deleteNode, cloneNode } from './utils';
|
|
4
6
|
export default function EditorTree({ prefabData, setPrefabData, selectedId, setSelectedId }) {
|
|
5
7
|
const [contextMenu, setContextMenu] = useState(null);
|
|
6
8
|
const [draggedId, setDraggedId] = useState(null);
|
|
7
9
|
const [collapsedIds, setCollapsedIds] = useState(new Set());
|
|
8
|
-
const [
|
|
9
|
-
const styles = {
|
|
10
|
-
panel: {
|
|
11
|
-
background: "rgba(0,0,0,0.55)",
|
|
12
|
-
color: "rgba(255,255,255,0.9)",
|
|
13
|
-
border: "1px solid rgba(255,255,255,0.12)",
|
|
14
|
-
borderRadius: 6,
|
|
15
|
-
overflow: "hidden",
|
|
16
|
-
maxHeight: "85vh",
|
|
17
|
-
display: "flex",
|
|
18
|
-
flexDirection: "column",
|
|
19
|
-
backdropFilter: "blur(6px)",
|
|
20
|
-
WebkitBackdropFilter: "blur(6px)",
|
|
21
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
|
|
22
|
-
fontSize: 11,
|
|
23
|
-
lineHeight: 1.2,
|
|
24
|
-
userSelect: "none",
|
|
25
|
-
WebkitUserSelect: "none",
|
|
26
|
-
},
|
|
27
|
-
panelHeader: {
|
|
28
|
-
padding: "4px 6px",
|
|
29
|
-
borderBottom: "1px solid rgba(255,255,255,0.10)",
|
|
30
|
-
display: "flex",
|
|
31
|
-
gap: 8,
|
|
32
|
-
alignItems: "center",
|
|
33
|
-
justifyContent: "space-between",
|
|
34
|
-
cursor: "pointer",
|
|
35
|
-
background: "rgba(255,255,255,0.05)",
|
|
36
|
-
textTransform: "uppercase",
|
|
37
|
-
letterSpacing: "0.08em",
|
|
38
|
-
fontSize: 10,
|
|
39
|
-
color: "rgba(255,255,255,0.7)",
|
|
40
|
-
},
|
|
41
|
-
scroll: {
|
|
42
|
-
overflowY: "auto",
|
|
43
|
-
},
|
|
44
|
-
row: {
|
|
45
|
-
display: "flex",
|
|
46
|
-
alignItems: "center",
|
|
47
|
-
padding: "2px 6px",
|
|
48
|
-
borderBottom: "1px solid rgba(255,255,255,0.07)",
|
|
49
|
-
cursor: "pointer",
|
|
50
|
-
whiteSpace: "nowrap",
|
|
51
|
-
},
|
|
52
|
-
rowSelected: {
|
|
53
|
-
background: "rgba(255,255,255,0.10)",
|
|
54
|
-
},
|
|
55
|
-
chevron: {
|
|
56
|
-
width: 12,
|
|
57
|
-
textAlign: "center",
|
|
58
|
-
opacity: 0.55,
|
|
59
|
-
fontSize: 10,
|
|
60
|
-
marginRight: 4,
|
|
61
|
-
cursor: "pointer",
|
|
62
|
-
},
|
|
63
|
-
idText: {
|
|
64
|
-
fontSize: 11,
|
|
65
|
-
overflow: "hidden",
|
|
66
|
-
textOverflow: "ellipsis",
|
|
67
|
-
},
|
|
68
|
-
dragHandle: {
|
|
69
|
-
width: 14,
|
|
70
|
-
height: 14,
|
|
71
|
-
display: "flex",
|
|
72
|
-
alignItems: "center",
|
|
73
|
-
justifyContent: "center",
|
|
74
|
-
marginRight: 4,
|
|
75
|
-
opacity: 0.4,
|
|
76
|
-
cursor: "grab",
|
|
77
|
-
fontSize: 10,
|
|
78
|
-
},
|
|
79
|
-
contextMenu: {
|
|
80
|
-
position: "fixed",
|
|
81
|
-
zIndex: 50,
|
|
82
|
-
minWidth: 120,
|
|
83
|
-
background: "rgba(0,0,0,0.82)",
|
|
84
|
-
border: "1px solid rgba(255,255,255,0.16)",
|
|
85
|
-
borderRadius: 6,
|
|
86
|
-
overflow: "hidden",
|
|
87
|
-
boxShadow: "0 12px 32px rgba(0,0,0,0.45)",
|
|
88
|
-
backdropFilter: "blur(6px)",
|
|
89
|
-
WebkitBackdropFilter: "blur(6px)",
|
|
90
|
-
},
|
|
91
|
-
menuItem: {
|
|
92
|
-
width: "100%",
|
|
93
|
-
textAlign: "left",
|
|
94
|
-
padding: "6px 8px",
|
|
95
|
-
background: "transparent",
|
|
96
|
-
border: "none",
|
|
97
|
-
color: "rgba(255,255,255,0.9)",
|
|
98
|
-
font: "inherit",
|
|
99
|
-
cursor: "pointer",
|
|
100
|
-
},
|
|
101
|
-
menuItemDanger: {
|
|
102
|
-
color: "rgba(255,120,120,0.95)",
|
|
103
|
-
},
|
|
104
|
-
menuDivider: {
|
|
105
|
-
borderTop: "1px solid rgba(255,255,255,0.10)",
|
|
106
|
-
}
|
|
107
|
-
};
|
|
10
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
108
11
|
if (!prefabData || !setPrefabData)
|
|
109
12
|
return null;
|
|
110
13
|
const handleContextMenu = (e, nodeId) => {
|
|
@@ -112,15 +15,11 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
112
15
|
e.stopPropagation();
|
|
113
16
|
setContextMenu({ x: e.clientX, y: e.clientY, nodeId });
|
|
114
17
|
};
|
|
115
|
-
const closeContextMenu = () => setContextMenu(null);
|
|
116
18
|
const toggleCollapse = (e, id) => {
|
|
117
19
|
e.stopPropagation();
|
|
118
20
|
setCollapsedIds(prev => {
|
|
119
21
|
const next = new Set(prev);
|
|
120
|
-
|
|
121
|
-
next.delete(id);
|
|
122
|
-
else
|
|
123
|
-
next.add(id);
|
|
22
|
+
next.has(id) ? next.delete(id) : next.add(id);
|
|
124
23
|
return next;
|
|
125
24
|
});
|
|
126
25
|
};
|
|
@@ -137,7 +36,7 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
137
36
|
}
|
|
138
37
|
};
|
|
139
38
|
setPrefabData(prev => {
|
|
140
|
-
const newRoot = JSON.parse(JSON.stringify(prev.root));
|
|
39
|
+
const newRoot = JSON.parse(JSON.stringify(prev.root));
|
|
141
40
|
const parent = findNode(newRoot, parentId);
|
|
142
41
|
if (parent) {
|
|
143
42
|
parent.children = parent.children || [];
|
|
@@ -145,11 +44,11 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
145
44
|
}
|
|
146
45
|
return Object.assign(Object.assign({}, prev), { root: newRoot });
|
|
147
46
|
});
|
|
148
|
-
|
|
47
|
+
setContextMenu(null);
|
|
149
48
|
};
|
|
150
49
|
const handleDuplicate = (nodeId) => {
|
|
151
50
|
if (nodeId === prefabData.root.id)
|
|
152
|
-
return;
|
|
51
|
+
return;
|
|
153
52
|
setPrefabData(prev => {
|
|
154
53
|
const newRoot = JSON.parse(JSON.stringify(prev.root));
|
|
155
54
|
const parent = findParent(newRoot, nodeId);
|
|
@@ -161,18 +60,15 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
161
60
|
}
|
|
162
61
|
return Object.assign(Object.assign({}, prev), { root: newRoot });
|
|
163
62
|
});
|
|
164
|
-
|
|
63
|
+
setContextMenu(null);
|
|
165
64
|
};
|
|
166
65
|
const handleDelete = (nodeId) => {
|
|
167
66
|
if (nodeId === prefabData.root.id)
|
|
168
|
-
return;
|
|
169
|
-
setPrefabData(prev => {
|
|
170
|
-
const newRoot = deleteNodeFromTree(JSON.parse(JSON.stringify(prev.root)), nodeId);
|
|
171
|
-
return Object.assign(Object.assign({}, prev), { root: newRoot });
|
|
172
|
-
});
|
|
67
|
+
return;
|
|
68
|
+
setPrefabData(prev => (Object.assign(Object.assign({}, prev), { root: deleteNode(prev.root, nodeId) })));
|
|
173
69
|
if (selectedId === nodeId)
|
|
174
70
|
setSelectedId(null);
|
|
175
|
-
|
|
71
|
+
setContextMenu(null);
|
|
176
72
|
};
|
|
177
73
|
// Drag and Drop
|
|
178
74
|
const handleDragStart = (e, id) => {
|
|
@@ -181,12 +77,8 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
181
77
|
return;
|
|
182
78
|
}
|
|
183
79
|
e.dataTransfer.effectAllowed = "move";
|
|
184
|
-
e.dataTransfer.setData("text/plain", id);
|
|
185
80
|
setDraggedId(id);
|
|
186
81
|
};
|
|
187
|
-
const handleDragEnd = () => {
|
|
188
|
-
setDraggedId(null);
|
|
189
|
-
};
|
|
190
82
|
const handleDragOver = (e, targetId) => {
|
|
191
83
|
if (!draggedId || draggedId === targetId)
|
|
192
84
|
return;
|
|
@@ -194,7 +86,6 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
194
86
|
if (draggedNode && findNode(draggedNode, targetId))
|
|
195
87
|
return;
|
|
196
88
|
e.preventDefault();
|
|
197
|
-
e.dataTransfer.dropEffect = "move";
|
|
198
89
|
};
|
|
199
90
|
const handleDrop = (e, targetId) => {
|
|
200
91
|
if (!draggedId || draggedId === targetId)
|
|
@@ -228,79 +119,14 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
228
119
|
const isSelected = node.id === selectedId;
|
|
229
120
|
const isCollapsed = collapsedIds.has(node.id);
|
|
230
121
|
const hasChildren = node.children && node.children.length > 0;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}, onPointerLeave: (e) => {
|
|
240
|
-
e.currentTarget.style.opacity = "0.55";
|
|
241
|
-
}, children: isCollapsed ? '▶' : '▼' }), node.id !== prefabData.root.id && (_jsx("span", { style: styles.dragHandle, onPointerEnter: (e) => {
|
|
242
|
-
e.currentTarget.style.opacity = "0.9";
|
|
243
|
-
}, onPointerLeave: (e) => {
|
|
244
|
-
e.currentTarget.style.opacity = "0.4";
|
|
245
|
-
}, children: "\u22EE\u22EE" })), _jsx("span", { style: styles.idText, children: node.id })] }), !isCollapsed && node.children && (_jsx("div", { children: node.children.map(child => renderNode(child, depth + 1)) }))] }, node.id));
|
|
122
|
+
const isRoot = node.id === prefabData.root.id;
|
|
123
|
+
return (_jsxs("div", { children: [_jsxs("div", { style: Object.assign(Object.assign(Object.assign({}, tree.row), (isSelected ? tree.selected : {})), { paddingLeft: `${depth * 12 + 6}px` }), draggable: !isRoot, onClick: (e) => { e.stopPropagation(); setSelectedId(node.id); }, onContextMenu: (e) => handleContextMenu(e, node.id), onDragStart: (e) => handleDragStart(e, node.id), onDragEnd: () => setDraggedId(null), onDragOver: (e) => handleDragOver(e, node.id), onDrop: (e) => handleDrop(e, node.id), children: [_jsx("span", { style: {
|
|
124
|
+
width: 12,
|
|
125
|
+
opacity: 0.6,
|
|
126
|
+
marginRight: 4,
|
|
127
|
+
cursor: 'pointer',
|
|
128
|
+
visibility: hasChildren ? 'visible' : 'hidden'
|
|
129
|
+
}, onClick: (e) => hasChildren && toggleCollapse(e, node.id), children: isCollapsed ? '▶' : '▼' }), !isRoot && _jsx("span", { style: { marginRight: 4, opacity: 0.4 }, children: "\u22EE\u22EE" }), _jsx("span", { style: { overflow: 'hidden', textOverflow: 'ellipsis' }, children: node.id })] }), !isCollapsed && node.children && node.children.map(child => renderNode(child, depth + 1))] }, node.id));
|
|
246
130
|
};
|
|
247
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { style: Object.assign(Object.assign({},
|
|
248
|
-
e.currentTarget.style.background = "rgba(255,255,255,0.08)";
|
|
249
|
-
}, onPointerLeave: (e) => {
|
|
250
|
-
e.currentTarget.style.background = "rgba(255,255,255,0.05)";
|
|
251
|
-
}, children: [_jsx("span", { children: "Prefab Graph" }), _jsx("span", { style: { fontSize: 10, opacity: 0.8 }, children: isTreeCollapsed ? '▶' : '◀' })] }), !isTreeCollapsed && (_jsx("div", { style: Object.assign(Object.assign({}, styles.scroll), { padding: 2 }), children: renderNode(prefabData.root) }))] }), contextMenu && (_jsxs("div", { style: Object.assign(Object.assign({}, styles.contextMenu), { top: contextMenu.y, left: contextMenu.x }), onClick: (e) => e.stopPropagation(), onPointerLeave: closeContextMenu, children: [_jsx("button", { style: Object.assign(Object.assign({}, styles.menuItem), styles.menuDivider), onClick: () => handleAddChild(contextMenu.nodeId), onPointerEnter: (e) => {
|
|
252
|
-
e.currentTarget.style.background = "rgba(255,255,255,0.08)";
|
|
253
|
-
}, onPointerLeave: (e) => {
|
|
254
|
-
e.currentTarget.style.background = "transparent";
|
|
255
|
-
}, children: "Add Child" }), contextMenu.nodeId !== prefabData.root.id && (_jsxs(_Fragment, { children: [_jsx("button", { style: Object.assign(Object.assign({}, styles.menuItem), styles.menuDivider), onClick: () => handleDuplicate(contextMenu.nodeId), onPointerEnter: (e) => {
|
|
256
|
-
e.currentTarget.style.background = "rgba(255,255,255,0.08)";
|
|
257
|
-
}, onPointerLeave: (e) => {
|
|
258
|
-
e.currentTarget.style.background = "transparent";
|
|
259
|
-
}, children: "Duplicate" }), _jsx("button", { style: Object.assign(Object.assign({}, styles.menuItem), styles.menuItemDanger), onClick: () => handleDelete(contextMenu.nodeId), onPointerEnter: (e) => {
|
|
260
|
-
e.currentTarget.style.background = "rgba(255,255,255,0.08)";
|
|
261
|
-
}, onPointerLeave: (e) => {
|
|
262
|
-
e.currentTarget.style.background = "transparent";
|
|
263
|
-
}, children: "Delete" })] }))] }))] }));
|
|
264
|
-
}
|
|
265
|
-
// --- Helpers ---
|
|
266
|
-
function findNode(root, id) {
|
|
267
|
-
if (root.id === id)
|
|
268
|
-
return root;
|
|
269
|
-
if (root.children) {
|
|
270
|
-
for (const child of root.children) {
|
|
271
|
-
const found = findNode(child, id);
|
|
272
|
-
if (found)
|
|
273
|
-
return found;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
return null;
|
|
277
|
-
}
|
|
278
|
-
function findParent(root, id) {
|
|
279
|
-
if (!root.children)
|
|
280
|
-
return null;
|
|
281
|
-
for (const child of root.children) {
|
|
282
|
-
if (child.id === id)
|
|
283
|
-
return root;
|
|
284
|
-
const found = findParent(child, id);
|
|
285
|
-
if (found)
|
|
286
|
-
return found;
|
|
287
|
-
}
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
function deleteNodeFromTree(root, id) {
|
|
291
|
-
if (root.id === id)
|
|
292
|
-
return null;
|
|
293
|
-
if (root.children) {
|
|
294
|
-
root.children = root.children
|
|
295
|
-
.map(child => deleteNodeFromTree(child, id))
|
|
296
|
-
.filter((child) => child !== null);
|
|
297
|
-
}
|
|
298
|
-
return root;
|
|
299
|
-
}
|
|
300
|
-
function cloneNode(node) {
|
|
301
|
-
const newNode = Object.assign(Object.assign({}, node), { id: crypto.randomUUID() });
|
|
302
|
-
if (newNode.children) {
|
|
303
|
-
newNode.children = newNode.children.map(child => cloneNode(child));
|
|
304
|
-
}
|
|
305
|
-
return newNode;
|
|
131
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { style: Object.assign(Object.assign({}, tree.panel), { width: collapsed ? 'auto' : 224 }), onClick: () => setContextMenu(null), children: [_jsxs("div", { style: base.header, onClick: () => setCollapsed(!collapsed), children: [_jsx("span", { children: "Scene" }), _jsx("span", { children: collapsed ? '▶' : '◀' })] }), !collapsed && _jsx("div", { style: tree.scroll, children: renderNode(prefabData.root) })] }), contextMenu && (_jsxs("div", { style: Object.assign(Object.assign({}, menu.container), { top: contextMenu.y, left: contextMenu.x }), onClick: (e) => e.stopPropagation(), onPointerLeave: () => setContextMenu(null), children: [_jsx("button", { style: menu.item, onClick: () => handleAddChild(contextMenu.nodeId), children: "Add Child" }), contextMenu.nodeId !== prefabData.root.id && (_jsxs(_Fragment, { children: [_jsx("button", { style: menu.item, onClick: () => handleDuplicate(contextMenu.nodeId), children: "Duplicate" }), _jsx("button", { style: Object.assign(Object.assign({}, menu.item), menu.danger), onClick: () => handleDelete(contextMenu.nodeId), children: "Delete" })] }))] }))] }));
|
|
306
132
|
}
|
|
@@ -1,244 +1,63 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
1
12
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
13
|
import { useState, useEffect } from 'react';
|
|
3
14
|
import EditorTree from './EditorTree';
|
|
4
15
|
import { getAllComponents } from './components/ComponentRegistry';
|
|
16
|
+
import { base, inspector } from './styles';
|
|
17
|
+
import { findNode, updateNode, deleteNode } from './utils';
|
|
5
18
|
function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transformMode, setTransformMode, basePath }) {
|
|
6
|
-
const [
|
|
7
|
-
const
|
|
8
|
-
panel: {
|
|
9
|
-
position: 'absolute',
|
|
10
|
-
top: 8,
|
|
11
|
-
right: 8,
|
|
12
|
-
zIndex: 20,
|
|
13
|
-
width: 260,
|
|
14
|
-
background: 'rgba(0,0,0,0.55)',
|
|
15
|
-
color: 'rgba(255,255,255,0.9)',
|
|
16
|
-
border: '1px solid rgba(255,255,255,0.12)',
|
|
17
|
-
borderRadius: 6,
|
|
18
|
-
overflow: 'hidden',
|
|
19
|
-
backdropFilter: 'blur(6px)',
|
|
20
|
-
WebkitBackdropFilter: 'blur(6px)',
|
|
21
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
|
|
22
|
-
fontSize: 11,
|
|
23
|
-
lineHeight: 1.2,
|
|
24
|
-
},
|
|
25
|
-
header: {
|
|
26
|
-
padding: '4px 6px',
|
|
27
|
-
display: 'flex',
|
|
28
|
-
alignItems: 'center',
|
|
29
|
-
justifyContent: 'space-between',
|
|
30
|
-
cursor: 'pointer',
|
|
31
|
-
background: 'rgba(255,255,255,0.05)',
|
|
32
|
-
borderBottom: '1px solid rgba(255,255,255,0.10)',
|
|
33
|
-
textTransform: 'uppercase',
|
|
34
|
-
letterSpacing: '0.08em',
|
|
35
|
-
fontSize: 10,
|
|
36
|
-
color: 'rgba(255,255,255,0.7)',
|
|
37
|
-
userSelect: 'none',
|
|
38
|
-
WebkitUserSelect: 'none',
|
|
39
|
-
},
|
|
40
|
-
left: {
|
|
41
|
-
position: 'absolute',
|
|
42
|
-
top: 8,
|
|
43
|
-
left: 8,
|
|
44
|
-
zIndex: 20,
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
const updateNode = (updater) => {
|
|
19
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
20
|
+
const updateNodeHandler = (updater) => {
|
|
48
21
|
if (!prefabData || !setPrefabData || !selectedId)
|
|
49
22
|
return;
|
|
50
|
-
setPrefabData(prev => (Object.assign(Object.assign({}, prev), { root:
|
|
23
|
+
setPrefabData(prev => (Object.assign(Object.assign({}, prev), { root: updateNode(prev.root, selectedId, updater) })));
|
|
51
24
|
};
|
|
52
|
-
const
|
|
53
|
-
if (!prefabData || !setPrefabData || !selectedId)
|
|
54
|
-
return;
|
|
55
|
-
if (selectedId === prefabData.root.id) {
|
|
56
|
-
alert("Cannot delete root node");
|
|
25
|
+
const deleteNodeHandler = () => {
|
|
26
|
+
if (!prefabData || !setPrefabData || !selectedId || selectedId === prefabData.root.id)
|
|
57
27
|
return;
|
|
58
|
-
}
|
|
59
|
-
setPrefabData(prev => {
|
|
60
|
-
const newRoot = deletePrefabNode(prev.root, selectedId);
|
|
61
|
-
return Object.assign(Object.assign({}, prev), { root: newRoot });
|
|
62
|
-
});
|
|
28
|
+
setPrefabData(prev => (Object.assign(Object.assign({}, prev), { root: deleteNode(prev.root, selectedId) })));
|
|
63
29
|
setSelectedId(null);
|
|
64
30
|
};
|
|
65
31
|
const selectedNode = selectedId && prefabData ? findNode(prefabData.root, selectedId) : null;
|
|
66
|
-
|
|
67
|
-
return _jsxs(_Fragment, { children: [_jsxs("div", { style: ui.panel, children: [_jsxs("div", { style: ui.header, onClick: () => setIsInspectorCollapsed(!isInspectorCollapsed), onPointerEnter: (e) => {
|
|
68
|
-
e.currentTarget.style.background = 'rgba(255,255,255,0.08)';
|
|
69
|
-
}, onPointerLeave: (e) => {
|
|
70
|
-
e.currentTarget.style.background = 'rgba(255,255,255,0.05)';
|
|
71
|
-
}, children: [_jsx("span", { children: "Inspector" }), _jsx("span", { style: { fontSize: 10, opacity: 0.8 }, children: isInspectorCollapsed ? '◀' : '▶' })] }), !isInspectorCollapsed && selectedNode && (_jsx(NodeInspector, { node: selectedNode, updateNode: updateNode, deleteNode: deleteNode, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath }))] }), _jsx("div", { style: ui.left, children: _jsx(EditorTree, { prefabData: prefabData, setPrefabData: setPrefabData, selectedId: selectedId, setSelectedId: setSelectedId }) })] });
|
|
32
|
+
return _jsxs(_Fragment, { children: [_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, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath }))] }), _jsx("div", { style: { position: 'absolute', top: 8, left: 8, zIndex: 20 }, children: _jsx(EditorTree, { prefabData: prefabData, setPrefabData: setPrefabData, selectedId: selectedId, setSelectedId: setSelectedId }) })] });
|
|
72
33
|
}
|
|
73
34
|
function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransformMode, basePath }) {
|
|
74
35
|
const ALL_COMPONENTS = getAllComponents();
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
root: {
|
|
79
|
-
display: 'flex',
|
|
80
|
-
flexDirection: 'column',
|
|
81
|
-
gap: 6,
|
|
82
|
-
padding: 6,
|
|
83
|
-
maxHeight: '80vh',
|
|
84
|
-
overflowY: 'auto',
|
|
85
|
-
},
|
|
86
|
-
section: {
|
|
87
|
-
paddingBottom: 6,
|
|
88
|
-
borderBottom: '1px solid rgba(255,255,255,0.10)',
|
|
89
|
-
},
|
|
90
|
-
label: {
|
|
91
|
-
display: 'block',
|
|
92
|
-
fontSize: 10,
|
|
93
|
-
opacity: 0.7,
|
|
94
|
-
textTransform: 'uppercase',
|
|
95
|
-
letterSpacing: '0.08em',
|
|
96
|
-
marginBottom: 4,
|
|
97
|
-
},
|
|
98
|
-
input: {
|
|
99
|
-
width: '100%',
|
|
100
|
-
background: 'rgba(255,255,255,0.06)',
|
|
101
|
-
border: '1px solid rgba(255,255,255,0.14)',
|
|
102
|
-
borderRadius: 4,
|
|
103
|
-
padding: '4px 6px',
|
|
104
|
-
color: 'rgba(255,255,255,0.92)',
|
|
105
|
-
font: 'inherit',
|
|
106
|
-
outline: 'none',
|
|
107
|
-
},
|
|
108
|
-
row: {
|
|
109
|
-
display: 'flex',
|
|
110
|
-
alignItems: 'center',
|
|
111
|
-
justifyContent: 'space-between',
|
|
112
|
-
gap: 8,
|
|
113
|
-
},
|
|
114
|
-
button: {
|
|
115
|
-
padding: '2px 6px',
|
|
116
|
-
background: 'transparent',
|
|
117
|
-
color: 'rgba(255,255,255,0.9)',
|
|
118
|
-
border: '1px solid rgba(255,255,255,0.14)',
|
|
119
|
-
borderRadius: 4,
|
|
120
|
-
cursor: 'pointer',
|
|
121
|
-
font: 'inherit',
|
|
122
|
-
},
|
|
123
|
-
buttonActive: {
|
|
124
|
-
background: 'rgba(255,255,255,0.10)',
|
|
125
|
-
},
|
|
126
|
-
smallDanger: {
|
|
127
|
-
background: 'transparent',
|
|
128
|
-
border: 'none',
|
|
129
|
-
cursor: 'pointer',
|
|
130
|
-
color: 'rgba(255,120,120,0.95)',
|
|
131
|
-
font: 'inherit',
|
|
132
|
-
padding: '2px 4px',
|
|
133
|
-
},
|
|
134
|
-
componentHeader: {
|
|
135
|
-
display: 'flex',
|
|
136
|
-
alignItems: 'center',
|
|
137
|
-
justifyContent: 'space-between',
|
|
138
|
-
padding: '4px 0',
|
|
139
|
-
borderBottom: '1px solid rgba(255,255,255,0.08)',
|
|
140
|
-
marginBottom: 4,
|
|
141
|
-
},
|
|
142
|
-
componentTitle: {
|
|
143
|
-
fontSize: 10,
|
|
144
|
-
textTransform: 'uppercase',
|
|
145
|
-
letterSpacing: '0.08em',
|
|
146
|
-
opacity: 0.8,
|
|
147
|
-
},
|
|
148
|
-
select: {
|
|
149
|
-
flex: 1,
|
|
150
|
-
background: 'rgba(255,255,255,0.06)',
|
|
151
|
-
border: '1px solid rgba(255,255,255,0.14)',
|
|
152
|
-
borderRadius: 4,
|
|
153
|
-
padding: '4px 6px',
|
|
154
|
-
color: 'rgba(255,255,255,0.92)',
|
|
155
|
-
font: 'inherit',
|
|
156
|
-
outline: 'none',
|
|
157
|
-
},
|
|
158
|
-
addButton: {
|
|
159
|
-
width: 28,
|
|
160
|
-
padding: '4px 0',
|
|
161
|
-
background: 'rgba(255,255,255,0.08)',
|
|
162
|
-
color: 'rgba(255,255,255,0.92)',
|
|
163
|
-
border: '1px solid rgba(255,255,255,0.14)',
|
|
164
|
-
borderRadius: 4,
|
|
165
|
-
cursor: 'pointer',
|
|
166
|
-
font: 'inherit',
|
|
167
|
-
},
|
|
168
|
-
disabled: {
|
|
169
|
-
opacity: 0.35,
|
|
170
|
-
cursor: 'not-allowed',
|
|
171
|
-
},
|
|
172
|
-
};
|
|
173
|
-
const componentKeys = Object.keys(node.components || {}).join(',');
|
|
36
|
+
const allKeys = Object.keys(ALL_COMPONENTS);
|
|
37
|
+
const available = allKeys.filter(k => { var _a; return !((_a = node.components) === null || _a === void 0 ? void 0 : _a[k.toLowerCase()]); });
|
|
38
|
+
const [addType, setAddType] = useState(available[0] || "");
|
|
174
39
|
useEffect(() => {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
delete components[key];
|
|
192
|
-
return Object.assign(Object.assign({}, n), { components });
|
|
193
|
-
}), style: s.smallDanger, title: "Remove component", children: "\u2715" })] }), EditorComp ? (_jsx(EditorComp, { component: comp, 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, transformMode: transformMode, setTransformMode: setTransformMode })) : null] }, key));
|
|
194
|
-
}), _jsxs("div", { style: Object.assign(Object.assign({}, s.section), { borderBottom: 'none', paddingBottom: 0 }), children: [_jsx("label", { style: s.label, children: "Add Component" }), _jsxs("div", { style: { display: 'flex', gap: 6 }, children: [_jsx("select", { style: s.select, value: addComponentType, onChange: e => setAddComponentType(e.target.value), children: allComponentKeys.filter(k => { var _a; return !((_a = node.components) === null || _a === void 0 ? void 0 : _a[k.toLowerCase()]); }).map(k => (_jsx("option", { value: k, children: k }, k))) }), _jsx("button", { style: Object.assign(Object.assign({}, s.addButton), (!addComponentType ? s.disabled : null)), disabled: !addComponentType, onClick: () => {
|
|
195
|
-
var _a;
|
|
196
|
-
if (!addComponentType)
|
|
40
|
+
const newAvailable = allKeys.filter(k => { var _a; return !((_a = node.components) === null || _a === void 0 ? void 0 : _a[k.toLowerCase()]); });
|
|
41
|
+
if (!newAvailable.includes(addType))
|
|
42
|
+
setAddType(newAvailable[0] || "");
|
|
43
|
+
}, [Object.keys(node.components || {}).join(',')]);
|
|
44
|
+
return _jsxs("div", { style: inspector.content, children: [_jsxs("div", { style: base.section, children: [_jsx("div", { style: base.label, children: "Node ID" }), _jsx("input", { style: base.input, value: node.id, onChange: e => updateNode(n => (Object.assign(Object.assign({}, n), { id: e.target.value }))) })] }), _jsxs("div", { style: base.section, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }, children: [_jsx("div", { style: base.label, children: "Components" }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), base.btnDanger), onClick: deleteNode, children: "Delete Node" })] }), node.components && Object.entries(node.components).map(([key, comp]) => {
|
|
45
|
+
if (!comp)
|
|
46
|
+
return null;
|
|
47
|
+
const def = ALL_COMPONENTS[comp.type];
|
|
48
|
+
if (!def)
|
|
49
|
+
return _jsxs("div", { style: { color: '#ff8888', fontSize: 11 }, children: ["Unknown: ", comp.type] }, key);
|
|
50
|
+
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' }), onClick: () => updateNode(n => {
|
|
51
|
+
const _a = n.components || {}, _b = key, _ = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]);
|
|
52
|
+
return Object.assign(Object.assign({}, n), { components: rest });
|
|
53
|
+
}), children: "\u2715" })] }), def.Editor && (_jsx(def.Editor, { component: comp, 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, transformMode: transformMode, setTransformMode: setTransformMode }))] }, key));
|
|
54
|
+
})] }), available.length > 0 && (_jsxs("div", { children: [_jsx("div", { style: base.label, children: "Add Component" }), _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: () => {
|
|
55
|
+
if (!addType)
|
|
197
56
|
return;
|
|
198
|
-
const def = ALL_COMPONENTS[
|
|
199
|
-
if (def
|
|
200
|
-
|
|
201
|
-
updateNode(n => (Object.assign(Object.assign({}, n), { components: Object.assign(Object.assign({}, n.components), { [key]: { type: def.name, properties: def.defaultProperties } }) })));
|
|
57
|
+
const def = ALL_COMPONENTS[addType];
|
|
58
|
+
if (def) {
|
|
59
|
+
updateNode(n => (Object.assign(Object.assign({}, n), { components: Object.assign(Object.assign({}, n.components), { [addType.toLowerCase()]: { type: def.name, properties: def.defaultProperties } }) })));
|
|
202
60
|
}
|
|
203
|
-
},
|
|
204
|
-
if (!addComponentType)
|
|
205
|
-
return;
|
|
206
|
-
e.currentTarget.style.background = 'rgba(255,255,255,0.12)';
|
|
207
|
-
}, onPointerLeave: (e) => {
|
|
208
|
-
if (!addComponentType)
|
|
209
|
-
return;
|
|
210
|
-
e.currentTarget.style.background = 'rgba(255,255,255,0.08)';
|
|
211
|
-
}, children: "+" })] })] })] });
|
|
212
|
-
}
|
|
213
|
-
function findNode(root, id) {
|
|
214
|
-
if (root.id === id)
|
|
215
|
-
return root;
|
|
216
|
-
if (root.children) {
|
|
217
|
-
for (const child of root.children) {
|
|
218
|
-
const found = findNode(child, id);
|
|
219
|
-
if (found)
|
|
220
|
-
return found;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return null;
|
|
224
|
-
}
|
|
225
|
-
function updatePrefabNode(root, id, update) {
|
|
226
|
-
if (root.id === id) {
|
|
227
|
-
return update(root);
|
|
228
|
-
}
|
|
229
|
-
if (root.children) {
|
|
230
|
-
return Object.assign(Object.assign({}, root), { children: root.children.map(child => updatePrefabNode(child, id, update)) });
|
|
231
|
-
}
|
|
232
|
-
return root;
|
|
233
|
-
}
|
|
234
|
-
function deletePrefabNode(root, id) {
|
|
235
|
-
if (root.id === id)
|
|
236
|
-
return null;
|
|
237
|
-
if (root.children) {
|
|
238
|
-
return Object.assign(Object.assign({}, root), { children: root.children
|
|
239
|
-
.map(child => deletePrefabNode(child, id))
|
|
240
|
-
.filter((child) => child !== null) });
|
|
241
|
-
}
|
|
242
|
-
return root;
|
|
61
|
+
}, children: "+" })] })] }))] });
|
|
243
62
|
}
|
|
244
63
|
export default EditorUI;
|