react-three-game 0.0.32 → 0.0.34
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/tools/prefabeditor/EditorTree.d.ts +7 -1
- package/dist/tools/prefabeditor/EditorTree.js +18 -29
- package/dist/tools/prefabeditor/EditorUI.d.ts +7 -1
- package/dist/tools/prefabeditor/EditorUI.js +4 -3
- package/dist/tools/prefabeditor/PrefabEditor.js +65 -86
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +13 -1
- package/dist/tools/prefabeditor/components/GeometryComponent.js +17 -9
- package/dist/tools/prefabeditor/components/Input.d.ts +19 -0
- package/dist/tools/prefabeditor/components/Input.js +123 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.js +15 -4
- package/dist/tools/prefabeditor/components/ModelComponent.js +2 -1
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +12 -3
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +12 -1
- package/dist/tools/prefabeditor/components/TransformComponent.d.ts +0 -5
- package/dist/tools/prefabeditor/components/TransformComponent.js +21 -89
- package/package.json +1 -1
- package/src/tools/prefabeditor/EditorTree.tsx +86 -39
- package/src/tools/prefabeditor/EditorUI.tsx +38 -5
- package/src/tools/prefabeditor/PrefabEditor.tsx +108 -148
- package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +32 -125
- package/src/tools/prefabeditor/components/GeometryComponent.tsx +27 -24
- package/src/tools/prefabeditor/components/Input.tsx +200 -0
- package/src/tools/prefabeditor/components/MaterialComponent.tsx +31 -22
- package/src/tools/prefabeditor/components/ModelComponent.tsx +5 -4
- package/src/tools/prefabeditor/components/PhysicsComponent.tsx +31 -17
- package/src/tools/prefabeditor/components/SpotLightComponent.tsx +26 -43
- package/src/tools/prefabeditor/components/TransformComponent.tsx +34 -165
|
@@ -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 : []),
|
|
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 : []),
|
|
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("
|
|
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:
|
|
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;
|
|
@@ -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
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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)
|
|
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.
|
|
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(
|
|
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),
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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 = () =>
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
153
|
-
}
|
|
130
|
+
reader.readAsText(file);
|
|
131
|
+
};
|
|
132
|
+
input.click();
|
|
154
133
|
});
|
|
155
134
|
export default PrefabEditor;
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useRef, useEffect } from "react";
|
|
3
3
|
import { useFrame } from "@react-three/fiber";
|
|
4
4
|
import { Vector3 } from "three";
|
|
5
|
+
import { Input, Label } from "./Input";
|
|
5
6
|
function DirectionalLightComponentEditor({ component, onUpdate }) {
|
|
6
7
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
7
8
|
const props = {
|
|
@@ -17,7 +18,18 @@ function DirectionalLightComponentEditor({ component, onUpdate }) {
|
|
|
17
18
|
shadowCameraRight: (_k = component.properties.shadowCameraRight) !== null && _k !== void 0 ? _k : 30,
|
|
18
19
|
targetOffset: (_l = component.properties.targetOffset) !== null && _l !== void 0 ? _l : [0, -5, 0]
|
|
19
20
|
};
|
|
20
|
-
|
|
21
|
+
const textInputStyle = {
|
|
22
|
+
flex: 1,
|
|
23
|
+
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
24
|
+
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
25
|
+
padding: '2px 4px',
|
|
26
|
+
fontSize: '10px',
|
|
27
|
+
color: 'rgba(165, 243, 252, 1)',
|
|
28
|
+
fontFamily: 'monospace',
|
|
29
|
+
outline: 'none',
|
|
30
|
+
};
|
|
31
|
+
const smallLabel = { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 };
|
|
32
|
+
return _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Color" }), _jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx("input", { type: "color", style: { height: 20, width: 20, backgroundColor: 'transparent', border: 'none', cursor: 'pointer' }, value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { color: e.target.value })) }), _jsx("input", { type: "text", style: textInputStyle, value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { color: e.target.value })) })] })] }), _jsxs("div", { children: [_jsx(Label, { children: "Intensity" }), _jsx(Input, { step: "0.1", value: props.intensity, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { intensity: value })) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Cast Shadow" }), _jsx("input", { type: "checkbox", style: { height: 16, width: 16, backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', cursor: 'pointer' }, checked: props.castShadow, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { castShadow: e.target.checked })) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Shadow Map Size" }), _jsx(Input, { step: "256", value: props.shadowMapSize, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowMapSize: value })) })] }), _jsxs("div", { style: { borderTop: '1px solid rgba(34, 211, 238, 0.2)', paddingTop: 8, marginTop: 8 }, children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }, children: "Shadow Camera" }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4 }, children: [_jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Near" }), _jsx(Input, { step: "0.1", value: props.shadowCameraNear, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraNear: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Far" }), _jsx(Input, { step: "1", value: props.shadowCameraFar, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraFar: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Top" }), _jsx(Input, { step: "1", value: props.shadowCameraTop, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraTop: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Bottom" }), _jsx(Input, { step: "1", value: props.shadowCameraBottom, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraBottom: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Left" }), _jsx(Input, { step: "1", value: props.shadowCameraLeft, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraLeft: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Right" }), _jsx(Input, { step: "1", value: props.shadowCameraRight, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraRight: value })) })] })] })] }), _jsxs("div", { style: { borderTop: '1px solid rgba(34, 211, 238, 0.2)', paddingTop: 8, marginTop: 8 }, children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }, children: "Target Offset" }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 4 }, children: [_jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "X" }), _jsx(Input, { step: "0.5", value: props.targetOffset[0], onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { targetOffset: [value, props.targetOffset[1], props.targetOffset[2]] })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Y" }), _jsx(Input, { step: "0.5", value: props.targetOffset[1], onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { targetOffset: [props.targetOffset[0], value, props.targetOffset[2]] })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Z" }), _jsx(Input, { step: "0.5", value: props.targetOffset[2], onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { targetOffset: [props.targetOffset[0], props.targetOffset[1], value] })) })] })] })] })] });
|
|
21
33
|
}
|
|
22
34
|
function DirectionalLightView({ properties, editMode }) {
|
|
23
35
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Input, Label } from "./Input";
|
|
2
3
|
const GEOMETRY_ARGS = {
|
|
3
4
|
box: {
|
|
4
5
|
labels: ["Width", "Height", "Depth"],
|
|
@@ -16,17 +17,24 @@ const GEOMETRY_ARGS = {
|
|
|
16
17
|
function GeometryComponentEditor({ component, onUpdate, }) {
|
|
17
18
|
const { geometryType, args = [] } = component.properties;
|
|
18
19
|
const schema = GEOMETRY_ARGS[geometryType];
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
const selectStyle = {
|
|
21
|
+
width: '100%',
|
|
22
|
+
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
23
|
+
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
24
|
+
padding: '2px 4px',
|
|
25
|
+
fontSize: '10px',
|
|
26
|
+
color: 'rgba(165, 243, 252, 1)',
|
|
27
|
+
fontFamily: 'monospace',
|
|
28
|
+
outline: 'none',
|
|
29
|
+
};
|
|
30
|
+
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Type" }), _jsxs("select", { style: selectStyle, value: geometryType, onChange: e => {
|
|
31
|
+
const type = e.target.value;
|
|
32
|
+
onUpdate({ geometryType: type, args: GEOMETRY_ARGS[type].defaults });
|
|
33
|
+
}, children: [_jsx("option", { value: "box", children: "Box" }), _jsx("option", { value: "sphere", children: "Sphere" }), _jsx("option", { value: "plane", children: "Plane" })] })] }), schema.labels.map((label, i) => {
|
|
26
34
|
var _a;
|
|
27
|
-
return (_jsxs("div", { children: [_jsx(
|
|
35
|
+
return (_jsxs("div", { children: [_jsx(Label, { children: label }), _jsx(Input, { value: (_a = args[i]) !== null && _a !== void 0 ? _a : schema.defaults[i], step: "0.1", onChange: value => {
|
|
28
36
|
const next = [...args];
|
|
29
|
-
next[i] =
|
|
37
|
+
next[i] = value;
|
|
30
38
|
onUpdate({ args: next });
|
|
31
39
|
} })] }, label));
|
|
32
40
|
})] }));
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface InputProps {
|
|
3
|
+
value: number;
|
|
4
|
+
onChange: (value: number) => void;
|
|
5
|
+
step?: string | number;
|
|
6
|
+
min?: number;
|
|
7
|
+
max?: number;
|
|
8
|
+
style?: React.CSSProperties;
|
|
9
|
+
}
|
|
10
|
+
export declare function Input({ value, onChange, step, min, max, style }: InputProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export declare function Label({ children }: {
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export declare function Vector3Input({ label, value, onChange }: {
|
|
15
|
+
label: string;
|
|
16
|
+
value: [number, number, number];
|
|
17
|
+
onChange: (v: [number, number, number]) => void;
|
|
18
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
// Shared styles
|
|
4
|
+
const styles = {
|
|
5
|
+
input: {
|
|
6
|
+
width: '100%',
|
|
7
|
+
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
8
|
+
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
9
|
+
padding: '2px 4px',
|
|
10
|
+
fontSize: '10px',
|
|
11
|
+
color: 'rgba(165, 243, 252, 1)',
|
|
12
|
+
fontFamily: 'monospace',
|
|
13
|
+
outline: 'none',
|
|
14
|
+
},
|
|
15
|
+
label: {
|
|
16
|
+
display: 'block',
|
|
17
|
+
fontSize: '9px',
|
|
18
|
+
color: 'rgba(34, 211, 238, 0.6)',
|
|
19
|
+
textTransform: 'uppercase',
|
|
20
|
+
letterSpacing: '0.05em',
|
|
21
|
+
marginBottom: 2,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
export function Input({ value, onChange, step, min, max, style }) {
|
|
25
|
+
return (_jsx("input", { type: "number", value: value, onChange: (e) => onChange(parseFloat(e.target.value)), step: step, min: min, max: max, style: Object.assign(Object.assign({}, styles.input), style) }));
|
|
26
|
+
}
|
|
27
|
+
export function Label({ children }) {
|
|
28
|
+
return _jsx("label", { style: styles.label, children: children });
|
|
29
|
+
}
|
|
30
|
+
export function Vector3Input({ label, value, onChange }) {
|
|
31
|
+
const [draft, setDraft] = useState(() => value.map(v => v.toString()));
|
|
32
|
+
// Sync external changes (gizmo, undo, etc.)
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
setDraft(value.map(v => v.toString()));
|
|
35
|
+
}, [value[0], value[1], value[2]]);
|
|
36
|
+
const dragState = useRef(null);
|
|
37
|
+
const commit = (index) => {
|
|
38
|
+
const num = parseFloat(draft[index]);
|
|
39
|
+
if (Number.isFinite(num)) {
|
|
40
|
+
const next = [...value];
|
|
41
|
+
next[index] = num;
|
|
42
|
+
onChange(next);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const startScrub = (e, index) => {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
dragState.current = {
|
|
48
|
+
index,
|
|
49
|
+
startX: e.clientX,
|
|
50
|
+
startValue: value[index]
|
|
51
|
+
};
|
|
52
|
+
e.target.setPointerCapture(e.pointerId);
|
|
53
|
+
document.body.style.cursor = "ew-resize";
|
|
54
|
+
};
|
|
55
|
+
const onScrubMove = (e) => {
|
|
56
|
+
if (!dragState.current)
|
|
57
|
+
return;
|
|
58
|
+
const { index, startX, startValue } = dragState.current;
|
|
59
|
+
const dx = e.clientX - startX;
|
|
60
|
+
let speed = 0.02;
|
|
61
|
+
if (e.shiftKey)
|
|
62
|
+
speed *= 0.1; // fine
|
|
63
|
+
if (e.altKey)
|
|
64
|
+
speed *= 5; // coarse
|
|
65
|
+
const nextValue = startValue + dx * speed;
|
|
66
|
+
const next = [...value];
|
|
67
|
+
next[index] = nextValue;
|
|
68
|
+
setDraft(d => {
|
|
69
|
+
const copy = [...d];
|
|
70
|
+
copy[index] = nextValue.toFixed(3);
|
|
71
|
+
return copy;
|
|
72
|
+
});
|
|
73
|
+
onChange(next);
|
|
74
|
+
};
|
|
75
|
+
const endScrub = (e) => {
|
|
76
|
+
if (!dragState.current)
|
|
77
|
+
return;
|
|
78
|
+
dragState.current = null;
|
|
79
|
+
document.body.style.cursor = "";
|
|
80
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
81
|
+
};
|
|
82
|
+
const axes = [
|
|
83
|
+
{ key: "x", color: 'rgba(248, 113, 113, 1)', index: 0 },
|
|
84
|
+
{ key: "y", color: 'rgba(134, 239, 172, 1)', index: 1 },
|
|
85
|
+
{ key: "z", color: 'rgba(96, 165, 250, 1)', index: 2 }
|
|
86
|
+
];
|
|
87
|
+
return (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsx("label", { style: Object.assign(Object.assign({}, styles.label), { marginBottom: 4 }), children: label }), _jsx("div", { style: { display: 'flex', gap: 4 }, children: axes.map(({ key, color, index }) => (_jsxs("div", { style: {
|
|
88
|
+
flex: 1,
|
|
89
|
+
display: 'flex',
|
|
90
|
+
alignItems: 'center',
|
|
91
|
+
gap: 4,
|
|
92
|
+
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
|
93
|
+
border: '1px solid rgba(34, 211, 238, 0.2)',
|
|
94
|
+
borderRadius: 4,
|
|
95
|
+
padding: '4px 6px',
|
|
96
|
+
minHeight: 32,
|
|
97
|
+
}, children: [_jsx("span", { style: {
|
|
98
|
+
fontSize: '12px',
|
|
99
|
+
fontWeight: 'bold',
|
|
100
|
+
color,
|
|
101
|
+
width: 12,
|
|
102
|
+
cursor: 'ew-resize',
|
|
103
|
+
userSelect: 'none',
|
|
104
|
+
}, onPointerDown: e => startScrub(e, index), onPointerMove: onScrubMove, onPointerUp: endScrub, children: key.toUpperCase() }), _jsx("input", { style: {
|
|
105
|
+
flex: 1,
|
|
106
|
+
backgroundColor: 'transparent',
|
|
107
|
+
border: 'none',
|
|
108
|
+
fontSize: '12px',
|
|
109
|
+
color: 'rgba(165, 243, 252, 1)',
|
|
110
|
+
fontFamily: 'monospace',
|
|
111
|
+
outline: 'none',
|
|
112
|
+
width: '100%',
|
|
113
|
+
minWidth: 0,
|
|
114
|
+
}, type: "text", value: draft[index], onChange: e => {
|
|
115
|
+
const next = [...draft];
|
|
116
|
+
next[index] = e.target.value;
|
|
117
|
+
setDraft(next);
|
|
118
|
+
}, onBlur: () => commit(index), onKeyDown: e => {
|
|
119
|
+
if (e.key === "Enter") {
|
|
120
|
+
e.target.blur();
|
|
121
|
+
}
|
|
122
|
+
} })] }, key))) })] }));
|
|
123
|
+
}
|