"use client"; import { useState } from "react"; import { ChevronDown, ChevronRight, Eye, EyeOff, Trash2 } from "lucide-react"; import { useEditorStore } from "@/lib/store"; import { useIsMobile } from "@/lib/use-mobile"; import type { SceneObject, Physics, LightType } from "@/lib/types"; function Section({ label, defaultOpen = true, children, }: { label: string; defaultOpen?: boolean; children: React.ReactNode; }) { const [open, setOpen] = useState(defaultOpen); return (
{open && (
{children}
)}
); } function Field({ label, children, }: { label: string; children: React.ReactNode; }) { return (
{children}
); } function NumberInput({ value, onChange, step = 0.1, }: { value: number; onChange: (v: number) => void; step?: number; }) { return ( onChange(parseFloat(e.target.value) || 0)} step={step} className="w-full bg-[#1a1a1a] border border-[#2a2a2a] rounded px-1.5 py-1 sm:py-0.5 text-xs sm:text-[11px] text-[#ccc] outline-none focus:border-[#555]" /> ); } function Vec3Input({ value, onChange, labels = ["X", "Y", "Z"], }: { value: [number, number, number]; onChange: (v: [number, number, number]) => void; labels?: [string, string, string]; }) { return (
{[0, 1, 2].map((i) => (
{labels[i]}
{ const next = [...value] as [number, number, number]; next[i] = v; onChange(next); }} />
))}
); } function ColorInput({ value, onChange, }: { value: string; onChange: (v: string) => void; }) { return (
onChange(e.target.value)} className="w-8 h-8 sm:w-6 sm:h-6 rounded border border-[#2a2a2a] cursor-pointer bg-transparent" /> onChange(e.target.value)} className="flex-1 bg-[#1a1a1a] border border-[#2a2a2a] rounded px-1.5 py-1 sm:py-0.5 text-xs sm:text-[11px] text-[#ccc] outline-none focus:border-[#555]" />
); } function SelectInput({ value, onChange, options, }: { value: string; onChange: (v: string) => void; options: { value: string; label: string }[]; }) { return ( ); } function CheckboxInput({ value, onChange, label, }: { value: boolean; onChange: (v: boolean) => void; label: string; }) { return ( ); } function ObjectListItem({ obj }: { obj: SceneObject }) { const { selectedObjectId, selectObject, removeObject, toggleObjectVisibility, } = useEditorStore(); const isSelected = selectedObjectId === obj.id; const isMobile = useIsMobile(); return (
selectObject(obj.id)} > {obj.name}
); } function TransformEditor({ obj }: { obj: SceneObject }) { const updateObjectTransform = useEditorStore((s) => s.updateObjectTransform); const saveToHistory = useEditorStore((s) => s.saveToHistory); return (
{ updateObjectTransform(obj.id, { position: v }); saveToHistory(); }} /> { updateObjectTransform(obj.id, { rotation: v }); saveToHistory(); }} /> { updateObjectTransform(obj.id, { scale: v }); saveToHistory(); }} />
); } function MaterialEditor({ obj }: { obj: SceneObject }) { const updateObjectMaterial = useEditorStore((s) => s.updateObjectMaterial); return (
updateObjectMaterial(obj.id, { color: v })} /> updateObjectMaterial(obj.id, { metalness: v })} step={0.05} /> updateObjectMaterial(obj.id, { roughness: v })} step={0.05} /> updateObjectMaterial(obj.id, { emissive: v })} /> updateObjectMaterial(obj.id, { emissiveIntensity: v }) } step={0.1} />
); } function PhysicsEditor({ obj }: { obj: SceneObject }) { const updateObjectPhysics = useEditorStore((s) => s.updateObjectPhysics); return (
updateObjectPhysics(obj.id, { colliderType: v as Physics["colliderType"], }) } options={[ { value: "none", label: "None" }, { value: "cuboid", label: "Cuboid" }, { value: "ball", label: "Ball" }, { value: "capsule", label: "Capsule" }, ]} /> {obj.physics.colliderType !== "none" && ( <> updateObjectPhysics(obj.id, { mass: v })} /> updateObjectPhysics(obj.id, { isStatic: v })} label="Static" /> updateObjectPhysics(obj.id, { restitution: v })} step={0.05} /> updateObjectPhysics(obj.id, { friction: v })} step={0.05} /> )}
); } function DamageEditor({ obj }: { obj: SceneObject }) { const updateDamage = useEditorStore((s) => s.updateDamage); const dmg = obj.damage || { amount: 0, enabled: false }; return (
updateDamage(obj.id, { enabled: v })} label="Enabled" /> {dmg.enabled && ( updateDamage(obj.id, { amount: v })} step={1} /> )}
); } function LightEditor({ obj }: { obj: SceneObject }) { const updateObjectGeneral = useEditorStore((s) => s.updateObjectGeneral); return (
updateObjectGeneral(obj.id, { lightType: v as LightType }) } options={[ { value: "ambient", label: "Ambient" }, { value: "directional", label: "Directional" }, { value: "point", label: "Point" }, { value: "spot", label: "Spot" }, ]} /> updateObjectGeneral(obj.id, { intensity: v })} step={0.1} /> {(obj.lightType === "point" || obj.lightType === "spot") && ( updateObjectGeneral(obj.id, { distance: v })} /> )} {obj.lightType === "spot" && ( <> updateObjectGeneral(obj.id, { angle: v })} step={0.05} /> updateObjectGeneral(obj.id, { penumbra: v })} step={0.05} /> )}
); } function TextInput({ value, onChange, placeholder, }: { value: string; onChange: (v: string) => void; placeholder?: string; }) { return ( onChange(e.target.value)} placeholder={placeholder} className="w-full bg-[#1a1a1a] border border-[#2a2a2a] rounded px-1.5 py-1 sm:py-0.5 text-xs sm:text-[11px] text-[#ccc] outline-none focus:border-[#555] placeholder:text-[#444]" /> ); } function ModelUrlEditor({ obj }: { obj: SceneObject }) { const updateObjectGeneral = useEditorStore((s) => s.updateObjectGeneral); return (
updateObjectGeneral(obj.id, { modelUrl: v })} placeholder="/models/example.glb" />

Enter a URL to a .glb or .gltf file. Place files in public/models/ to use local paths like /models/file.glb

); } function CharacterEditor({ obj }: { obj: SceneObject }) { const updateCharacter = useEditorStore((s) => s.updateCharacter); const role = obj.character?.role || ""; return (
updateCharacter(obj.id, { role: v })} placeholder="e.g. village guard, merchant" />
); } function EnvironmentEditor() { const scenes = useEditorStore((s) => s.scenes); const activeSceneId = useEditorStore((s) => s.activeSceneId); const updateEnvironmentSettings = useEditorStore( (s) => s.updateEnvironmentSettings, ); const activeScene = scenes.find((s) => s.id === activeSceneId); if (!activeScene) return null; const env = activeScene.environmentSettings; return (
updateEnvironmentSettings({ preset: v })} options={[ { value: "apartment", label: "Apartment" }, { value: "city", label: "City" }, { value: "dawn", label: "Dawn" }, { value: "forest", label: "Forest" }, { value: "lobby", label: "Lobby" }, { value: "night", label: "Night" }, { value: "park", label: "Park" }, { value: "studio", label: "Studio" }, { value: "sunset", label: "Sunset" }, { value: "warehouse", label: "Warehouse" }, ]} /> updateEnvironmentSettings({ intensity: v })} step={0.1} /> updateEnvironmentSettings({ blur: v })} step={0.1} />
); } function FogEditor() { const scenes = useEditorStore((s) => s.scenes); const activeSceneId = useEditorStore((s) => s.activeSceneId); const updateSceneSettings = useEditorStore((s) => s.updateSceneSettings); const activeScene = scenes.find((s) => s.id === activeSceneId); if (!activeScene) return null; const fog = activeScene.sceneSettings.fog; return (
updateSceneSettings({ fog: { ...fog, enabled: v } })} label="Enabled" /> {fog.enabled && ( <> updateSceneSettings({ fog: { ...fog, color: v } }) } /> updateSceneSettings({ fog: { ...fog, near: v } }) } /> updateSceneSettings({ fog: { ...fog, far: v } })} /> )}
); } function GridEditor() { const scenes = useEditorStore((s) => s.scenes); const activeSceneId = useEditorStore((s) => s.activeSceneId); const updateSceneSettings = useEditorStore((s) => s.updateSceneSettings); const activeScene = scenes.find((s) => s.id === activeSceneId); if (!activeScene) return null; const grid = activeScene.sceneSettings.grid; return (
updateSceneSettings({ grid: { ...grid, visible: v } })} label="Visible" /> {grid.visible && ( <> updateSceneSettings({ grid: { ...grid, size: v } }) } step={0.5} /> updateSceneSettings({ grid: { ...grid, divisions: Math.max(1, Math.round(v)) }, }) } step={1} /> updateSceneSettings({ grid: { ...grid, fadeDistance: v } }) } step={10} /> updateSceneSettings({ grid: { ...grid, fadeStrength: v } }) } step={0.1} /> )}
); } export function SceneInspector() { const scenes = useEditorStore((s) => s.scenes); const activeSceneId = useEditorStore((s) => s.activeSceneId); const selectedObjectId = useEditorStore((s) => s.selectedObjectId); const activeScene = scenes.find((s) => s.id === activeSceneId); const objects = activeScene?.objects ?? []; const selectedObj = selectedObjectId ? objects.find((o) => o.id === selectedObjectId) : null; return (
{/* Object list */}
{objects.map((obj) => ( ))}
{/* Selected object editors */} {selectedObj && ( <>
{selectedObj.name}
{selectedObj.type !== "light" && selectedObj.type !== "player" && selectedObj.type !== "sound" && ( )} {selectedObj.type !== "light" && selectedObj.type !== "player" && ( )} {selectedObj.type !== "light" && selectedObj.type !== "player" && selectedObj.type !== "sound" && } {selectedObj.type === "light" && } {(selectedObj.type === "model" || selectedObj.type === "character") && ( )} {selectedObj.type === "character" && ( )} )} {/* Scene-level editors */}
); }