import React from 'react'; import { useProjectsStore } from '@/stores/useProjectsStore'; import { useUIStore } from '@/stores/useUIStore'; import { Button } from '@/components/ui/button'; import { SettingsSidebarLayout } from '@/components/sections/shared/SettingsSidebarLayout'; import { SettingsSidebarItem } from '@/components/sections/shared/SettingsSidebarItem'; import { PROJECT_COLOR_MAP, PROJECT_ICON_MAP, getProjectIconImageUrl } from '@/lib/projectMeta'; import { cn } from '@/lib/utils'; import { RiAddLine, RiFolderLine } from '@remixicon/react'; import { isDesktopLocalOriginActive, isTauriShell, isVSCodeRuntime, requestDirectoryAccess } from '@/lib/desktop'; import { sessionEvents } from '@/lib/sessionEvents'; import { toast } from '@/components/ui'; export const ProjectsSidebar: React.FC<{ onItemSelect?: () => void }> = ({ onItemSelect }) => { const projects = useProjectsStore((state) => state.projects); const addProject = useProjectsStore((state) => state.addProject); const selectedId = useUIStore((state) => state.settingsProjectsSelectedId); const setSelectedId = useUIStore((state) => state.setSettingsProjectsSelectedId); const [brokenIconIds, setBrokenIconIds] = React.useState>(new Set()); const isVSCode = React.useMemo(() => isVSCodeRuntime(), []); const tauriIpcAvailable = React.useMemo(() => isTauriShell(), []); const handleAddProject = React.useCallback(() => { if (!tauriIpcAvailable || !isDesktopLocalOriginActive()) { sessionEvents.requestDirectoryDialog(); return; } requestDirectoryAccess('') .then((result) => { if (result.success && result.path) { const added = addProject(result.path, { id: result.projectId }); if (!added) { toast.error('Failed to add project', { description: 'Please select a valid directory.', }); return; } setSelectedId(added.id); } else if (result.error && result.error !== 'Directory selection cancelled') { toast.error('Failed to select directory', { description: result.error, }); } }) .catch((error) => { console.error('Failed to select directory:', error); toast.error('Failed to select directory'); }); }, [addProject, setSelectedId, tauriIpcAvailable]); React.useEffect(() => { if (projects.length === 0) { if (selectedId !== null) { setSelectedId(null); } return; } if (selectedId && projects.some((p) => p.id === selectedId)) { return; } setSelectedId(projects[0].id); }, [projects, selectedId, setSelectedId]); return (

Projects

Total {projects.length} {!isVSCode && ( )}
} > {projects.map((project) => { const selected = project.id === selectedId; const Icon = project.icon ? PROJECT_ICON_MAP[project.icon] : null; const imageFailureKey = `${project.id}:${project.iconImage?.updatedAt ?? 0}`; const imageUrl = brokenIconIds.has(imageFailureKey) ? null : getProjectIconImageUrl(project); const color = project.color ? (PROJECT_COLOR_MAP[project.color] ?? null) : null; const icon = imageUrl ? ( { setBrokenIconIds((prev) => { if (prev.has(imageFailureKey)) { return prev; } const next = new Set(prev); next.add(imageFailureKey); return next; }); }} /> ) : Icon ? ( ) : ( ); return ( { setSelectedId(project.id); onItemSelect?.(); }} /> ); })}
); };