react-three-game 0.0.59 → 0.0.61

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.
Files changed (67) hide show
  1. package/dist/tools/dragdrop/DragDropLoader.d.ts +8 -8
  2. package/dist/tools/dragdrop/DragDropLoader.js +33 -15
  3. package/dist/tools/dragdrop/index.d.ts +3 -3
  4. package/dist/tools/dragdrop/index.js +1 -1
  5. package/dist/tools/dragdrop/modelLoader.d.ts +10 -1
  6. package/dist/tools/dragdrop/modelLoader.js +39 -0
  7. package/dist/tools/prefabeditor/PrefabEditor.js +17 -26
  8. package/dist/tools/prefabeditor/PrefabRoot.d.ts +1 -1
  9. package/dist/tools/prefabeditor/PrefabRoot.js +2 -8
  10. package/package.json +9 -3
  11. package/.gitattributes +0 -2
  12. package/.github/copilot-instructions.md +0 -83
  13. package/.github/workflows/nextjs.yml +0 -99
  14. package/.gitmodules +0 -3
  15. package/assets/architecture.png +0 -0
  16. package/assets/editor.gif +0 -0
  17. package/assets/favicon.ico +0 -0
  18. package/assets/react-three-game-logo.png +0 -0
  19. package/dist/tools/dragdrop/page.d.ts +0 -1
  20. package/dist/tools/dragdrop/page.js +0 -11
  21. package/dist/tools/prefabeditor/EntityEvents.d.ts +0 -54
  22. package/dist/tools/prefabeditor/EntityEvents.js +0 -85
  23. package/dist/tools/prefabeditor/page.d.ts +0 -1
  24. package/dist/tools/prefabeditor/page.js +0 -5
  25. package/react-three-game-skill/.gitattributes +0 -2
  26. package/react-three-game-skill/README.md +0 -7
  27. package/react-three-game-skill/react-three-game/SKILL.md +0 -514
  28. package/react-three-game-skill/react-three-game/rules/ADVANCED_PHYSICS.md +0 -472
  29. package/react-three-game-skill/react-three-game/rules/LIGHTING.md +0 -6
  30. package/src/helpers/SoundManager.ts +0 -130
  31. package/src/helpers/index.ts +0 -91
  32. package/src/index.ts +0 -59
  33. package/src/shared/ContactShadow.tsx +0 -74
  34. package/src/shared/GameCanvas.tsx +0 -52
  35. package/src/tools/assetviewer/page.tsx +0 -425
  36. package/src/tools/dragdrop/DragDropLoader.tsx +0 -136
  37. package/src/tools/dragdrop/index.ts +0 -4
  38. package/src/tools/dragdrop/modelLoader.ts +0 -145
  39. package/src/tools/dragdrop/page.tsx +0 -45
  40. package/src/tools/prefabeditor/Dropdown.tsx +0 -112
  41. package/src/tools/prefabeditor/EditorContext.tsx +0 -25
  42. package/src/tools/prefabeditor/EditorTree.tsx +0 -452
  43. package/src/tools/prefabeditor/EditorTreeMenus.tsx +0 -307
  44. package/src/tools/prefabeditor/EditorUI.tsx +0 -204
  45. package/src/tools/prefabeditor/EventSystem.tsx +0 -36
  46. package/src/tools/prefabeditor/GameEvents.ts +0 -191
  47. package/src/tools/prefabeditor/InstanceProvider.tsx +0 -466
  48. package/src/tools/prefabeditor/PrefabEditor.tsx +0 -262
  49. package/src/tools/prefabeditor/PrefabRoot.tsx +0 -773
  50. package/src/tools/prefabeditor/components/AmbientLightComponent.tsx +0 -34
  51. package/src/tools/prefabeditor/components/CameraComponent.tsx +0 -117
  52. package/src/tools/prefabeditor/components/ComponentRegistry.ts +0 -40
  53. package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +0 -210
  54. package/src/tools/prefabeditor/components/EnvironmentComponent.tsx +0 -47
  55. package/src/tools/prefabeditor/components/GeometryComponent.tsx +0 -133
  56. package/src/tools/prefabeditor/components/Input.tsx +0 -820
  57. package/src/tools/prefabeditor/components/MaterialComponent.tsx +0 -431
  58. package/src/tools/prefabeditor/components/ModelComponent.tsx +0 -176
  59. package/src/tools/prefabeditor/components/PhysicsComponent.tsx +0 -188
  60. package/src/tools/prefabeditor/components/SpotLightComponent.tsx +0 -109
  61. package/src/tools/prefabeditor/components/TextComponent.tsx +0 -137
  62. package/src/tools/prefabeditor/components/TransformComponent.tsx +0 -173
  63. package/src/tools/prefabeditor/components/index.ts +0 -26
  64. package/src/tools/prefabeditor/page.tsx +0 -10
  65. package/src/tools/prefabeditor/styles.ts +0 -235
  66. package/src/tools/prefabeditor/types.ts +0 -20
  67. package/src/tools/prefabeditor/utils.ts +0 -312
@@ -1,452 +0,0 @@
1
- import { Dispatch, SetStateAction, useState, MouseEvent } from 'react';
2
- import { Prefab, GameObject } from "./types";
3
- import { getComponent } from './components/ComponentRegistry';
4
- import { base, colors, tree } from './styles';
5
- import { findNode, findParent, deleteNode, cloneNode, updateNodeById } from './utils';
6
- import { useEditorContext } from './EditorContext';
7
- import { Dropdown } from './Dropdown';
8
- import { FileMenu, MenuTriggerButton, TreeContextMenu, TreeContextMenuState, TreeNodeMenu } from './EditorTreeMenus';
9
-
10
- type DropPosition = 'before' | 'inside';
11
-
12
- function moveNode(root: GameObject, draggedId: string, targetId: string, position: DropPosition): GameObject {
13
- const draggedNode = findNode(root, draggedId);
14
- const oldParent = findParent(root, draggedId);
15
-
16
- if (!draggedNode || !oldParent || findNode(draggedNode, targetId)) {
17
- return root;
18
- }
19
-
20
- if (position === 'before') {
21
- const targetParent = findParent(root, targetId);
22
- if (!targetParent?.children) return root;
23
-
24
- if (targetParent.id === oldParent.id) {
25
- const siblings = targetParent.children.filter(child => child.id !== draggedId);
26
- const targetIndex = siblings.findIndex(child => child.id === targetId);
27
- if (targetIndex === -1) return root;
28
-
29
- siblings.splice(targetIndex, 0, draggedNode);
30
- return updateNodeById(root, targetParent.id, parent => ({ ...parent, children: siblings }));
31
- }
32
-
33
- const rootWithoutDragged = updateNodeById(root, oldParent.id, parent => ({
34
- ...parent,
35
- children: (parent.children ?? []).filter(child => child.id !== draggedId)
36
- }));
37
-
38
- return updateNodeById(rootWithoutDragged, targetParent.id, parent => {
39
- const children = [...(parent.children ?? [])];
40
- const targetIndex = children.findIndex(child => child.id === targetId);
41
- if (targetIndex === -1) return parent;
42
-
43
- children.splice(targetIndex, 0, draggedNode);
44
- return { ...parent, children };
45
- });
46
- }
47
-
48
- const rootWithoutDragged = updateNodeById(root, oldParent.id, parent => ({
49
- ...parent,
50
- children: (parent.children ?? []).filter(child => child.id !== draggedId)
51
- }));
52
-
53
- return updateNodeById(rootWithoutDragged, targetId, target => ({
54
- ...target,
55
- children: [...(target.children ?? []), draggedNode]
56
- }));
57
- }
58
-
59
- function duplicateNodeBelow(root: GameObject, nodeId: string): { root: GameObject; duplicatedId: string } | null {
60
- const node = findNode(root, nodeId);
61
- const parent = findParent(root, nodeId);
62
- if (!node || !parent) return null;
63
-
64
- const duplicate = cloneNode(node);
65
- const nextRoot = updateNodeById(root, parent.id, currentParent => ({
66
- ...currentParent,
67
- children: (() => {
68
- const children = [...(currentParent.children ?? [])];
69
- const index = children.findIndex(child => child.id === nodeId);
70
- if (index === -1) return [...children, duplicate];
71
- children.splice(index + 1, 0, duplicate);
72
- return children;
73
- })()
74
- }));
75
-
76
- return { root: nextRoot, duplicatedId: duplicate.id };
77
- }
78
-
79
- export default function EditorTree({
80
- prefabData,
81
- setPrefabData,
82
- selectedId,
83
- setSelectedId,
84
- onUndo,
85
- onRedo,
86
- canUndo,
87
- canRedo
88
- }: {
89
- prefabData?: Prefab;
90
- setPrefabData?: Dispatch<SetStateAction<Prefab>>;
91
- selectedId: string | null;
92
- setSelectedId: Dispatch<SetStateAction<string | null>>;
93
- onUndo?: () => void;
94
- onRedo?: () => void;
95
- canUndo?: boolean;
96
- canRedo?: boolean;
97
- }) {
98
- const { onFocusNode } = useEditorContext();
99
- const [draggedId, setDraggedId] = useState<string | null>(null);
100
- const [dropTarget, setDropTarget] = useState<{ id: string; position: DropPosition } | null>(null);
101
- const [collapsedIds, setCollapsedIds] = useState<Set<string>>(new Set());
102
- const [collapsed, setCollapsed] = useState(false);
103
- const [searchQuery, setSearchQuery] = useState('');
104
- const [contextMenu, setContextMenu] = useState<TreeContextMenuState>(null);
105
-
106
- if (!prefabData || !setPrefabData) return null;
107
-
108
- const toggleCollapse = (e: MouseEvent, id: string) => {
109
- e.stopPropagation();
110
- setCollapsedIds(prev => {
111
- const next = new Set(prev);
112
- next.has(id) ? next.delete(id) : next.add(id);
113
- return next;
114
- });
115
- };
116
-
117
- const handleAddChild = (parentId: string) => {
118
- const newNode = {
119
- id: crypto.randomUUID(),
120
- name: "New Node",
121
- components: {
122
- transform: {
123
- type: "Transform",
124
- properties: { ...getComponent('Transform')?.defaultProperties }
125
- }
126
- }
127
- };
128
-
129
- setPrefabData(prev => ({
130
- ...prev,
131
- root: updateNodeById(prev.root, parentId, parent => ({
132
- ...parent,
133
- children: [...(parent.children ?? []), newNode]
134
- }))
135
- }));
136
- setSelectedId(newNode.id);
137
- };
138
-
139
- const handleDuplicate = (nodeId: string) => {
140
- if (nodeId === prefabData.root.id) return;
141
- setPrefabData(prev => {
142
- const result = duplicateNodeBelow(prev.root, nodeId);
143
- if (!result) return prev;
144
-
145
- setSelectedId(result.duplicatedId);
146
-
147
- return {
148
- ...prev,
149
- root: result.root
150
- };
151
- });
152
- };
153
-
154
- const handleDelete = (nodeId: string) => {
155
- if (nodeId === prefabData.root.id) return;
156
- setPrefabData(prev => ({ ...prev, root: deleteNode(prev.root, nodeId)! }));
157
- if (selectedId === nodeId) setSelectedId(null);
158
- };
159
-
160
- const handleToggleDisabled = (nodeId: string) => {
161
- setPrefabData(prev => ({
162
- ...prev,
163
- root: updateNodeById(prev.root, nodeId, node => ({
164
- ...node,
165
- disabled: !node.disabled
166
- }))
167
- }));
168
- };
169
-
170
- const closeContextMenu = () => setContextMenu(null);
171
-
172
- const openContextMenu = (nodeId: string, x: number, y: number) => {
173
- setSelectedId(nodeId);
174
- setContextMenu({ nodeId, x, y });
175
- };
176
-
177
- const handleFocus = (nodeId: string) => {
178
- setSelectedId(nodeId);
179
- onFocusNode?.(nodeId);
180
- };
181
-
182
- const renderTreeNodeMenu = (nodeId: string, isRoot: boolean, onClose: () => void) => (
183
- <TreeNodeMenu
184
- isRoot={isRoot}
185
- nodeId={nodeId}
186
- onAddChild={handleAddChild}
187
- onFocus={handleFocus}
188
- onDuplicate={isRoot ? undefined : handleDuplicate}
189
- onDelete={isRoot ? undefined : handleDelete}
190
- onClose={onClose}
191
- />
192
- );
193
-
194
- const handleDragStart = (e: React.DragEvent, id: string) => {
195
- if (id === prefabData.root.id) return e.preventDefault();
196
- e.dataTransfer.effectAllowed = "move";
197
- setDraggedId(id);
198
- };
199
-
200
- const getDropPosition = (e: React.DragEvent<HTMLDivElement>, isRoot: boolean): DropPosition => {
201
- if (isRoot) return 'inside';
202
- const rect = e.currentTarget.getBoundingClientRect();
203
- return e.clientY <= rect.top + rect.height * 0.35 ? 'before' : 'inside';
204
- };
205
-
206
- const handleDragOver = (e: React.DragEvent<HTMLDivElement>, targetId: string, isRoot: boolean) => {
207
- if (!draggedId || draggedId === targetId) return;
208
- const draggedNode = findNode(prefabData.root, draggedId);
209
- if (draggedNode && findNode(draggedNode, targetId)) return;
210
- e.preventDefault();
211
- setDropTarget({ id: targetId, position: getDropPosition(e, isRoot) });
212
- };
213
-
214
- const handleDragLeave = (e: React.DragEvent<HTMLDivElement>, targetId: string) => {
215
- const relatedTarget = e.relatedTarget;
216
- if (relatedTarget instanceof Node && e.currentTarget.contains(relatedTarget)) return;
217
- setDropTarget(current => current?.id === targetId ? null : current);
218
- };
219
-
220
- const handleDrop = (e: React.DragEvent<HTMLDivElement>, targetId: string, isRoot: boolean) => {
221
- if (!draggedId || draggedId === targetId) return;
222
- e.preventDefault();
223
- const dropPosition = getDropPosition(e, isRoot);
224
- setPrefabData(prev => {
225
- const root = moveNode(prev.root, draggedId, targetId, dropPosition);
226
- return root === prev.root ? prev : { ...prev, root };
227
- });
228
- setDraggedId(null);
229
- setDropTarget(null);
230
- };
231
-
232
-
233
- const matchesSearch = (node: GameObject, query: string): boolean => {
234
- if (!query) return true;
235
- const lowerQuery = query.toLowerCase();
236
- const nodeName = (node.name ?? node.id).toLowerCase();
237
- if (nodeName.includes(lowerQuery)) return true;
238
- return node.children?.some(child => matchesSearch(child, query)) ?? false;
239
- };
240
-
241
- const renderNode = (node: GameObject, depth = 0): React.ReactNode => {
242
- if (!node) return null;
243
- if (!matchesSearch(node, searchQuery)) return null;
244
-
245
- const isSelected = node.id === selectedId;
246
- const isCollapsed = collapsedIds.has(node.id);
247
- const hasChildren = node.children && node.children.length > 0;
248
- const isRoot = node.id === prefabData.root.id;
249
- const isDropTarget = dropTarget?.id === node.id;
250
- const showDropBefore = isDropTarget && dropTarget?.position === 'before';
251
- const showDropInside = isDropTarget && dropTarget?.position === 'inside';
252
-
253
- return (
254
- <div key={node.id}>
255
- <div
256
- style={{
257
- ...tree.row,
258
- ...(isSelected ? tree.selected : {}),
259
- paddingLeft: `${depth * 12 + 6}px`,
260
- opacity: node.disabled ? 0.4 : 1,
261
- display: 'flex',
262
- alignItems: 'center',
263
- justifyContent: 'space-between',
264
- borderTop: showDropBefore ? `2px solid ${colors.accent}` : undefined,
265
- boxShadow: showDropInside ? `inset 0 0 0 1px ${colors.accentBorder}` : undefined,
266
- }}
267
- draggable={!isRoot}
268
- onClick={(e) => { e.stopPropagation(); setSelectedId(node.id); }}
269
- onContextMenu={(e) => {
270
- e.preventDefault();
271
- e.stopPropagation();
272
- openContextMenu(node.id, e.clientX, e.clientY);
273
- }}
274
- onDragStart={(e) => handleDragStart(e, node.id)}
275
- onDragEnd={() => { setDraggedId(null); setDropTarget(null); }}
276
- onDragOver={(e) => handleDragOver(e, node.id, isRoot)}
277
- onDragLeave={(e) => handleDragLeave(e, node.id)}
278
- onDrop={(e) => handleDrop(e, node.id, isRoot)}
279
- >
280
- <div style={{ display: 'flex', alignItems: 'center', flex: 1, minWidth: 0 }}>
281
- <span
282
- style={{
283
- width: 12,
284
- opacity: 0.6,
285
- marginRight: 4,
286
- cursor: 'pointer',
287
- visibility: hasChildren ? 'visible' : 'hidden'
288
- }}
289
- onClick={(e) => hasChildren && toggleCollapse(e, node.id)}
290
- >
291
- {isCollapsed ? '▶' : '▼'}
292
- </span>
293
- {!isRoot && <span style={{ marginRight: 4, opacity: 0.4 }}>⋮⋮</span>}
294
- <span style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>
295
- {node.name ?? node.id}
296
- </span>
297
- </div>
298
- {!isRoot && (
299
- <div style={{ display: 'flex', alignItems: 'center', gap: 2 }}>
300
- <Dropdown
301
- placement="bottom-end"
302
- trigger={({ ref, toggle }) => (
303
- <MenuTriggerButton
304
- buttonRef={ref}
305
- onToggle={toggle}
306
- title="Node Actions"
307
- style={{
308
- background: 'none',
309
- border: 'none',
310
- cursor: 'pointer',
311
- padding: '0 4px',
312
- fontSize: 14,
313
- opacity: 0.7,
314
- color: 'inherit',
315
- }}
316
- >
317
-
318
- </MenuTriggerButton>
319
- )}
320
- >
321
- {(close) => renderTreeNodeMenu(node.id, false, close)}
322
- </Dropdown>
323
- <button
324
- style={{
325
- background: 'none',
326
- border: 'none',
327
- cursor: 'pointer',
328
- padding: '0 4px',
329
- fontSize: 14,
330
- opacity: node.disabled ? 0.5 : 0.7,
331
- color: 'inherit',
332
- }}
333
- onClick={(e) => {
334
- e.stopPropagation();
335
- handleToggleDisabled(node.id);
336
- }}
337
- title={node.disabled ? 'Enable' : 'Disable'}
338
- >
339
- {node.disabled ? '◎' : '◉'}
340
- </button>
341
- </div>
342
- )}
343
- {isRoot && (
344
- <Dropdown
345
- placement="bottom-end"
346
- trigger={({ ref, toggle }) => (
347
- <MenuTriggerButton
348
- buttonRef={ref}
349
- onToggle={toggle}
350
- title="Scene Actions"
351
- style={{
352
- background: 'none',
353
- border: 'none',
354
- cursor: 'pointer',
355
- padding: '0 4px',
356
- fontSize: 14,
357
- opacity: 0.7,
358
- color: 'inherit',
359
- }}
360
- >
361
-
362
- </MenuTriggerButton>
363
- )}
364
- >
365
- {(close) => renderTreeNodeMenu(node.id, true, close)}
366
- </Dropdown>
367
- )}
368
- </div>
369
- {!isCollapsed && node.children && node.children.map(child => renderNode(child, depth + 1))}
370
- </div>
371
- );
372
- };
373
-
374
- return (
375
- <>
376
- <div style={{ ...tree.panel, width: collapsed ? 'auto' : 224 }}>
377
- <div style={base.header}>
378
- <div style={{ display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer' }} onClick={() => setCollapsed(!collapsed)}>
379
- <span>{collapsed ? '▶' : '▼'}</span>
380
- <span>Scene</span>
381
- </div>
382
- {!collapsed && (
383
- <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
384
- <button
385
- style={{ ...base.btn, padding: '2px 6px', fontSize: 10, opacity: canUndo ? 1 : 0.4 }}
386
- onClick={(e) => { e.stopPropagation(); onUndo?.(); }}
387
- disabled={!canUndo}
388
- title="Undo"
389
- >
390
-
391
- </button>
392
- <button
393
- style={{ ...base.btn, padding: '2px 6px', fontSize: 10, opacity: canRedo ? 1 : 0.4 }}
394
- onClick={(e) => { e.stopPropagation(); onRedo?.(); }}
395
- disabled={!canRedo}
396
- title="Redo"
397
- >
398
-
399
- </button>
400
- <Dropdown
401
- placement="bottom-end"
402
- trigger={({ ref, toggle }) => (
403
- <MenuTriggerButton
404
- buttonRef={ref}
405
- onToggle={toggle}
406
- title="Menu"
407
- style={{ ...base.btn, padding: '2px 6px', fontSize: 10 }}
408
- >
409
-
410
- </MenuTriggerButton>
411
- )}
412
- >
413
- {(close) => (
414
- <FileMenu
415
- prefabData={prefabData}
416
- setPrefabData={setPrefabData}
417
- onClose={close}
418
- />
419
- )}
420
- </Dropdown>
421
- </div>
422
- )}
423
- </div>
424
- {!collapsed && (
425
- <>
426
- <div style={{ padding: '4px 4px', borderBottom: `1px solid ${colors.borderLight}` }}>
427
- <input
428
- type="text"
429
- placeholder="Search nodes..."
430
- value={searchQuery}
431
- onChange={(e) => setSearchQuery(e.target.value)}
432
- onClick={(e) => e.stopPropagation()}
433
- style={{
434
- ...base.input,
435
- padding: '4px 8px',
436
- }}
437
- />
438
- </div>
439
- <div className="tree-scroll" style={tree.scroll}>{renderNode(prefabData.root)}</div>
440
- </>
441
- )}
442
- </div>
443
- <TreeContextMenu
444
- contextMenu={contextMenu}
445
- onClose={closeContextMenu}
446
- >
447
- {(nodeId, close) => renderTreeNodeMenu(nodeId, nodeId === prefabData.root.id, close)}
448
- </TreeContextMenu>
449
-
450
- </>
451
- );
452
- }