({ allExtensions, open }: { allExtensions: WorkflowExtension[]; open: boolean })
| 184 | } |
| 185 | |
| 186 | function ExtensionsPanel({ allExtensions, open }: { allExtensions: WorkflowExtension[]; open: boolean }) { |
| 187 | const [search, setSearch] = useState('') |
| 188 | const [collapsed, setCollapsed] = useState<Record<string, boolean>>({}) |
| 189 | const [width, setWidth] = useState(288) |
| 190 | const dragging = useRef(false) |
| 191 | const startX = useRef(0) |
| 192 | const startW = useRef(0) |
| 193 | |
| 194 | useEffect(() => { |
| 195 | const onMove = (e: MouseEvent) => { |
| 196 | if (!dragging.current) return |
| 197 | const delta = startX.current - e.clientX |
| 198 | setWidth((w) => Math.min(PANEL_MAX, Math.max(PANEL_MIN, startW.current + delta))) |
| 199 | } |
| 200 | const onUp = () => { dragging.current = false; document.body.style.cursor = '' } |
| 201 | document.addEventListener('mousemove', onMove) |
| 202 | document.addEventListener('mouseup', onUp) |
| 203 | return () => { |
| 204 | document.removeEventListener('mousemove', onMove) |
| 205 | document.removeEventListener('mouseup', onUp) |
| 206 | } |
| 207 | }, []) |
| 208 | |
| 209 | const cols = width >= 580 ? 3 : width >= 370 ? 2 : 1 |
| 210 | const gridClass = cols === 3 ? 'grid-cols-3' : cols === 2 ? 'grid-cols-2' : 'grid-cols-1' |
| 211 | const query = search.trim().toLowerCase() |
| 212 | |
| 213 | const toggleGroup = (id: string) => setCollapsed((c) => ({ ...c, [id]: !c[id] })) |
| 214 | const isExpanded = (id: string, hasMatches: boolean) => (query && hasMatches) || !collapsed[id] |
| 215 | |
| 216 | // Base group |
| 217 | const filteredBuiltinNodes = PANEL_BUILTIN_NODES.filter((n) => !query || n.label.toLowerCase().includes(query)) |
| 218 | const filteredBuiltinExts = allExtensions.filter((e) => e.builtin && (!query || e.name.toLowerCase().includes(query))) |
| 219 | const baseCount = filteredBuiltinNodes.length + filteredBuiltinExts.length |
| 220 | const baseVisible = !query || baseCount > 0 |
| 221 | |
| 222 | // Non-builtin groups: grouped by extensionId |
| 223 | const nonBuiltinMap = useMemo(() => { |
| 224 | const map = new Map<string, { extensionName: string; nodes: WorkflowExtension[] }>() |
| 225 | for (const ext of allExtensions) { |
| 226 | if (ext.builtin) continue |
| 227 | if (!map.has(ext.extensionId)) map.set(ext.extensionId, { extensionName: ext.extensionName, nodes: [] }) |
| 228 | map.get(ext.extensionId)!.nodes.push(ext) |
| 229 | } |
| 230 | return map |
| 231 | }, [allExtensions]) |
| 232 | |
| 233 | return ( |
| 234 | <div |
| 235 | style={{ width: open ? width : 0 }} |
| 236 | className="flex overflow-hidden border-l border-zinc-800 transition-[width] duration-300 ease-in-out shrink-0" |
| 237 | > |
| 238 | <div className="flex shrink-0" style={{ width }}> |
| 239 | |
| 240 | {/* Resize handle */} |
| 241 | <div |
| 242 | onMouseDown={(e) => { |
| 243 | dragging.current = true; startX.current = e.clientX; startW.current = width |
nothing calls this directly
no test coverage detected