Spaces:
Sleeping
Sleeping
File size: 3,191 Bytes
43024e4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | import React, { useRef, useState } from 'react';
import { useStudioStore, SceneObject } from '../store/useStudioStore';
import './LeftPanel.css';
const LeftPanel: React.FC = () => {
const { objects, selectedId, setSelectedId, addObject, removeObject } = useStudioStore();
const fileInputRef = useRef<HTMLInputElement>(null);
const [dragging, setDragging] = useState(false);
const handleFiles = (files: FileList | null) => {
if (!files) return;
Array.from(files).forEach((file) => {
if (!file.name.endsWith('.glb') && !file.name.endsWith('.gltf')) {
alert('Please upload .glb or .gltf files only');
return;
}
const url = URL.createObjectURL(file);
const newObj: SceneObject = {
id: Math.random().toString(36).slice(2),
name: file.name.replace(/\.(glb|gltf)$/, ''),
url,
file,
position: [0, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1],
color: '#ffffff',
metalness: 0.2,
roughness: 0.8,
envMapIntensity: 1,
animations: [],
selectedAnimIndex: 0,
animPlaying: false,
animSpeed: 1,
animLoop: true,
};
addObject(newObj);
});
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
setDragging(false);
handleFiles(e.dataTransfer.files);
};
return (
<div className="left-panel">
<div className="panel-header">
<span className="panel-icon">⬡</span>
<span>ASSETS</span>
</div>
<div
className={`drop-zone ${dragging ? 'drop-zone--active' : ''}`}
onDrop={handleDrop}
onDragOver={(e) => { e.preventDefault(); setDragging(true); }}
onDragLeave={() => setDragging(false)}
onClick={() => fileInputRef.current?.click()}
>
<div className="drop-zone-icon">↑</div>
<div className="drop-zone-text">Drop .GLB / .GLTF</div>
<div className="drop-zone-sub">or click to browse</div>
<input
ref={fileInputRef}
type="file"
accept=".glb,.gltf"
multiple
style={{ display: 'none' }}
onChange={(e) => handleFiles(e.target.files)}
/>
</div>
<div className="object-list-header">SCENE OBJECTS</div>
<div className="object-list">
{objects.length === 0 && (
<div className="empty-list">No models loaded</div>
)}
{objects.map((obj) => (
<div
key={obj.id}
className={`object-item ${selectedId === obj.id ? 'object-item--selected' : ''}`}
onClick={() => setSelectedId(obj.id)}
>
<span className="obj-icon">◈</span>
<span className="obj-name">{obj.name}</span>
{obj.animations.length > 0 && (
<span className="anim-badge">{obj.animations.length}A</span>
)}
<button
className="obj-delete"
onClick={(e) => { e.stopPropagation(); removeObject(obj.id); }}
>
✕
</button>
</div>
))}
</div>
</div>
);
};
export default LeftPanel;
|