import { useMemo, useState } from "react" import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Slider } from "@/components/ui/slider" import { Switch } from "@/components/ui/switch" import { cn } from "@/lib/utils" import type { ShaderUniform } from "../../types/shader-system" interface UniformsPanelProps { uniforms: ShaderUniform[] onChange: (name: string, value: any) => void className?: string } export function UniformsPanel({ uniforms, onChange, className }: UniformsPanelProps) { const [searchQuery, setSearchQuery] = useState("") // Group uniforms by category const groupedUniforms = useMemo(() => { const groups: Record = { General: [], } uniforms.forEach((uniform) => { const group = uniform.group || "General" if (!groups[group]) { groups[group] = [] } groups[group].push(uniform) }) // Filter by search if (searchQuery) { const filtered: Record = {} const query = searchQuery.toLowerCase() Object.entries(groups).forEach(([groupName, groupUniforms]) => { const matchingUniforms = groupUniforms.filter( (u) => u.name.toLowerCase().includes(query) || u.description?.toLowerCase().includes(query), ) if (matchingUniforms.length > 0) { filtered[groupName] = matchingUniforms } }) return filtered } return groups }, [uniforms, searchQuery]) const renderUniformControl = (uniform: ShaderUniform) => { switch (uniform.type) { case "float": return (
onChange(uniform.name, Number.parseFloat(e.target.value))} className="w-20 h-8 text-xs" step={uniform.step || 0.01} />
{uniform.min !== undefined && uniform.max !== undefined && ( onChange(uniform.name, value)} min={uniform.min} max={uniform.max} step={uniform.step || 0.01} className="w-full" /> )} {uniform.description &&

{uniform.description}

}
) case "int": return (
onChange(uniform.name, Number.parseInt(e.target.value))} className="w-20 h-8 text-xs" step={1} />
{uniform.min !== undefined && uniform.max !== undefined && ( onChange(uniform.name, Math.round(value))} min={uniform.min} max={uniform.max} step={1} className="w-full" /> )} {uniform.description &&

{uniform.description}

}
) case "bool": return (
onChange(uniform.name, checked)} />
{uniform.description &&

{uniform.description}

}
) case "vec2": return (
onChange(uniform.name, [Number.parseFloat(e.target.value), uniform.value[1]])} className="h-8 text-xs" step={uniform.step || 0.01} />
onChange(uniform.name, [uniform.value[0], Number.parseFloat(e.target.value)])} className="h-8 text-xs" step={uniform.step || 0.01} />
{uniform.description &&

{uniform.description}

}
) case "vec3": const isColor = uniform.name.toLowerCase().includes("color") || uniform.name.toLowerCase().includes("tint") if (isColor) { // Convert vec3 to hex color const toHex = (val: number) => Math.round(val * 255) .toString(16) .padStart(2, "0") const hexColor = `#${toHex(uniform.value[0])}${toHex(uniform.value[1])}${toHex(uniform.value[2])}` return (
{ const hex = e.target.value const r = Number.parseInt(hex.substring(1, 3), 16) / 255 const g = Number.parseInt(hex.substring(3, 5), 16) / 255 const b = Number.parseInt(hex.substring(5, 7), 16) / 255 onChange(uniform.name, [r, g, b]) }} className="w-8 h-8 rounded cursor-pointer" /> {hexColor}
{uniform.description &&

{uniform.description}

}
) } return (
onChange(uniform.name, [Number.parseFloat(e.target.value), uniform.value[1], uniform.value[2]]) } className="h-8 text-xs" step={uniform.step || 0.01} />
onChange(uniform.name, [uniform.value[0], Number.parseFloat(e.target.value), uniform.value[2]]) } className="h-8 text-xs" step={uniform.step || 0.01} />
onChange(uniform.name, [uniform.value[0], uniform.value[1], Number.parseFloat(e.target.value)]) } className="h-8 text-xs" step={uniform.step || 0.01} />
{uniform.description &&

{uniform.description}

}
) case "vec4": const isColorWithAlpha = uniform.name.toLowerCase().includes("color") || uniform.name.toLowerCase().includes("tint") if (isColorWithAlpha) { const toHex = (val: number) => Math.round(val * 255) .toString(16) .padStart(2, "0") const hexColor = `#${toHex(uniform.value[0])}${toHex(uniform.value[1])}${toHex(uniform.value[2])}` return (
{ const hex = e.target.value const r = Number.parseInt(hex.substring(1, 3), 16) / 255 const g = Number.parseInt(hex.substring(3, 5), 16) / 255 const b = Number.parseInt(hex.substring(5, 7), 16) / 255 onChange(uniform.name, [r, g, b, uniform.value[3]]) }} className="w-8 h-8 rounded cursor-pointer" />
onChange(uniform.name, [...uniform.value.slice(0, 3), value])} min={0} max={1} step={0.01} className="w-full" />
{uniform.description &&

{uniform.description}

}
) } return (
{ const newValue = [...uniform.value] newValue[0] = Number.parseFloat(e.target.value) onChange(uniform.name, newValue) }} className="h-8 text-xs" step={uniform.step || 0.01} />
{ const newValue = [...uniform.value] newValue[1] = Number.parseFloat(e.target.value) onChange(uniform.name, newValue) }} className="h-8 text-xs" step={uniform.step || 0.01} />
{ const newValue = [...uniform.value] newValue[2] = Number.parseFloat(e.target.value) onChange(uniform.name, newValue) }} className="h-8 text-xs" step={uniform.step || 0.01} />
{ const newValue = [...uniform.value] newValue[3] = Number.parseFloat(e.target.value) onChange(uniform.name, newValue) }} className="h-8 text-xs" step={uniform.step || 0.01} />
{uniform.description &&

{uniform.description}

}
) default: return
Unsupported uniform type: {uniform.type}
} } return (
{/* Header */}

Shader Uniforms

setSearchQuery(e.target.value)} className="bg-gray-800 border-gray-700 text-white placeholder-gray-500" />
{/* Uniforms list */}
{Object.keys(groupedUniforms).length === 0 ? (
No uniforms found
) : Object.keys(groupedUniforms).length === 1 && groupedUniforms.General ? ( // Single group - no accordion
{groupedUniforms.General.map((uniform) => (
{renderUniformControl(uniform)}
))}
) : ( // Multiple groups - use accordion {Object.entries(groupedUniforms).map(([groupName, groupUniforms]) => ( {groupName} ({groupUniforms.length})
{groupUniforms.map((uniform) => (
{renderUniformControl(uniform)}
))}
))}
)}
{/* Footer */}
{uniforms.length} uniforms
{uniforms.filter((u) => u.animatable).length} animatable
) }