react-three-game 0.0.100 → 0.0.101
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/tools/assetviewer/page.js +70 -58
- package/dist/tools/dragdrop/DragDropLoader.d.ts +3 -0
- package/dist/tools/dragdrop/DragDropLoader.js +183 -44
- package/dist/tools/dragdrop/index.d.ts +1 -1
- package/dist/tools/dragdrop/index.js +1 -1
- package/dist/tools/dragdrop/modelLoader.js +2 -0
- package/dist/tools/prefabeditor/EditorUI.js +7 -8
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +3 -0
- package/dist/tools/prefabeditor/PrefabEditor.js +28 -11
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +2 -0
- package/dist/tools/prefabeditor/PrefabRoot.js +51 -35
- package/dist/tools/prefabeditor/components/BufferGeometryComponent.js +20 -0
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +5 -0
- package/dist/tools/prefabeditor/components/ComponentRegistry.js +31 -0
- package/dist/tools/prefabeditor/components/DataComponent.js +1 -0
- package/dist/tools/prefabeditor/components/EnvironmentComponent.js +1 -0
- package/dist/tools/prefabeditor/components/GeometryComponent.js +1 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +1 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.js +89 -52
- package/dist/tools/prefabeditor/components/ModelComponent.js +45 -3
- package/dist/tools/prefabeditor/components/SpriteComponent.js +1 -0
- package/dist/tools/prefabeditor/components/TransformComponent.js +1 -0
- package/dist/tools/prefabeditor/modelPrefab.d.ts +16 -0
- package/dist/tools/prefabeditor/modelPrefab.js +180 -0
- package/dist/tools/prefabeditor/prefabStore.d.ts +1 -0
- package/dist/tools/prefabeditor/prefabStore.js +75 -42
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Fiel
|
|
|
18
18
|
export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, } from './tools/prefabeditor/utils';
|
|
19
19
|
export type { ExportGLBOptions } from './tools/prefabeditor/utils';
|
|
20
20
|
export { createModelNode, createImageNode, } from './tools/prefabeditor/prefab';
|
|
21
|
+
export { decomposeModelToPrefabNodes } from './tools/prefabeditor/modelPrefab';
|
|
22
|
+
export type { DecomposeModelOptions } from './tools/prefabeditor/modelPrefab';
|
|
21
23
|
export type { PrefabEditorProps, PrefabNode, PrefabEditorRef, } from './tools/prefabeditor/PrefabEditor';
|
|
22
24
|
export type { PrefabRootProps } from './tools/prefabeditor/PrefabRoot';
|
|
23
25
|
export type { AssetRuntime, NodeApi, LiveRef } from './tools/prefabeditor/assetRuntime';
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,7 @@ export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Fiel
|
|
|
19
19
|
// Prefab Editor - Utils
|
|
20
20
|
export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, } from './tools/prefabeditor/utils';
|
|
21
21
|
export { createModelNode, createImageNode, } from './tools/prefabeditor/prefab';
|
|
22
|
+
export { decomposeModelToPrefabNodes } from './tools/prefabeditor/modelPrefab';
|
|
22
23
|
export { useAssetRuntime, useNode, useNodeHandle, useNodeObject } from './tools/prefabeditor/assetRuntime';
|
|
23
24
|
export { useScene } from './tools/prefabeditor/PrefabRoot';
|
|
24
25
|
export { MaterialOverridesProvider, useMaterialOverrides } from './tools/prefabeditor/components/MaterialComponent';
|
|
@@ -1,45 +1,71 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Canvas } from "@react-three/fiber";
|
|
2
|
+
import { Canvas, useLoader } from "@react-three/fiber";
|
|
3
3
|
import { OrbitControls, View, PerspectiveCamera } from "@react-three/drei";
|
|
4
4
|
import { Suspense, useEffect, useLayoutEffect, useState, useRef } from "react";
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
6
|
-
import { TextureLoader } from "three";
|
|
6
|
+
import { Mesh, TextureLoader } from "three";
|
|
7
7
|
import { loadModel } from "../dragdrop/modelLoader";
|
|
8
|
+
import { resolvePrefabAssetPath } from "../prefabeditor/PrefabEditor";
|
|
9
|
+
import { base, colors, fonts } from "../prefabeditor/styles";
|
|
8
10
|
const styles = {
|
|
9
|
-
errorIcon: { color:
|
|
11
|
+
errorIcon: { color: colors.danger, fontSize: 12 },
|
|
10
12
|
flexFillRelative: { flex: 1, position: 'relative' },
|
|
11
|
-
bottomLabel: { backgroundColor:
|
|
12
|
-
textLight: { color:
|
|
13
|
+
bottomLabel: { backgroundColor: colors.bgLight, color: colors.text, fontSize: fonts.sizeSm, padding: '1px 4px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center', borderTop: `1px solid ${colors.borderFaint}` },
|
|
14
|
+
textLight: { color: colors.text, fontFamily: fonts.family, fontSize: fonts.size },
|
|
13
15
|
iconLarge: { fontSize: 20 }
|
|
14
16
|
};
|
|
15
17
|
const assetViewerColors = {
|
|
16
|
-
panelBg:
|
|
17
|
-
controlBg:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
panelBg: colors.bg,
|
|
19
|
+
controlBg: colors.bgSurface,
|
|
20
|
+
previewBg: colors.bgLight,
|
|
21
|
+
text: colors.text,
|
|
22
|
+
border: colors.border,
|
|
23
|
+
borderFaint: colors.borderFaint,
|
|
24
|
+
accentBorder: colors.accentBorder,
|
|
25
|
+
errorBg: colors.dangerBg,
|
|
21
26
|
};
|
|
22
27
|
const assetPickerPopupBaseStyle = {
|
|
23
28
|
background: assetViewerColors.panelBg,
|
|
24
29
|
border: `1px solid ${assetViewerColors.border}`,
|
|
25
30
|
borderRadius: 0,
|
|
26
|
-
boxShadow: '
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
color: 'inherit',
|
|
31
|
-
fontSize: 10,
|
|
32
|
-
cursor: 'pointer',
|
|
33
|
-
border: `1px solid ${assetViewerColors.border}`,
|
|
34
|
-
borderRadius: 0,
|
|
31
|
+
boxShadow: 'none',
|
|
32
|
+
color: colors.text,
|
|
33
|
+
fontFamily: fonts.family,
|
|
34
|
+
fontSize: fonts.size,
|
|
35
35
|
};
|
|
36
|
+
const assetPickerButtonBaseStyle = Object.assign(Object.assign({}, base.btn), { background: assetViewerColors.controlBg, color: colors.text, fontSize: fonts.size, cursor: 'pointer' });
|
|
36
37
|
const assetPickerWideButtonStyle = Object.assign(Object.assign({}, assetPickerButtonBaseStyle), { width: '100%', padding: '6px 8px' });
|
|
38
|
+
function disposeMaterial(material) {
|
|
39
|
+
if (Array.isArray(material)) {
|
|
40
|
+
material.forEach(entry => entry.dispose());
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
material.dispose();
|
|
44
|
+
}
|
|
45
|
+
function disposeObject3D(object) {
|
|
46
|
+
object.traverse(child => {
|
|
47
|
+
var _a;
|
|
48
|
+
if (!(child instanceof Mesh))
|
|
49
|
+
return;
|
|
50
|
+
(_a = child.geometry) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
51
|
+
disposeMaterial(child.material);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
37
54
|
const assetPickerSmallButtonStyle = Object.assign(Object.assign({}, assetPickerButtonBaseStyle), { padding: '4px 8px' });
|
|
38
55
|
const assetPickerEmptyPreviewStyle = {
|
|
39
|
-
backgroundColor: assetViewerColors.
|
|
56
|
+
backgroundColor: assetViewerColors.previewBg,
|
|
40
57
|
border: `1px dashed ${assetViewerColors.border}`,
|
|
41
58
|
borderRadius: 0,
|
|
42
59
|
};
|
|
60
|
+
const assetTileStyle = {
|
|
61
|
+
backgroundColor: assetViewerColors.previewBg,
|
|
62
|
+
color: assetViewerColors.text,
|
|
63
|
+
border: `1px solid ${assetViewerColors.borderFaint}`,
|
|
64
|
+
cursor: 'pointer',
|
|
65
|
+
display: 'flex',
|
|
66
|
+
flexDirection: 'column',
|
|
67
|
+
boxSizing: 'border-box',
|
|
68
|
+
};
|
|
43
69
|
function getItemsInPath(files, currentPath) {
|
|
44
70
|
// Remove the leading category folder (e.g., /textures/, /models/, /sounds/)
|
|
45
71
|
const filesWithoutCategory = files.map(file => {
|
|
@@ -64,17 +90,7 @@ function getItemsInPath(files, currentPath) {
|
|
|
64
90
|
return { folders: Array.from(folders), filesInCurrentPath };
|
|
65
91
|
}
|
|
66
92
|
function FolderTile({ name, onClick }) {
|
|
67
|
-
return (_jsxs("div", { onClick: onClick, style: {
|
|
68
|
-
maxWidth: 60,
|
|
69
|
-
aspectRatio: '1 / 1',
|
|
70
|
-
backgroundColor: assetViewerColors.controlBg,
|
|
71
|
-
color: assetViewerColors.text,
|
|
72
|
-
cursor: 'pointer',
|
|
73
|
-
display: 'flex',
|
|
74
|
-
flexDirection: 'column',
|
|
75
|
-
alignItems: 'center',
|
|
76
|
-
justifyContent: 'center'
|
|
77
|
-
}, children: [_jsx("div", { style: { fontSize: 24 }, children: "\uD83D\uDCC1" }), _jsx("div", { style: { fontSize: 10, textAlign: 'center', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', width: '100%', padding: '0 4px', marginTop: 4 }, children: name })] }));
|
|
93
|
+
return (_jsxs("div", { onClick: onClick, style: Object.assign(Object.assign({ maxWidth: 60, aspectRatio: '1 / 1' }, assetTileStyle), { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }), children: [_jsx("div", { style: { fontSize: 24 }, children: "\uD83D\uDCC1" }), _jsx("div", { style: { fontSize: 10, textAlign: 'center', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', width: '100%', padding: '0 4px', marginTop: 4 }, children: name })] }));
|
|
78
94
|
}
|
|
79
95
|
function useInView() {
|
|
80
96
|
const [isInView, setIsInView] = useState(false);
|
|
@@ -101,33 +117,19 @@ function AssetListViewer({ files, selected, onSelect, renderCard }) {
|
|
|
101
117
|
const pathParts = currentPath.split('/').filter(Boolean);
|
|
102
118
|
pathParts.pop();
|
|
103
119
|
setCurrentPath(pathParts.join('/'));
|
|
104
|
-
}, style: Object.assign(Object.assign({}, assetPickerSmallButtonStyle), { marginBottom: 4
|
|
120
|
+
}, style: Object.assign(Object.assign({}, assetPickerSmallButtonStyle), { marginBottom: 4 }), children: "\u2190 Back" })), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4 }, children: [folders.map((folder) => (_jsx(FolderTile, { name: folder, onClick: () => setCurrentPath(currentPath ? `${currentPath}/${folder}` : folder) }, folder))), filesInCurrentPath.map((file) => (_jsx("div", { children: renderCard(file, onSelect) }, file)))] })] }));
|
|
105
121
|
}
|
|
106
122
|
export function TextureListViewer({ files, selected, onSelect, basePath = "" }) {
|
|
107
123
|
return (_jsxs("div", { style: { position: 'relative', width: '100%', height: '100%' }, children: [_jsx("div", { style: { width: '100%', height: '100%', overflowY: 'auto', overflowX: 'hidden', paddingRight: 4 }, children: _jsx(AssetListViewer, { files: files, selected: selected, onSelect: onSelect, renderCard: (file, onSelectHandler) => (_jsx(TextureCard, { file: file, basePath: basePath, onSelect: onSelectHandler })) }) }), _jsx(SharedCanvas, {})] }));
|
|
108
124
|
}
|
|
109
125
|
function TextureCard({ file, onSelect, basePath = "" }) {
|
|
110
|
-
const [error, setError] = useState(false);
|
|
111
126
|
const [isHovered, setIsHovered] = useState(false);
|
|
112
127
|
const { ref, isInView } = useInView();
|
|
113
|
-
const fullPath = basePath
|
|
114
|
-
|
|
115
|
-
return (_jsx("div", { ref: ref, style: { aspectRatio: '1 / 1', backgroundColor: '#c30000', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }, onClick: () => onSelect(file), children: _jsx("div", { style: styles.errorIcon, children: "\u2717" }) }));
|
|
116
|
-
}
|
|
117
|
-
return (_jsxs("div", { ref: ref, style: { maxWidth: 60, aspectRatio: '1 / 1', backgroundColor: '#aeaeae', color: '#f9fafb', cursor: 'pointer', display: 'flex', flexDirection: 'column' }, onClick: () => onSelect(file), onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [_jsx("div", { style: { flex: 1, position: 'relative' }, children: isInView ? (_jsxs(View, { style: { width: '100%', height: '100%' }, children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 0, 2.5], fov: 50 }), _jsx("ambientLight", { intensity: 0.8 }), _jsx("pointLight", { position: [5, 5, 5], intensity: 0.5 }), _jsx(TextureSphere, { url: fullPath, onError: () => setError(true) }), _jsx(OrbitControls, { enableZoom: false, enablePan: false, autoRotate: isHovered, autoRotateSpeed: 2 })] })) : null }), _jsx("div", { style: styles.bottomLabel, children: file.split('/').pop() })] }));
|
|
128
|
+
const fullPath = resolvePrefabAssetPath(basePath, file);
|
|
129
|
+
return (_jsxs("div", { ref: ref, style: Object.assign({ maxWidth: 60, aspectRatio: '1 / 1' }, assetTileStyle), onClick: () => onSelect(file), onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [_jsx("div", { style: { flex: 1, position: 'relative' }, children: isInView ? (_jsxs(View, { style: { width: '100%', height: '100%' }, children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 0, 2.5], fov: 50 }), _jsx("ambientLight", { intensity: 0.8 }), _jsx("pointLight", { position: [5, 5, 5], intensity: 0.5 }), _jsx(TextureSphere, { url: fullPath }), _jsx(OrbitControls, { enableZoom: false, enablePan: false, autoRotate: isHovered, autoRotateSpeed: 2 })] })) : null }), _jsx("div", { style: styles.bottomLabel, children: file.split('/').pop() })] }));
|
|
118
130
|
}
|
|
119
|
-
function TextureSphere({ url
|
|
120
|
-
const
|
|
121
|
-
useEffect(() => {
|
|
122
|
-
setTexture(null);
|
|
123
|
-
const loader = new TextureLoader();
|
|
124
|
-
loader.load(url, (tex) => setTexture(tex), undefined, (err) => {
|
|
125
|
-
console.warn('Failed to load texture:', url, err);
|
|
126
|
-
onError === null || onError === void 0 ? void 0 : onError();
|
|
127
|
-
});
|
|
128
|
-
}, [url]);
|
|
129
|
-
if (!texture)
|
|
130
|
-
return null;
|
|
131
|
+
function TextureSphere({ url }) {
|
|
132
|
+
const texture = useLoader(TextureLoader, url);
|
|
131
133
|
return (_jsxs("mesh", { position: [0, 0, 0], children: [_jsx("sphereGeometry", { args: [1, 32, 32] }), _jsx("meshStandardMaterial", { map: texture })] }));
|
|
132
134
|
}
|
|
133
135
|
export function ModelListViewer({ files, selected, onSelect, basePath = "" }) {
|
|
@@ -136,31 +138,41 @@ export function ModelListViewer({ files, selected, onSelect, basePath = "" }) {
|
|
|
136
138
|
function ModelCard({ file, onSelect, basePath = "", size = 60, }) {
|
|
137
139
|
const [error, setError] = useState(false);
|
|
138
140
|
const { ref, isInView } = useInView();
|
|
139
|
-
const fullPath = basePath
|
|
141
|
+
const fullPath = resolvePrefabAssetPath(basePath, file);
|
|
140
142
|
if (error) {
|
|
141
|
-
return (_jsx("div", { ref: ref, style: { aspectRatio: '1 / 1', backgroundColor:
|
|
143
|
+
return (_jsx("div", { ref: ref, style: { aspectRatio: '1 / 1', backgroundColor: assetViewerColors.errorBg, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', border: `1px solid ${colors.dangerBorder}` }, onClick: () => onSelect(file), children: _jsx("div", { style: styles.errorIcon, children: "\u2717" }) }));
|
|
142
144
|
}
|
|
143
|
-
return (_jsxs("div", { ref: ref, style: { width: size, aspectRatio: '1 / 1'
|
|
145
|
+
return (_jsxs("div", { ref: ref, style: Object.assign({ width: size, aspectRatio: '1 / 1' }, assetTileStyle), onClick: () => onSelect(file), children: [_jsx("div", { style: styles.flexFillRelative, children: isInView ? (_jsxs(View, { style: { width: '100%', height: '100%' }, children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 1, 3], fov: 50 }), _jsxs(Suspense, { fallback: null, children: [_jsx("ambientLight", { intensity: 1 }), _jsx("pointLight", { position: [5, 5, 5], intensity: 0.5 }), _jsx(ModelPreview, { url: fullPath, onError: () => setError(true) }), _jsx(OrbitControls, { enableZoom: false })] })] })) : null }), _jsx("div", { style: styles.bottomLabel, children: file.split('/').pop() })] }));
|
|
144
146
|
}
|
|
145
147
|
function ModelPreview({ url, onError }) {
|
|
146
148
|
const [model, setModel] = useState(null);
|
|
149
|
+
const modelRef = useRef(null);
|
|
147
150
|
const onErrorRef = useRef(onError);
|
|
148
151
|
onErrorRef.current = onError;
|
|
149
152
|
useEffect(() => {
|
|
150
153
|
let cancelled = false;
|
|
154
|
+
modelRef.current && disposeObject3D(modelRef.current);
|
|
155
|
+
modelRef.current = null;
|
|
151
156
|
setModel(null);
|
|
152
157
|
loadModel(url).then((result) => {
|
|
153
158
|
var _a;
|
|
154
|
-
if (cancelled)
|
|
159
|
+
if (cancelled) {
|
|
160
|
+
result.model && disposeObject3D(result.model);
|
|
155
161
|
return;
|
|
162
|
+
}
|
|
156
163
|
if (result.success && result.model) {
|
|
164
|
+
modelRef.current = result.model;
|
|
157
165
|
setModel(result.model);
|
|
158
166
|
}
|
|
159
167
|
else {
|
|
160
168
|
(_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef);
|
|
161
169
|
}
|
|
162
170
|
});
|
|
163
|
-
return () => {
|
|
171
|
+
return () => {
|
|
172
|
+
cancelled = true;
|
|
173
|
+
modelRef.current && disposeObject3D(modelRef.current);
|
|
174
|
+
modelRef.current = null;
|
|
175
|
+
};
|
|
164
176
|
}, [url]);
|
|
165
177
|
if (!model)
|
|
166
178
|
return null;
|
|
@@ -171,8 +183,8 @@ export function SoundListViewer({ files, selected, onSelect, basePath = "" }) {
|
|
|
171
183
|
}
|
|
172
184
|
function SoundCard({ file, onSelect, basePath = "" }) {
|
|
173
185
|
const fileName = file.split('/').pop() || '';
|
|
174
|
-
const fullPath = basePath
|
|
175
|
-
return (_jsxs("div", { onClick: () => onSelect(file), style: { aspectRatio: '1 / 1'
|
|
186
|
+
const fullPath = resolvePrefabAssetPath(basePath, file);
|
|
187
|
+
return (_jsxs("div", { onClick: () => onSelect(file), style: Object.assign(Object.assign({ aspectRatio: '1 / 1' }, assetTileStyle), { alignItems: 'center', justifyContent: 'center' }), children: [_jsx("div", { style: styles.iconLarge, children: "\uD83D\uDD0A" }), _jsx("div", { style: { color: colors.text, fontSize: fonts.size, padding: '0 4px', marginTop: 4, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center', width: '100%' }, children: fileName })] }));
|
|
176
188
|
}
|
|
177
189
|
const PICKER_POPUP_WIDTH = 260;
|
|
178
190
|
const PICKER_POPUP_HEIGHT = 360;
|
|
@@ -225,7 +237,7 @@ export function TexturePicker({ value, onChange, basePath = "" }) {
|
|
|
225
237
|
return (_jsx(AssetPicker, { value: value, onChange: onChange, basePath: basePath, manifestFolder: "textures", rootStyle: { maxHeight: 128, overflow: 'visible', position: 'relative', display: 'flex', alignItems: 'center' }, changeButtonStyle: Object.assign(Object.assign({}, assetPickerSmallButtonStyle), { marginTop: 4 }), clearButtonStyle: Object.assign(Object.assign({}, assetPickerSmallButtonStyle), { marginTop: 4, marginLeft: 4 }), preview: _jsx(SingleTextureViewer, { file: value, basePath: basePath }), renderList: ({ files, value: selectedValue, onSelect, basePath: currentBasePath }) => (_jsx(TextureListViewer, { files: files, selected: selectedValue || undefined, onSelect: onSelect, basePath: currentBasePath })) }));
|
|
226
238
|
}
|
|
227
239
|
export function ModelPicker({ value, onChange, basePath = "", pickerKey }) {
|
|
228
|
-
return (_jsx(AssetPicker, { value: value, onChange: onChange, basePath: basePath, manifestFolder: "models", rootStyle: { maxHeight: 160, overflow: 'visible', position: 'relative', display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'center' }, controlsStyle: { display: 'flex', flexDirection: 'column', gap: 6, flex: '0 0 84px', minWidth: 84, justifyContent: 'flex-end' }, changeButtonStyle: Object.assign(Object.assign({}, assetPickerWideButtonStyle), { border: `1px solid ${assetViewerColors.accentBorder}` }), clearButtonStyle: Object.assign(Object.assign({}, assetPickerWideButtonStyle), { border: `1px solid ${assetViewerColors.accentBorder}` }), popupStyle: {
|
|
240
|
+
return (_jsx(AssetPicker, { value: value, onChange: onChange, basePath: basePath, manifestFolder: "models", rootStyle: { maxHeight: 160, overflow: 'visible', position: 'relative', display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'center' }, controlsStyle: { display: 'flex', flexDirection: 'column', gap: 6, flex: '0 0 84px', minWidth: 84, justifyContent: 'flex-end' }, changeButtonStyle: Object.assign(Object.assign({}, assetPickerWideButtonStyle), { border: `1px solid ${assetViewerColors.accentBorder}` }), clearButtonStyle: Object.assign(Object.assign({}, assetPickerWideButtonStyle), { border: `1px solid ${assetViewerColors.accentBorder}` }), popupStyle: { border: `1px solid ${assetViewerColors.accentBorder}` }, preview: _jsx("div", { style: { flex: '0 0 auto' }, children: _jsx(SingleModelViewer, { file: value, basePath: basePath }) }), renderList: ({ files, value: selectedValue, onSelect, basePath: currentBasePath }) => (_jsx(ModelListViewer, { files: files, selected: selectedValue ? `/${selectedValue}` : undefined, onSelect: (file) => onSelect(file.startsWith('/') ? file.slice(1) : file), basePath: currentBasePath }, pickerKey)) }));
|
|
229
241
|
}
|
|
230
242
|
export function SoundPicker({ value, onChange, basePath = "" }) {
|
|
231
243
|
return (_jsx(AssetPicker, { value: value, onChange: onChange, basePath: basePath, manifestFolder: "sound", rootStyle: { maxHeight: 76, overflow: 'visible', position: 'relative', display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'center' }, controlsStyle: { display: 'flex', flexDirection: 'column', gap: 6, flex: '0 0 84px', minWidth: 84, justifyContent: 'flex-end' }, changeButtonStyle: assetPickerWideButtonStyle, clearButtonStyle: assetPickerWideButtonStyle, preview: _jsx("div", { style: { flex: '0 0 auto', minWidth: 84 }, children: value ? _jsx(SingleSoundViewer, { file: value, basePath: basePath }) : _jsx("div", { style: Object.assign({ width: 84, height: 60 }, assetPickerEmptyPreviewStyle) }) }), renderList: ({ files, value: selectedValue, onSelect, basePath: currentBasePath }) => (_jsx(SoundListViewer, { files: files, selected: selectedValue || undefined, onSelect: onSelect, basePath: currentBasePath })) }));
|
|
@@ -17,6 +17,9 @@ export interface FilePickerProps extends AssetLoadOptions, DivProps {
|
|
|
17
17
|
children?: ReactNode;
|
|
18
18
|
multiple?: boolean;
|
|
19
19
|
}
|
|
20
|
+
export declare function loadDroppedAssets(dataTransfer: DataTransfer | null, options: AssetLoadOptions): Promise<void>;
|
|
21
|
+
export declare function loadUrls(urls: string[], options: AssetLoadOptions): Promise<void>;
|
|
22
|
+
export declare function loadUrl(url: string, options: AssetLoadOptions): Promise<void>;
|
|
20
23
|
export declare function loadFiles(files: File[], { onModelLoaded, onTextureLoaded, onSoundLoaded, onUnhandledFile, onFilesLoaded, onLoadError }: AssetLoadOptions): Promise<void>;
|
|
21
24
|
export declare function DragDropLoader({ children, ...divProps }: DragDropLoaderProps): import("react/jsx-runtime").JSX.Element;
|
|
22
25
|
export declare function FilePicker({ accept, children, multiple, ...divProps }: FilePickerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -25,56 +25,196 @@ const DEFAULT_ACCEPT = ".glb,.gltf,.fbx,.png,.jpg,.jpeg,.webp,.gif,.bmp,.svg,.mp
|
|
|
25
25
|
function getFiles(fileList) {
|
|
26
26
|
return fileList ? Array.from(fileList) : [];
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
return
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
function getStringFromItem(item) {
|
|
29
|
+
return new Promise(resolve => item.getAsString(value => resolve(value !== null && value !== void 0 ? value : "")));
|
|
30
|
+
}
|
|
31
|
+
function parseUriList(value) {
|
|
32
|
+
return value
|
|
33
|
+
.split(/\r?\n/)
|
|
34
|
+
.map(line => line.trim())
|
|
35
|
+
.filter(line => line && !line.startsWith("#"));
|
|
36
|
+
}
|
|
37
|
+
function parseDroppedUrls(value) {
|
|
38
|
+
const urls = new Set();
|
|
39
|
+
const addUrl = (candidate) => {
|
|
40
|
+
try {
|
|
41
|
+
const url = new URL(candidate.trim());
|
|
42
|
+
if (url.protocol === "http:" || url.protocol === "https:") {
|
|
43
|
+
urls.add(url.href);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (_a) {
|
|
47
|
+
// Ignore non-URL dropped text.
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
parseUriList(value).forEach(addUrl);
|
|
51
|
+
if (typeof DOMParser !== "undefined" && value.includes("<")) {
|
|
52
|
+
const document = new DOMParser().parseFromString(value, "text/html");
|
|
53
|
+
document.querySelectorAll("[href], [src]").forEach(element => {
|
|
54
|
+
var _a;
|
|
55
|
+
const rawUrl = (_a = element.getAttribute("href")) !== null && _a !== void 0 ? _a : element.getAttribute("src");
|
|
56
|
+
if (rawUrl)
|
|
57
|
+
addUrl(rawUrl);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return Array.from(urls);
|
|
61
|
+
}
|
|
62
|
+
function getDroppedUrls(dataTransfer) {
|
|
63
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
64
|
+
var _a;
|
|
65
|
+
if (!dataTransfer)
|
|
66
|
+
return [];
|
|
67
|
+
const urls = new Set();
|
|
68
|
+
const itemValues = yield Promise.all(Array.from((_a = dataTransfer.items) !== null && _a !== void 0 ? _a : [])
|
|
69
|
+
.filter(item => item.kind === "string")
|
|
70
|
+
.map(getStringFromItem));
|
|
71
|
+
itemValues.forEach(value => parseDroppedUrls(value).forEach(url => urls.add(url)));
|
|
72
|
+
const uriList = dataTransfer.getData("text/uri-list");
|
|
73
|
+
const plainText = dataTransfer.getData("text/plain");
|
|
74
|
+
const html = dataTransfer.getData("text/html");
|
|
75
|
+
[uriList, plainText, html].forEach(value => {
|
|
76
|
+
if (value)
|
|
77
|
+
parseDroppedUrls(value).forEach(url => urls.add(url));
|
|
78
|
+
});
|
|
79
|
+
return Array.from(urls);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function getExtensionFromMimeType(mimeType) {
|
|
83
|
+
var _a;
|
|
84
|
+
const type = mimeType.split(";", 1)[0].toLowerCase();
|
|
85
|
+
const extensions = {
|
|
86
|
+
"image/jpeg": ".jpg",
|
|
87
|
+
"image/png": ".png",
|
|
88
|
+
"image/webp": ".webp",
|
|
89
|
+
"image/gif": ".gif",
|
|
90
|
+
"image/bmp": ".bmp",
|
|
91
|
+
"image/svg+xml": ".svg",
|
|
92
|
+
"model/gltf-binary": ".glb",
|
|
93
|
+
"model/gltf+json": ".gltf",
|
|
94
|
+
"audio/mpeg": ".mp3",
|
|
95
|
+
"audio/wav": ".wav",
|
|
96
|
+
"audio/ogg": ".ogg",
|
|
97
|
+
"audio/mp4": ".m4a",
|
|
98
|
+
};
|
|
99
|
+
return (_a = extensions[type]) !== null && _a !== void 0 ? _a : "";
|
|
100
|
+
}
|
|
101
|
+
function getFilenameFromUrl(url, mimeType) {
|
|
102
|
+
const parsedUrl = new URL(url);
|
|
103
|
+
const rawName = parsedUrl.pathname.split("/").filter(Boolean).pop() || "asset";
|
|
104
|
+
const decodedName = decodeURIComponent(rawName).replace(/[^\w.\-]+/g, "-");
|
|
105
|
+
const hasExtension = /\.[a-z0-9]+$/i.test(decodedName);
|
|
106
|
+
return hasExtension ? decodedName : `${decodedName}${getExtensionFromMimeType(mimeType)}`;
|
|
107
|
+
}
|
|
108
|
+
function fetchUrlAsFile(url) {
|
|
109
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
110
|
+
const response = yield fetch(url);
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
113
|
+
}
|
|
114
|
+
const blob = yield response.blob();
|
|
115
|
+
return new File([blob], getFilenameFromUrl(url, blob.type), { type: blob.type });
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
export function loadDroppedAssets(dataTransfer, options) {
|
|
119
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
120
|
+
const localFiles = getFiles(dataTransfer === null || dataTransfer === void 0 ? void 0 : dataTransfer.files);
|
|
121
|
+
const remoteUrls = yield getDroppedUrls(dataTransfer);
|
|
122
|
+
const localNames = new Set(localFiles.map(file => file.name));
|
|
123
|
+
yield Promise.all(remoteUrls.map((url) => __awaiter(this, void 0, void 0, function* () {
|
|
124
|
+
var _a;
|
|
125
|
+
try {
|
|
126
|
+
const file = yield fetchUrlAsFile(url);
|
|
127
|
+
if (!localNames.has(file.name)) {
|
|
128
|
+
yield loadFile(file, options, url);
|
|
39
129
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
const fallbackName = getFilenameFromUrl(url, "");
|
|
133
|
+
const fallbackFile = new File([], fallbackName);
|
|
134
|
+
yield ((_a = options.onLoadError) === null || _a === void 0 ? void 0 : _a.call(options, error, url, fallbackFile));
|
|
135
|
+
if (!options.onLoadError) {
|
|
136
|
+
console.error("URL load error:", error);
|
|
43
137
|
}
|
|
44
|
-
|
|
138
|
+
}
|
|
139
|
+
})));
|
|
140
|
+
yield loadFiles(localFiles, options);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
export function loadUrls(urls, options) {
|
|
144
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
145
|
+
yield Promise.all(urls.map(url => loadUrl(url, options)));
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
export function loadUrl(url, options) {
|
|
149
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
150
|
+
var _a;
|
|
151
|
+
try {
|
|
152
|
+
const file = yield fetchUrlAsFile(url);
|
|
153
|
+
yield loadFile(file, options, url);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
const fallbackName = getFilenameFromUrl(url, "");
|
|
157
|
+
const fallbackFile = new File([], fallbackName);
|
|
158
|
+
yield ((_a = options.onLoadError) === null || _a === void 0 ? void 0 : _a.call(options, error, url, fallbackFile));
|
|
159
|
+
if (!options.onLoadError) {
|
|
160
|
+
console.error("URL load error:", error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
export function loadFiles(files_1, _a) {
|
|
166
|
+
return __awaiter(this, arguments, void 0, function* (files, { onModelLoaded, onTextureLoaded, onSoundLoaded, onUnhandledFile, onFilesLoaded, onLoadError }) {
|
|
167
|
+
yield Promise.all(files.map(file => loadFile(file, { onModelLoaded, onTextureLoaded, onSoundLoaded, onUnhandledFile, onLoadError })));
|
|
168
|
+
yield (onFilesLoaded === null || onFilesLoaded === void 0 ? void 0 : onFilesLoaded(files));
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function loadFile(file_1, _a) {
|
|
172
|
+
return __awaiter(this, arguments, void 0, function* (file, { onModelLoaded, onTextureLoaded, onSoundLoaded, onUnhandledFile, onLoadError }, assetRef = file.name) {
|
|
173
|
+
const shouldParseModel = canParseModelFile(file);
|
|
174
|
+
const shouldParseTexture = canParseTextureFile(file);
|
|
175
|
+
const shouldParseSound = canParseSoundFile(file);
|
|
176
|
+
if (shouldParseModel) {
|
|
177
|
+
const result = yield parseModelFromFile(file);
|
|
178
|
+
if (result.success && result.model) {
|
|
179
|
+
yield (onModelLoaded === null || onModelLoaded === void 0 ? void 0 : onModelLoaded(result.model, assetRef, file));
|
|
45
180
|
return;
|
|
46
181
|
}
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
if (result.success && result.texture) {
|
|
50
|
-
yield (onTextureLoaded === null || onTextureLoaded === void 0 ? void 0 : onTextureLoaded(result.texture, file.name, file));
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
if (onLoadError) {
|
|
54
|
-
yield onLoadError(result.error, file.name, file);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
console.error("Texture parse error:", result.error);
|
|
182
|
+
if (onLoadError) {
|
|
183
|
+
yield onLoadError(result.error, assetRef, file);
|
|
58
184
|
return;
|
|
59
185
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
yield onLoadError(result.error, file.name, file);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
console.error("Sound parse error:", result.error);
|
|
186
|
+
console.error("Model parse error:", result.error);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (shouldParseTexture) {
|
|
190
|
+
const result = yield parseTextureFromFile(file);
|
|
191
|
+
if (result.success && result.texture) {
|
|
192
|
+
yield (onTextureLoaded === null || onTextureLoaded === void 0 ? void 0 : onTextureLoaded(result.texture, assetRef, file));
|
|
71
193
|
return;
|
|
72
194
|
}
|
|
73
|
-
if (
|
|
74
|
-
yield
|
|
195
|
+
if (onLoadError) {
|
|
196
|
+
yield onLoadError(result.error, assetRef, file);
|
|
197
|
+
return;
|
|
75
198
|
}
|
|
76
|
-
|
|
77
|
-
|
|
199
|
+
console.error("Texture parse error:", result.error);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (shouldParseSound) {
|
|
203
|
+
const result = yield parseSoundFromFile(file);
|
|
204
|
+
if (result.success && result.sound) {
|
|
205
|
+
yield (onSoundLoaded === null || onSoundLoaded === void 0 ? void 0 : onSoundLoaded(result.sound, assetRef, file));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (onLoadError) {
|
|
209
|
+
yield onLoadError(result.error, assetRef, file);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
console.error("Sound parse error:", result.error);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (onUnhandledFile) {
|
|
216
|
+
yield onUnhandledFile(file);
|
|
217
|
+
}
|
|
78
218
|
});
|
|
79
219
|
}
|
|
80
220
|
function reportFileLoadError(error) {
|
|
@@ -94,16 +234,15 @@ export function DragDropLoader(_a) {
|
|
|
94
234
|
var { children } = _a, divProps = __rest(_a, ["children"]);
|
|
95
235
|
const loadOptions = createLoadHandlers(divProps);
|
|
96
236
|
function handleDrop(event) {
|
|
97
|
-
var _a;
|
|
98
237
|
event.preventDefault();
|
|
99
238
|
event.stopPropagation();
|
|
100
|
-
void
|
|
239
|
+
void loadDroppedAssets(event.dataTransfer, loadOptions).catch(reportFileLoadError);
|
|
101
240
|
}
|
|
102
241
|
function handleDragOver(event) {
|
|
103
242
|
event.preventDefault();
|
|
104
243
|
event.stopPropagation();
|
|
105
244
|
}
|
|
106
|
-
return (_jsx("div", Object.assign({}, divProps, { onDrop: handleDrop, onDragOver: handleDragOver, children: children })));
|
|
245
|
+
return (_jsx("div", Object.assign({ role: "application" }, divProps, { onDrop: handleDrop, onDragOver: handleDragOver, children: children })));
|
|
107
246
|
}
|
|
108
247
|
export function FilePicker(_a) {
|
|
109
248
|
var { accept = DEFAULT_ACCEPT, children, multiple = true } = _a, divProps = __rest(_a, ["accept", "children", "multiple"]);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { DragDropLoader, FilePicker, loadFiles } from "./DragDropLoader";
|
|
1
|
+
export { DragDropLoader, FilePicker, loadDroppedAssets, loadFiles, loadUrl, loadUrls } from "./DragDropLoader";
|
|
2
2
|
export type { AssetLoadOptions, DragDropLoaderProps, FilePickerProps } from "./DragDropLoader";
|
|
3
3
|
export { loadModel, loadSound, loadTexture, parseModelFromFile, parseSoundFromFile, parseTextureFromFile } from "./modelLoader";
|
|
4
4
|
export type { LoadedModel, LoadedSound, LoadedTexture, LoadedModels, LoadedSounds, LoadedTextures, ModelLoadResult, ProgressCallback, SoundLoadResult, TextureLoadResult } from "./modelLoader";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { DragDropLoader, FilePicker, loadFiles } from "./DragDropLoader";
|
|
1
|
+
export { DragDropLoader, FilePicker, loadDroppedAssets, loadFiles, loadUrl, loadUrls } from "./DragDropLoader";
|
|
2
2
|
export { loadModel, loadSound, loadTexture, parseModelFromFile, parseSoundFromFile, parseTextureFromFile } from "./modelLoader";
|
|
@@ -50,6 +50,8 @@ export function canParseModelFile(file) {
|
|
|
50
50
|
}
|
|
51
51
|
export function canParseTextureFile(file) {
|
|
52
52
|
const filename = typeof file === "string" ? file : file.name;
|
|
53
|
+
if (filename.startsWith("data:image/"))
|
|
54
|
+
return true;
|
|
53
55
|
const normalizedName = normalizeModelPath(filename);
|
|
54
56
|
return TEXTURE_FILE_EXTENSIONS.some(extension => normalizedName.endsWith(extension));
|
|
55
57
|
}
|
|
@@ -11,9 +11,8 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
13
13
|
import { useState } from 'react';
|
|
14
|
-
import { hasComponent } from "./types";
|
|
15
14
|
import EditorTree from './EditorTree';
|
|
16
|
-
import { getAllComponentDefs } from './components/ComponentRegistry';
|
|
15
|
+
import { canAddComponentToNode, getAllComponentDefs, getNextComponentKey } from './components/ComponentRegistry';
|
|
17
16
|
import { createComponentData } from './prefab';
|
|
18
17
|
import { useEditorRef } from './PrefabEditor';
|
|
19
18
|
import { base, colors, inspector, componentCard } from './styles';
|
|
@@ -34,32 +33,32 @@ function EditorUI({ selectedId, setSelectedId, getPrefab, onReplacePrefab, onImp
|
|
|
34
33
|
editor.remove(selectedId);
|
|
35
34
|
setSelectedId(null);
|
|
36
35
|
};
|
|
37
|
-
return _jsxs(_Fragment, { children: [_jsxs("div", { style: inspector.panel, children: [_jsxs("
|
|
36
|
+
return _jsxs(_Fragment, { children: [_jsxs("div", { style: inspector.panel, children: [_jsxs("button", { type: "button", 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, basePath: basePath }))] }), _jsx("div", { style: { position: 'absolute', top: 8, left: 8, zIndex: 20 }, children: _jsx(EditorTree, { selectedId: selectedId, setSelectedId: setSelectedId, getPrefab: getPrefab, onReplacePrefab: onReplacePrefab, onImportPrefab: onImportPrefab, onUndo: onUndo, onRedo: onRedo, canUndo: canUndo, canRedo: canRedo }) })] });
|
|
38
37
|
}
|
|
39
38
|
function NodeInspector({ node, updateNode, deleteNode, basePath }) {
|
|
40
39
|
var _a;
|
|
41
40
|
const ALL_COMPONENTS = getAllComponentDefs();
|
|
42
41
|
const allKeys = Object.keys(ALL_COMPONENTS);
|
|
43
|
-
const available = allKeys.filter(k =>
|
|
42
|
+
const available = allKeys.filter(k => canAddComponentToNode(node, ALL_COMPONENTS[k], ALL_COMPONENTS));
|
|
44
43
|
const [preferredAddType, setAddType] = useState(available[0] || "");
|
|
45
44
|
const addType = available.includes(preferredAddType) ? preferredAddType : (available[0] || "");
|
|
46
|
-
return _jsxs("div", { style: inspector.content, children: [_jsxs("div", { style: base.section, children: [_jsxs("div", { style: { display: "flex", marginBottom: 4, alignItems: 'center', gap: 4 }, children: [_jsx("div", { style: { fontSize: 10, color: colors.textDim, wordBreak: 'break-all', background: colors.bgLight, padding: '2px 4px', flex: 1, fontFamily: 'monospace', minHeight: 18, boxSizing: 'border-box' }, children: node.id }), _jsx("button", { style: Object.assign(Object.assign(Object.assign({}, base.btn), base.btnDanger), { minWidth: 22, padding: '2px 4px' }), title: "Delete Node", onClick: deleteNode, children: "\u2715" })] }), _jsx("input", { style: base.input, value: (_a = node.name) !== null && _a !== void 0 ? _a : "", placeholder: 'Node name', onChange: e => updateNode(n => (Object.assign(Object.assign({}, n), { name: e.target.value }))) })] }), _jsxs("div", { style: base.section, children: [_jsx("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }, children: _jsx("div", { style: base.label, children: "Components" }) }), node.components && Object.entries(node.components).map(([key, comp]) => {
|
|
45
|
+
return _jsxs("div", { style: inspector.content, children: [_jsxs("div", { style: base.section, children: [_jsxs("div", { style: { display: "flex", marginBottom: 4, alignItems: 'center', gap: 4 }, children: [_jsx("div", { style: { fontSize: 10, color: colors.textDim, wordBreak: 'break-all', background: colors.bgLight, padding: '2px 4px', flex: 1, fontFamily: 'monospace', minHeight: 18, boxSizing: 'border-box' }, children: node.id }), _jsx("button", { style: Object.assign(Object.assign(Object.assign({}, base.btn), base.btnDanger), { minWidth: 22, padding: '2px 4px' }), type: "button", title: "Delete Node", onClick: deleteNode, children: "\u2715" })] }), _jsx("input", { style: base.input, value: (_a = node.name) !== null && _a !== void 0 ? _a : "", placeholder: 'Node name', onChange: e => updateNode(n => (Object.assign(Object.assign({}, n), { name: e.target.value }))) })] }), _jsxs("div", { style: base.section, children: [_jsx("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }, children: _jsx("div", { style: base.label, children: "Components" }) }), node.components && Object.entries(node.components).map(([key, comp]) => {
|
|
47
46
|
if (!comp)
|
|
48
47
|
return null;
|
|
49
48
|
const def = ALL_COMPONENTS[comp.type];
|
|
50
49
|
if (!def)
|
|
51
50
|
return _jsxs("div", { style: { color: colors.danger, fontSize: 11 }, children: ["Unknown: ", comp.type] }, key);
|
|
52
|
-
return (_jsxs("div", { style: componentCard.container, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 3 }, children: [_jsx("div", { style: { fontSize: 11, fontWeight: 500 }, children: key }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 4px', minWidth: 20 }), title: "Remove Component", onClick: () => updateNode(n => {
|
|
51
|
+
return (_jsxs("div", { style: componentCard.container, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 3 }, children: [_jsx("div", { style: { fontSize: 11, fontWeight: 500 }, children: key }), _jsx("button", { type: "button", style: Object.assign(Object.assign({}, base.btn), { padding: '2px 4px', minWidth: 20 }), title: "Remove Component", onClick: () => updateNode(n => {
|
|
53
52
|
var _a;
|
|
54
53
|
const _b = (_a = n.components) !== null && _a !== void 0 ? _a : {}, _c = key, _ = _b[_c], rest = __rest(_b, [typeof _c === "symbol" ? _c : _c + ""]);
|
|
55
54
|
return Object.assign(Object.assign({}, n), { components: rest });
|
|
56
55
|
}), children: "\u2715" })] }), def.Editor && (_jsx(def.Editor, { component: comp, node: node, onUpdate: (newProps) => updateNode(n => (Object.assign(Object.assign({}, n), { components: Object.assign(Object.assign({}, n.components), { [key]: Object.assign(Object.assign({}, comp), { properties: Object.assign(Object.assign({}, comp.properties), newProps) }) }) }))), basePath: basePath }))] }, key));
|
|
57
|
-
})] }), available.length > 0 && (_jsx("div", { children: _jsxs("div", { style: base.row, children: [_jsx("select", { style: Object.assign(Object.assign({}, base.input), { flex: 1, background: colors.bgInput, border: `1px solid ${colors.border}`, minHeight: 22 }), value: addType, onChange: e => setAddType(e.target.value), children: available.map(k => _jsx("option", { value: k, children: k }, k)) }), _jsx("button", { style: base.btn, disabled: !addType, onClick: () => {
|
|
56
|
+
})] }), available.length > 0 && (_jsx("div", { children: _jsxs("div", { style: base.row, children: [_jsx("select", { style: Object.assign(Object.assign({}, base.input), { flex: 1, background: colors.bgInput, border: `1px solid ${colors.border}`, minHeight: 22 }), value: addType, onChange: e => setAddType(e.target.value), children: available.map(k => _jsx("option", { value: k, children: k }, k)) }), _jsx("button", { type: "button", style: base.btn, disabled: !addType, onClick: () => {
|
|
58
57
|
if (!addType)
|
|
59
58
|
return;
|
|
60
59
|
const def = ALL_COMPONENTS[addType];
|
|
61
60
|
if (def) {
|
|
62
|
-
updateNode(n => (Object.assign(Object.assign({}, n), { components: Object.assign(Object.assign({}, n.components), { [
|
|
61
|
+
updateNode(n => (Object.assign(Object.assign({}, n), { components: Object.assign(Object.assign({}, n.components), { [getNextComponentKey(n, def.name)]: createComponentData(def.name) }) })));
|
|
63
62
|
}
|
|
64
63
|
}, title: "Add Component", children: "+" })] }) }))] });
|
|
65
64
|
}
|
|
@@ -2,6 +2,9 @@ import GameCanvas from "../../shared/GameCanvas";
|
|
|
2
2
|
import type { Prefab } from "./types";
|
|
3
3
|
import { PrefabEditorMode, type Scene } from "./PrefabRoot";
|
|
4
4
|
import type { ExportGLBOptions } from "./utils";
|
|
5
|
+
export declare function isAbsoluteAssetPath(path: string): boolean;
|
|
6
|
+
export declare function resolvePrefabAssetPath(basePath: string, file: string): string;
|
|
7
|
+
export declare function getPrefabAssetRef(assetRef: string, folder: "models" | "textures" | "sound"): string;
|
|
5
8
|
export interface PrefabEditorRef extends Scene {
|
|
6
9
|
save: () => Prefab;
|
|
7
10
|
load: (prefab: Prefab, options?: {
|