studio3d / src /components /LeftPanel.tsx
Studio3D Deploy
๐Ÿš€ Initial Studio3D deployment โ€” React Three Fiber 3D Animation Studio
43024e4
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;