react-three-game 0.0.16 → 0.0.17
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 +88 -113
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/tools/prefabeditor/EditorTree.js +27 -15
- package/dist/tools/prefabeditor/EditorUI.js +2 -8
- package/dist/tools/prefabeditor/PrefabEditor.js +128 -59
- package/dist/tools/prefabeditor/PrefabRoot.js +34 -15
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +2 -0
- package/dist/tools/prefabeditor/components/RotatorComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/RotatorComponent.js +42 -0
- package/dist/tools/prefabeditor/components/TransformComponent.js +28 -3
- package/package.json +7 -7
- package/src/index.ts +4 -0
- package/src/tools/prefabeditor/EditorTree.tsx +39 -16
- package/src/tools/prefabeditor/EditorUI.tsx +2 -27
- package/src/tools/prefabeditor/PrefabEditor.tsx +202 -86
- package/src/tools/prefabeditor/PrefabRoot.tsx +38 -19
- package/src/tools/prefabeditor/components/ComponentRegistry.ts +7 -1
- package/src/tools/prefabeditor/components/TransformComponent.tsx +69 -16
|
@@ -188,7 +188,7 @@ function renderCoreNode(gameObject, ctx, parentMatrix) {
|
|
|
188
188
|
const geometryDef = geometry ? getComponent('Geometry') : undefined;
|
|
189
189
|
const materialDef = material ? getComponent('Material') : undefined;
|
|
190
190
|
const isModelAvailable = !!(modelComp && modelComp.properties && modelComp.properties.filename && ctx.loadedModels[modelComp.properties.filename]);
|
|
191
|
-
// Generic component views (exclude geometry/material/model)
|
|
191
|
+
// Generic component views (exclude geometry/material/model/transform/physics)
|
|
192
192
|
const contextProps = {
|
|
193
193
|
loadedModels: ctx.loadedModels,
|
|
194
194
|
loadedTextures: ctx.loadedTextures,
|
|
@@ -197,29 +197,48 @@ function renderCoreNode(gameObject, ctx, parentMatrix) {
|
|
|
197
197
|
parentMatrix,
|
|
198
198
|
registerRef: ctx.registerRef,
|
|
199
199
|
};
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
// Separate wrapper components (that accept children) from leaf components
|
|
201
|
+
const wrapperComponents = [];
|
|
202
|
+
const leafComponents = [];
|
|
203
|
+
if (gameObject.components) {
|
|
204
|
+
Object.entries(gameObject.components)
|
|
202
205
|
.filter(([key]) => key !== 'geometry' && key !== 'material' && key !== 'model' && key !== 'transform' && key !== 'physics')
|
|
203
|
-
.
|
|
206
|
+
.forEach(([key, comp]) => {
|
|
204
207
|
if (!comp || !comp.type)
|
|
205
|
-
return
|
|
208
|
+
return;
|
|
206
209
|
const def = getComponent(comp.type);
|
|
207
210
|
if (!def || !def.View)
|
|
208
|
-
return
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
211
|
+
return;
|
|
212
|
+
// Check if the component View accepts children by checking function signature
|
|
213
|
+
// Components that wrap content should accept children prop
|
|
214
|
+
const viewString = def.View.toString();
|
|
215
|
+
if (viewString.includes('children')) {
|
|
216
|
+
wrapperComponents.push({ key, View: def.View, properties: comp.properties });
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
leafComponents.push(_jsx(def.View, Object.assign({ properties: comp.properties }, contextProps), key));
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
// Build the core content (model or mesh)
|
|
224
|
+
let coreContent;
|
|
212
225
|
// If we have a model (non-instanced) render it as a primitive with material override
|
|
213
226
|
if (isModelAvailable) {
|
|
214
227
|
const modelObj = ctx.loadedModels[modelComp.properties.filename].clone();
|
|
215
|
-
|
|
228
|
+
coreContent = (_jsxs("primitive", { object: modelObj, children: [material && materialDef && materialDef.View && (_jsx(materialDef.View, { properties: material.properties, loadedTextures: ctx.loadedTextures, isSelected: ctx.selectedId === gameObject.id, editMode: ctx.editMode, parentMatrix: parentMatrix, registerRef: ctx.registerRef }, "material")), leafComponents] }));
|
|
229
|
+
}
|
|
230
|
+
else if (geometry && geometryDef && geometryDef.View) {
|
|
231
|
+
// Otherwise, if geometry present, render a mesh
|
|
232
|
+
coreContent = (_jsxs("mesh", { children: [_jsx(geometryDef.View, Object.assign({ properties: geometry.properties }, contextProps), "geometry"), material && materialDef && materialDef.View && (_jsx(materialDef.View, { properties: material.properties, loadedTextures: ctx.loadedTextures, isSelected: ctx.selectedId === gameObject.id, editMode: ctx.editMode, parentMatrix: parentMatrix, registerRef: ctx.registerRef }, "material")), leafComponents] }));
|
|
216
233
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
234
|
+
else {
|
|
235
|
+
// No geometry or model, just render leaf components
|
|
236
|
+
coreContent = _jsx(_Fragment, { children: leafComponents });
|
|
220
237
|
}
|
|
221
|
-
//
|
|
222
|
-
return
|
|
238
|
+
// Wrap core content with wrapper components (in order)
|
|
239
|
+
return wrapperComponents.reduce((content, { key, View, properties }) => {
|
|
240
|
+
return _jsx(View, Object.assign({ properties: properties }, contextProps, { children: content }), key);
|
|
241
|
+
}, coreContent);
|
|
223
242
|
}
|
|
224
243
|
// Helper: wrap core content with physics component when necessary
|
|
225
244
|
function wrapPhysicsIfNeeded(gameObject, content, ctx) {
|
|
@@ -5,6 +5,8 @@ export interface Component {
|
|
|
5
5
|
component: any;
|
|
6
6
|
onUpdate: (newComp: any) => void;
|
|
7
7
|
basePath?: string;
|
|
8
|
+
transformMode?: "translate" | "rotate" | "scale";
|
|
9
|
+
setTransformMode?: (m: "translate" | "rotate" | "scale") => void;
|
|
8
10
|
}>;
|
|
9
11
|
defaultProperties: any;
|
|
10
12
|
View?: FC<any>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useFrame } from "@react-three/fiber";
|
|
3
|
+
import { useRef } from "react";
|
|
4
|
+
function RotatorComponentEditor({ component, onUpdate }) {
|
|
5
|
+
var _a, _b;
|
|
6
|
+
const props = {
|
|
7
|
+
speed: (_a = component.properties.speed) !== null && _a !== void 0 ? _a : 1.0,
|
|
8
|
+
axis: (_b = component.properties.axis) !== null && _b !== void 0 ? _b : 'y'
|
|
9
|
+
};
|
|
10
|
+
return _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Rotation Speed" }), _jsx("input", { type: "number", step: "0.1", className: "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: props.speed, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { speed: parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Rotation Axis" }), _jsxs("select", { className: "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: props.axis, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { axis: e.target.value })), children: [_jsx("option", { value: "x", children: "X" }), _jsx("option", { value: "y", children: "Y" }), _jsx("option", { value: "z", children: "Z" })] })] })] });
|
|
11
|
+
}
|
|
12
|
+
// The view component for Rotator
|
|
13
|
+
function RotatorView({ properties, children }) {
|
|
14
|
+
var _a, _b;
|
|
15
|
+
const groupRef = useRef(null);
|
|
16
|
+
const speed = (_a = properties.speed) !== null && _a !== void 0 ? _a : 1.0;
|
|
17
|
+
const axis = (_b = properties.axis) !== null && _b !== void 0 ? _b : 'y';
|
|
18
|
+
useFrame((state, delta) => {
|
|
19
|
+
if (groupRef.current) {
|
|
20
|
+
if (axis === 'x') {
|
|
21
|
+
groupRef.current.rotation.x += delta * speed;
|
|
22
|
+
}
|
|
23
|
+
else if (axis === 'y') {
|
|
24
|
+
groupRef.current.rotation.y += delta * speed;
|
|
25
|
+
}
|
|
26
|
+
else if (axis === 'z') {
|
|
27
|
+
groupRef.current.rotation.z += delta * speed;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return (_jsx("group", { ref: groupRef, children: children }));
|
|
32
|
+
}
|
|
33
|
+
const RotatorComponent = {
|
|
34
|
+
name: 'Rotator',
|
|
35
|
+
Editor: RotatorComponentEditor,
|
|
36
|
+
View: RotatorView,
|
|
37
|
+
defaultProperties: {
|
|
38
|
+
speed: 1.0,
|
|
39
|
+
axis: 'y'
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
export default RotatorComponent;
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
function TransformComponentEditor({ component, onUpdate }) {
|
|
3
|
-
|
|
2
|
+
function TransformComponentEditor({ component, onUpdate, transformMode, setTransformMode }) {
|
|
3
|
+
const s = {
|
|
4
|
+
button: {
|
|
5
|
+
padding: '2px 6px',
|
|
6
|
+
background: 'transparent',
|
|
7
|
+
color: 'rgba(255,255,255,0.9)',
|
|
8
|
+
border: '1px solid rgba(255,255,255,0.14)',
|
|
9
|
+
borderRadius: 4,
|
|
10
|
+
cursor: 'pointer',
|
|
11
|
+
font: 'inherit',
|
|
12
|
+
},
|
|
13
|
+
buttonActive: {
|
|
14
|
+
background: 'rgba(255,255,255,0.10)',
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
return _jsxs("div", { className: "flex flex-col", children: [transformMode && setTransformMode && (_jsxs("div", { className: "mb-2", children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-1", children: "Transform Mode" }), _jsx("div", { style: { display: 'flex', gap: 6 }, children: ["translate", "rotate", "scale"].map(mode => (_jsx("button", { onClick: () => setTransformMode(mode), style: Object.assign(Object.assign(Object.assign({}, s.button), { flex: 1 }), (transformMode === mode ? s.buttonActive : {})), onPointerEnter: (e) => {
|
|
18
|
+
if (transformMode !== mode)
|
|
19
|
+
e.currentTarget.style.background = 'rgba(255,255,255,0.08)';
|
|
20
|
+
}, onPointerLeave: (e) => {
|
|
21
|
+
if (transformMode !== mode)
|
|
22
|
+
e.currentTarget.style.background = 'transparent';
|
|
23
|
+
}, children: mode }, mode))) })] })), _jsx(Vector3Input, { label: "Position", value: component.properties.position, onChange: v => onUpdate({ position: v }) }), _jsx(Vector3Input, { label: "Rotation", value: component.properties.rotation, onChange: v => onUpdate({ rotation: v }) }), _jsx(Vector3Input, { label: "Scale", value: component.properties.scale, onChange: v => onUpdate({ scale: v }) })] });
|
|
4
24
|
}
|
|
5
25
|
const TransformComponent = {
|
|
6
26
|
name: 'Transform',
|
|
@@ -18,5 +38,10 @@ export function Vector3Input({ label, value, onChange }) {
|
|
|
18
38
|
newValue[index] = parseFloat(val) || 0;
|
|
19
39
|
onChange(newValue);
|
|
20
40
|
};
|
|
21
|
-
|
|
41
|
+
const axes = [
|
|
42
|
+
{ key: 'x', color: 'red', index: 0 },
|
|
43
|
+
{ key: 'y', color: 'green', index: 1 },
|
|
44
|
+
{ key: 'z', color: 'blue', index: 2 }
|
|
45
|
+
];
|
|
46
|
+
return _jsxs("div", { className: "mb-2", children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-1", children: label }), _jsx("div", { className: "flex gap-1", children: axes.map(({ key, color, index }) => (_jsxs("div", { className: "flex-1 flex items-center gap-1 bg-black/30 border border-cyan-500/20 rounded px-1.5 py-1 min-h-[32px]", children: [_jsx("span", { className: `text-xs font-bold text-${color}-400 w-3`, children: key.toUpperCase() }), _jsx("input", { className: "flex-1 bg-transparent text-xs text-cyan-200 font-mono outline-none w-full min-w-0", type: "number", step: "0.1", value: value[index].toFixed(2), onChange: e => handleChange(index, e.target.value), onFocus: e => e.target.select() })] }, key))) })] });
|
|
22
47
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-three-game",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"description": "Batteries included React Three Fiber game engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
"type": "module",
|
|
18
18
|
"workspaces": ["docs"],
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@react-three/fiber": "
|
|
21
|
-
"@react-three/drei": "
|
|
22
|
-
"@react-three/rapier": "
|
|
23
|
-
"react": "
|
|
24
|
-
"react-dom": "
|
|
25
|
-
"three": "
|
|
20
|
+
"@react-three/fiber": ">=9.0.0",
|
|
21
|
+
"@react-three/drei": ">=10.0.0",
|
|
22
|
+
"@react-three/rapier": ">=2.0.0",
|
|
23
|
+
"react": ">=18.0.0",
|
|
24
|
+
"react-dom": ">=18.0.0",
|
|
25
|
+
"three": ">=0.181.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@react-three/drei": "^10.7.7",
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,10 @@ export {
|
|
|
10
10
|
SharedCanvas,
|
|
11
11
|
} from './tools/assetviewer/page';
|
|
12
12
|
|
|
13
|
+
// Component Registry
|
|
14
|
+
export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
|
|
15
|
+
export type { Component } from './tools/prefabeditor/components/ComponentRegistry';
|
|
16
|
+
|
|
13
17
|
// Helpers
|
|
14
18
|
export * from './helpers';
|
|
15
19
|
|
|
@@ -74,6 +74,17 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
74
74
|
overflow: "hidden",
|
|
75
75
|
textOverflow: "ellipsis",
|
|
76
76
|
},
|
|
77
|
+
dragHandle: {
|
|
78
|
+
width: 14,
|
|
79
|
+
height: 14,
|
|
80
|
+
display: "flex",
|
|
81
|
+
alignItems: "center",
|
|
82
|
+
justifyContent: "center",
|
|
83
|
+
marginRight: 4,
|
|
84
|
+
opacity: 0.4,
|
|
85
|
+
cursor: "grab",
|
|
86
|
+
fontSize: 10,
|
|
87
|
+
},
|
|
77
88
|
contextMenu: {
|
|
78
89
|
position: "fixed",
|
|
79
90
|
zIndex: 50,
|
|
@@ -179,40 +190,38 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
179
190
|
|
|
180
191
|
// Drag and Drop
|
|
181
192
|
const handleDragStart = (e: React.DragEvent, id: string) => {
|
|
182
|
-
e.stopPropagation();
|
|
183
193
|
if (id === prefabData.root.id) {
|
|
184
|
-
e.preventDefault();
|
|
194
|
+
e.preventDefault();
|
|
185
195
|
return;
|
|
186
196
|
}
|
|
187
|
-
setDraggedId(id);
|
|
188
197
|
e.dataTransfer.effectAllowed = "move";
|
|
198
|
+
e.dataTransfer.setData("text/plain", id);
|
|
199
|
+
setDraggedId(id);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const handleDragEnd = () => {
|
|
203
|
+
setDraggedId(null);
|
|
189
204
|
};
|
|
190
205
|
|
|
191
206
|
const handleDragOver = (e: React.DragEvent, targetId: string) => {
|
|
192
|
-
e.preventDefault();
|
|
193
|
-
e.stopPropagation();
|
|
194
207
|
if (!draggedId || draggedId === targetId) return;
|
|
195
|
-
|
|
196
|
-
// Check for cycles: target cannot be a descendant of dragged node
|
|
197
208
|
const draggedNode = findNode(prefabData.root, draggedId);
|
|
198
209
|
if (draggedNode && findNode(draggedNode, targetId)) return;
|
|
199
210
|
|
|
211
|
+
e.preventDefault();
|
|
200
212
|
e.dataTransfer.dropEffect = "move";
|
|
201
213
|
};
|
|
202
214
|
|
|
203
215
|
const handleDrop = (e: React.DragEvent, targetId: string) => {
|
|
204
|
-
e.preventDefault();
|
|
205
|
-
e.stopPropagation();
|
|
206
216
|
if (!draggedId || draggedId === targetId) return;
|
|
207
217
|
|
|
218
|
+
e.preventDefault();
|
|
219
|
+
|
|
208
220
|
setPrefabData(prev => {
|
|
209
221
|
const newRoot = JSON.parse(JSON.stringify(prev.root));
|
|
222
|
+
const draggedNode = findNode(newRoot, draggedId);
|
|
223
|
+
if (draggedNode && findNode(draggedNode, targetId)) return prev;
|
|
210
224
|
|
|
211
|
-
// Check cycle again on the fresh tree
|
|
212
|
-
const draggedNodeRef = findNode(newRoot, draggedId);
|
|
213
|
-
if (draggedNodeRef && findNode(draggedNodeRef, targetId)) return prev;
|
|
214
|
-
|
|
215
|
-
// Remove from old parent
|
|
216
225
|
const parent = findParent(newRoot, draggedId);
|
|
217
226
|
if (!parent) return prev;
|
|
218
227
|
|
|
@@ -221,7 +230,6 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
221
230
|
|
|
222
231
|
parent.children = parent.children!.filter(c => c.id !== draggedId);
|
|
223
232
|
|
|
224
|
-
// Add to new parent
|
|
225
233
|
const target = findNode(newRoot, targetId);
|
|
226
234
|
if (target) {
|
|
227
235
|
target.children = target.children || [];
|
|
@@ -247,11 +255,13 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
247
255
|
...styles.row,
|
|
248
256
|
...(isSelected ? styles.rowSelected : null),
|
|
249
257
|
paddingLeft: `${depth * 10 + 6}px`,
|
|
258
|
+
cursor: node.id !== prefabData.root.id ? "grab" : "pointer",
|
|
250
259
|
}}
|
|
260
|
+
draggable={node.id !== prefabData.root.id}
|
|
251
261
|
onClick={(e) => { e.stopPropagation(); setSelectedId(node.id); }}
|
|
252
262
|
onContextMenu={(e) => handleContextMenu(e, node.id)}
|
|
253
|
-
draggable={node.id !== prefabData.root.id}
|
|
254
263
|
onDragStart={(e) => handleDragStart(e, node.id)}
|
|
264
|
+
onDragEnd={handleDragEnd}
|
|
255
265
|
onDragOver={(e) => handleDragOver(e, node.id)}
|
|
256
266
|
onDrop={(e) => handleDrop(e, node.id)}
|
|
257
267
|
onPointerEnter={(e) => {
|
|
@@ -276,6 +286,19 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
276
286
|
>
|
|
277
287
|
{isCollapsed ? '▶' : '▼'}
|
|
278
288
|
</span>
|
|
289
|
+
{node.id !== prefabData.root.id && (
|
|
290
|
+
<span
|
|
291
|
+
style={styles.dragHandle}
|
|
292
|
+
onPointerEnter={(e) => {
|
|
293
|
+
(e.currentTarget as HTMLSpanElement).style.opacity = "0.9";
|
|
294
|
+
}}
|
|
295
|
+
onPointerLeave={(e) => {
|
|
296
|
+
(e.currentTarget as HTMLSpanElement).style.opacity = "0.4";
|
|
297
|
+
}}
|
|
298
|
+
>
|
|
299
|
+
⋮⋮
|
|
300
|
+
</span>
|
|
301
|
+
)}
|
|
279
302
|
<span style={styles.idText}>
|
|
280
303
|
{node.id}
|
|
281
304
|
</span>
|
|
@@ -256,33 +256,6 @@ function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransfo
|
|
|
256
256
|
</button>
|
|
257
257
|
</div>
|
|
258
258
|
|
|
259
|
-
<div style={s.section}>
|
|
260
|
-
<label style={s.label}>Mode</label>
|
|
261
|
-
<div style={{ display: 'flex', gap: 6 }}>
|
|
262
|
-
{["translate", "rotate", "scale"].map(mode => (
|
|
263
|
-
<button
|
|
264
|
-
key={mode}
|
|
265
|
-
onClick={() => setTransformMode(mode as any)}
|
|
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
|
-
}}
|
|
277
|
-
>
|
|
278
|
-
{mode[0].toUpperCase()}
|
|
279
|
-
</button>
|
|
280
|
-
))}
|
|
281
|
-
</div>
|
|
282
|
-
</div>
|
|
283
|
-
|
|
284
|
-
{/* Components (legacy renderer removed) */}
|
|
285
|
-
|
|
286
259
|
{node.components && Object.entries(node.components).map(([key, comp]: [string, any]) => {
|
|
287
260
|
if (!comp) return null;
|
|
288
261
|
const componentDef = ALL_COMPONENTS[comp.type];
|
|
@@ -322,6 +295,8 @@ function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransfo
|
|
|
322
295
|
}
|
|
323
296
|
}))}
|
|
324
297
|
basePath={basePath}
|
|
298
|
+
transformMode={transformMode}
|
|
299
|
+
setTransformMode={setTransformMode}
|
|
325
300
|
/>
|
|
326
301
|
) : null}
|
|
327
302
|
</div>
|