react-three-game 0.0.56 → 0.0.57
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/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/shared/GameCanvas.js +1 -3
- package/dist/tools/assetviewer/page.js +35 -14
- package/dist/tools/prefabeditor/Dropdown.d.ts +15 -0
- package/dist/tools/prefabeditor/Dropdown.js +82 -0
- package/dist/tools/prefabeditor/EditorContext.d.ts +5 -0
- package/dist/tools/prefabeditor/EditorTree.js +138 -56
- package/dist/tools/prefabeditor/EditorUI.js +1 -1
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +1 -0
- package/dist/tools/prefabeditor/PrefabEditor.js +13 -2
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +1 -0
- package/dist/tools/prefabeditor/PrefabRoot.js +120 -34
- package/dist/tools/prefabeditor/components/AmbientLightComponent.js +3 -7
- package/dist/tools/prefabeditor/components/CameraComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/CameraComponent.js +25 -0
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +2 -2
- package/dist/tools/prefabeditor/components/EnvironmentComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/EnvironmentComponent.js +15 -0
- package/dist/tools/prefabeditor/components/GeometryComponent.js +46 -46
- package/dist/tools/prefabeditor/components/Input.d.ts +51 -1
- package/dist/tools/prefabeditor/components/Input.js +73 -21
- package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +8 -2
- package/dist/tools/prefabeditor/components/MaterialComponent.js +122 -14
- package/dist/tools/prefabeditor/components/ModelComponent.js +44 -3
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +16 -81
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +4 -12
- package/dist/tools/prefabeditor/components/TextComponent.js +7 -53
- package/dist/tools/prefabeditor/components/TransformComponent.js +18 -8
- package/dist/tools/prefabeditor/components/index.js +5 -1
- package/dist/tools/prefabeditor/styles.d.ts +5 -2
- package/dist/tools/prefabeditor/styles.js +7 -3
- package/dist/tools/prefabeditor/utils.d.ts +4 -3
- package/dist/tools/prefabeditor/utils.js +53 -5
- package/package.json +1 -1
- package/src/index.ts +7 -0
- package/src/shared/GameCanvas.tsx +0 -3
- package/src/tools/assetviewer/page.tsx +77 -45
- package/src/tools/prefabeditor/Dropdown.tsx +112 -0
- package/src/tools/prefabeditor/EditorContext.tsx +5 -0
- package/src/tools/prefabeditor/EditorTree.tsx +234 -101
- package/src/tools/prefabeditor/EditorUI.tsx +1 -1
- package/src/tools/prefabeditor/PrefabEditor.tsx +17 -4
- package/src/tools/prefabeditor/PrefabRoot.tsx +208 -58
- package/src/tools/prefabeditor/components/AmbientLightComponent.tsx +5 -11
- package/src/tools/prefabeditor/components/CameraComponent.tsx +80 -0
- package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +2 -2
- package/src/tools/prefabeditor/components/EnvironmentComponent.tsx +47 -0
- package/src/tools/prefabeditor/components/GeometryComponent.tsx +69 -63
- package/src/tools/prefabeditor/components/Input.tsx +220 -27
- package/src/tools/prefabeditor/components/MaterialComponent.tsx +178 -16
- package/src/tools/prefabeditor/components/ModelComponent.tsx +51 -4
- package/src/tools/prefabeditor/components/PhysicsComponent.tsx +44 -85
- package/src/tools/prefabeditor/components/SpotLightComponent.tsx +11 -17
- package/src/tools/prefabeditor/components/TextComponent.tsx +58 -57
- package/src/tools/prefabeditor/components/TransformComponent.tsx +61 -9
- package/src/tools/prefabeditor/components/index.ts +5 -1
- package/src/tools/prefabeditor/styles.ts +7 -3
- package/src/tools/prefabeditor/utils.ts +55 -4
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { sound as soundManager } from './helpers/SoundManager';
|
|
|
4
4
|
export { default as PrefabEditor } from './tools/prefabeditor/PrefabEditor';
|
|
5
5
|
export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
|
|
6
6
|
export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
|
|
7
|
-
export { FieldRenderer, Input, Label, Vector3Input, ColorInput, StringInput, BooleanInput, SelectInput, } from './tools/prefabeditor/components/Input';
|
|
7
|
+
export { FieldRenderer, FieldGroup, Input, Label, Vector3Input, Vector3Field, NumberField, ColorInput, ColorField, StringInput, StringField, BooleanInput, BooleanField, SelectInput, SelectField, } from './tools/prefabeditor/components/Input';
|
|
8
8
|
export * from './tools/prefabeditor/utils';
|
|
9
9
|
export type { ExportGLBOptions } from './tools/prefabeditor/utils';
|
|
10
10
|
export type { PrefabEditorRef } from './tools/prefabeditor/PrefabEditor';
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
|
|
|
9
9
|
// Prefab Editor - Component Registry
|
|
10
10
|
export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
|
|
11
11
|
// Prefab Editor - Input Components
|
|
12
|
-
export { FieldRenderer, Input, Label, Vector3Input, ColorInput, StringInput, BooleanInput, SelectInput, } from './tools/prefabeditor/components/Input';
|
|
12
|
+
export { FieldRenderer, FieldGroup, Input, Label, Vector3Input, Vector3Field, NumberField, ColorInput, ColorField, StringInput, StringField, BooleanInput, BooleanField, SelectInput, SelectField, } from './tools/prefabeditor/components/Input';
|
|
13
13
|
// Prefab Editor - Styles & Utils
|
|
14
14
|
export * from './tools/prefabeditor/utils';
|
|
15
15
|
// Game Events (physics + custom events)
|
|
@@ -41,7 +41,5 @@ export default function GameCanvas(_a) {
|
|
|
41
41
|
setFrameloop("always");
|
|
42
42
|
});
|
|
43
43
|
return renderer;
|
|
44
|
-
}),
|
|
45
|
-
position: [0, 1, 5],
|
|
46
|
-
} }, props, { children: [_jsx(Suspense, { children: children }), loader ? _jsx(Loader, {}) : null] })) });
|
|
44
|
+
}) }, props, { children: [_jsx(Suspense, { children: children }), loader ? _jsx(Loader, {}) : null] })) });
|
|
47
45
|
}
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Canvas
|
|
2
|
+
import { Canvas } from "@react-three/fiber";
|
|
3
3
|
import { OrbitControls, View, PerspectiveCamera } from "@react-three/drei";
|
|
4
|
-
import { Suspense, useEffect, useState, useRef } from "react";
|
|
4
|
+
import { Component as ReactComponent, Suspense, useEffect, useState, useRef } from "react";
|
|
5
5
|
import { TextureLoader } from "three";
|
|
6
6
|
import { loadModel } from "../dragdrop/modelLoader";
|
|
7
|
+
class ErrorBoundary extends ReactComponent {
|
|
8
|
+
constructor(props) {
|
|
9
|
+
super(props);
|
|
10
|
+
this.state = { hasError: false };
|
|
11
|
+
}
|
|
12
|
+
static getDerivedStateFromError() { return { hasError: true }; }
|
|
13
|
+
componentDidCatch() { var _a, _b; (_b = (_a = this.props).onError) === null || _b === void 0 ? void 0 : _b.call(_a); }
|
|
14
|
+
render() { return this.state.hasError ? null : this.props.children; }
|
|
15
|
+
}
|
|
7
16
|
// view models and textures in manifest, onselect callback
|
|
8
17
|
const styles = {
|
|
9
18
|
errorIcon: { color: '#fca5a5', fontSize: 12 }, // text-red-400 text-xs
|
|
10
19
|
flexFillRelative: { flex: 1, position: 'relative' },
|
|
11
|
-
bottomLabel: { backgroundColor: 'rgba(0,0,0,0.6)', fontSize: 10, padding: '0 4px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center' },
|
|
20
|
+
bottomLabel: { backgroundColor: 'rgba(0,0,0,0.6)', color: '#f9fafb', fontSize: 10, padding: '0 4px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center' },
|
|
21
|
+
textLight: { color: '#f9fafb' },
|
|
12
22
|
iconLarge: { fontSize: 20 }
|
|
13
23
|
};
|
|
14
24
|
function getItemsInPath(files, currentPath) {
|
|
@@ -39,6 +49,7 @@ function FolderTile({ name, onClick }) {
|
|
|
39
49
|
maxWidth: 60,
|
|
40
50
|
aspectRatio: '1 / 1',
|
|
41
51
|
backgroundColor: '#1f2937', /* gray-800 */
|
|
52
|
+
color: '#f9fafb',
|
|
42
53
|
cursor: 'pointer',
|
|
43
54
|
display: 'flex',
|
|
44
55
|
flexDirection: 'column',
|
|
@@ -67,14 +78,14 @@ function useInView() {
|
|
|
67
78
|
function AssetListViewer({ files, selected, onSelect, renderCard }) {
|
|
68
79
|
const [currentPath, setCurrentPath] = useState('');
|
|
69
80
|
const { folders, filesInCurrentPath } = getItemsInPath(files, currentPath);
|
|
70
|
-
return (_jsxs("div", { children: [currentPath && (_jsx("button", { onClick: () => {
|
|
81
|
+
return (_jsxs("div", { style: styles.textLight, children: [currentPath && (_jsx("button", { onClick: () => {
|
|
71
82
|
const pathParts = currentPath.split('/').filter(Boolean);
|
|
72
83
|
pathParts.pop();
|
|
73
84
|
setCurrentPath(pathParts.join('/'));
|
|
74
85
|
}, style: { marginBottom: 4, padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 12, cursor: 'pointer', border: 'none' }, children: "\u2190 Back" })), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4 }, children: [folders.map((folder) => (_jsx(FolderTile, { name: folder, onClick: () => setCurrentPath(currentPath ? `${currentPath}/${folder}` : folder) }, folder))), filesInCurrentPath.map((file) => (_jsx("div", { children: renderCard(file, onSelect) }, file)))] })] }));
|
|
75
86
|
}
|
|
76
87
|
export function TextureListViewer({ files, selected, onSelect, basePath = "" }) {
|
|
77
|
-
return (_jsxs(
|
|
88
|
+
return (_jsxs("div", { style: { position: 'relative', width: '100%', height: '100%' }, children: [_jsx("div", { style: { width: '100%', height: '100%', overflowY: 'auto', overflowX: 'hidden', paddingRight: 4 }, children: _jsx(AssetListViewer, { files: files, selected: selected, onSelect: onSelect, renderCard: (file, onSelectHandler) => (_jsx(TextureCard, { file: file, basePath: basePath, onSelect: onSelectHandler })) }) }), _jsx(SharedCanvas, {})] }));
|
|
78
89
|
}
|
|
79
90
|
function TextureCard({ file, onSelect, basePath = "" }) {
|
|
80
91
|
const [error, setError] = useState(false);
|
|
@@ -84,17 +95,24 @@ function TextureCard({ file, onSelect, basePath = "" }) {
|
|
|
84
95
|
if (error) {
|
|
85
96
|
return (_jsx("div", { ref: ref, style: { aspectRatio: '1 / 1', backgroundColor: '#374151', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }, onClick: () => onSelect(file), children: _jsx("div", { style: styles.errorIcon, children: "\u2717" }) }));
|
|
86
97
|
}
|
|
87
|
-
return (_jsxs("div", { ref: ref, style: { maxWidth: 60, aspectRatio: '1 / 1', backgroundColor: '#1f2937', cursor: 'pointer', display: 'flex', flexDirection: 'column' }, onClick: () => onSelect(file), onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [_jsx("div", { style: { flex: 1, position: 'relative' }, children: isInView ? (_jsxs(View, { style: { width: '100%', height: '100%' }, children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 0, 2.5], fov: 50 }),
|
|
98
|
+
return (_jsxs("div", { ref: ref, style: { maxWidth: 60, aspectRatio: '1 / 1', backgroundColor: '#1f2937', color: '#f9fafb', cursor: 'pointer', display: 'flex', flexDirection: 'column' }, onClick: () => onSelect(file), onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [_jsx("div", { style: { flex: 1, position: 'relative' }, children: isInView ? (_jsxs(View, { style: { width: '100%', height: '100%' }, children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 0, 2.5], fov: 50 }), _jsx("ambientLight", { intensity: 0.8 }), _jsx("pointLight", { position: [5, 5, 5], intensity: 0.5 }), _jsx(TextureSphere, { url: fullPath, onError: () => setError(true) }), _jsx(OrbitControls, { enableZoom: false, enablePan: false, autoRotate: isHovered, autoRotateSpeed: 2 })] })) : null }), _jsx("div", { style: styles.bottomLabel, children: file.split('/').pop() })] }));
|
|
88
99
|
}
|
|
89
100
|
function TextureSphere({ url, onError }) {
|
|
90
|
-
const texture
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
101
|
+
const [texture, setTexture] = useState(null);
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
setTexture(null);
|
|
104
|
+
const loader = new TextureLoader();
|
|
105
|
+
loader.load(url, (tex) => setTexture(tex), undefined, (err) => {
|
|
106
|
+
console.warn('Failed to load texture:', url, err);
|
|
107
|
+
onError === null || onError === void 0 ? void 0 : onError();
|
|
108
|
+
});
|
|
109
|
+
}, [url]);
|
|
110
|
+
if (!texture)
|
|
111
|
+
return null;
|
|
94
112
|
return (_jsxs("mesh", { position: [0, 0, 0], children: [_jsx("sphereGeometry", { args: [1, 32, 32] }), _jsx("meshStandardMaterial", { map: texture })] }));
|
|
95
113
|
}
|
|
96
114
|
export function ModelListViewer({ files, selected, onSelect, basePath = "" }) {
|
|
97
|
-
return (_jsxs(
|
|
115
|
+
return (_jsxs("div", { style: { position: 'relative', width: '100%', height: '100%' }, children: [_jsx("div", { style: { width: '100%', height: '100%', overflowY: 'auto', overflowX: 'hidden', paddingRight: 4 }, children: _jsx(AssetListViewer, { files: files, selected: selected, onSelect: onSelect, renderCard: (file, onSelectHandler) => (_jsx(ModelCard, { file: file, basePath: basePath, onSelect: onSelectHandler })) }) }), _jsx(SharedCanvas, {})] }));
|
|
98
116
|
}
|
|
99
117
|
function ModelCard({ file, onSelect, basePath = "" }) {
|
|
100
118
|
const [error, setError] = useState(false);
|
|
@@ -103,7 +121,7 @@ function ModelCard({ file, onSelect, basePath = "" }) {
|
|
|
103
121
|
if (error) {
|
|
104
122
|
return (_jsx("div", { ref: ref, style: { aspectRatio: '1 / 1', backgroundColor: '#374151', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }, onClick: () => onSelect(file), children: _jsx("div", { style: styles.errorIcon, children: "\u2717" }) }));
|
|
105
123
|
}
|
|
106
|
-
return (_jsxs("div", { ref: ref, style: { maxWidth: 60, aspectRatio: '1 / 1', backgroundColor: '#111827', cursor: 'pointer', display: 'flex', flexDirection: 'column' }, onClick: () => onSelect(file), children: [_jsx("div", { style: styles.flexFillRelative, children: isInView ? (_jsxs(View, { style: { width: '100%', height: '100%' }, children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 1, 3], fov: 50 }), _jsxs(Suspense, { fallback: null, children: [_jsx("ambientLight", { intensity: 1 }), _jsx("pointLight", { position: [5, 5, 5], intensity: 0.5 }), _jsx(ModelPreview, { url: fullPath, onError: () => setError(true) }), _jsx(OrbitControls, { enableZoom: false })] })] })) : null }), _jsx("div", { style:
|
|
124
|
+
return (_jsxs("div", { ref: ref, style: { maxWidth: 60, aspectRatio: '1 / 1', backgroundColor: '#111827', color: '#f9fafb', cursor: 'pointer', display: 'flex', flexDirection: 'column' }, onClick: () => onSelect(file), children: [_jsx("div", { style: styles.flexFillRelative, children: isInView ? (_jsxs(View, { style: { width: '100%', height: '100%' }, children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 1, 3], fov: 50 }), _jsxs(Suspense, { fallback: null, children: [_jsx("ambientLight", { intensity: 1 }), _jsx("pointLight", { position: [5, 5, 5], intensity: 0.5 }), _jsx(ModelPreview, { url: fullPath, onError: () => setError(true) }), _jsx(OrbitControls, { enableZoom: false })] })] })) : null }), _jsx("div", { style: styles.bottomLabel, children: file.split('/').pop() })] }));
|
|
107
125
|
}
|
|
108
126
|
function ModelPreview({ url, onError }) {
|
|
109
127
|
const [model, setModel] = useState(null);
|
|
@@ -135,7 +153,7 @@ export function SoundListViewer({ files, selected, onSelect, basePath = "" }) {
|
|
|
135
153
|
function SoundCard({ file, onSelect, basePath = "" }) {
|
|
136
154
|
const fileName = file.split('/').pop() || '';
|
|
137
155
|
const fullPath = basePath ? `/${basePath}${file}` : file;
|
|
138
|
-
return (_jsxs("div", { onClick: () => onSelect(file), style: { aspectRatio: '1 / 1', backgroundColor: '#374151', cursor: 'pointer', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }, children: [_jsx("div", { style: styles.iconLarge, children: "\uD83D\uDD0A" }), _jsx("div", { style: { fontSize: 12, padding: '0 4px', marginTop: 4, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center', width: '100%' }, children: fileName })] }));
|
|
156
|
+
return (_jsxs("div", { onClick: () => onSelect(file), style: { aspectRatio: '1 / 1', backgroundColor: '#374151', color: '#f9fafb', cursor: 'pointer', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }, children: [_jsx("div", { style: styles.iconLarge, children: "\uD83D\uDD0A" }), _jsx("div", { style: { color: '#f9fafb', fontSize: 12, padding: '0 4px', marginTop: 4, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center', width: '100%' }, children: fileName })] }));
|
|
139
157
|
}
|
|
140
158
|
// Single Asset Viewer Components - display only one selected asset
|
|
141
159
|
export function SingleTextureViewer({ file, basePath = "" }) {
|
|
@@ -155,12 +173,15 @@ export function SingleSoundViewer({ file, basePath = "" }) {
|
|
|
155
173
|
}
|
|
156
174
|
// Shared Canvas Component - can be used independently in any viewer
|
|
157
175
|
export function SharedCanvas() {
|
|
158
|
-
return (_jsx(Canvas, { shadows: true, dpr: [1, 1.5], camera: { position: [0, 0, 3], fov: 45, near: 0.1, far: 1000 },
|
|
176
|
+
return (_jsx(Canvas, { shadows: true, dpr: [1, 1.5], gl: { alpha: true }, camera: { position: [0, 0, 3], fov: 45, near: 0.1, far: 1000 }, onCreated: ({ gl }) => {
|
|
177
|
+
gl.setClearAlpha(0);
|
|
178
|
+
}, style: {
|
|
159
179
|
position: 'fixed',
|
|
160
180
|
top: 0,
|
|
161
181
|
left: 0,
|
|
162
182
|
width: '100vw',
|
|
163
183
|
height: '100vh',
|
|
164
184
|
pointerEvents: 'none',
|
|
185
|
+
background: 'transparent',
|
|
165
186
|
}, eventSource: typeof document !== 'undefined' ? document.getElementById('root') || undefined : undefined, eventPrefix: "client", children: _jsx(View.Port, {}) }));
|
|
166
187
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
type Placement = 'bottom-start' | 'bottom-end' | 'left-start' | 'right-start';
|
|
3
|
+
export declare function Dropdown({ trigger, children, placement, offset, zIndex, }: {
|
|
4
|
+
trigger: (props: {
|
|
5
|
+
ref: React.RefObject<HTMLButtonElement | null>;
|
|
6
|
+
isOpen: boolean;
|
|
7
|
+
toggle: () => void;
|
|
8
|
+
close: () => void;
|
|
9
|
+
}) => ReactNode;
|
|
10
|
+
children: ReactNode | ((close: () => void) => ReactNode);
|
|
11
|
+
placement?: Placement;
|
|
12
|
+
offset?: number;
|
|
13
|
+
zIndex?: number;
|
|
14
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
export function Dropdown({ trigger, children, placement = 'bottom-end', offset = 6, zIndex = 1000, }) {
|
|
5
|
+
var _a, _b;
|
|
6
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
7
|
+
const [position, setPosition] = useState(null);
|
|
8
|
+
const triggerRef = useRef(null);
|
|
9
|
+
const panelRef = useRef(null);
|
|
10
|
+
const close = () => setIsOpen(false);
|
|
11
|
+
const toggle = () => setIsOpen(prev => !prev);
|
|
12
|
+
useLayoutEffect(() => {
|
|
13
|
+
if (!isOpen || !triggerRef.current || !panelRef.current || typeof window === 'undefined')
|
|
14
|
+
return;
|
|
15
|
+
const updatePosition = () => {
|
|
16
|
+
var _a, _b;
|
|
17
|
+
const triggerRect = (_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
|
|
18
|
+
const panelRect = (_b = panelRef.current) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect();
|
|
19
|
+
if (!triggerRect || !panelRect)
|
|
20
|
+
return;
|
|
21
|
+
let left = triggerRect.left;
|
|
22
|
+
let top = triggerRect.bottom + offset;
|
|
23
|
+
if (placement === 'bottom-end') {
|
|
24
|
+
left = triggerRect.right - panelRect.width;
|
|
25
|
+
top = triggerRect.bottom + offset;
|
|
26
|
+
}
|
|
27
|
+
else if (placement === 'bottom-start') {
|
|
28
|
+
left = triggerRect.left;
|
|
29
|
+
top = triggerRect.bottom + offset;
|
|
30
|
+
}
|
|
31
|
+
else if (placement === 'left-start') {
|
|
32
|
+
left = triggerRect.left - panelRect.width - offset;
|
|
33
|
+
top = triggerRect.top;
|
|
34
|
+
}
|
|
35
|
+
else if (placement === 'right-start') {
|
|
36
|
+
left = triggerRect.right + offset;
|
|
37
|
+
top = triggerRect.top;
|
|
38
|
+
}
|
|
39
|
+
left = Math.max(8, Math.min(left, window.innerWidth - panelRect.width - 8));
|
|
40
|
+
top = Math.max(8, Math.min(top, window.innerHeight - panelRect.height - 8));
|
|
41
|
+
setPosition({ left, top });
|
|
42
|
+
};
|
|
43
|
+
updatePosition();
|
|
44
|
+
window.addEventListener('resize', updatePosition);
|
|
45
|
+
window.addEventListener('scroll', updatePosition, true);
|
|
46
|
+
return () => {
|
|
47
|
+
window.removeEventListener('resize', updatePosition);
|
|
48
|
+
window.removeEventListener('scroll', updatePosition, true);
|
|
49
|
+
};
|
|
50
|
+
}, [isOpen, placement, offset]);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!isOpen)
|
|
53
|
+
return;
|
|
54
|
+
const handlePointerDown = (event) => {
|
|
55
|
+
var _a, _b;
|
|
56
|
+
const target = event.target;
|
|
57
|
+
if (!target)
|
|
58
|
+
return;
|
|
59
|
+
if ((_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.contains(target))
|
|
60
|
+
return;
|
|
61
|
+
if ((_b = panelRef.current) === null || _b === void 0 ? void 0 : _b.contains(target))
|
|
62
|
+
return;
|
|
63
|
+
close();
|
|
64
|
+
};
|
|
65
|
+
const handleKeyDown = (event) => {
|
|
66
|
+
if (event.key === 'Escape')
|
|
67
|
+
close();
|
|
68
|
+
};
|
|
69
|
+
document.addEventListener('pointerdown', handlePointerDown);
|
|
70
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
71
|
+
return () => {
|
|
72
|
+
document.removeEventListener('pointerdown', handlePointerDown);
|
|
73
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
74
|
+
};
|
|
75
|
+
}, [isOpen]);
|
|
76
|
+
return (_jsxs(_Fragment, { children: [trigger({ ref: triggerRef, isOpen, toggle, close }), isOpen && typeof document !== 'undefined' && createPortal(_jsx("div", { ref: panelRef, onMouseLeave: close, style: {
|
|
77
|
+
position: 'fixed',
|
|
78
|
+
left: (_a = position === null || position === void 0 ? void 0 : position.left) !== null && _a !== void 0 ? _a : -9999,
|
|
79
|
+
top: (_b = position === null || position === void 0 ? void 0 : position.top) !== null && _b !== void 0 ? _b : -9999,
|
|
80
|
+
zIndex,
|
|
81
|
+
}, children: typeof children === 'function' ? children(close) : children }), document.body)] }));
|
|
82
|
+
}
|
|
@@ -3,6 +3,11 @@ interface EditorContextType {
|
|
|
3
3
|
setTransformMode: (mode: "translate" | "rotate" | "scale") => void;
|
|
4
4
|
snapResolution: number;
|
|
5
5
|
setSnapResolution: (resolution: number) => void;
|
|
6
|
+
positionSnap: number;
|
|
7
|
+
setPositionSnap: (resolution: number) => void;
|
|
8
|
+
rotationSnap: number;
|
|
9
|
+
setRotationSnap: (resolution: number) => void;
|
|
10
|
+
onFocusNode?: (nodeId: string) => void;
|
|
6
11
|
onScreenshot?: () => void;
|
|
7
12
|
onExportGLB?: () => void;
|
|
8
13
|
}
|
|
@@ -13,20 +13,74 @@ import { getComponent } from './components/ComponentRegistry';
|
|
|
13
13
|
import { base, colors, tree, menu } from './styles';
|
|
14
14
|
import { findNode, findParent, deleteNode, cloneNode, updateNodeById, loadJson, saveJson, regenerateIds } from './utils';
|
|
15
15
|
import { useEditorContext } from './EditorContext';
|
|
16
|
+
import { Dropdown } from './Dropdown';
|
|
17
|
+
function moveNode(root, draggedId, targetId, position) {
|
|
18
|
+
const draggedNode = findNode(root, draggedId);
|
|
19
|
+
const oldParent = findParent(root, draggedId);
|
|
20
|
+
if (!draggedNode || !oldParent || findNode(draggedNode, targetId)) {
|
|
21
|
+
return root;
|
|
22
|
+
}
|
|
23
|
+
if (position === 'before') {
|
|
24
|
+
const targetParent = findParent(root, targetId);
|
|
25
|
+
if (!(targetParent === null || targetParent === void 0 ? void 0 : targetParent.children))
|
|
26
|
+
return root;
|
|
27
|
+
if (targetParent.id === oldParent.id) {
|
|
28
|
+
const siblings = targetParent.children.filter(child => child.id !== draggedId);
|
|
29
|
+
const targetIndex = siblings.findIndex(child => child.id === targetId);
|
|
30
|
+
if (targetIndex === -1)
|
|
31
|
+
return root;
|
|
32
|
+
siblings.splice(targetIndex, 0, draggedNode);
|
|
33
|
+
return updateNodeById(root, targetParent.id, parent => (Object.assign(Object.assign({}, parent), { children: siblings })));
|
|
34
|
+
}
|
|
35
|
+
const rootWithoutDragged = updateNodeById(root, oldParent.id, parent => {
|
|
36
|
+
var _a;
|
|
37
|
+
return (Object.assign(Object.assign({}, parent), { children: ((_a = parent.children) !== null && _a !== void 0 ? _a : []).filter(child => child.id !== draggedId) }));
|
|
38
|
+
});
|
|
39
|
+
return updateNodeById(rootWithoutDragged, targetParent.id, parent => {
|
|
40
|
+
var _a;
|
|
41
|
+
const children = [...((_a = parent.children) !== null && _a !== void 0 ? _a : [])];
|
|
42
|
+
const targetIndex = children.findIndex(child => child.id === targetId);
|
|
43
|
+
if (targetIndex === -1)
|
|
44
|
+
return parent;
|
|
45
|
+
children.splice(targetIndex, 0, draggedNode);
|
|
46
|
+
return Object.assign(Object.assign({}, parent), { children });
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const rootWithoutDragged = updateNodeById(root, oldParent.id, parent => {
|
|
50
|
+
var _a;
|
|
51
|
+
return (Object.assign(Object.assign({}, parent), { children: ((_a = parent.children) !== null && _a !== void 0 ? _a : []).filter(child => child.id !== draggedId) }));
|
|
52
|
+
});
|
|
53
|
+
return updateNodeById(rootWithoutDragged, targetId, target => {
|
|
54
|
+
var _a;
|
|
55
|
+
return (Object.assign(Object.assign({}, target), { children: [...((_a = target.children) !== null && _a !== void 0 ? _a : []), draggedNode] }));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function duplicateNodeBelow(root, nodeId) {
|
|
59
|
+
const node = findNode(root, nodeId);
|
|
60
|
+
const parent = findParent(root, nodeId);
|
|
61
|
+
if (!node || !parent)
|
|
62
|
+
return null;
|
|
63
|
+
const duplicate = cloneNode(node);
|
|
64
|
+
const nextRoot = updateNodeById(root, parent.id, currentParent => (Object.assign(Object.assign({}, currentParent), { children: (() => {
|
|
65
|
+
var _a;
|
|
66
|
+
const children = [...((_a = currentParent.children) !== null && _a !== void 0 ? _a : [])];
|
|
67
|
+
const index = children.findIndex(child => child.id === nodeId);
|
|
68
|
+
if (index === -1)
|
|
69
|
+
return [...children, duplicate];
|
|
70
|
+
children.splice(index + 1, 0, duplicate);
|
|
71
|
+
return children;
|
|
72
|
+
})() })));
|
|
73
|
+
return { root: nextRoot, duplicatedId: duplicate.id };
|
|
74
|
+
}
|
|
16
75
|
export default function EditorTree({ prefabData, setPrefabData, selectedId, setSelectedId, onUndo, onRedo, canUndo, canRedo }) {
|
|
17
|
-
const
|
|
76
|
+
const { onFocusNode } = useEditorContext();
|
|
18
77
|
const [draggedId, setDraggedId] = useState(null);
|
|
78
|
+
const [dropTarget, setDropTarget] = useState(null);
|
|
19
79
|
const [collapsedIds, setCollapsedIds] = useState(new Set());
|
|
20
80
|
const [collapsed, setCollapsed] = useState(false);
|
|
21
|
-
const [fileMenuOpen, setFileMenuOpen] = useState(false);
|
|
22
81
|
const [searchQuery, setSearchQuery] = useState('');
|
|
23
82
|
if (!prefabData || !setPrefabData)
|
|
24
83
|
return null;
|
|
25
|
-
const handleContextMenu = (e, nodeId) => {
|
|
26
|
-
e.preventDefault();
|
|
27
|
-
e.stopPropagation();
|
|
28
|
-
setContextMenu({ x: e.clientX, y: e.clientY, nodeId });
|
|
29
|
-
};
|
|
30
84
|
const toggleCollapse = (e, id) => {
|
|
31
85
|
e.stopPropagation();
|
|
32
86
|
setCollapsedIds(prev => {
|
|
@@ -36,35 +90,33 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
36
90
|
});
|
|
37
91
|
};
|
|
38
92
|
const handleAddChild = (parentId) => {
|
|
93
|
+
var _a;
|
|
94
|
+
const newNode = {
|
|
95
|
+
id: crypto.randomUUID(),
|
|
96
|
+
name: "New Node",
|
|
97
|
+
components: {
|
|
98
|
+
transform: {
|
|
99
|
+
type: "Transform",
|
|
100
|
+
properties: Object.assign({}, (_a = getComponent('Transform')) === null || _a === void 0 ? void 0 : _a.defaultProperties)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
39
104
|
setPrefabData(prev => (Object.assign(Object.assign({}, prev), { root: updateNodeById(prev.root, parentId, parent => {
|
|
40
|
-
var _a
|
|
41
|
-
return (Object.assign(Object.assign({}, parent), { children: [...((_a = parent.children) !== null && _a !== void 0 ? _a : []),
|
|
42
|
-
id: crypto.randomUUID(),
|
|
43
|
-
name: "New Node",
|
|
44
|
-
components: {
|
|
45
|
-
transform: {
|
|
46
|
-
type: "Transform",
|
|
47
|
-
properties: Object.assign({}, (_b = getComponent('Transform')) === null || _b === void 0 ? void 0 : _b.defaultProperties)
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}] }));
|
|
105
|
+
var _a;
|
|
106
|
+
return (Object.assign(Object.assign({}, parent), { children: [...((_a = parent.children) !== null && _a !== void 0 ? _a : []), newNode] }));
|
|
51
107
|
}) })));
|
|
52
|
-
|
|
108
|
+
setSelectedId(newNode.id);
|
|
53
109
|
};
|
|
54
110
|
const handleDuplicate = (nodeId) => {
|
|
55
111
|
if (nodeId === prefabData.root.id)
|
|
56
112
|
return;
|
|
57
113
|
setPrefabData(prev => {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
if (!node || !parent)
|
|
114
|
+
const result = duplicateNodeBelow(prev.root, nodeId);
|
|
115
|
+
if (!result)
|
|
61
116
|
return prev;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return (Object.assign(Object.assign({}, p), { children: [...((_a = p.children) !== null && _a !== void 0 ? _a : []), cloneNode(node)] }));
|
|
65
|
-
}) });
|
|
117
|
+
setSelectedId(result.duplicatedId);
|
|
118
|
+
return Object.assign(Object.assign({}, prev), { root: result.root });
|
|
66
119
|
});
|
|
67
|
-
setContextMenu(null);
|
|
68
120
|
};
|
|
69
121
|
const handleDelete = (nodeId) => {
|
|
70
122
|
if (nodeId === prefabData.root.id)
|
|
@@ -72,11 +124,9 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
72
124
|
setPrefabData(prev => (Object.assign(Object.assign({}, prev), { root: deleteNode(prev.root, nodeId) })));
|
|
73
125
|
if (selectedId === nodeId)
|
|
74
126
|
setSelectedId(null);
|
|
75
|
-
setContextMenu(null);
|
|
76
127
|
};
|
|
77
128
|
const handleToggleDisabled = (nodeId) => {
|
|
78
129
|
setPrefabData(prev => (Object.assign(Object.assign({}, prev), { root: updateNodeById(prev.root, nodeId, node => (Object.assign(Object.assign({}, node), { disabled: !node.disabled }))) })));
|
|
79
|
-
setContextMenu(null);
|
|
80
130
|
};
|
|
81
131
|
const handleDragStart = (e, id) => {
|
|
82
132
|
if (id === prefabData.root.id)
|
|
@@ -84,31 +134,38 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
84
134
|
e.dataTransfer.effectAllowed = "move";
|
|
85
135
|
setDraggedId(id);
|
|
86
136
|
};
|
|
87
|
-
const
|
|
137
|
+
const getDropPosition = (e, isRoot) => {
|
|
138
|
+
if (isRoot)
|
|
139
|
+
return 'inside';
|
|
140
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
141
|
+
return e.clientY <= rect.top + rect.height * 0.35 ? 'before' : 'inside';
|
|
142
|
+
};
|
|
143
|
+
const handleDragOver = (e, targetId, isRoot) => {
|
|
88
144
|
if (!draggedId || draggedId === targetId)
|
|
89
145
|
return;
|
|
90
146
|
const draggedNode = findNode(prefabData.root, draggedId);
|
|
91
147
|
if (draggedNode && findNode(draggedNode, targetId))
|
|
92
148
|
return;
|
|
93
149
|
e.preventDefault();
|
|
150
|
+
setDropTarget({ id: targetId, position: getDropPosition(e, isRoot) });
|
|
94
151
|
};
|
|
95
|
-
const
|
|
152
|
+
const handleDragLeave = (e, targetId) => {
|
|
153
|
+
const relatedTarget = e.relatedTarget;
|
|
154
|
+
if (relatedTarget instanceof Node && e.currentTarget.contains(relatedTarget))
|
|
155
|
+
return;
|
|
156
|
+
setDropTarget(current => (current === null || current === void 0 ? void 0 : current.id) === targetId ? null : current);
|
|
157
|
+
};
|
|
158
|
+
const handleDrop = (e, targetId, isRoot) => {
|
|
96
159
|
if (!draggedId || draggedId === targetId)
|
|
97
160
|
return;
|
|
98
161
|
e.preventDefault();
|
|
162
|
+
const dropPosition = getDropPosition(e, isRoot);
|
|
99
163
|
setPrefabData(prev => {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
if (!draggedNode || !oldParent || findNode(draggedNode, targetId))
|
|
103
|
-
return prev;
|
|
104
|
-
let root = updateNodeById(prev.root, oldParent.id, p => (Object.assign(Object.assign({}, p), { children: p.children.filter(c => c.id !== draggedId) })));
|
|
105
|
-
root = updateNodeById(root, targetId, t => {
|
|
106
|
-
var _a;
|
|
107
|
-
return (Object.assign(Object.assign({}, t), { children: [...((_a = t.children) !== null && _a !== void 0 ? _a : []), draggedNode] }));
|
|
108
|
-
});
|
|
109
|
-
return Object.assign(Object.assign({}, prev), { root });
|
|
164
|
+
const root = moveNode(prev.root, draggedId, targetId, dropPosition);
|
|
165
|
+
return root === prev.root ? prev : Object.assign(Object.assign({}, prev), { root });
|
|
110
166
|
});
|
|
111
167
|
setDraggedId(null);
|
|
168
|
+
setDropTarget(null);
|
|
112
169
|
};
|
|
113
170
|
const matchesSearch = (node, query) => {
|
|
114
171
|
var _a, _b, _c;
|
|
@@ -130,26 +187,51 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
130
187
|
const isCollapsed = collapsedIds.has(node.id);
|
|
131
188
|
const hasChildren = node.children && node.children.length > 0;
|
|
132
189
|
const isRoot = node.id === prefabData.root.id;
|
|
133
|
-
|
|
190
|
+
const isDropTarget = (dropTarget === null || dropTarget === void 0 ? void 0 : dropTarget.id) === node.id;
|
|
191
|
+
const showDropBefore = isDropTarget && (dropTarget === null || dropTarget === void 0 ? void 0 : dropTarget.position) === 'before';
|
|
192
|
+
const showDropInside = isDropTarget && (dropTarget === null || dropTarget === void 0 ? void 0 : dropTarget.position) === 'inside';
|
|
193
|
+
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(node.id); }, onDragStart: (e) => handleDragStart(e, node.id), onDragEnd: () => { setDraggedId(null); setDropTarget(null); }, onDragOver: (e) => handleDragOver(e, node.id, isRoot), onDragLeave: (e) => handleDragLeave(e, node.id), onDrop: (e) => handleDrop(e, node.id, isRoot), children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', flex: 1, minWidth: 0 }, children: [_jsx("span", { style: {
|
|
134
194
|
width: 12,
|
|
135
195
|
opacity: 0.6,
|
|
136
196
|
marginRight: 4,
|
|
137
197
|
cursor: 'pointer',
|
|
138
198
|
visibility: hasChildren ? 'visible' : 'hidden'
|
|
139
|
-
}, 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 })] }), !isRoot && (_jsx("button", { style: {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
199
|
+
}, 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 })] }), !isRoot && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(Dropdown, { placement: "bottom-end", trigger: ({ ref, toggle }) => (_jsx("button", { ref: ref, style: {
|
|
200
|
+
background: 'none',
|
|
201
|
+
border: 'none',
|
|
202
|
+
cursor: 'pointer',
|
|
203
|
+
padding: '0 4px',
|
|
204
|
+
fontSize: 14,
|
|
205
|
+
opacity: 0.7,
|
|
206
|
+
color: 'inherit',
|
|
207
|
+
}, onClick: (e) => {
|
|
208
|
+
e.stopPropagation();
|
|
209
|
+
toggle();
|
|
210
|
+
}, title: "Node Actions", children: "\u22EF" })), children: (close) => (_jsxs("div", { style: Object.assign(Object.assign({}, menu.container), { position: 'static' }), onClick: (e) => e.stopPropagation(), children: [_jsx("button", { style: menu.item, onClick: () => { handleAddChild(node.id); close(); }, children: "Add Child" }), _jsx("button", { style: menu.item, onClick: () => { setSelectedId(node.id); onFocusNode === null || onFocusNode === void 0 ? void 0 : onFocusNode(node.id); close(); }, children: "Focus Camera" }), _jsx("button", { style: menu.item, onClick: () => { handleDuplicate(node.id); close(); }, children: "Duplicate" }), _jsx("button", { style: Object.assign(Object.assign({}, menu.item), menu.danger), onClick: () => { handleDelete(node.id); close(); }, children: "Delete" })] })) }), _jsx("button", { style: {
|
|
211
|
+
background: 'none',
|
|
212
|
+
border: 'none',
|
|
213
|
+
cursor: 'pointer',
|
|
214
|
+
padding: '0 4px',
|
|
215
|
+
fontSize: 14,
|
|
216
|
+
opacity: node.disabled ? 0.5 : 0.7,
|
|
217
|
+
color: 'inherit',
|
|
218
|
+
}, onClick: (e) => {
|
|
219
|
+
e.stopPropagation();
|
|
220
|
+
handleToggleDisabled(node.id);
|
|
221
|
+
}, title: node.disabled ? 'Enable' : 'Disable', children: node.disabled ? '◎' : '◉' })] })), isRoot && (_jsx(Dropdown, { placement: "bottom-end", trigger: ({ ref, toggle }) => (_jsx("button", { ref: ref, style: {
|
|
222
|
+
background: 'none',
|
|
223
|
+
border: 'none',
|
|
224
|
+
cursor: 'pointer',
|
|
225
|
+
padding: '0 4px',
|
|
226
|
+
fontSize: 14,
|
|
227
|
+
opacity: 0.7,
|
|
228
|
+
color: 'inherit',
|
|
229
|
+
}, onClick: (e) => {
|
|
230
|
+
e.stopPropagation();
|
|
231
|
+
toggle();
|
|
232
|
+
}, title: "Scene Actions", children: "\u22EF" })), children: (close) => (_jsxs("div", { style: Object.assign(Object.assign({}, menu.container), { position: 'static' }), onClick: (e) => e.stopPropagation(), children: [_jsx("button", { style: menu.item, onClick: () => { handleAddChild(node.id); close(); }, children: "Add Child" }), _jsx("button", { style: menu.item, onClick: () => { setSelectedId(node.id); onFocusNode === null || onFocusNode === void 0 ? void 0 : onFocusNode(node.id); close(); }, children: "Focus Camera" })] })) }))] }), !isCollapsed && node.children && node.children.map(child => renderNode(child, depth + 1))] }, node.id));
|
|
151
233
|
};
|
|
152
|
-
return (
|
|
234
|
+
return (_jsx(_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, style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10 }), onClick: (e) => { e.stopPropagation(); toggle(); }, title: "File", children: "\u22EE" })), children: (close) => (_jsx(FileMenu, { prefabData: prefabData, setPrefabData: setPrefabData, 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", { className: "tree-scroll", style: tree.scroll, children: renderNode(prefabData.root) })] }))] }) }));
|
|
153
235
|
}
|
|
154
236
|
function FileMenu({ prefabData, setPrefabData, onClose }) {
|
|
155
237
|
const { onScreenshot, onExportGLB } = useEditorContext();
|
|
@@ -174,5 +256,5 @@ function FileMenu({ prefabData, setPrefabData, onClose }) {
|
|
|
174
256
|
}) })));
|
|
175
257
|
onClose();
|
|
176
258
|
});
|
|
177
|
-
return (_jsxs("div", { style: Object.assign(Object.assign({}, menu.container), { position: '
|
|
259
|
+
return (_jsxs("div", { style: Object.assign(Object.assign({}, menu.container), { position: 'static' }), onClick: (e) => e.stopPropagation(), children: [_jsx("button", { style: menu.item, onClick: handleLoad, children: "\uD83D\uDCE5 Load Prefab JSON" }), _jsx("button", { style: menu.item, onClick: handleSave, children: "\uD83D\uDCBE Save Prefab JSON" }), _jsx("button", { style: menu.item, onClick: handleLoadIntoScene, children: "\uD83D\uDCC2 Load into Scene" }), _jsx("button", { style: menu.item, onClick: () => { onScreenshot === null || onScreenshot === void 0 ? void 0 : onScreenshot(); onClose(); }, children: "\uD83D\uDCF8 Screenshot" }), _jsx("button", { style: menu.item, onClick: () => { onExportGLB === null || onExportGLB === void 0 ? void 0 : onExportGLB(); onClose(); }, children: "\uD83D\uDCE6 Export GLB" })] }));
|
|
178
260
|
}
|
|
@@ -42,7 +42,7 @@ function NodeInspector({ node, updateNode, deleteNode, basePath }) {
|
|
|
42
42
|
if (!newAvailable.includes(addType))
|
|
43
43
|
setAddType(newAvailable[0] || "");
|
|
44
44
|
}, [Object.keys(node.components || {}).join(',')]);
|
|
45
|
-
return _jsxs("div", { style:
|
|
45
|
+
return _jsxs("div", { style: inspector.content, className: "prefab-scroll", 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]) => {
|
|
46
46
|
if (!comp)
|
|
47
47
|
return null;
|
|
48
48
|
const def = ALL_COMPONENTS[comp.type];
|
|
@@ -12,6 +12,7 @@ declare const PrefabEditor: import("react").ForwardRefExoticComponent<{
|
|
|
12
12
|
initialPrefab?: Prefab;
|
|
13
13
|
physics?: boolean;
|
|
14
14
|
onPrefabChange?: (prefab: Prefab) => void;
|
|
15
|
+
uiPlugins?: React.ReactNode[] | React.ReactNode;
|
|
15
16
|
children?: React.ReactNode;
|
|
16
17
|
} & import("react").RefAttributes<PrefabEditorRef>>;
|
|
17
18
|
export default PrefabEditor;
|