react-three-game 0.0.67 → 0.0.69
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 +109 -304
- package/dist/index.d.ts +15 -8
- package/dist/index.js +11 -8
- package/dist/shared/GameCanvas.d.ts +1 -2
- package/dist/tools/prefabeditor/EditorContext.d.ts +2 -2
- package/dist/tools/prefabeditor/EditorTree.d.ts +6 -6
- package/dist/tools/prefabeditor/EditorTree.js +92 -142
- package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +4 -11
- package/dist/tools/prefabeditor/EditorTreeMenus.js +16 -25
- package/dist/tools/prefabeditor/EditorUI.d.ts +5 -5
- package/dist/tools/prefabeditor/EditorUI.js +14 -11
- package/dist/tools/prefabeditor/GameEvents.d.ts +0 -30
- package/dist/tools/prefabeditor/GameEvents.js +0 -7
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +12 -13
- package/dist/tools/prefabeditor/PrefabEditor.js +168 -138
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +8 -5
- package/dist/tools/prefabeditor/PrefabRoot.js +141 -123
- package/dist/tools/prefabeditor/components/AmbientLightComponent.js +3 -3
- package/dist/tools/prefabeditor/components/CameraComponent.js +2 -2
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +2 -2
- package/dist/tools/prefabeditor/components/ModelComponent.js +0 -1
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +2 -2
- package/dist/tools/prefabeditor/components/TextComponent.js +2 -3
- package/dist/tools/prefabeditor/components/TransformComponent.js +9 -14
- package/dist/tools/prefabeditor/prefabStore.d.ts +42 -0
- package/dist/tools/prefabeditor/prefabStore.js +347 -0
- package/dist/tools/prefabeditor/sceneApi.d.ts +44 -0
- package/dist/tools/prefabeditor/sceneApi.js +161 -0
- package/dist/tools/prefabeditor/styles.d.ts +2 -1
- package/dist/tools/prefabeditor/styles.js +2 -12
- package/dist/tools/prefabeditor/utils.d.ts +15 -36
- package/dist/tools/prefabeditor/utils.js +36 -162
- package/package.json +4 -3
- package/dist/tools/prefabeditor/EventSystem.d.ts +0 -7
- package/dist/tools/prefabeditor/EventSystem.js +0 -23
|
@@ -1,79 +1,26 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
2
|
+
import { memo, useCallback, useState } from 'react';
|
|
3
3
|
import { getComponent } from './components/ComponentRegistry';
|
|
4
4
|
import { base, colors, tree } from './styles';
|
|
5
|
-
import { findNode, findParent, deleteNode, cloneNode, updateNodeById } from './utils';
|
|
6
5
|
import { useEditorContext } from './EditorContext';
|
|
7
6
|
import { Dropdown } from './Dropdown';
|
|
8
|
-
import { FileMenu,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const oldParent = findParent(root, draggedId);
|
|
12
|
-
if (!draggedNode || !oldParent || findNode(draggedNode, targetId)) {
|
|
13
|
-
return root;
|
|
14
|
-
}
|
|
15
|
-
if (position === 'before') {
|
|
16
|
-
const targetParent = findParent(root, targetId);
|
|
17
|
-
if (!(targetParent === null || targetParent === void 0 ? void 0 : targetParent.children))
|
|
18
|
-
return root;
|
|
19
|
-
if (targetParent.id === oldParent.id) {
|
|
20
|
-
const siblings = targetParent.children.filter(child => child.id !== draggedId);
|
|
21
|
-
const targetIndex = siblings.findIndex(child => child.id === targetId);
|
|
22
|
-
if (targetIndex === -1)
|
|
23
|
-
return root;
|
|
24
|
-
siblings.splice(targetIndex, 0, draggedNode);
|
|
25
|
-
return updateNodeById(root, targetParent.id, parent => (Object.assign(Object.assign({}, parent), { children: siblings })));
|
|
26
|
-
}
|
|
27
|
-
const rootWithoutDragged = updateNodeById(root, oldParent.id, parent => {
|
|
28
|
-
var _a;
|
|
29
|
-
return (Object.assign(Object.assign({}, parent), { children: ((_a = parent.children) !== null && _a !== void 0 ? _a : []).filter(child => child.id !== draggedId) }));
|
|
30
|
-
});
|
|
31
|
-
return updateNodeById(rootWithoutDragged, targetParent.id, parent => {
|
|
32
|
-
var _a;
|
|
33
|
-
const children = [...((_a = parent.children) !== null && _a !== void 0 ? _a : [])];
|
|
34
|
-
const targetIndex = children.findIndex(child => child.id === targetId);
|
|
35
|
-
if (targetIndex === -1)
|
|
36
|
-
return parent;
|
|
37
|
-
children.splice(targetIndex, 0, draggedNode);
|
|
38
|
-
return Object.assign(Object.assign({}, parent), { children });
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
const rootWithoutDragged = updateNodeById(root, oldParent.id, parent => {
|
|
42
|
-
var _a;
|
|
43
|
-
return (Object.assign(Object.assign({}, parent), { children: ((_a = parent.children) !== null && _a !== void 0 ? _a : []).filter(child => child.id !== draggedId) }));
|
|
44
|
-
});
|
|
45
|
-
return updateNodeById(rootWithoutDragged, targetId, target => {
|
|
46
|
-
var _a;
|
|
47
|
-
return (Object.assign(Object.assign({}, target), { children: [...((_a = target.children) !== null && _a !== void 0 ? _a : []), draggedNode] }));
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
function duplicateNodeBelow(root, nodeId) {
|
|
51
|
-
const node = findNode(root, nodeId);
|
|
52
|
-
const parent = findParent(root, nodeId);
|
|
53
|
-
if (!node || !parent)
|
|
54
|
-
return null;
|
|
55
|
-
const duplicate = cloneNode(node);
|
|
56
|
-
const nextRoot = updateNodeById(root, parent.id, currentParent => (Object.assign(Object.assign({}, currentParent), { children: (() => {
|
|
57
|
-
var _a;
|
|
58
|
-
const children = [...((_a = currentParent.children) !== null && _a !== void 0 ? _a : [])];
|
|
59
|
-
const index = children.findIndex(child => child.id === nodeId);
|
|
60
|
-
if (index === -1)
|
|
61
|
-
return [...children, duplicate];
|
|
62
|
-
children.splice(index + 1, 0, duplicate);
|
|
63
|
-
return children;
|
|
64
|
-
})() })));
|
|
65
|
-
return { root: nextRoot, duplicatedId: duplicate.id };
|
|
66
|
-
}
|
|
67
|
-
export default function EditorTree({ prefabData, setPrefabData, selectedId, setSelectedId, onUndo, onRedo, canUndo, canRedo }) {
|
|
7
|
+
import { FileMenu, TreeContextMenu, TreeNodeMenu } from './EditorTreeMenus';
|
|
8
|
+
import { usePrefabChildIds, usePrefabNode, usePrefabRootId, usePrefabStore, usePrefabStoreApi } from './prefabStore';
|
|
9
|
+
export default function EditorTree({ selectedId, setSelectedId, getPrefab, onReplacePrefab, onImportPrefab, onUndo, onRedo, canUndo, canRedo }) {
|
|
68
10
|
const { onFocusNode } = useEditorContext();
|
|
11
|
+
const rootId = usePrefabRootId();
|
|
12
|
+
const store = usePrefabStoreApi();
|
|
13
|
+
const addChild = usePrefabStore(state => state.addChild);
|
|
14
|
+
const duplicateNode = usePrefabStore(state => state.duplicateNode);
|
|
15
|
+
const deleteNode = usePrefabStore(state => state.deleteNode);
|
|
16
|
+
const toggleNodeFlag = usePrefabStore(state => state.toggleNodeFlag);
|
|
17
|
+
const moveNode = usePrefabStore(state => state.moveNode);
|
|
69
18
|
const [draggedId, setDraggedId] = useState(null);
|
|
70
19
|
const [dropTarget, setDropTarget] = useState(null);
|
|
71
20
|
const [collapsedIds, setCollapsedIds] = useState(new Set());
|
|
72
21
|
const [collapsed, setCollapsed] = useState(false);
|
|
73
22
|
const [searchQuery, setSearchQuery] = useState('');
|
|
74
23
|
const [contextMenu, setContextMenu] = useState(null);
|
|
75
|
-
if (!prefabData || !setPrefabData)
|
|
76
|
-
return null;
|
|
77
24
|
const toggleCollapse = (e, id) => {
|
|
78
25
|
e.stopPropagation();
|
|
79
26
|
setCollapsedIds(prev => {
|
|
@@ -94,43 +41,32 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
94
41
|
}
|
|
95
42
|
}
|
|
96
43
|
};
|
|
97
|
-
|
|
98
|
-
var _a;
|
|
99
|
-
return (Object.assign(Object.assign({}, parent), { children: [...((_a = parent.children) !== null && _a !== void 0 ? _a : []), newNode] }));
|
|
100
|
-
}) })));
|
|
44
|
+
addChild(parentId, newNode);
|
|
101
45
|
setSelectedId(newNode.id);
|
|
102
46
|
};
|
|
103
47
|
const handleDuplicate = (nodeId) => {
|
|
104
|
-
if (nodeId ===
|
|
48
|
+
if (nodeId === rootId)
|
|
105
49
|
return;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return prev;
|
|
110
|
-
setSelectedId(result.duplicatedId);
|
|
111
|
-
return Object.assign(Object.assign({}, prev), { root: result.root });
|
|
112
|
-
});
|
|
50
|
+
const duplicatedId = duplicateNode(nodeId);
|
|
51
|
+
if (duplicatedId)
|
|
52
|
+
setSelectedId(duplicatedId);
|
|
113
53
|
};
|
|
114
54
|
const handleDelete = (nodeId) => {
|
|
115
|
-
if (nodeId ===
|
|
55
|
+
if (nodeId === rootId)
|
|
116
56
|
return;
|
|
117
|
-
|
|
57
|
+
deleteNode(nodeId);
|
|
118
58
|
if (selectedId === nodeId)
|
|
119
59
|
setSelectedId(null);
|
|
120
60
|
};
|
|
121
|
-
const toggleNodeFlag = (nodeId, key) => {
|
|
122
|
-
setPrefabData(prev => (Object.assign(Object.assign({}, prev), { root: updateNodeById(prev.root, nodeId, node => (Object.assign(Object.assign({}, node), { [key]: !node[key] }))) })));
|
|
123
|
-
};
|
|
124
61
|
const handleToggleDisabled = (nodeId) => {
|
|
125
62
|
toggleNodeFlag(nodeId, 'disabled');
|
|
126
63
|
};
|
|
127
64
|
const handleToggleLocked = (nodeId) => {
|
|
128
65
|
var _a;
|
|
129
|
-
const willLock = !((_a =
|
|
66
|
+
const willLock = !((_a = store.getState().nodesById[nodeId]) === null || _a === void 0 ? void 0 : _a.locked);
|
|
130
67
|
toggleNodeFlag(nodeId, 'locked');
|
|
131
|
-
if (willLock && selectedId === nodeId)
|
|
68
|
+
if (willLock && selectedId === nodeId)
|
|
132
69
|
setSelectedId(null);
|
|
133
|
-
}
|
|
134
70
|
};
|
|
135
71
|
const closeContextMenu = () => setContextMenu(null);
|
|
136
72
|
const openContextMenu = (nodeId, x, y) => {
|
|
@@ -143,10 +79,10 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
143
79
|
};
|
|
144
80
|
const renderTreeNodeMenu = (nodeId, isRoot, onClose) => {
|
|
145
81
|
var _a;
|
|
146
|
-
return (_jsx(TreeNodeMenu, { isRoot: isRoot, nodeId: nodeId, locked: (_a =
|
|
82
|
+
return (_jsx(TreeNodeMenu, { isRoot: isRoot, nodeId: nodeId, locked: (_a = store.getState().nodesById[nodeId]) === null || _a === void 0 ? void 0 : _a.locked, onAddChild: handleAddChild, onFocus: handleFocus, onToggleLock: isRoot ? undefined : handleToggleLocked, onDuplicate: isRoot ? undefined : handleDuplicate, onDelete: isRoot ? undefined : handleDelete, onClose: onClose }));
|
|
147
83
|
};
|
|
148
84
|
const handleDragStart = (e, id) => {
|
|
149
|
-
if (id ===
|
|
85
|
+
if (id === rootId)
|
|
150
86
|
return e.preventDefault();
|
|
151
87
|
e.dataTransfer.effectAllowed = "move";
|
|
152
88
|
setDraggedId(id);
|
|
@@ -160,9 +96,6 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
160
96
|
const handleDragOver = (e, targetId, isRoot) => {
|
|
161
97
|
if (!draggedId || draggedId === targetId)
|
|
162
98
|
return;
|
|
163
|
-
const draggedNode = findNode(prefabData.root, draggedId);
|
|
164
|
-
if (draggedNode && findNode(draggedNode, targetId))
|
|
165
|
-
return;
|
|
166
99
|
e.preventDefault();
|
|
167
100
|
setDropTarget({ id: targetId, position: getDropPosition(e, isRoot) });
|
|
168
101
|
};
|
|
@@ -176,75 +109,92 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
176
109
|
if (!draggedId || draggedId === targetId)
|
|
177
110
|
return;
|
|
178
111
|
e.preventDefault();
|
|
179
|
-
|
|
180
|
-
setPrefabData(prev => {
|
|
181
|
-
const root = moveNode(prev.root, draggedId, targetId, dropPosition);
|
|
182
|
-
return root === prev.root ? prev : Object.assign(Object.assign({}, prev), { root });
|
|
183
|
-
});
|
|
112
|
+
moveNode(draggedId, targetId, getDropPosition(e, isRoot));
|
|
184
113
|
setDraggedId(null);
|
|
185
114
|
setDropTarget(null);
|
|
186
115
|
};
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
marginRight: 4,
|
|
218
|
-
cursor: 'pointer',
|
|
219
|
-
visibility: hasChildren ? 'visible' : 'hidden'
|
|
220
|
-
}, 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: (_a = node.name) !== null && _a !== void 0 ? _a : node.id }), node.locked && _jsx("span", { style: { marginLeft: 6, opacity: 0.6 }, children: "\uD83D\uDD12" })] }), !isRoot && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(Dropdown, { placement: "bottom-end", trigger: ({ ref, toggle }) => (_jsx(MenuTriggerButton, { buttonRef: ref, onToggle: toggle, title: "Node Actions", style: {
|
|
221
|
-
background: 'none',
|
|
222
|
-
border: 'none',
|
|
223
|
-
cursor: 'pointer',
|
|
224
|
-
padding: '0 4px',
|
|
225
|
-
fontSize: 14,
|
|
226
|
-
opacity: 0.7,
|
|
227
|
-
color: 'inherit',
|
|
228
|
-
}, children: "\u22EF" })), children: (close) => renderTreeNodeMenu(node.id, false, close) }), _jsx("button", { style: {
|
|
116
|
+
const visibleIds = usePrefabStore(useCallback(state => searchQuery ? buildVisibleIds(state, rootId, searchQuery) : null, [rootId, searchQuery]));
|
|
117
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { style: Object.assign(Object.assign({}, tree.panel), { width: collapsed ? 'auto' : 224 }), children: [_jsxs("div", { style: base.header, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer' }, onClick: () => setCollapsed(!collapsed), children: [_jsx("span", { children: collapsed ? '▶' : '▼' }), _jsx("span", { children: "Scene" })] }), !collapsed && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4 }, children: [_jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10, opacity: canUndo ? 1 : 0.4 }), onClick: (e) => { e.stopPropagation(); onUndo === null || onUndo === void 0 ? void 0 : onUndo(); }, disabled: !canUndo, title: "Undo", children: "\u21B6" }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10, opacity: canRedo ? 1 : 0.4 }), onClick: (e) => { e.stopPropagation(); onRedo === null || onRedo === void 0 ? void 0 : onRedo(); }, disabled: !canRedo, title: "Redo", children: "\u21B7" }), _jsx(Dropdown, { placement: "bottom-end", trigger: ({ ref, toggle }) => (_jsx("button", { ref: ref, title: "Menu", style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10 }), onClick: (e) => {
|
|
118
|
+
e.stopPropagation();
|
|
119
|
+
toggle();
|
|
120
|
+
}, children: "\u22EE" })), children: (close) => (_jsx(FileMenu, { getPrefab: getPrefab, onReplacePrefab: onReplacePrefab, onImportPrefab: onImportPrefab, onClose: close })) })] }))] }), !collapsed && (_jsxs(_Fragment, { children: [_jsx("div", { style: { padding: '4px 4px', borderBottom: `1px solid ${colors.borderLight}` }, children: _jsx("input", { type: "text", placeholder: "Search nodes...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), onClick: (e) => e.stopPropagation(), style: Object.assign(Object.assign({}, base.input), { padding: '4px 8px' }) }) }), _jsx("div", { style: tree.scroll, children: _jsx(TreeNode, { nodeId: rootId, depth: 0, rootId: rootId, visibleIds: visibleIds, collapsedIds: collapsedIds, dropTarget: dropTarget, selectedNodeId: selectedId, onToggleCollapse: toggleCollapse, onOpenContextMenu: openContextMenu, onDragStart: handleDragStart, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, onDragEnd: () => { setDraggedId(null); setDropTarget(null); }, renderTreeNodeMenu: renderTreeNodeMenu, onToggleDisabled: handleToggleDisabled, setSelectedId: setSelectedId }) })] }))] }), _jsx(TreeContextMenu, { contextMenu: contextMenu, onClose: closeContextMenu, children: (nodeId, close) => renderTreeNodeMenu(nodeId, nodeId === rootId, close) })] }));
|
|
121
|
+
}
|
|
122
|
+
const TreeNode = memo(function TreeNode({ nodeId, depth, rootId, visibleIds, collapsedIds, dropTarget, selectedNodeId, onToggleCollapse, onOpenContextMenu, onDragStart, onDragOver, onDragLeave, onDrop, onDragEnd, renderTreeNodeMenu, onToggleDisabled, setSelectedId, }) {
|
|
123
|
+
var _a;
|
|
124
|
+
const node = usePrefabNode(nodeId);
|
|
125
|
+
const childIds = usePrefabChildIds(nodeId);
|
|
126
|
+
const isSelected = selectedNodeId === nodeId;
|
|
127
|
+
if (!node || (visibleIds && !visibleIds.has(nodeId)))
|
|
128
|
+
return null;
|
|
129
|
+
const isCollapsed = collapsedIds.has(nodeId);
|
|
130
|
+
const hasChildren = childIds.length > 0;
|
|
131
|
+
const isRoot = nodeId === rootId;
|
|
132
|
+
const isDropTarget = (dropTarget === null || dropTarget === void 0 ? void 0 : dropTarget.id) === nodeId;
|
|
133
|
+
const showDropBefore = isDropTarget && (dropTarget === null || dropTarget === void 0 ? void 0 : dropTarget.position) === 'before';
|
|
134
|
+
const showDropInside = isDropTarget && (dropTarget === null || dropTarget === void 0 ? void 0 : dropTarget.position) === 'inside';
|
|
135
|
+
return (_jsxs("div", { children: [_jsxs("div", { style: Object.assign(Object.assign(Object.assign({}, tree.row), (isSelected ? tree.selected : {})), { paddingLeft: `${depth * 12 + 6}px`, opacity: node.disabled ? 0.4 : 1, display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderTop: showDropBefore ? `2px solid ${colors.accent}` : undefined, boxShadow: showDropInside ? `inset 0 0 0 1px ${colors.accentBorder}` : undefined }), draggable: !isRoot, onClick: (e) => { e.stopPropagation(); setSelectedId(nodeId); }, onContextMenu: (e) => {
|
|
136
|
+
e.preventDefault();
|
|
137
|
+
e.stopPropagation();
|
|
138
|
+
onOpenContextMenu(nodeId, e.clientX, e.clientY);
|
|
139
|
+
}, onDragStart: (e) => onDragStart(e, nodeId), onDragEnd: onDragEnd, onDragOver: (e) => onDragOver(e, nodeId, isRoot), onDragLeave: (e) => onDragLeave(e, nodeId), onDrop: (e) => onDrop(e, nodeId, isRoot), children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', flex: 1, minWidth: 0 }, children: [_jsx("span", { style: {
|
|
140
|
+
width: 12,
|
|
141
|
+
opacity: 0.6,
|
|
142
|
+
marginRight: 4,
|
|
143
|
+
cursor: 'pointer',
|
|
144
|
+
visibility: hasChildren ? 'visible' : 'hidden'
|
|
145
|
+
}, onClick: (e) => hasChildren && onToggleCollapse(e, nodeId), children: isCollapsed ? '▶' : '▼' }), !isRoot && _jsx("span", { style: { marginRight: 4, opacity: 0.4 }, children: "\u22EE\u22EE" }), _jsx("span", { style: { overflow: 'hidden', textOverflow: 'ellipsis' }, children: (_a = node.name) !== null && _a !== void 0 ? _a : node.id }), node.locked && _jsx("span", { style: { marginLeft: 6, opacity: 0.6 }, children: "\uD83D\uDD12" })] }), !isRoot && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(Dropdown, { placement: "bottom-end", trigger: ({ ref, toggle }) => (_jsx("button", { ref: ref, title: "Node Actions", style: {
|
|
229
146
|
background: 'none',
|
|
230
147
|
border: 'none',
|
|
231
148
|
cursor: 'pointer',
|
|
232
149
|
padding: '0 4px',
|
|
233
150
|
fontSize: 14,
|
|
234
|
-
opacity:
|
|
151
|
+
opacity: 0.7,
|
|
235
152
|
color: 'inherit',
|
|
236
153
|
}, onClick: (e) => {
|
|
237
154
|
e.stopPropagation();
|
|
238
|
-
|
|
239
|
-
},
|
|
155
|
+
toggle();
|
|
156
|
+
}, children: "\u22EF" })), children: (close) => renderTreeNodeMenu(nodeId, false, close) }), _jsx("button", { style: {
|
|
240
157
|
background: 'none',
|
|
241
158
|
border: 'none',
|
|
242
159
|
cursor: 'pointer',
|
|
243
160
|
padding: '0 4px',
|
|
244
161
|
fontSize: 14,
|
|
245
|
-
opacity: 0.7,
|
|
162
|
+
opacity: node.disabled ? 0.5 : 0.7,
|
|
246
163
|
color: 'inherit',
|
|
247
|
-
},
|
|
164
|
+
}, onClick: (e) => {
|
|
165
|
+
e.stopPropagation();
|
|
166
|
+
onToggleDisabled(nodeId);
|
|
167
|
+
}, title: node.disabled ? 'Enable' : 'Disable', children: node.disabled ? '◎' : '◉' })] })), isRoot && (_jsx(Dropdown, { placement: "bottom-end", trigger: ({ ref, toggle }) => (_jsx("button", { ref: ref, title: "Scene Actions", style: {
|
|
168
|
+
background: 'none',
|
|
169
|
+
border: 'none',
|
|
170
|
+
cursor: 'pointer',
|
|
171
|
+
padding: '0 4px',
|
|
172
|
+
fontSize: 14,
|
|
173
|
+
opacity: 0.7,
|
|
174
|
+
color: 'inherit',
|
|
175
|
+
}, onClick: (e) => {
|
|
176
|
+
e.stopPropagation();
|
|
177
|
+
toggle();
|
|
178
|
+
}, children: "\u22EF" })), children: (close) => renderTreeNodeMenu(nodeId, true, close) }))] }), !isCollapsed && childIds.map(childId => (_jsx(TreeNode, { nodeId: childId, depth: depth + 1, rootId: rootId, visibleIds: visibleIds, collapsedIds: collapsedIds, dropTarget: dropTarget, selectedNodeId: selectedNodeId, onToggleCollapse: onToggleCollapse, onOpenContextMenu: onOpenContextMenu, onDragStart: onDragStart, onDragOver: onDragOver, onDragLeave: onDragLeave, onDrop: onDrop, onDragEnd: onDragEnd, renderTreeNodeMenu: renderTreeNodeMenu, onToggleDisabled: onToggleDisabled, setSelectedId: setSelectedId }, childId)))] }));
|
|
179
|
+
});
|
|
180
|
+
function buildVisibleIds(state, rootId, query) {
|
|
181
|
+
if (!query)
|
|
182
|
+
return null;
|
|
183
|
+
const visibleIds = new Set();
|
|
184
|
+
const lowerQuery = query.toLowerCase();
|
|
185
|
+
const visit = (nodeId) => {
|
|
186
|
+
var _a, _b;
|
|
187
|
+
const node = state.nodesById[nodeId];
|
|
188
|
+
if (!node)
|
|
189
|
+
return false;
|
|
190
|
+
const selfMatches = ((_a = node.name) !== null && _a !== void 0 ? _a : node.id).toLowerCase().includes(lowerQuery);
|
|
191
|
+
const childMatches = ((_b = state.childIdsById[nodeId]) !== null && _b !== void 0 ? _b : []).some(visit);
|
|
192
|
+
if (selfMatches || childMatches) {
|
|
193
|
+
visibleIds.add(nodeId);
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
248
197
|
};
|
|
249
|
-
|
|
198
|
+
visit(rootId);
|
|
199
|
+
return visibleIds;
|
|
250
200
|
}
|
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
import { Dispatch, SetStateAction } from 'react';
|
|
2
1
|
import { Prefab } from './types';
|
|
3
2
|
export type TreeContextMenuState = {
|
|
4
3
|
nodeId: string;
|
|
5
4
|
x: number;
|
|
6
5
|
y: number;
|
|
7
6
|
} | null;
|
|
8
|
-
export declare function MenuTriggerButton({ buttonRef, onToggle, title, style, children, }: {
|
|
9
|
-
buttonRef: React.RefObject<HTMLButtonElement | null>;
|
|
10
|
-
onToggle: () => void;
|
|
11
|
-
title: string;
|
|
12
|
-
style: React.CSSProperties;
|
|
13
|
-
children: React.ReactNode;
|
|
14
|
-
}): import("react/jsx-runtime").JSX.Element;
|
|
15
7
|
export declare function TreeNodeMenu({ isRoot, nodeId, locked, onAddChild, onFocus, onToggleLock, onDuplicate, onDelete, onClose, }: {
|
|
16
8
|
isRoot: boolean;
|
|
17
9
|
nodeId: string;
|
|
@@ -28,8 +20,9 @@ export declare function TreeContextMenu({ contextMenu, onClose, children, }: {
|
|
|
28
20
|
onClose: () => void;
|
|
29
21
|
children: (nodeId: string, onClose: () => void) => React.ReactNode;
|
|
30
22
|
}): import("react").ReactPortal | null;
|
|
31
|
-
export declare function FileMenu({
|
|
32
|
-
|
|
33
|
-
|
|
23
|
+
export declare function FileMenu({ getPrefab, onReplacePrefab, onImportPrefab, onClose }: {
|
|
24
|
+
getPrefab: () => Prefab;
|
|
25
|
+
onReplacePrefab: (prefab: Prefab) => void;
|
|
26
|
+
onImportPrefab: (prefab: Prefab) => void;
|
|
34
27
|
onClose: () => void;
|
|
35
28
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -13,15 +13,15 @@ import { createPortal } from 'react-dom';
|
|
|
13
13
|
import { menu } from './styles';
|
|
14
14
|
import { useEditorContext } from './EditorContext';
|
|
15
15
|
import { getComponent } from './components/ComponentRegistry';
|
|
16
|
-
import { loadJson, saveJson
|
|
16
|
+
import { loadJson, saveJson } from './utils';
|
|
17
17
|
function createEmptyPrefab() {
|
|
18
18
|
var _a;
|
|
19
19
|
return {
|
|
20
20
|
id: crypto.randomUUID(),
|
|
21
|
-
name: 'New
|
|
21
|
+
name: 'New Prefab',
|
|
22
22
|
root: {
|
|
23
23
|
id: crypto.randomUUID(),
|
|
24
|
-
name: '
|
|
24
|
+
name: 'Root',
|
|
25
25
|
components: {
|
|
26
26
|
transform: {
|
|
27
27
|
type: 'Transform',
|
|
@@ -47,12 +47,6 @@ function MenuSubmenu({ label, children, }) {
|
|
|
47
47
|
zIndex: 1,
|
|
48
48
|
}, children: _jsx(MenuPanel, { children: children }) }))] }));
|
|
49
49
|
}
|
|
50
|
-
export function MenuTriggerButton({ buttonRef, onToggle, title, style, children, }) {
|
|
51
|
-
return (_jsx("button", { ref: buttonRef, style: style, onClick: (e) => {
|
|
52
|
-
e.stopPropagation();
|
|
53
|
-
onToggle();
|
|
54
|
-
}, title: title, children: children }));
|
|
55
|
-
}
|
|
56
50
|
export function TreeNodeMenu({ isRoot, nodeId, locked, onAddChild, onFocus, onToggleLock, onDuplicate, onDelete, onClose, }) {
|
|
57
51
|
return (_jsxs(MenuPanel, { children: [_jsx(MenuItemButton, { onClick: () => { onAddChild(nodeId); onClose(); }, children: "Add Child" }), _jsx(MenuItemButton, { onClick: () => { onFocus(nodeId); onClose(); }, children: "Focus Camera" }), !isRoot && onToggleLock && (_jsx(MenuItemButton, { onClick: () => { onToggleLock(nodeId); onClose(); }, children: locked ? 'Unlock' : 'Lock' })), !isRoot && onDuplicate && (_jsx(MenuItemButton, { onClick: () => { onDuplicate(nodeId); onClose(); }, children: "Duplicate" })), !isRoot && onDelete && (_jsx(MenuItemButton, { danger: true, onClick: () => { onDelete(nodeId); onClose(); }, children: "Delete" }))] }));
|
|
58
52
|
}
|
|
@@ -104,32 +98,29 @@ export function TreeContextMenu({ contextMenu, onClose, children, }) {
|
|
|
104
98
|
zIndex: 1000,
|
|
105
99
|
}, onMouseLeave: onClose, onContextMenu: (e) => e.preventDefault(), children: children(contextMenu.nodeId, onClose) }), document.body);
|
|
106
100
|
}
|
|
107
|
-
export function FileMenu({
|
|
101
|
+
export function FileMenu({ getPrefab, onReplacePrefab, onImportPrefab, onClose }) {
|
|
108
102
|
const { onScreenshot, onExportGLB } = useEditorContext();
|
|
109
|
-
const
|
|
110
|
-
|
|
103
|
+
const handleNew = () => {
|
|
104
|
+
onReplacePrefab(createEmptyPrefab());
|
|
111
105
|
onClose();
|
|
112
106
|
};
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
if (!
|
|
107
|
+
const handleOpen = () => __awaiter(this, void 0, void 0, function* () {
|
|
108
|
+
const loaded = yield loadJson();
|
|
109
|
+
if (!loaded)
|
|
116
110
|
return;
|
|
117
|
-
|
|
111
|
+
onReplacePrefab(loaded);
|
|
118
112
|
onClose();
|
|
119
113
|
});
|
|
120
114
|
const handleSave = () => {
|
|
121
|
-
saveJson(
|
|
115
|
+
saveJson(getPrefab(), 'prefab');
|
|
122
116
|
onClose();
|
|
123
117
|
};
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
if (!
|
|
118
|
+
const handleImport = () => __awaiter(this, void 0, void 0, function* () {
|
|
119
|
+
const loaded = yield loadJson();
|
|
120
|
+
if (!loaded)
|
|
127
121
|
return;
|
|
128
|
-
|
|
129
|
-
var _a;
|
|
130
|
-
return (Object.assign(Object.assign({}, root), { children: [...((_a = root.children) !== null && _a !== void 0 ? _a : []), regenerateIds(loadedPrefab.root)] }));
|
|
131
|
-
}) })));
|
|
122
|
+
onImportPrefab(loaded);
|
|
132
123
|
onClose();
|
|
133
124
|
});
|
|
134
|
-
return (_jsxs(MenuPanel, { style: { overflow: 'visible' }, children: [_jsxs(MenuSubmenu, { label: "File", children: [_jsx(MenuItemButton, { onClick:
|
|
125
|
+
return (_jsxs(MenuPanel, { style: { overflow: 'visible' }, children: [_jsxs(MenuSubmenu, { label: "File", children: [_jsx(MenuItemButton, { onClick: handleNew, children: "New Prefab" }), _jsx(MenuItemButton, { onClick: handleOpen, children: "Open Prefab" }), _jsx(MenuItemButton, { onClick: handleImport, children: "Import Prefab" }), _jsx(MenuItemButton, { onClick: handleSave, children: "Save Prefab" })] }), _jsxs(MenuSubmenu, { label: "Export", children: [_jsx(MenuItemButton, { onClick: () => { onExportGLB === null || onExportGLB === void 0 ? void 0 : onExportGLB(); onClose(); }, children: "GLB" }), _jsx(MenuItemButton, { onClick: () => { onScreenshot === null || onScreenshot === void 0 ? void 0 : onScreenshot(); onClose(); }, children: "PNG" })] })] }));
|
|
135
126
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Dispatch, SetStateAction } from 'react';
|
|
2
1
|
import { Prefab } from "./types";
|
|
3
|
-
declare function EditorUI({
|
|
4
|
-
prefabData?: Prefab;
|
|
5
|
-
setPrefabData?: Dispatch<SetStateAction<Prefab>>;
|
|
2
|
+
declare function EditorUI({ selectedId, setSelectedId, getPrefab, onReplacePrefab, onImportPrefab, basePath, onUndo, onRedo, canUndo, canRedo }: {
|
|
6
3
|
selectedId: string | null;
|
|
7
|
-
setSelectedId:
|
|
4
|
+
setSelectedId: (id: string | null) => void;
|
|
5
|
+
getPrefab: () => Prefab;
|
|
6
|
+
onReplacePrefab: (prefab: Prefab) => void;
|
|
7
|
+
onImportPrefab: (prefab: Prefab) => void;
|
|
8
8
|
basePath?: string;
|
|
9
9
|
onUndo?: () => void;
|
|
10
10
|
onRedo?: () => void;
|
|
@@ -13,23 +13,26 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
13
13
|
import { useState } from 'react';
|
|
14
14
|
import EditorTree from './EditorTree';
|
|
15
15
|
import { getAllComponents } from './components/ComponentRegistry';
|
|
16
|
-
import { base, colors, inspector,
|
|
17
|
-
import {
|
|
18
|
-
function EditorUI({
|
|
16
|
+
import { base, colors, inspector, componentCard } from './styles';
|
|
17
|
+
import { usePrefabStore } from './prefabStore';
|
|
18
|
+
function EditorUI({ selectedId, setSelectedId, getPrefab, onReplacePrefab, onImportPrefab, basePath, onUndo, onRedo, canUndo, canRedo }) {
|
|
19
19
|
const [collapsed, setCollapsed] = useState(false);
|
|
20
|
-
const
|
|
21
|
-
|
|
20
|
+
const rootId = usePrefabStore(state => state.rootId);
|
|
21
|
+
const selectedNode = usePrefabStore(state => { var _a; return selectedId ? (_a = state.nodesById[selectedId]) !== null && _a !== void 0 ? _a : null : null; });
|
|
22
|
+
const updateNode = usePrefabStore(state => state.updateNode);
|
|
23
|
+
const deleteNode = usePrefabStore(state => state.deleteNode);
|
|
24
|
+
const updateNodeHandler = (update) => {
|
|
25
|
+
if (!selectedId)
|
|
22
26
|
return;
|
|
23
|
-
|
|
27
|
+
updateNode(selectedId, update);
|
|
24
28
|
};
|
|
25
29
|
const deleteNodeHandler = () => {
|
|
26
|
-
if (!
|
|
30
|
+
if (!selectedId || selectedId === rootId)
|
|
27
31
|
return;
|
|
28
|
-
|
|
32
|
+
deleteNode(selectedId);
|
|
29
33
|
setSelectedId(null);
|
|
30
34
|
};
|
|
31
|
-
|
|
32
|
-
return _jsxs(_Fragment, { children: [_jsx("style", { children: scrollbarCSS }), _jsxs("div", { style: inspector.panel, children: [_jsxs("div", { style: base.header, onClick: () => setCollapsed(!collapsed), children: [_jsx("span", { children: "Inspector" }), _jsx("span", { children: collapsed ? '◀' : '▼' })] }), !collapsed && selectedNode && (_jsx(NodeInspector, { node: selectedNode, updateNode: updateNodeHandler, deleteNode: deleteNodeHandler, basePath: basePath }))] }), _jsx("div", { style: { position: 'absolute', top: 8, left: 8, zIndex: 20 }, children: _jsx(EditorTree, { prefabData: prefabData, setPrefabData: setPrefabData, selectedId: selectedId, setSelectedId: setSelectedId, onUndo: onUndo, onRedo: onRedo, canUndo: canUndo, canRedo: canRedo }) })] });
|
|
35
|
+
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, basePath: basePath }))] }), _jsx("div", { style: { position: 'absolute', top: 8, left: 8, zIndex: 20 }, children: _jsx(EditorTree, { selectedId: selectedId, setSelectedId: setSelectedId, getPrefab: getPrefab, onReplacePrefab: onReplacePrefab, onImportPrefab: onImportPrefab, onUndo: onUndo, onRedo: onRedo, canUndo: canUndo, canRedo: canRedo }) })] });
|
|
33
36
|
}
|
|
34
37
|
function NodeInspector({ node, updateNode, deleteNode, basePath }) {
|
|
35
38
|
var _a;
|
|
@@ -38,7 +41,7 @@ function NodeInspector({ node, updateNode, deleteNode, basePath }) {
|
|
|
38
41
|
const available = allKeys.filter(k => { var _a; return !((_a = node.components) === null || _a === void 0 ? void 0 : _a[k.toLowerCase()]); });
|
|
39
42
|
const [preferredAddType, setAddType] = useState(available[0] || "");
|
|
40
43
|
const addType = available.includes(preferredAddType) ? preferredAddType : (available[0] || "");
|
|
41
|
-
return _jsxs("div", { style: inspector.content,
|
|
44
|
+
return _jsxs("div", { style: inspector.content, children: [_jsxs("div", { style: base.section, children: [_jsxs("div", { style: { display: "flex", marginBottom: 8, alignItems: 'center', gap: 8 }, children: [_jsx("div", { style: { fontSize: 10, color: colors.textDim, wordBreak: 'break-all', border: `1px solid ${colors.border}`, padding: '2px 6px', borderRadius: 3, flex: 1, fontFamily: 'monospace' }, children: node.id }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), base.btnDanger), title: "Delete Node", onClick: deleteNode, children: "\u274C" })] }), _jsx("input", { style: base.input, value: (_a = node.name) !== null && _a !== void 0 ? _a : "", placeholder: 'Node name', onChange: e => updateNode(n => (Object.assign(Object.assign({}, n), { name: e.target.value }))) })] }), _jsxs("div", { style: base.section, children: [_jsx("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }, children: _jsx("div", { style: base.label, children: "Components" }) }), node.components && Object.entries(node.components).map(([key, comp]) => {
|
|
42
45
|
if (!comp)
|
|
43
46
|
return null;
|
|
44
47
|
const def = ALL_COMPONENTS[comp.type];
|
|
@@ -106,34 +106,4 @@ export declare function useGameEvent<T extends string>(type: T, handler: EventHa
|
|
|
106
106
|
* Entity IDs are stored in RigidBody userData.
|
|
107
107
|
*/
|
|
108
108
|
export declare function getEntityIdFromRigidBody(rigidBody: RapierRigidBody | null | undefined): string | null;
|
|
109
|
-
/** @deprecated Use gameEvents instead */
|
|
110
|
-
export declare const entityEvents: {
|
|
111
|
-
/**
|
|
112
|
-
* Emit an event to all subscribers
|
|
113
|
-
*/
|
|
114
|
-
emit<T extends string>(type: T, payload: GameEventPayload<T>): void;
|
|
115
|
-
/**
|
|
116
|
-
* Subscribe to an event type
|
|
117
|
-
* @returns Unsubscribe function
|
|
118
|
-
*/
|
|
119
|
-
on<T extends string>(type: T, handler: EventHandler<GameEventPayload<T>>): () => void;
|
|
120
|
-
/**
|
|
121
|
-
* Unsubscribe from an event type
|
|
122
|
-
*/
|
|
123
|
-
off<T extends string>(type: T, handler: EventHandler<GameEventPayload<T>>): void;
|
|
124
|
-
/**
|
|
125
|
-
* Remove all subscribers (useful for cleanup/reset)
|
|
126
|
-
*/
|
|
127
|
-
clear(): void;
|
|
128
|
-
/**
|
|
129
|
-
* Check if an event type has any subscribers
|
|
130
|
-
*/
|
|
131
|
-
hasListeners(type: string): boolean;
|
|
132
|
-
};
|
|
133
|
-
/** @deprecated Use useGameEvent instead */
|
|
134
|
-
export declare const useEntityEvent: typeof useGameEvent;
|
|
135
|
-
/** @deprecated Use GameEventType instead */
|
|
136
|
-
export type EntityEventType = PhysicsEventType;
|
|
137
|
-
/** @deprecated Use PhysicsEventPayload instead */
|
|
138
|
-
export type EntityEventPayload = PhysicsEventPayload;
|
|
139
109
|
export {};
|
|
@@ -111,10 +111,3 @@ export function getEntityIdFromRigidBody(rigidBody) {
|
|
|
111
111
|
const userData = rigidBody.userData;
|
|
112
112
|
return (_a = userData === null || userData === void 0 ? void 0 : userData.entityId) !== null && _a !== void 0 ? _a : null;
|
|
113
113
|
}
|
|
114
|
-
// ============================================================================
|
|
115
|
-
// Backward Compatibility Aliases
|
|
116
|
-
// ============================================================================
|
|
117
|
-
/** @deprecated Use gameEvents instead */
|
|
118
|
-
export const entityEvents = gameEvents;
|
|
119
|
-
/** @deprecated Use useGameEvent instead */
|
|
120
|
-
export const useEntityEvent = useGameEvent;
|
|
@@ -3,28 +3,27 @@ import { Object3D, Texture } from "three";
|
|
|
3
3
|
import { GameObject, Prefab } from "./types";
|
|
4
4
|
import { PrefabRootRef } from "./PrefabRoot";
|
|
5
5
|
import type { ExportGLBOptions } from "./utils";
|
|
6
|
-
|
|
7
|
-
name?: string;
|
|
8
|
-
parentId?: string;
|
|
9
|
-
select?: boolean;
|
|
10
|
-
}
|
|
6
|
+
import { type Scene, type SpawnOptions } from "./sceneApi";
|
|
11
7
|
export interface PrefabEditorRef {
|
|
12
8
|
screenshot: () => void;
|
|
13
|
-
exportGLB: (options?: ExportGLBOptions) => Promise<ArrayBuffer |
|
|
9
|
+
exportGLB: (options?: ExportGLBOptions) => Promise<ArrayBuffer | undefined>;
|
|
14
10
|
exportGLBData: () => Promise<ArrayBuffer | undefined>;
|
|
15
11
|
clearSelection: () => Promise<void>;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
save: () => Prefab;
|
|
13
|
+
scene: Scene;
|
|
14
|
+
load: (prefab: Prefab, options?: {
|
|
15
|
+
resetHistory?: boolean;
|
|
16
|
+
notifyChange?: boolean;
|
|
17
|
+
}) => void;
|
|
18
|
+
addModel: (path: string, model: Object3D, options?: SpawnOptions) => GameObject;
|
|
19
|
+
addTexture: (path: string, texture: Texture, options?: SpawnOptions) => GameObject;
|
|
20
|
+
viewRef: React.RefObject<PrefabRootRef | null>;
|
|
22
21
|
}
|
|
23
22
|
export interface PrefabEditorProps {
|
|
24
23
|
basePath?: string;
|
|
25
24
|
initialPrefab?: Prefab;
|
|
26
25
|
physics?: boolean;
|
|
27
|
-
|
|
26
|
+
onChange?: (prefab: Prefab) => void;
|
|
28
27
|
showUI?: boolean;
|
|
29
28
|
enableWindowDrop?: boolean;
|
|
30
29
|
canvasProps?: Omit<React.ComponentProps<typeof GameCanvas>, 'children' | 'canvasRef'>;
|