react-three-game 0.0.4 → 0.0.6
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 +2 -0
- package/dist/index.js +2 -0
- package/dist/tools/assetviewer/page.d.ts +9 -4
- package/dist/tools/assetviewer/page.js +21 -17
- package/dist/tools/dragdrop/modelLoader.js +2 -3
- package/dist/tools/prefabeditor/EditorUI.d.ts +2 -1
- package/dist/tools/prefabeditor/EditorUI.js +4 -4
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +4 -1
- package/dist/tools/prefabeditor/PrefabEditor.js +4 -5
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +1 -0
- package/dist/tools/prefabeditor/PrefabRoot.js +4 -3
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +1 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.js +5 -4
- package/dist/tools/prefabeditor/components/ModelComponent.js +5 -4
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -2,3 +2,5 @@ export { default as GameCanvas } from './shared/GameCanvas';
|
|
|
2
2
|
export { default as PrefabEditor } from './tools/prefabeditor/PrefabEditor';
|
|
3
3
|
export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
|
|
4
4
|
export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
|
|
5
|
+
export { default as AssetViewerPage, TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
|
|
6
|
+
export type { Prefab, GameObject } from './tools/prefabeditor/types';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
// Components
|
|
1
2
|
export { default as GameCanvas } from './shared/GameCanvas';
|
|
2
3
|
export { default as PrefabEditor } from './tools/prefabeditor/PrefabEditor';
|
|
3
4
|
export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
|
|
4
5
|
export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
|
|
6
|
+
export { default as AssetViewerPage, TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
|
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
export default function AssetViewerPage(
|
|
1
|
+
export default function AssetViewerPage({ basePath }?: {
|
|
2
|
+
basePath?: string;
|
|
3
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
2
4
|
interface TextureListViewerProps {
|
|
3
5
|
files: string[];
|
|
4
6
|
selected?: string;
|
|
5
7
|
onSelect: (file: string) => void;
|
|
8
|
+
basePath?: string;
|
|
6
9
|
}
|
|
7
|
-
export declare function TextureListViewer({ files, selected, onSelect }: TextureListViewerProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export declare function TextureListViewer({ files, selected, onSelect, basePath }: TextureListViewerProps): import("react/jsx-runtime").JSX.Element;
|
|
8
11
|
interface ModelListViewerProps {
|
|
9
12
|
files: string[];
|
|
10
13
|
selected?: string;
|
|
11
14
|
onSelect: (file: string) => void;
|
|
15
|
+
basePath?: string;
|
|
12
16
|
}
|
|
13
|
-
export declare function ModelListViewer({ files, selected, onSelect }: ModelListViewerProps): import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
export declare function ModelListViewer({ files, selected, onSelect, basePath }: ModelListViewerProps): import("react/jsx-runtime").JSX.Element;
|
|
14
18
|
interface SoundListViewerProps {
|
|
15
19
|
files: string[];
|
|
16
20
|
selected?: string;
|
|
17
21
|
onSelect: (file: string) => void;
|
|
22
|
+
basePath?: string;
|
|
18
23
|
}
|
|
19
|
-
export declare function SoundListViewer({ files, selected, onSelect }: SoundListViewerProps): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export declare function SoundListViewer({ files, selected, onSelect, basePath }: SoundListViewerProps): import("react/jsx-runtime").JSX.Element;
|
|
20
25
|
export declare function SharedCanvas(): import("react/jsx-runtime").JSX.Element;
|
|
21
26
|
export {};
|
|
@@ -49,27 +49,28 @@ function useInView() {
|
|
|
49
49
|
}, []);
|
|
50
50
|
return { ref, isInView };
|
|
51
51
|
}
|
|
52
|
-
export default function AssetViewerPage() {
|
|
52
|
+
export default function AssetViewerPage({ basePath = "" } = {}) {
|
|
53
53
|
const [textures, setTextures] = useState([]);
|
|
54
54
|
const [models, setModels] = useState([]);
|
|
55
55
|
const [sounds, setSounds] = useState([]);
|
|
56
56
|
const [loading, setLoading] = useState(true);
|
|
57
57
|
useEffect(() => {
|
|
58
|
+
const base = basePath ? `${basePath}/` : '';
|
|
58
59
|
Promise.all([
|
|
59
|
-
fetch(
|
|
60
|
-
fetch(
|
|
61
|
-
fetch(
|
|
60
|
+
fetch(`/${base}textures/manifest.json`).then(r => r.json()),
|
|
61
|
+
fetch(`/${base}models/manifest.json`).then(r => r.json()),
|
|
62
|
+
fetch(`/${base}sound/manifest.json`).then(r => r.json()).catch(() => [])
|
|
62
63
|
]).then(([textureData, modelData, soundData]) => {
|
|
63
64
|
setTextures(textureData);
|
|
64
65
|
setModels(modelData);
|
|
65
66
|
setSounds(soundData);
|
|
66
67
|
setLoading(false);
|
|
67
68
|
});
|
|
68
|
-
}, []);
|
|
69
|
+
}, [basePath]);
|
|
69
70
|
if (loading) {
|
|
70
71
|
return _jsx("div", { className: "p-4 text-gray-300", children: "Loading manifests..." });
|
|
71
72
|
}
|
|
72
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "p-2 text-gray-300 overflow-y-auto h-screen text-sm", children: [_jsx("h1", { className: "text-lg mb-2 font-bold", children: "Asset Viewer" }), _jsxs("h2", { className: "text-sm mt-4 mb-1 font-semibold", children: ["Textures (", textures.length, ")"] }), _jsx(TextureListViewer, { files: textures, onSelect: (file) => console.log('Selected texture:', file) }), _jsxs("h2", { className: "text-sm mt-4 mb-1 font-semibold", children: ["Models (", models.length, ")"] }), _jsx(ModelListViewer, { files: models, onSelect: (file) => console.log('Selected model:', file) }), sounds.length > 0 && (_jsxs(_Fragment, { children: [_jsxs("h2", { className: "text-sm mt-4 mb-1 font-semibold", children: ["Sounds (", sounds.length, ")"] }), _jsx(SoundListViewer, { files: sounds, onSelect: (file) => console.log('Selected sound:', file) })] }))] }), _jsx(SharedCanvas, {})] }));
|
|
73
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "p-2 text-gray-300 overflow-y-auto h-screen text-sm", children: [_jsx("h1", { className: "text-lg mb-2 font-bold", children: "Asset Viewer" }), _jsxs("h2", { className: "text-sm mt-4 mb-1 font-semibold", children: ["Textures (", textures.length, ")"] }), _jsx(TextureListViewer, { files: textures, basePath: basePath, onSelect: (file) => console.log('Selected texture:', file) }), _jsxs("h2", { className: "text-sm mt-4 mb-1 font-semibold", children: ["Models (", models.length, ")"] }), _jsx(ModelListViewer, { files: models, basePath: basePath, onSelect: (file) => console.log('Selected model:', file) }), sounds.length > 0 && (_jsxs(_Fragment, { children: [_jsxs("h2", { className: "text-sm mt-4 mb-1 font-semibold", children: ["Sounds (", sounds.length, ")"] }), _jsx(SoundListViewer, { files: sounds, basePath: basePath, onSelect: (file) => console.log('Selected sound:', file) })] }))] }), _jsx(SharedCanvas, {})] }));
|
|
73
74
|
}
|
|
74
75
|
function AssetListViewer({ files, selected, onSelect, renderCard }) {
|
|
75
76
|
const [currentPath, setCurrentPath] = useState('');
|
|
@@ -89,17 +90,18 @@ function AssetListViewer({ files, selected, onSelect, renderCard }) {
|
|
|
89
90
|
setShowPicker(false);
|
|
90
91
|
}) }, file)))] })] }));
|
|
91
92
|
}
|
|
92
|
-
export function TextureListViewer({ files, selected, onSelect }) {
|
|
93
|
-
return (_jsxs(_Fragment, { children: [_jsx(AssetListViewer, { files: files, selected: selected, onSelect: onSelect, renderCard: (file, onSelectHandler) => (_jsx(TextureCard, { file: file, onSelect: onSelectHandler })) }), _jsx(SharedCanvas, {})] }));
|
|
93
|
+
export function TextureListViewer({ files, selected, onSelect, basePath = "" }) {
|
|
94
|
+
return (_jsxs(_Fragment, { children: [_jsx(AssetListViewer, { files: files, selected: selected, onSelect: onSelect, renderCard: (file, onSelectHandler) => (_jsx(TextureCard, { file: file, basePath: basePath, onSelect: onSelectHandler })) }), _jsx(SharedCanvas, {})] }));
|
|
94
95
|
}
|
|
95
|
-
function TextureCard({ file, onSelect }) {
|
|
96
|
+
function TextureCard({ file, onSelect, basePath = "" }) {
|
|
96
97
|
const [error, setError] = useState(false);
|
|
97
98
|
const [isHovered, setIsHovered] = useState(false);
|
|
98
99
|
const { ref, isInView } = useInView();
|
|
100
|
+
const fullPath = basePath ? `/${basePath}${file}` : file;
|
|
99
101
|
if (error) {
|
|
100
102
|
return (_jsx("div", { ref: ref, className: "aspect-square bg-gray-700 cursor-pointer hover:bg-gray-600 flex items-center justify-center", onClick: () => onSelect(file), children: _jsx("div", { className: "text-red-400 text-xs", children: "\u2717" }) }));
|
|
101
103
|
}
|
|
102
|
-
return (_jsxs("div", { ref: ref, className: "aspect-square bg-gray-800 cursor-pointer hover:bg-gray-700 flex flex-col", onClick: () => onSelect(file), onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [_jsx("div", { className: "flex-1 relative", children: isInView ? (_jsxs(View, { className: "w-full h-full", children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 0, 2.5], fov: 50 }), _jsxs(Suspense, { fallback: null, children: [_jsx("ambientLight", { intensity: 0.8 }), _jsx("pointLight", { position: [5, 5, 5], intensity: 0.5 }), _jsx(TextureSphere, { url:
|
|
104
|
+
return (_jsxs("div", { ref: ref, className: "aspect-square bg-gray-800 cursor-pointer hover:bg-gray-700 flex flex-col", onClick: () => onSelect(file), onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [_jsx("div", { className: "flex-1 relative", children: isInView ? (_jsxs(View, { className: "w-full h-full", children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 0, 2.5], fov: 50 }), _jsxs(Suspense, { fallback: null, children: [_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", { className: "bg-black/60 text-[10px] px-1 truncate text-center", children: file.split('/').pop() })] }));
|
|
103
105
|
}
|
|
104
106
|
function TextureSphere({ url, onError }) {
|
|
105
107
|
const texture = useLoader(TextureLoader, url, undefined, (error) => {
|
|
@@ -108,16 +110,17 @@ function TextureSphere({ url, onError }) {
|
|
|
108
110
|
});
|
|
109
111
|
return (_jsxs("mesh", { position: [0, 0, 0], children: [_jsx("sphereGeometry", { args: [1, 32, 32] }), _jsx("meshStandardMaterial", { map: texture })] }));
|
|
110
112
|
}
|
|
111
|
-
export function ModelListViewer({ files, selected, onSelect }) {
|
|
112
|
-
return (_jsxs(_Fragment, { children: [_jsx(AssetListViewer, { files: files, selected: selected, onSelect: onSelect, renderCard: (file, onSelectHandler) => (_jsx(ModelCard, { file: file, onSelect: onSelectHandler })) }), _jsx(SharedCanvas, {})] }));
|
|
113
|
+
export function ModelListViewer({ files, selected, onSelect, basePath = "" }) {
|
|
114
|
+
return (_jsxs(_Fragment, { children: [_jsx(AssetListViewer, { files: files, selected: selected, onSelect: onSelect, renderCard: (file, onSelectHandler) => (_jsx(ModelCard, { file: file, basePath: basePath, onSelect: onSelectHandler })) }), _jsx(SharedCanvas, {})] }));
|
|
113
115
|
}
|
|
114
|
-
function ModelCard({ file, onSelect }) {
|
|
116
|
+
function ModelCard({ file, onSelect, basePath = "" }) {
|
|
115
117
|
const [error, setError] = useState(false);
|
|
116
118
|
const { ref, isInView } = useInView();
|
|
119
|
+
const fullPath = basePath ? `/${basePath}${file}` : file;
|
|
117
120
|
if (error) {
|
|
118
121
|
return (_jsx("div", { ref: ref, className: "aspect-square bg-gray-700 cursor-pointer hover:bg-gray-600 flex items-center justify-center", onClick: () => onSelect(file), children: _jsx("div", { className: "text-red-400 text-xs", children: "\u2717" }) }));
|
|
119
122
|
}
|
|
120
|
-
return (_jsxs("div", { ref: ref, className: "aspect-square bg-gray-900 cursor-pointer hover:bg-gray-800 flex flex-col", onClick: () => onSelect(file), children: [_jsx("div", { className: "flex-1 relative", children: isInView ? (_jsxs(View, { className: "w-full h-full", children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 1, 3], fov: 50 }), _jsxs(Suspense, { fallback: null, children: [_jsx(Stage, { intensity: 0.5, environment: "city", children: _jsx(ModelPreview, { url:
|
|
123
|
+
return (_jsxs("div", { ref: ref, className: "aspect-square bg-gray-900 cursor-pointer hover:bg-gray-800 flex flex-col", onClick: () => onSelect(file), children: [_jsx("div", { className: "flex-1 relative", children: isInView ? (_jsxs(View, { className: "w-full h-full", children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 1, 3], fov: 50 }), _jsxs(Suspense, { fallback: null, children: [_jsx(Stage, { intensity: 0.5, environment: "city", children: _jsx(ModelPreview, { url: fullPath, onError: () => setError(true) }) }), _jsx(OrbitControls, { enableZoom: false })] })] })) : null }), _jsx("div", { className: "bg-black/60 text-[10px] px-1 truncate text-center", children: file.split('/').pop() })] }));
|
|
121
124
|
}
|
|
122
125
|
function ModelPreview({ url, onError }) {
|
|
123
126
|
const isFbx = url.toLowerCase().endsWith('.fbx');
|
|
@@ -133,11 +136,12 @@ function FBXModel({ url, onError }) {
|
|
|
133
136
|
const fbx = useFBX(url);
|
|
134
137
|
return _jsx("primitive", { object: fbx, scale: 0.01 });
|
|
135
138
|
}
|
|
136
|
-
export function SoundListViewer({ files, selected, onSelect }) {
|
|
137
|
-
return (_jsx(AssetListViewer, { files: files, selected: selected, onSelect: onSelect, renderCard: (file, onSelectHandler) => (_jsx(SoundCard, { file: file, onSelect: onSelectHandler })) }));
|
|
139
|
+
export function SoundListViewer({ files, selected, onSelect, basePath = "" }) {
|
|
140
|
+
return (_jsx(AssetListViewer, { files: files, selected: selected, onSelect: onSelect, renderCard: (file, onSelectHandler) => (_jsx(SoundCard, { file: file, basePath: basePath, onSelect: onSelectHandler })) }));
|
|
138
141
|
}
|
|
139
|
-
function SoundCard({ file, onSelect }) {
|
|
142
|
+
function SoundCard({ file, onSelect, basePath = "" }) {
|
|
140
143
|
const fileName = file.split('/').pop() || '';
|
|
144
|
+
const fullPath = basePath ? `/${basePath}${file}` : file;
|
|
141
145
|
return (_jsxs("div", { onClick: () => onSelect(file), className: "aspect-square bg-gray-700 cursor-pointer hover:bg-gray-600 flex flex-col items-center justify-center", children: [_jsx("div", { className: "text-2xl", children: "\uD83D\uDD0A" }), _jsx("div", { className: "text-[10px] px-1 mt-1 truncate text-center w-full", children: fileName })] }));
|
|
142
146
|
}
|
|
143
147
|
// Shared Canvas Component - can be used independently in any viewer
|
|
@@ -17,9 +17,8 @@ const fbxLoader = new FBXLoader();
|
|
|
17
17
|
export function loadModel(filename_1) {
|
|
18
18
|
return __awaiter(this, arguments, void 0, function* (filename, resourcePath = "", onProgress) {
|
|
19
19
|
try {
|
|
20
|
-
// Construct full path -
|
|
21
|
-
|
|
22
|
-
const fullPath = `${resourcePath}/${filename}`;
|
|
20
|
+
// Construct full path - handle empty resourcePath to avoid double slashes
|
|
21
|
+
const fullPath = resourcePath ? `${resourcePath}/${filename}` : filename;
|
|
23
22
|
if (filename.endsWith('.glb') || filename.endsWith('.gltf')) {
|
|
24
23
|
return new Promise((resolve) => {
|
|
25
24
|
gltfLoader.load(fullPath, (gltf) => resolve({ success: true, model: gltf.scene }), (progressEvent) => {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Dispatch, SetStateAction } from 'react';
|
|
2
2
|
import { Prefab } from "./types";
|
|
3
|
-
declare function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transformMode, setTransformMode }: {
|
|
3
|
+
declare function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transformMode, setTransformMode, basePath }: {
|
|
4
4
|
prefabData?: Prefab;
|
|
5
5
|
setPrefabData?: Dispatch<SetStateAction<Prefab>>;
|
|
6
6
|
selectedId: string | null;
|
|
7
7
|
setSelectedId: Dispatch<SetStateAction<string | null>>;
|
|
8
8
|
transformMode: "translate" | "rotate" | "scale";
|
|
9
9
|
setTransformMode: (m: "translate" | "rotate" | "scale") => void;
|
|
10
|
+
basePath?: string;
|
|
10
11
|
}): import("react/jsx-runtime").JSX.Element;
|
|
11
12
|
export default EditorUI;
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useState, useEffect } from 'react';
|
|
3
3
|
import EditorTree from './EditorTree';
|
|
4
4
|
import { getAllComponents } from './components/ComponentRegistry';
|
|
5
|
-
function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transformMode, setTransformMode }) {
|
|
5
|
+
function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transformMode, setTransformMode, basePath }) {
|
|
6
6
|
const [isInspectorCollapsed, setIsInspectorCollapsed] = useState(false);
|
|
7
7
|
const updateNode = (updater) => {
|
|
8
8
|
if (!prefabData || !setPrefabData || !selectedId)
|
|
@@ -24,9 +24,9 @@ function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transf
|
|
|
24
24
|
};
|
|
25
25
|
const selectedNode = selectedId && prefabData ? findNode(prefabData.root, selectedId) : null;
|
|
26
26
|
// if (!selectedNode) return null;
|
|
27
|
-
return _jsxs(_Fragment, { children: [_jsxs("div", { style: { position: 'absolute', top: "0.5rem", right: "0.5rem", zIndex: 20, backgroundColor: "rgba(0,0,0,0.7)", backdropFilter: "blur(4px)", color: "white", border: "1px solid rgba(0,255,255,0.3)" }, children: [_jsxs("div", { className: "px-1.5 py-1 font-mono text-[10px] bg-cyan-500/10 border-b border-cyan-500/30 sticky top-0 uppercase tracking-wider text-cyan-400/80 cursor-pointer hover:bg-cyan-500/20 flex items-center justify-between", onClick: () => setIsInspectorCollapsed(!isInspectorCollapsed), children: [_jsx("span", { children: "Inspector" }), _jsx("span", { className: "text-[8px]", children: isInspectorCollapsed ? '◀' : '▶' })] }), !isInspectorCollapsed && selectedNode && (_jsx(NodeInspector, { node: selectedNode, updateNode: updateNode, deleteNode: deleteNode, transformMode: transformMode, setTransformMode: setTransformMode }))] }), _jsx("div", { style: { position: 'absolute', top: "0.5rem", left: "0.5rem", zIndex: 20 }, children: _jsx(EditorTree, { prefabData: prefabData, setPrefabData: setPrefabData, selectedId: selectedId, setSelectedId: setSelectedId }) })] });
|
|
27
|
+
return _jsxs(_Fragment, { children: [_jsxs("div", { style: { position: 'absolute', top: "0.5rem", right: "0.5rem", zIndex: 20, backgroundColor: "rgba(0,0,0,0.7)", backdropFilter: "blur(4px)", color: "white", border: "1px solid rgba(0,255,255,0.3)" }, children: [_jsxs("div", { className: "px-1.5 py-1 font-mono text-[10px] bg-cyan-500/10 border-b border-cyan-500/30 sticky top-0 uppercase tracking-wider text-cyan-400/80 cursor-pointer hover:bg-cyan-500/20 flex items-center justify-between", onClick: () => setIsInspectorCollapsed(!isInspectorCollapsed), children: [_jsx("span", { children: "Inspector" }), _jsx("span", { className: "text-[8px]", children: isInspectorCollapsed ? '◀' : '▶' })] }), !isInspectorCollapsed && selectedNode && (_jsx(NodeInspector, { node: selectedNode, updateNode: updateNode, deleteNode: deleteNode, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath }))] }), _jsx("div", { style: { position: 'absolute', top: "0.5rem", left: "0.5rem", zIndex: 20 }, children: _jsx(EditorTree, { prefabData: prefabData, setPrefabData: setPrefabData, selectedId: selectedId, setSelectedId: setSelectedId }) })] });
|
|
28
28
|
}
|
|
29
|
-
function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransformMode }) {
|
|
29
|
+
function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransformMode, basePath }) {
|
|
30
30
|
const ALL_COMPONENTS = getAllComponents();
|
|
31
31
|
const allComponentKeys = Object.keys(ALL_COMPONENTS);
|
|
32
32
|
const [addComponentType, setAddComponentType] = useState(allComponentKeys[0]);
|
|
@@ -50,7 +50,7 @@ function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransfo
|
|
|
50
50
|
const components = Object.assign({}, n.components);
|
|
51
51
|
delete components[key];
|
|
52
52
|
return Object.assign(Object.assign({}, n), { components });
|
|
53
|
-
}), className: "text-[9px] text-red-400/60 hover:text-red-400", children: "\u2715" })] }), EditorComp ? (_jsx(EditorComp, { component: comp, onUpdate: (newProps) => updateNode(n => (Object.assign(Object.assign({}, n), { components: Object.assign(Object.assign({}, n.components), { [key]: Object.assign(Object.assign({}, comp), { properties: Object.assign(Object.assign({}, comp.properties), newProps) }) }) }))) })) : null] }, key));
|
|
53
|
+
}), className: "text-[9px] text-red-400/60 hover:text-red-400", children: "\u2715" })] }), EditorComp ? (_jsx(EditorComp, { component: comp, onUpdate: (newProps) => updateNode(n => (Object.assign(Object.assign({}, n), { components: Object.assign(Object.assign({}, n.components), { [key]: Object.assign(Object.assign({}, comp), { properties: Object.assign(Object.assign({}, comp.properties), newProps) }) }) }))), basePath: basePath })) : null] }, key));
|
|
54
54
|
}), _jsxs("div", { className: "px-1.5 py-1 border-t border-cyan-500/20", children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Add Component" }), _jsxs("div", { className: "flex gap-0.5", children: [_jsx("select", { className: "bg-black/40 border border-cyan-500/30 px-1 py-0.5 flex-1 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: addComponentType, onChange: e => setAddComponentType(e.target.value), children: allComponentKeys.filter(k => { var _a; return !((_a = node.components) === null || _a === void 0 ? void 0 : _a[k.toLowerCase()]); }).map(k => (_jsx("option", { value: k, children: k }, k))) }), _jsx("button", { className: "bg-cyan-500/20 hover:bg-cyan-500/30 border border-cyan-500/30 px-2 py-0.5 text-[10px] text-cyan-300 font-mono disabled:opacity-30", disabled: !addComponentType, onClick: () => {
|
|
55
55
|
var _a;
|
|
56
56
|
if (!addComponentType)
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import { Prefab } from "./types";
|
|
2
|
+
declare const PrefabEditor: ({ basePath, initialPrefab, children }: {
|
|
3
|
+
basePath?: string;
|
|
4
|
+
initialPrefab?: Prefab;
|
|
2
5
|
children?: React.ReactNode;
|
|
3
6
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
4
7
|
export default PrefabEditor;
|
|
@@ -13,11 +13,10 @@ import GameCanvas from "../../shared/GameCanvas";
|
|
|
13
13
|
import { useState, useRef, } from "react";
|
|
14
14
|
import PrefabRoot from "./PrefabRoot";
|
|
15
15
|
import { Physics } from "@react-three/rapier";
|
|
16
|
-
// import testPrefab from "./samples/test.json";
|
|
17
16
|
import EditorUI from "./EditorUI";
|
|
18
|
-
const PrefabEditor = ({ children }) => {
|
|
17
|
+
const PrefabEditor = ({ basePath, initialPrefab, children }) => {
|
|
19
18
|
const [editMode, setEditMode] = useState(true);
|
|
20
|
-
const [loadedPrefab, setLoadedPrefab] = useState({
|
|
19
|
+
const [loadedPrefab, setLoadedPrefab] = useState(initialPrefab !== null && initialPrefab !== void 0 ? initialPrefab : {
|
|
21
20
|
"id": "prefab-default",
|
|
22
21
|
"name": "New Prefab",
|
|
23
22
|
"root": {
|
|
@@ -41,11 +40,11 @@ const PrefabEditor = ({ children }) => {
|
|
|
41
40
|
const prefabRef = useRef(null);
|
|
42
41
|
return _jsxs(_Fragment, { children: [_jsx(GameCanvas, { children: _jsxs(Physics, { paused: editMode, children: [_jsx("ambientLight", { intensity: 1.5 }), _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }), _jsx(PrefabRoot, { data: loadedPrefab, ref: prefabRef,
|
|
43
42
|
// props for edit mode
|
|
44
|
-
editMode: editMode, onPrefabChange: setLoadedPrefab, selectedId: selectedId, onSelect: setSelectedId, transformMode: transformMode, setTransformMode: setTransformMode }), children] }) }), _jsxs("div", { style: { position: "absolute", top: "0.5rem", left: "50%", transform: "translateX(-50%)" }, className: "bg-black/70 backdrop-blur-sm border border-cyan-500/30 px-2 py-1 flex items-center gap-1", children: [_jsx("button", { className: "px-1 py-0.5 text-[10px] font-mono text-cyan-300 hover:bg-cyan-500/20 border border-cyan-500/30", onClick: () => setEditMode(!editMode), children: editMode ? "▶" : "⏸" }), _jsx("span", { className: "text-cyan-500/30 text-[10px]", children: "|" }), _jsx("button", { className: "px-1 py-0.5 text-[10px] font-mono text-cyan-300 hover:bg-cyan-500/20 border border-cyan-500/30", onClick: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
43
|
+
editMode: editMode, onPrefabChange: setLoadedPrefab, selectedId: selectedId, onSelect: setSelectedId, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath }), children] }) }), _jsxs("div", { style: { position: "absolute", top: "0.5rem", left: "50%", transform: "translateX(-50%)" }, className: "bg-black/70 backdrop-blur-sm border border-cyan-500/30 px-2 py-1 flex items-center gap-1", children: [_jsx("button", { className: "px-1 py-0.5 text-[10px] font-mono text-cyan-300 hover:bg-cyan-500/20 border border-cyan-500/30", onClick: () => setEditMode(!editMode), children: editMode ? "▶" : "⏸" }), _jsx("span", { className: "text-cyan-500/30 text-[10px]", children: "|" }), _jsx("button", { className: "px-1 py-0.5 text-[10px] font-mono text-cyan-300 hover:bg-cyan-500/20 border border-cyan-500/30", onClick: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
45
44
|
const prefab = yield loadJson();
|
|
46
45
|
if (prefab)
|
|
47
46
|
setLoadedPrefab(prefab);
|
|
48
|
-
}), children: "\uD83D\uDCE5" }), _jsx("button", { className: "px-1 py-0.5 text-[10px] font-mono text-cyan-300 hover:bg-cyan-500/20 border border-cyan-500/30", onClick: () => saveJson(loadedPrefab, "prefab"), children: "\uD83D\uDCBE" })] }), editMode && _jsx(EditorUI, { prefabData: loadedPrefab, setPrefabData: setLoadedPrefab, selectedId: selectedId, setSelectedId: setSelectedId, transformMode: transformMode, setTransformMode: setTransformMode })] });
|
|
47
|
+
}), children: "\uD83D\uDCE5" }), _jsx("button", { className: "px-1 py-0.5 text-[10px] font-mono text-cyan-300 hover:bg-cyan-500/20 border border-cyan-500/30", onClick: () => saveJson(loadedPrefab, "prefab"), children: "\uD83D\uDCBE" })] }), editMode && _jsx(EditorUI, { prefabData: loadedPrefab, setPrefabData: setLoadedPrefab, selectedId: selectedId, setSelectedId: setSelectedId, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath })] });
|
|
49
48
|
};
|
|
50
49
|
const saveJson = (data, filename) => {
|
|
51
50
|
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(data, null, 2));
|
|
@@ -8,5 +8,6 @@ export declare const PrefabRoot: import("react").ForwardRefExoticComponent<{
|
|
|
8
8
|
onSelect?: (id: string | null) => void;
|
|
9
9
|
transformMode?: "translate" | "rotate" | "scale";
|
|
10
10
|
setTransformMode?: (mode: "translate" | "rotate" | "scale") => void;
|
|
11
|
+
basePath?: string;
|
|
11
12
|
} & import("react").RefAttributes<Group<import("three").Object3DEventMap>>>;
|
|
12
13
|
export default PrefabRoot;
|
|
@@ -28,7 +28,7 @@ function updatePrefabNode(root, id, update) {
|
|
|
28
28
|
}
|
|
29
29
|
return root;
|
|
30
30
|
}
|
|
31
|
-
export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selectedId, onSelect, transformMode, setTransformMode }, ref) => {
|
|
31
|
+
export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selectedId, onSelect, transformMode, setTransformMode, basePath = "" }, ref) => {
|
|
32
32
|
const [loadedModels, setLoadedModels] = useState({});
|
|
33
33
|
const [loadedTextures, setLoadedTextures] = useState({});
|
|
34
34
|
// const [prefabRoot, setPrefabRoot] = useState<Prefab>(data); // Removed local state
|
|
@@ -108,7 +108,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
|
|
|
108
108
|
for (const filename of modelsToLoad) {
|
|
109
109
|
if (!loadedModels[filename] && !loadingRefs.current.has(filename)) {
|
|
110
110
|
loadingRefs.current.add(filename);
|
|
111
|
-
const result = yield loadModel(filename,
|
|
111
|
+
const result = yield loadModel(filename, basePath);
|
|
112
112
|
if (result.success && result.model) {
|
|
113
113
|
setLoadedModels(prev => (Object.assign(Object.assign({}, prev), { [filename]: result.model })));
|
|
114
114
|
}
|
|
@@ -118,7 +118,8 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
|
|
|
118
118
|
for (const filename of texturesToLoad) {
|
|
119
119
|
if (!loadedTextures[filename] && !loadingRefs.current.has(filename)) {
|
|
120
120
|
loadingRefs.current.add(filename);
|
|
121
|
-
|
|
121
|
+
const texturePath = basePath ? `${basePath}/${filename}` : filename;
|
|
122
|
+
textureLoader.load(texturePath, (texture) => {
|
|
122
123
|
texture.colorSpace = SRGBColorSpace;
|
|
123
124
|
setLoadedTextures(prev => (Object.assign(Object.assign({}, prev), { [filename]: texture })));
|
|
124
125
|
});
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { TextureListViewer } from '../../assetviewer/page';
|
|
3
3
|
import { useEffect, useState } from 'react';
|
|
4
|
-
function MaterialComponentEditor({ component, onUpdate }) {
|
|
4
|
+
function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
5
5
|
var _a, _b, _c, _d;
|
|
6
6
|
const [textureFiles, setTextureFiles] = useState([]);
|
|
7
7
|
useEffect(() => {
|
|
8
|
-
|
|
8
|
+
const base = basePath ? `${basePath}/` : '';
|
|
9
|
+
fetch(`/${base}textures/manifest.json`)
|
|
9
10
|
.then(r => r.json())
|
|
10
11
|
.then(data => setTextureFiles(Array.isArray(data) ? data : data.files || []))
|
|
11
12
|
.catch(console.error);
|
|
12
|
-
}, []);
|
|
13
|
-
return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "mb-1", children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Color" }), _jsxs("div", { className: "flex gap-0.5", children: [_jsx("input", { type: "color", className: "h-5 w-5 bg-transparent border-none cursor-pointer", value: component.properties.color, onChange: e => onUpdate({ 'color': e.target.value }) }), _jsx("input", { type: "text", className: "flex-1 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: component.properties.color, onChange: e => onUpdate({ 'color': e.target.value }) })] })] }), _jsxs("div", { className: "flex items-center gap-1 mb-1", children: [_jsx("input", { type: "checkbox", className: "w-3 h-3", checked: component.properties.wireframe || false, onChange: e => onUpdate({ 'wireframe': e.target.checked }) }), _jsx("label", { className: "text-[9px] text-cyan-400/60", children: "Wireframe" })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Texture" }), _jsx("div", { className: "max-h-32 overflow-y-auto", children: _jsx(TextureListViewer, { files: textureFiles, selected: component.properties.texture || undefined, onSelect: (file) => onUpdate({ 'texture': file }) }) })] }), component.properties.texture && (_jsxs("div", { className: "border-t border-cyan-500/20 pt-1 mt-1", children: [_jsxs("div", { className: "flex items-center gap-1 mb-1", children: [_jsx("input", { type: "checkbox", className: "w-3 h-3", checked: component.properties.repeat || false, onChange: e => onUpdate({ 'repeat': e.target.checked }) }), _jsx("label", { className: "text-[9px] text-cyan-400/60", children: "Repeat Texture" })] }), component.properties.repeat && (_jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Repeat (X, Y)" }), _jsxs("div", { className: "flex gap-0.5", children: [_jsx("input", { type: "number", 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: (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 1, onChange: e => {
|
|
13
|
+
}, [basePath]);
|
|
14
|
+
return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "mb-1", children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Color" }), _jsxs("div", { className: "flex gap-0.5", children: [_jsx("input", { type: "color", className: "h-5 w-5 bg-transparent border-none cursor-pointer", value: component.properties.color, onChange: e => onUpdate({ 'color': e.target.value }) }), _jsx("input", { type: "text", className: "flex-1 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: component.properties.color, onChange: e => onUpdate({ 'color': e.target.value }) })] })] }), _jsxs("div", { className: "flex items-center gap-1 mb-1", children: [_jsx("input", { type: "checkbox", className: "w-3 h-3", checked: component.properties.wireframe || false, onChange: e => onUpdate({ 'wireframe': e.target.checked }) }), _jsx("label", { className: "text-[9px] text-cyan-400/60", children: "Wireframe" })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Texture" }), _jsx("div", { className: "max-h-32 overflow-y-auto", children: _jsx(TextureListViewer, { files: textureFiles, selected: component.properties.texture || undefined, onSelect: (file) => onUpdate({ 'texture': file }), basePath: basePath }) })] }), component.properties.texture && (_jsxs("div", { className: "border-t border-cyan-500/20 pt-1 mt-1", children: [_jsxs("div", { className: "flex items-center gap-1 mb-1", children: [_jsx("input", { type: "checkbox", className: "w-3 h-3", checked: component.properties.repeat || false, onChange: e => onUpdate({ 'repeat': e.target.checked }) }), _jsx("label", { className: "text-[9px] text-cyan-400/60", children: "Repeat Texture" })] }), component.properties.repeat && (_jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Repeat (X, Y)" }), _jsxs("div", { className: "flex gap-0.5", children: [_jsx("input", { type: "number", 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: (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 1, onChange: e => {
|
|
14
15
|
var _a, _b;
|
|
15
16
|
const y = (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : 1;
|
|
16
17
|
onUpdate({ 'repeatCount': [parseFloat(e.target.value), y] });
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { ModelListViewer } from '../../assetviewer/page';
|
|
3
3
|
import { useEffect, useState } from 'react';
|
|
4
|
-
function ModelComponentEditor({ component, onUpdate }) {
|
|
4
|
+
function ModelComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
5
5
|
const [modelFiles, setModelFiles] = useState([]);
|
|
6
6
|
useEffect(() => {
|
|
7
|
-
|
|
7
|
+
const base = basePath ? `${basePath}/` : '';
|
|
8
|
+
fetch(`/${base}models/manifest.json`)
|
|
8
9
|
.then(r => r.json())
|
|
9
10
|
.then(data => setModelFiles(Array.isArray(data) ? data : data.files || []))
|
|
10
11
|
.catch(console.error);
|
|
11
|
-
}, []);
|
|
12
|
+
}, [basePath]);
|
|
12
13
|
const handleModelSelect = (file) => {
|
|
13
14
|
// Remove leading slash for prefab compatibility
|
|
14
15
|
const filename = file.startsWith('/') ? file.slice(1) : file;
|
|
15
16
|
onUpdate({ 'filename': filename });
|
|
16
17
|
};
|
|
17
|
-
return _jsxs("div", { children: [_jsxs("div", { className: "mb-1", children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Model" }), _jsx("div", { className: "max-h-32 overflow-y-auto", children: _jsx(ModelListViewer, { files: modelFiles, selected: component.properties.filename ? `/${component.properties.filename}` : undefined, onSelect: handleModelSelect }) })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("input", { type: "checkbox", id: "instanced-checkbox", checked: component.properties.instanced || false, onChange: e => onUpdate({ 'instanced': e.target.checked }), className: "w-3 h-3" }), _jsx("label", { htmlFor: "instanced-checkbox", className: "text-[9px] text-cyan-400/60", children: "Instanced" })] })] });
|
|
18
|
+
return _jsxs("div", { children: [_jsxs("div", { className: "mb-1", children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Model" }), _jsx("div", { className: "max-h-32 overflow-y-auto", children: _jsx(ModelListViewer, { files: modelFiles, selected: component.properties.filename ? `/${component.properties.filename}` : undefined, onSelect: handleModelSelect, basePath: basePath }) })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("input", { type: "checkbox", id: "instanced-checkbox", checked: component.properties.instanced || false, onChange: e => onUpdate({ 'instanced': e.target.checked }), className: "w-3 h-3" }), _jsx("label", { htmlFor: "instanced-checkbox", className: "text-[9px] text-cyan-400/60", children: "Instanced" })] })] });
|
|
18
19
|
}
|
|
19
20
|
// View for Model component
|
|
20
21
|
function ModelComponentView({ properties, loadedModels, children }) {
|