react-three-game 0.0.33 → 0.0.35

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.
@@ -1,8 +1,14 @@
1
1
  import { Dispatch, SetStateAction } from 'react';
2
2
  import { Prefab } from "./types";
3
- export default function EditorTree({ prefabData, setPrefabData, selectedId, setSelectedId }: {
3
+ export default function EditorTree({ prefabData, setPrefabData, selectedId, setSelectedId, onSave, onLoad, onUndo, onRedo, canUndo, canRedo }: {
4
4
  prefabData?: Prefab;
5
5
  setPrefabData?: Dispatch<SetStateAction<Prefab>>;
6
6
  selectedId: string | null;
7
7
  setSelectedId: Dispatch<SetStateAction<string | null>>;
8
+ onSave?: () => void;
9
+ onLoad?: () => void;
10
+ onUndo?: () => void;
11
+ onRedo?: () => void;
12
+ canUndo?: boolean;
13
+ canRedo?: boolean;
8
14
  }): import("react/jsx-runtime").JSX.Element | null;
@@ -3,11 +3,12 @@ import { useState } from 'react';
3
3
  import { getComponent } from './components/ComponentRegistry';
4
4
  import { base, tree, menu } from './styles';
5
5
  import { findNode, findParent, deleteNode, cloneNode, updateNodeById } from './utils';
6
- export default function EditorTree({ prefabData, setPrefabData, selectedId, setSelectedId }) {
6
+ export default function EditorTree({ prefabData, setPrefabData, selectedId, setSelectedId, onSave, onLoad, onUndo, onRedo, canUndo, canRedo }) {
7
7
  const [contextMenu, setContextMenu] = useState(null);
8
8
  const [draggedId, setDraggedId] = useState(null);
9
9
  const [collapsedIds, setCollapsedIds] = useState(new Set());
10
10
  const [collapsed, setCollapsed] = useState(false);
11
+ const [fileMenuOpen, setFileMenuOpen] = useState(false);
11
12
  if (!prefabData || !setPrefabData)
12
13
  return null;
13
14
  const handleContextMenu = (e, nodeId) => {
@@ -23,22 +24,19 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
23
24
  return next;
24
25
  });
25
26
  };
26
- // Actions
27
27
  const handleAddChild = (parentId) => {
28
- var _a;
29
- const newNode = {
30
- id: crypto.randomUUID(),
31
- name: "New Node",
32
- components: {
33
- transform: {
34
- type: "Transform",
35
- properties: Object.assign({}, (_a = getComponent('Transform')) === null || _a === void 0 ? void 0 : _a.defaultProperties)
36
- }
37
- }
38
- };
39
28
  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 : []), newNode] }));
29
+ var _a, _b;
30
+ return (Object.assign(Object.assign({}, parent), { children: [...((_a = parent.children) !== null && _a !== void 0 ? _a : []), {
31
+ id: crypto.randomUUID(),
32
+ name: "New Node",
33
+ components: {
34
+ transform: {
35
+ type: "Transform",
36
+ properties: Object.assign({}, (_b = getComponent('Transform')) === null || _b === void 0 ? void 0 : _b.defaultProperties)
37
+ }
38
+ }
39
+ }] }));
42
40
  }) })));
43
41
  setContextMenu(null);
44
42
  };
@@ -50,10 +48,9 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
50
48
  const parent = findParent(prev.root, nodeId);
51
49
  if (!node || !parent)
52
50
  return prev;
53
- const clone = cloneNode(node);
54
51
  return Object.assign(Object.assign({}, prev), { root: updateNodeById(prev.root, parent.id, p => {
55
52
  var _a;
56
- return (Object.assign(Object.assign({}, p), { children: [...((_a = p.children) !== null && _a !== void 0 ? _a : []), clone] }));
53
+ return (Object.assign(Object.assign({}, p), { children: [...((_a = p.children) !== null && _a !== void 0 ? _a : []), cloneNode(node)] }));
57
54
  }) });
58
55
  });
59
56
  setContextMenu(null);
@@ -66,12 +63,9 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
66
63
  setSelectedId(null);
67
64
  setContextMenu(null);
68
65
  };
69
- // Drag and Drop
70
66
  const handleDragStart = (e, id) => {
71
- if (id === prefabData.root.id) {
72
- e.preventDefault();
73
- return;
74
- }
67
+ if (id === prefabData.root.id)
68
+ return e.preventDefault();
75
69
  e.dataTransfer.effectAllowed = "move";
76
70
  setDraggedId(id);
77
71
  };
@@ -90,14 +84,9 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
90
84
  setPrefabData(prev => {
91
85
  const draggedNode = findNode(prev.root, draggedId);
92
86
  const oldParent = findParent(prev.root, draggedId);
93
- if (!draggedNode || !oldParent)
94
- return prev;
95
- // Prevent dropping into own subtree
96
- if (findNode(draggedNode, targetId))
87
+ if (!draggedNode || !oldParent || findNode(draggedNode, targetId))
97
88
  return prev;
98
- // 1. Remove from old parent
99
89
  let root = updateNodeById(prev.root, oldParent.id, p => (Object.assign(Object.assign({}, p), { children: p.children.filter(c => c.id !== draggedId) })));
100
- // 2. Add to new parent
101
90
  root = updateNodeById(root, targetId, t => {
102
91
  var _a;
103
92
  return (Object.assign(Object.assign({}, t), { children: [...((_a = t.children) !== null && _a !== void 0 ? _a : []), draggedNode] }));
@@ -122,5 +111,5 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
122
111
  visibility: hasChildren ? 'visible' : 'hidden'
123
112
  }, 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 })] }), !isCollapsed && node.children && node.children.map(child => renderNode(child, depth + 1))] }, node.id));
124
113
  };
125
- return (_jsxs(_Fragment, { children: [_jsxs("div", { style: Object.assign(Object.assign({}, tree.panel), { width: collapsed ? 'auto' : 224 }), onClick: () => setContextMenu(null), children: [_jsxs("div", { style: base.header, onClick: () => setCollapsed(!collapsed), children: [_jsx("span", { children: "Scene" }), _jsx("span", { children: collapsed ? '' : '' })] }), !collapsed && _jsx("div", { style: tree.scroll, children: renderNode(prefabData.root) })] }), contextMenu && (_jsxs("div", { style: Object.assign(Object.assign({}, menu.container), { top: contextMenu.y, left: contextMenu.x }), onClick: (e) => e.stopPropagation(), onPointerLeave: () => setContextMenu(null), children: [_jsx("button", { style: menu.item, onClick: () => handleAddChild(contextMenu.nodeId), children: "Add Child" }), contextMenu.nodeId !== prefabData.root.id && (_jsxs(_Fragment, { children: [_jsx("button", { style: menu.item, onClick: () => handleDuplicate(contextMenu.nodeId), children: "Duplicate" }), _jsx("button", { style: Object.assign(Object.assign({}, menu.item), menu.danger), onClick: () => handleDelete(contextMenu.nodeId), children: "Delete" })] }))] }))] }));
114
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { style: Object.assign(Object.assign({}, tree.panel), { width: collapsed ? 'auto' : 224 }), onClick: () => { setContextMenu(null); setFileMenuOpen(false); }, 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" }), _jsxs("div", { style: { position: 'relative' }, children: [_jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10 }), onClick: (e) => { e.stopPropagation(); setFileMenuOpen(!fileMenuOpen); }, title: "File", children: "\u22EE" }), fileMenuOpen && (_jsxs("div", { style: Object.assign(Object.assign({}, menu.container), { top: 28, right: 0 }), onClick: (e) => e.stopPropagation(), children: [_jsx("button", { style: menu.item, onClick: () => { onLoad === null || onLoad === void 0 ? void 0 : onLoad(); setFileMenuOpen(false); }, children: "\uD83D\uDCE5 Load" }), _jsx("button", { style: menu.item, onClick: () => { onSave === null || onSave === void 0 ? void 0 : onSave(); setFileMenuOpen(false); }, children: "\uD83D\uDCBE Save" })] }))] })] }))] }), !collapsed && _jsx("div", { style: tree.scroll, children: renderNode(prefabData.root) })] }), contextMenu && (_jsxs("div", { style: Object.assign(Object.assign({}, menu.container), { top: contextMenu.y, left: contextMenu.x }), onClick: (e) => e.stopPropagation(), onPointerLeave: () => setContextMenu(null), children: [_jsx("button", { style: menu.item, onClick: () => handleAddChild(contextMenu.nodeId), children: "Add Child" }), contextMenu.nodeId !== prefabData.root.id && (_jsxs(_Fragment, { children: [_jsx("button", { style: menu.item, onClick: () => handleDuplicate(contextMenu.nodeId), children: "Duplicate" }), _jsx("button", { style: Object.assign(Object.assign({}, menu.item), menu.danger), onClick: () => handleDelete(contextMenu.nodeId), children: "Delete" })] }))] }))] }));
126
115
  }
@@ -1,6 +1,6 @@
1
1
  import { Dispatch, SetStateAction } from 'react';
2
2
  import { Prefab } from "./types";
3
- declare function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transformMode, setTransformMode, basePath }: {
3
+ declare function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transformMode, setTransformMode, basePath, onSave, onLoad, onUndo, onRedo, canUndo, canRedo }: {
4
4
  prefabData?: Prefab;
5
5
  setPrefabData?: Dispatch<SetStateAction<Prefab>>;
6
6
  selectedId: string | null;
@@ -8,5 +8,11 @@ declare function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId
8
8
  transformMode: "translate" | "rotate" | "scale";
9
9
  setTransformMode: (m: "translate" | "rotate" | "scale") => void;
10
10
  basePath?: string;
11
+ onSave?: () => void;
12
+ onLoad?: () => void;
13
+ onUndo?: () => void;
14
+ onRedo?: () => void;
15
+ canUndo?: boolean;
16
+ canRedo?: boolean;
11
17
  }): import("react/jsx-runtime").JSX.Element;
12
18
  export default EditorUI;
@@ -15,7 +15,7 @@ import EditorTree from './EditorTree';
15
15
  import { getAllComponents } from './components/ComponentRegistry';
16
16
  import { base, inspector } from './styles';
17
17
  import { findNode, updateNode, deleteNode } from './utils';
18
- function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transformMode, setTransformMode, basePath }) {
18
+ function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transformMode, setTransformMode, basePath, onSave, onLoad, onUndo, onRedo, canUndo, canRedo }) {
19
19
  const [collapsed, setCollapsed] = useState(false);
20
20
  const updateNodeHandler = (updater) => {
21
21
  if (!prefabData || !setPrefabData || !selectedId)
@@ -29,11 +29,12 @@ function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transf
29
29
  setSelectedId(null);
30
30
  };
31
31
  const selectedNode = selectedId && prefabData ? findNode(prefabData.root, selectedId) : null;
32
- return _jsxs(_Fragment, { children: [_jsx("style", { children: `.prefab-scroll::-webkit-scrollbar { width: 8px; height: 8px; }
32
+ return _jsxs(_Fragment, { children: [_jsx("style", { children: `
33
+ .prefab-scroll::-webkit-scrollbar { width: 8px; height: 8px; }
33
34
  .prefab-scroll::-webkit-scrollbar-track { background: transparent; }
34
35
  .prefab-scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.06); border-radius: 8px; }
35
36
  .prefab-scroll { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.06) transparent; }
36
- ` }), _jsxs("div", { style: inspector.panel, children: [_jsxs("div", { style: base.header, onClick: () => setCollapsed(!collapsed), children: [_jsx("span", { children: "Inspector" }), _jsx("span", { children: collapsed ? '◀' : '▼' })] }), !collapsed && selectedNode && (_jsx(NodeInspector, { node: selectedNode, updateNode: updateNodeHandler, deleteNode: deleteNodeHandler, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath }))] }), _jsx("div", { style: { position: 'absolute', top: 8, left: 8, zIndex: 20 }, children: _jsx(EditorTree, { prefabData: prefabData, setPrefabData: setPrefabData, selectedId: selectedId, setSelectedId: setSelectedId }) })] });
37
+ ` }), _jsxs("div", { style: inspector.panel, children: [_jsxs("div", { style: base.header, onClick: () => setCollapsed(!collapsed), children: [_jsx("span", { children: "Inspector" }), _jsx("span", { children: collapsed ? '◀' : '▼' })] }), !collapsed && selectedNode && (_jsx(NodeInspector, { node: selectedNode, updateNode: updateNodeHandler, deleteNode: deleteNodeHandler, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath }))] }), _jsx("div", { style: { position: 'absolute', top: 8, left: 8, zIndex: 20 }, children: _jsx(EditorTree, { prefabData: prefabData, setPrefabData: setPrefabData, selectedId: selectedId, setSelectedId: setSelectedId, onSave: onSave, onLoad: onLoad, onUndo: onUndo, onRedo: onRedo, canUndo: canUndo, canRedo: canRedo }) })] });
37
38
  }
38
39
  function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransformMode, basePath }) {
39
40
  var _a;
@@ -1,14 +1,13 @@
1
1
  import React from "react";
2
2
  import { Object3D, Group } from "three";
3
+ import { PhysicsProps } from "./components/PhysicsComponent";
3
4
  export type InstanceData = {
4
5
  id: string;
5
6
  position: [number, number, number];
6
7
  rotation: [number, number, number];
7
8
  scale: [number, number, number];
8
9
  meshPath: string;
9
- physics?: {
10
- type: 'dynamic' | 'fixed';
11
- };
10
+ physics?: PhysicsProps | undefined;
12
11
  };
13
12
  export declare function GameInstanceProvider({ children, models, onSelect, registerRef, selectedId, editMode }: {
14
13
  children: React.ReactNode;
@@ -26,7 +25,5 @@ export declare const GameInstance: React.ForwardRefExoticComponent<{
26
25
  position: [number, number, number];
27
26
  rotation: [number, number, number];
28
27
  scale: [number, number, number];
29
- physics?: {
30
- type: "dynamic" | "fixed";
31
- };
28
+ physics?: PhysicsProps | undefined;
32
29
  } & React.RefAttributes<Group<import("three").Object3DEventMap>>>;
@@ -15,65 +15,54 @@ import PrefabRoot from "./PrefabRoot";
15
15
  import { Physics } from "@react-three/rapier";
16
16
  import EditorUI from "./EditorUI";
17
17
  import { base, toolbar } from "./styles";
18
- const PrefabEditor = ({ basePath, initialPrefab, onPrefabChange, children }) => {
19
- const [editMode, setEditMode] = useState(true);
20
- const [loadedPrefab, setLoadedPrefab] = useState(initialPrefab !== null && initialPrefab !== void 0 ? initialPrefab : {
21
- id: "prefab-default",
22
- name: "New Prefab",
23
- root: {
24
- id: "root",
25
- components: {
26
- transform: {
27
- type: "Transform",
28
- properties: { position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1] }
29
- }
18
+ const DEFAULT_PREFAB = {
19
+ id: "prefab-default",
20
+ name: "New Prefab",
21
+ root: {
22
+ id: "root",
23
+ components: {
24
+ transform: {
25
+ type: "Transform",
26
+ properties: { position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1] }
30
27
  }
31
28
  }
32
- });
29
+ }
30
+ };
31
+ const PrefabEditor = ({ basePath, initialPrefab, onPrefabChange, children }) => {
32
+ const [editMode, setEditMode] = useState(true);
33
+ const [loadedPrefab, setLoadedPrefab] = useState(initialPrefab !== null && initialPrefab !== void 0 ? initialPrefab : DEFAULT_PREFAB);
33
34
  const [selectedId, setSelectedId] = useState(null);
34
35
  const [transformMode, setTransformMode] = useState("translate");
35
- // Sync internal state with external initialPrefab prop
36
+ const [history, setHistory] = useState([loadedPrefab]);
37
+ const [historyIndex, setHistoryIndex] = useState(0);
38
+ const throttleRef = useRef(null);
39
+ const lastDataRef = useRef(JSON.stringify(loadedPrefab));
36
40
  useEffect(() => {
37
- if (initialPrefab) {
41
+ if (initialPrefab)
38
42
  setLoadedPrefab(initialPrefab);
39
- }
40
43
  }, [initialPrefab]);
41
- // Wrapper to update prefab and notify parent
42
44
  const updatePrefab = (newPrefab) => {
43
45
  setLoadedPrefab(newPrefab);
44
46
  const resolved = typeof newPrefab === 'function' ? newPrefab(loadedPrefab) : newPrefab;
45
47
  onPrefabChange === null || onPrefabChange === void 0 ? void 0 : onPrefabChange(resolved);
46
48
  };
47
- 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, editMode: editMode, onPrefabChange: updatePrefab, selectedId: selectedId, onSelect: setSelectedId, transformMode: transformMode, basePath: basePath }), children] }) }), _jsx(SaveDataPanel, { currentData: loadedPrefab, onDataChange: updatePrefab, editMode: editMode, onEditModeChange: setEditMode }), editMode && _jsx(EditorUI, { prefabData: loadedPrefab, setPrefabData: updatePrefab, selectedId: selectedId, setSelectedId: setSelectedId, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath })] });
48
- };
49
- const SaveDataPanel = ({ currentData, onDataChange, editMode, onEditModeChange }) => {
50
- const [history, setHistory] = useState([currentData]);
51
- const [historyIndex, setHistoryIndex] = useState(0);
52
- const throttleRef = useRef(null);
53
- const lastDataRef = useRef(JSON.stringify(currentData));
54
- const undo = () => {
55
- if (historyIndex > 0) {
56
- const newIndex = historyIndex - 1;
57
- setHistoryIndex(newIndex);
58
- lastDataRef.current = JSON.stringify(history[newIndex]);
59
- onDataChange(history[newIndex]);
60
- }
61
- };
62
- const redo = () => {
63
- if (historyIndex < history.length - 1) {
64
- const newIndex = historyIndex + 1;
65
- setHistoryIndex(newIndex);
66
- lastDataRef.current = JSON.stringify(history[newIndex]);
67
- onDataChange(history[newIndex]);
68
- }
49
+ const applyHistory = (index) => {
50
+ setHistoryIndex(index);
51
+ lastDataRef.current = JSON.stringify(history[index]);
52
+ setLoadedPrefab(history[index]);
53
+ onPrefabChange === null || onPrefabChange === void 0 ? void 0 : onPrefabChange(history[index]);
69
54
  };
55
+ const undo = () => historyIndex > 0 && applyHistory(historyIndex - 1);
56
+ const redo = () => historyIndex < history.length - 1 && applyHistory(historyIndex + 1);
70
57
  useEffect(() => {
71
58
  const handleKeyDown = (e) => {
72
- if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
59
+ if (!(e.ctrlKey || e.metaKey))
60
+ return;
61
+ if (e.key === 'z' && !e.shiftKey) {
73
62
  e.preventDefault();
74
63
  undo();
75
64
  }
76
- else if ((e.ctrlKey || e.metaKey) && (e.shiftKey && e.key === 'z' || e.key === 'y')) {
65
+ else if ((e.shiftKey && e.key === 'z') || e.key === 'y') {
77
66
  e.preventDefault();
78
67
  redo();
79
68
  }
@@ -82,7 +71,7 @@ const SaveDataPanel = ({ currentData, onDataChange, editMode, onEditModeChange }
82
71
  return () => window.removeEventListener('keydown', handleKeyDown);
83
72
  }, [historyIndex, history]);
84
73
  useEffect(() => {
85
- const currentStr = JSON.stringify(currentData);
74
+ const currentStr = JSON.stringify(loadedPrefab);
86
75
  if (currentStr === lastDataRef.current)
87
76
  return;
88
77
  if (throttleRef.current)
@@ -90,66 +79,56 @@ const SaveDataPanel = ({ currentData, onDataChange, editMode, onEditModeChange }
90
79
  throttleRef.current = setTimeout(() => {
91
80
  lastDataRef.current = currentStr;
92
81
  setHistory(prev => {
93
- const newHistory = [...prev.slice(0, historyIndex + 1), currentData];
82
+ const newHistory = [...prev.slice(0, historyIndex + 1), loadedPrefab];
94
83
  return newHistory.length > 50 ? newHistory.slice(1) : newHistory;
95
84
  });
96
85
  setHistoryIndex(prev => Math.min(prev + 1, 49));
97
86
  }, 500);
98
- return () => {
99
- if (throttleRef.current)
100
- clearTimeout(throttleRef.current);
101
- };
102
- }, [currentData]);
87
+ return () => { if (throttleRef.current)
88
+ clearTimeout(throttleRef.current); };
89
+ }, [loadedPrefab]);
103
90
  const handleLoad = () => __awaiter(void 0, void 0, void 0, function* () {
104
91
  const prefab = yield loadJson();
105
92
  if (prefab) {
106
- onDataChange(prefab);
93
+ setLoadedPrefab(prefab);
94
+ onPrefabChange === null || onPrefabChange === void 0 ? void 0 : onPrefabChange(prefab);
107
95
  setHistory([prefab]);
108
96
  setHistoryIndex(0);
109
97
  lastDataRef.current = JSON.stringify(prefab);
110
98
  }
111
99
  });
112
- const canUndo = historyIndex > 0;
113
- const canRedo = historyIndex < history.length - 1;
114
- return _jsxs("div", { style: toolbar.panel, children: [_jsx("button", { style: base.btn, onClick: () => onEditModeChange(!editMode), children: editMode ? "▶" : "⏸" }), _jsx("div", { style: toolbar.divider }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), (canUndo ? {} : toolbar.disabled)), onClick: undo, disabled: !canUndo, children: "\u21B6" }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), (canRedo ? {} : toolbar.disabled)), onClick: redo, disabled: !canRedo, children: "\u21B7" }), _jsx("div", { style: toolbar.divider }), _jsx("button", { style: base.btn, onClick: handleLoad, children: "\uD83D\uDCE5" }), _jsx("button", { style: base.btn, onClick: () => saveJson(currentData, "prefab"), children: "\uD83D\uDCBE" })] });
100
+ 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, editMode: editMode, onPrefabChange: updatePrefab, selectedId: selectedId, onSelect: setSelectedId, transformMode: transformMode, basePath: basePath }), children] }) }), _jsx("div", { style: toolbar.panel, children: _jsx("button", { style: base.btn, onClick: () => setEditMode(!editMode), children: editMode ? "▶" : "⏸" }) }), editMode && _jsx(EditorUI, { prefabData: loadedPrefab, setPrefabData: updatePrefab, selectedId: selectedId, setSelectedId: setSelectedId, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath, onSave: () => saveJson(loadedPrefab, "prefab"), onLoad: handleLoad, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 })] });
115
101
  };
116
102
  const saveJson = (data, filename) => {
117
- const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(data, null, 2));
118
- const downloadAnchorNode = document.createElement('a');
119
- downloadAnchorNode.setAttribute("href", dataStr);
120
- downloadAnchorNode.setAttribute("download", (filename || 'prefab') + ".json");
121
- document.body.appendChild(downloadAnchorNode);
122
- downloadAnchorNode.click();
123
- downloadAnchorNode.remove();
103
+ const a = document.createElement('a');
104
+ a.href = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(data, null, 2));
105
+ a.download = `${filename || 'prefab'}.json`;
106
+ a.click();
124
107
  };
125
- const loadJson = () => __awaiter(void 0, void 0, void 0, function* () {
126
- return new Promise((resolve) => {
127
- const input = document.createElement('input');
128
- input.type = 'file';
129
- input.accept = '.json,application/json';
130
- input.onchange = e => {
108
+ const loadJson = () => new Promise(resolve => {
109
+ const input = document.createElement('input');
110
+ input.type = 'file';
111
+ input.accept = '.json,application/json';
112
+ input.onchange = e => {
113
+ var _a;
114
+ const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
115
+ if (!file)
116
+ return resolve(undefined);
117
+ const reader = new FileReader();
118
+ reader.onload = e => {
131
119
  var _a;
132
- const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
133
- if (!file)
134
- return resolve(undefined);
135
- const reader = new FileReader();
136
- reader.onload = e => {
137
- var _a;
138
- try {
139
- const text = (_a = e.target) === null || _a === void 0 ? void 0 : _a.result;
140
- if (typeof text === 'string') {
141
- const json = JSON.parse(text);
142
- resolve(json);
143
- }
144
- }
145
- catch (err) {
146
- console.error('Error parsing prefab JSON:', err);
147
- resolve(undefined);
148
- }
149
- };
150
- reader.readAsText(file);
120
+ try {
121
+ const text = (_a = e.target) === null || _a === void 0 ? void 0 : _a.result;
122
+ if (typeof text === 'string')
123
+ resolve(JSON.parse(text));
124
+ }
125
+ catch (err) {
126
+ console.error('Error parsing prefab JSON:', err);
127
+ resolve(undefined);
128
+ }
151
129
  };
152
- input.click();
153
- });
130
+ reader.readAsText(file);
131
+ };
132
+ input.click();
154
133
  });
155
134
  export default PrefabEditor;
@@ -1,5 +1,5 @@
1
- import { Group } from "three";
2
- import { Prefab } from "./types";
1
+ import { Group, Matrix4, Object3D, Texture } from "three";
2
+ import { Prefab, GameObject as GameObjectType } from "./types";
3
3
  export declare const PrefabRoot: import("react").ForwardRefExoticComponent<{
4
4
  editMode?: boolean;
5
5
  data: Prefab;
@@ -9,4 +9,15 @@ export declare const PrefabRoot: import("react").ForwardRefExoticComponent<{
9
9
  transformMode?: "translate" | "rotate" | "scale";
10
10
  basePath?: string;
11
11
  } & import("react").RefAttributes<Group<import("three").Object3DEventMap>>>;
12
+ export declare function GameObjectRenderer(props: RendererProps): import("react/jsx-runtime").JSX.Element | null;
13
+ interface RendererProps {
14
+ gameObject: GameObjectType;
15
+ selectedId?: string | null;
16
+ onSelect?: (id: string) => void;
17
+ registerRef: (id: string, obj: Object3D | null) => void;
18
+ loadedModels: Record<string, Object3D>;
19
+ loadedTextures: Record<string, Texture>;
20
+ editMode?: boolean;
21
+ parentMatrix?: Matrix4;
22
+ }
12
23
  export default PrefabRoot;