import { useMemo, useState } from "react" import { Input } from "@/components/ui/input" import { ScrollArea } from "@/components/ui/scroll-area" import { cn } from "@/lib/utils" import { nodeLibrary } from "../../services/node-library" import type { NodeCategory, NodeLibraryItem } from "../../types/node-compositing" interface NodeLibraryPanelProps { onNodeAdd: (nodeType: string) => void className?: string } const categoryIcons: Record = { source: "📥", filter: "🎨", transform: "🔄", composite: "🔀", color: "🎨", time: "⏱️", mask: "🎭", utility: "🔧", output: "📤", } const categoryNames: Record = { source: "Sources", filter: "Filters", transform: "Transform", composite: "Composite", color: "Color", time: "Time", mask: "Masks", utility: "Utility", output: "Output", } export function NodeLibraryPanel({ onNodeAdd, className }: NodeLibraryPanelProps) { const [searchQuery, setSearchQuery] = useState("") const [selectedCategory, setSelectedCategory] = useState("all") const [expandedCategories, setExpandedCategories] = useState>( new Set(["source", "filter", "composite"]), ) // Group nodes by category const nodesByCategory = useMemo(() => { const groups: Record = { source: [], filter: [], transform: [], composite: [], color: [], time: [], mask: [], utility: [], output: [], } nodeLibrary.forEach((node) => { groups[node.category].push(node) }) return groups }, []) // Filter nodes based on search const filteredNodes = useMemo(() => { const query = searchQuery.toLowerCase() if (!query && selectedCategory === "all") { return nodesByCategory } const filtered: Record = { source: [], filter: [], transform: [], composite: [], color: [], time: [], mask: [], utility: [], output: [], } Object.entries(nodesByCategory).forEach(([category, nodes]) => { if (selectedCategory !== "all" && category !== selectedCategory) { return } filtered[category as NodeCategory] = nodes.filter( (node) => !query || node.name.toLowerCase().includes(query) || node.description.toLowerCase().includes(query) || node.tags?.some((tag) => tag.toLowerCase().includes(query)), ) }) return filtered }, [searchQuery, selectedCategory, nodesByCategory]) // Count total filtered nodes const totalFilteredNodes = useMemo(() => { return Object.values(filteredNodes).reduce((sum, nodes) => sum + nodes.length, 0) }, [filteredNodes]) const handleCategoryToggle = (category: NodeCategory) => { setExpandedCategories((prev) => { const next = new Set(prev) if (next.has(category)) { next.delete(category) } else { next.add(category) } return next }) } const handleNodeClick = (nodeType: string) => { onNodeAdd(nodeType) } return (
{/* Header */}

Node Library

{/* Search */} setSearchQuery(e.target.value)} className="mb-3 bg-gray-800 border-gray-700 text-white placeholder-gray-500" /> {/* Category filter */}
{Object.entries(categoryNames).map(([key, name]) => { const category = key as NodeCategory const count = filteredNodes[category]?.length || 0 return ( ) })}
{/* Node list */}
{Object.entries(filteredNodes).map(([category, nodes]) => { if (nodes.length === 0) return null const isExpanded = expandedCategories.has(category as NodeCategory) return (
{isExpanded && (
{nodes.map((node) => ( ))}
)}
) })} {totalFilteredNodes === 0 && (
No nodes found {searchQuery && ( )}
)}
{/* Footer stats */}
Total nodes: {nodeLibrary.length}
Categories: {Object.keys(categoryNames).length}
) }