import React, { useState, useEffect, useCallback, useRef } from 'react'; import { searchLibraries, installLibrary, getInstalledLibraries } from '../../services/libraryService'; import type { ArduinoLibrary, InstalledLibrary } from '../../services/libraryService'; import './LibraryManagerModal.css'; interface LibraryManagerModalProps { isOpen: boolean; onClose: () => void; } type Tab = 'search' | 'installed'; export const LibraryManagerModal: React.FC = ({ isOpen, onClose }) => { const [activeTab, setActiveTab] = useState('search'); const [searchQuery, setSearchQuery] = useState(''); const [searchResults, setSearchResults] = useState([]); const [installedLibraries, setInstalledLibraries] = useState([]); const [loadingSearch, setLoadingSearch] = useState(false); const [loadingInstalled, setLoadingInstalled] = useState(false); const [installingLib, setInstallingLib] = useState(null); const [statusMsg, setStatusMsg] = useState<{ type: 'success' | 'error'; text: string } | null>(null); const debounceRef = useRef | null>(null); const fetchInstalled = useCallback(async () => { setLoadingInstalled(true); try { const libs = await getInstalledLibraries(); setInstalledLibraries(libs); } catch (e: unknown) { setStatusMsg({ type: 'error', text: e instanceof Error ? e.message : 'Failed to load installed libraries' }); } finally { setLoadingInstalled(false); } }, []); // Fetch installed list when modal opens (to cross-reference in search tab) useEffect(() => { if (isOpen) { fetchInstalled(); } }, [isOpen, fetchInstalled]); // Also refresh when switching to the installed tab useEffect(() => { if (isOpen && activeTab === 'installed') { fetchInstalled(); } }, [isOpen, activeTab, fetchInstalled]); useEffect(() => { if (!searchQuery.trim()) { setSearchResults([]); return; } if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(async () => { setLoadingSearch(true); setStatusMsg(null); try { const results = await searchLibraries(searchQuery); setSearchResults(results); } catch (e: unknown) { setStatusMsg({ type: 'error', text: e instanceof Error ? e.message : 'Search failed' }); setSearchResults([]); } finally { setLoadingSearch(false); } }, 500); return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; }, [searchQuery]); const handleInstall = async (libName: string) => { setInstallingLib(libName); setStatusMsg(null); try { const result = await installLibrary(libName); if (result.success) { setStatusMsg({ type: 'success', text: `"${libName}" installed successfully!` }); fetchInstalled(); // Refresh installed list so search tab reflects new state } else { setStatusMsg({ type: 'error', text: result.error || `Failed to install "${libName}"` }); } } catch (e: unknown) { setStatusMsg({ type: 'error', text: e instanceof Error ? e.message : 'Installation failed' }); } finally { setInstallingLib(null); } }; const handleClose = () => { setStatusMsg(null); setSearchQuery(''); setSearchResults([]); onClose(); }; if (!isOpen) return null; const isInstalled = (libName: string): boolean => installedLibraries.some( (il) => (il.library?.name || il.name || '').toLowerCase() === libName.toLowerCase() ); const getLibName = (lib: ArduinoLibrary): string => lib.name || 'Unknown'; const getLibVersion = (lib: ArduinoLibrary): string => lib.latest?.version || lib.version || ''; const getLibAuthor = (lib: ArduinoLibrary): string => lib.latest?.author || lib.author || ''; const getLibDesc = (lib: ArduinoLibrary): string => lib.latest?.sentence || lib.sentence || ''; const getInstalledName = (lib: InstalledLibrary): string => lib.library?.name || lib.name || 'Unknown'; const getInstalledVersion = (lib: InstalledLibrary): string => lib.library?.version || lib.version || ''; const getInstalledAuthor = (lib: InstalledLibrary): string => lib.library?.author || lib.author || ''; const getInstalledDesc = (lib: InstalledLibrary): string => lib.library?.sentence || lib.sentence || ''; return (
e.stopPropagation()}> {/* Header */}
LIBRARY MANAGER
{/* Tabs */}
{/* Status bar */} {statusMsg && (
{statusMsg.type === 'success' ? ( ) : ( )} {statusMsg.text}
)} {/* Search Tab */} {activeTab === 'search' && (
setSearchQuery(e.target.value)} autoFocus /> {loadingSearch && ( )}
{!searchQuery.trim() && (

Type a library name to search

e.g. "ArduinoJson", "Servo", "LiquidCrystal"

)} {searchQuery.trim() && searchResults.length === 0 && !loadingSearch && (

No libraries found for "{searchQuery}"

)} {searchResults.map((lib, i) => (
{getLibName(lib)} {getLibAuthor(lib) && ( by {getLibAuthor(lib)} )}
{getLibDesc(lib) && (

{getLibDesc(lib)}

)}
{getLibVersion(lib) && ( {getLibVersion(lib)} )} {isInstalled(getLibName(lib)) ? ( INSTALLED ) : ( )}
))}
)} {/* Installed Tab */} {activeTab === 'installed' && (
{loadingInstalled && (

Loading installed libraries...

)} {!loadingInstalled && installedLibraries.length === 0 && (

No libraries installed yet.

Use the Search tab to install libraries.

)} {installedLibraries.map((lib, i) => (
{getInstalledName(lib)} {getInstalledAuthor(lib) && ( by {getInstalledAuthor(lib)} )}
{getInstalledDesc(lib) && (

{getInstalledDesc(lib)}

)}
{getInstalledVersion(lib) && ( {getInstalledVersion(lib)} )}
))}
)}
); };