react-three-game 0.0.10 → 0.0.11

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 CHANGED
@@ -2,5 +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';
5
+ export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
6
6
  export type { Prefab, GameObject } from './tools/prefabeditor/types';
package/dist/index.js CHANGED
@@ -3,4 +3,4 @@ export { default as GameCanvas } from './shared/GameCanvas';
3
3
  export { default as PrefabEditor } from './tools/prefabeditor/PrefabEditor';
4
4
  export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
5
5
  export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
6
- export { default as AssetViewerPage, TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
6
+ export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
@@ -1,6 +1,3 @@
1
- export default function AssetViewerPage({ basePath }?: {
2
- basePath?: string;
3
- }): import("react/jsx-runtime").JSX.Element;
4
1
  interface TextureListViewerProps {
5
2
  files: string[];
6
3
  selected?: string;
@@ -49,29 +49,6 @@ function useInView() {
49
49
  }, []);
50
50
  return { ref, isInView };
51
51
  }
52
- export default function AssetViewerPage({ basePath = "" } = {}) {
53
- const [textures, setTextures] = useState([]);
54
- const [models, setModels] = useState([]);
55
- const [sounds, setSounds] = useState([]);
56
- const [loading, setLoading] = useState(true);
57
- useEffect(() => {
58
- const base = basePath ? `${basePath}/` : '';
59
- Promise.all([
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(() => [])
63
- ]).then(([textureData, modelData, soundData]) => {
64
- setTextures(textureData);
65
- setModels(modelData);
66
- setSounds(soundData);
67
- setLoading(false);
68
- });
69
- }, [basePath]);
70
- if (loading) {
71
- return _jsx("div", { className: "p-4 text-gray-300", children: "Loading manifests..." });
72
- }
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, {})] }));
74
- }
75
52
  function AssetListViewer({ files, selected, onSelect, renderCard }) {
76
53
  const [currentPath, setCurrentPath] = useState('');
77
54
  const [showPicker, setShowPicker] = useState(false);
@@ -136,7 +136,7 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
136
136
  const hasChildren = node.children && node.children.length > 0;
137
137
  return (_jsxs("div", { className: "select-none", children: [_jsxs("div", { className: `flex items-center py-0.5 px-1 cursor-pointer border-b border-cyan-500/10 ${isSelected ? 'bg-cyan-500/30 hover:bg-cyan-500/40 border-cyan-400/30' : 'hover:bg-cyan-500/10'}`, style: { paddingLeft: `${depth * 8 + 4}px` }, onClick: (e) => { e.stopPropagation(); setSelectedId(node.id); }, onContextMenu: (e) => handleContextMenu(e, node.id), draggable: node.id !== prefabData.root.id, onDragStart: (e) => handleDragStart(e, node.id), onDragOver: (e) => handleDragOver(e, node.id), onDrop: (e) => handleDrop(e, node.id), children: [_jsx("span", { className: `mr-0.5 w-3 text-center text-cyan-400/50 hover:text-cyan-400 cursor-pointer text-[8px] ${hasChildren ? '' : 'invisible'}`, onClick: (e) => hasChildren && toggleCollapse(e, node.id), children: isCollapsed ? '▶' : '▼' }), _jsx("span", { className: "text-[10px] truncate font-mono text-cyan-300", children: node.id })] }), !isCollapsed && node.children && (_jsx("div", { children: node.children.map(child => renderNode(child, depth + 1)) }))] }, node.id));
138
138
  };
139
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "bg-black/70 backdrop-blur-sm text-white border border-cyan-500/30 max-h-[85vh] overflow-y-auto flex flex-col", style: { width: isTreeCollapsed ? 'auto' : '14rem' }, onClick: closeContextMenu, 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: (e) => { e.stopPropagation(); setIsTreeCollapsed(!isTreeCollapsed); }, children: [_jsx("span", { children: "Prefab Graph" }), _jsx("span", { className: "text-[8px]", children: isTreeCollapsed ? '▶' : '◀' })] }), !isTreeCollapsed && (_jsx("div", { className: "flex-1 py-0.5", children: renderNode(prefabData.root) }))] }), contextMenu && (_jsxs("div", { className: "fixed bg-black/90 backdrop-blur-sm border border-cyan-500/40 z-50 min-w-[100px]", style: { top: contextMenu.y, left: contextMenu.x }, onClick: (e) => e.stopPropagation(), children: [_jsx("button", { className: "w-full text-left px-2 py-1 hover:bg-cyan-500/20 text-[10px] text-cyan-300 font-mono border-b border-cyan-500/20", onClick: () => handleAddChild(contextMenu.nodeId), children: "Add Child" }), contextMenu.nodeId !== prefabData.root.id && (_jsxs(_Fragment, { children: [_jsx("button", { className: "w-full text-left px-2 py-1 hover:bg-cyan-500/20 text-[10px] text-cyan-300 font-mono border-b border-cyan-500/20", onClick: () => handleDuplicate(contextMenu.nodeId), children: "Duplicate" }), _jsx("button", { className: "w-full text-left px-2 py-1 hover:bg-red-500/20 text-[10px] text-red-400 font-mono", onClick: () => handleDelete(contextMenu.nodeId), children: "Delete" })] }))] }))] }));
139
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "bg-black/70 backdrop-blur-sm text-white border border-cyan-500/30 max-h-[85vh] overflow-y-auto flex flex-col", style: { width: isTreeCollapsed ? 'auto' : '14rem' }, onClick: closeContextMenu, 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: (e) => { e.stopPropagation(); setIsTreeCollapsed(!isTreeCollapsed); }, children: [_jsx("span", { children: "Prefab Graph" }), _jsx("span", { className: "text-[8px]", children: isTreeCollapsed ? '▶' : '◀' })] }), !isTreeCollapsed && (_jsx("div", { className: "flex-1 py-0.5", children: renderNode(prefabData.root) }))] }), contextMenu && (_jsxs("div", { className: "fixed bg-black/90 backdrop-blur-sm border border-cyan-500/40 z-50 min-w-[100px]", style: { top: contextMenu.y, left: contextMenu.x }, onClick: (e) => e.stopPropagation(), children: [_jsx("button", { className: "w-full text-left px-2 py-1 hover:bg-cyan-500/20 text-[10px] text-cyan-300 font-mono border-b border-cyan-500/20", onClick: () => handleAddChild(contextMenu.nodeId), onPointerLeave: closeContextMenu, children: "Add Child" }), contextMenu.nodeId !== prefabData.root.id && (_jsxs(_Fragment, { children: [_jsx("button", { className: "w-full text-left px-2 py-1 hover:bg-cyan-500/20 text-[10px] text-cyan-300 font-mono border-b border-cyan-500/20", onClick: () => handleDuplicate(contextMenu.nodeId), children: "Duplicate" }), _jsx("button", { className: "w-full text-left px-2 py-1 hover:bg-red-500/20 text-[10px] text-red-400 font-mono", onClick: () => handleDelete(contextMenu.nodeId), children: "Delete" })] }))] }))] }));
140
140
  }
141
141
  // --- Helpers ---
142
142
  function findNode(root, id) {
@@ -1,7 +1,8 @@
1
1
  import { Prefab } from "./types";
2
- declare const PrefabEditor: ({ basePath, initialPrefab, children }: {
2
+ declare const PrefabEditor: ({ basePath, initialPrefab, onPrefabChange, children }: {
3
3
  basePath?: string;
4
4
  initialPrefab?: Prefab;
5
+ onPrefabChange?: (prefab: Prefab) => void;
5
6
  children?: React.ReactNode;
6
7
  }) => import("react/jsx-runtime").JSX.Element;
7
8
  export default PrefabEditor;
@@ -10,11 +10,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
12
12
  import GameCanvas from "../../shared/GameCanvas";
13
- import { useState, useRef, } from "react";
13
+ import { useState, useRef, useEffect } from "react";
14
14
  import PrefabRoot from "./PrefabRoot";
15
15
  import { Physics } from "@react-three/rapier";
16
16
  import EditorUI from "./EditorUI";
17
- const PrefabEditor = ({ basePath, initialPrefab, children }) => {
17
+ const PrefabEditor = ({ basePath, initialPrefab, onPrefabChange, children }) => {
18
18
  const [editMode, setEditMode] = useState(true);
19
19
  const [loadedPrefab, setLoadedPrefab] = useState(initialPrefab !== null && initialPrefab !== void 0 ? initialPrefab : {
20
20
  "id": "prefab-default",
@@ -38,13 +38,25 @@ const PrefabEditor = ({ basePath, initialPrefab, children }) => {
38
38
  const [selectedId, setSelectedId] = useState(null);
39
39
  const [transformMode, setTransformMode] = useState("translate");
40
40
  const prefabRef = useRef(null);
41
+ // Sync internal state with external initialPrefab prop
42
+ useEffect(() => {
43
+ if (initialPrefab) {
44
+ setLoadedPrefab(initialPrefab);
45
+ }
46
+ }, [initialPrefab]);
47
+ // Wrapper to update prefab and notify parent
48
+ const updatePrefab = (newPrefab) => {
49
+ setLoadedPrefab(newPrefab);
50
+ const resolved = typeof newPrefab === 'function' ? newPrefab(loadedPrefab) : newPrefab;
51
+ onPrefabChange === null || onPrefabChange === void 0 ? void 0 : onPrefabChange(resolved);
52
+ };
41
53
  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,
42
54
  // props for edit mode
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* () {
55
+ editMode: editMode, onPrefabChange: updatePrefab, 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* () {
44
56
  const prefab = yield loadJson();
45
57
  if (prefab)
46
58
  setLoadedPrefab(prefab);
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 })] });
59
+ }), 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: updatePrefab, selectedId: selectedId, setSelectedId: setSelectedId, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath })] });
48
60
  };
49
61
  const saveJson = (data, filename) => {
50
62
  const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(data, null, 2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "Batteries included React Three Fiber game engine",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
package/src/index.ts CHANGED
@@ -4,7 +4,6 @@ export { default as PrefabEditor } from './tools/prefabeditor/PrefabEditor';
4
4
  export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
5
5
  export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
6
6
  export {
7
- default as AssetViewerPage,
8
7
  TextureListViewer,
9
8
  ModelListViewer,
10
9
  SoundListViewer,
@@ -3,7 +3,6 @@
3
3
  import { Canvas, useLoader } from "@react-three/fiber";
4
4
  import { OrbitControls, useGLTF, useFBX, Stage, View, PerspectiveCamera } from "@react-three/drei";
5
5
  import { Suspense, useEffect, useState, useRef } from "react";
6
- import * as React from "react";
7
6
  import { TextureLoader } from "three";
8
7
 
9
8
  // view models and textures in manifest, onselect callback
@@ -74,53 +73,6 @@ function useInView() {
74
73
  return { ref, isInView };
75
74
  }
76
75
 
77
- export default function AssetViewerPage({ basePath = "" }: { basePath?: string } = {}) {
78
- const [textures, setTextures] = useState<string[]>([]);
79
- const [models, setModels] = useState<string[]>([]);
80
- const [sounds, setSounds] = useState<string[]>([]);
81
- const [loading, setLoading] = useState(true);
82
-
83
- useEffect(() => {
84
- const base = basePath ? `${basePath}/` : '';
85
- Promise.all([
86
- fetch(`/${base}textures/manifest.json`).then(r => r.json()),
87
- fetch(`/${base}models/manifest.json`).then(r => r.json()),
88
- fetch(`/${base}sound/manifest.json`).then(r => r.json()).catch(() => [])
89
- ]).then(([textureData, modelData, soundData]) => {
90
- setTextures(textureData);
91
- setModels(modelData);
92
- setSounds(soundData);
93
- setLoading(false);
94
- });
95
- }, [basePath]);
96
-
97
- if (loading) {
98
- return <div className="p-4 text-gray-300">Loading manifests...</div>;
99
- }
100
-
101
- return (
102
- <>
103
- <div className="p-2 text-gray-300 overflow-y-auto h-screen text-sm">
104
- <h1 className="text-lg mb-2 font-bold">Asset Viewer</h1>
105
-
106
- <h2 className="text-sm mt-4 mb-1 font-semibold">Textures ({textures.length})</h2>
107
- <TextureListViewer files={textures} basePath={basePath} onSelect={(file) => console.log('Selected texture:', file)} />
108
-
109
- <h2 className="text-sm mt-4 mb-1 font-semibold">Models ({models.length})</h2>
110
- <ModelListViewer files={models} basePath={basePath} onSelect={(file) => console.log('Selected model:', file)} />
111
-
112
- {sounds.length > 0 && (
113
- <>
114
- <h2 className="text-sm mt-4 mb-1 font-semibold">Sounds ({sounds.length})</h2>
115
- <SoundListViewer files={sounds} basePath={basePath} onSelect={(file) => console.log('Selected sound:', file)} />
116
- </>
117
- )}
118
- </div>
119
- <SharedCanvas />
120
- </>
121
- );
122
- }
123
-
124
76
  interface AssetListViewerProps {
125
77
  files: string[];
126
78
  selected?: string;
@@ -210,6 +210,7 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
210
210
  <button
211
211
  className="w-full text-left px-2 py-1 hover:bg-cyan-500/20 text-[10px] text-cyan-300 font-mono border-b border-cyan-500/20"
212
212
  onClick={() => handleAddChild(contextMenu.nodeId)}
213
+ onPointerLeave={closeContextMenu}
213
214
  >
214
215
  Add Child
215
216
  </button>
@@ -1,14 +1,14 @@
1
1
  "use client";
2
2
 
3
3
  import GameCanvas from "../../shared/GameCanvas";
4
- import { useState, useRef, } from "react";
4
+ import { useState, useRef, useEffect } from "react";
5
5
  import { Group, } from "three";
6
6
  import { Prefab, } from "./types";
7
7
  import PrefabRoot from "./PrefabRoot";
8
8
  import { Physics } from "@react-three/rapier";
9
9
  import EditorUI from "./EditorUI";
10
10
 
11
- const PrefabEditor = ({ basePath, initialPrefab, children }: { basePath?: string, initialPrefab?: Prefab, children?: React.ReactNode }) => {
11
+ const PrefabEditor = ({ basePath, initialPrefab, onPrefabChange, children }: { basePath?: string, initialPrefab?: Prefab, onPrefabChange?: (prefab: Prefab) => void, children?: React.ReactNode }) => {
12
12
  const [editMode, setEditMode] = useState(true);
13
13
  const [loadedPrefab, setLoadedPrefab] = useState<Prefab>(initialPrefab ?? {
14
14
  "id": "prefab-default",
@@ -33,6 +33,20 @@ const PrefabEditor = ({ basePath, initialPrefab, children }: { basePath?: string
33
33
  const [transformMode, setTransformMode] = useState<"translate" | "rotate" | "scale">("translate");
34
34
  const prefabRef = useRef<Group>(null);
35
35
 
36
+ // Sync internal state with external initialPrefab prop
37
+ useEffect(() => {
38
+ if (initialPrefab) {
39
+ setLoadedPrefab(initialPrefab);
40
+ }
41
+ }, [initialPrefab]);
42
+
43
+ // Wrapper to update prefab and notify parent
44
+ const updatePrefab = (newPrefab: Prefab | ((prev: Prefab) => Prefab)) => {
45
+ setLoadedPrefab(newPrefab);
46
+ const resolved = typeof newPrefab === 'function' ? newPrefab(loadedPrefab) : newPrefab;
47
+ onPrefabChange?.(resolved);
48
+ };
49
+
36
50
  return <>
37
51
  <GameCanvas>
38
52
  <Physics paused={editMode}>
@@ -44,7 +58,7 @@ const PrefabEditor = ({ basePath, initialPrefab, children }: { basePath?: string
44
58
 
45
59
  // props for edit mode
46
60
  editMode={editMode}
47
- onPrefabChange={setLoadedPrefab}
61
+ onPrefabChange={updatePrefab}
48
62
  selectedId={selectedId}
49
63
  onSelect={setSelectedId}
50
64
  transformMode={transformMode}
@@ -81,7 +95,7 @@ const PrefabEditor = ({ basePath, initialPrefab, children }: { basePath?: string
81
95
  </div>
82
96
  {editMode && <EditorUI
83
97
  prefabData={loadedPrefab}
84
- setPrefabData={setLoadedPrefab}
98
+ setPrefabData={updatePrefab}
85
99
  selectedId={selectedId}
86
100
  setSelectedId={setSelectedId}
87
101
  transformMode={transformMode}