import { useCallback, useEffect, useState } from 'react'; import { BookOpen, Download, Loader2, Plus, Sparkles } from 'lucide-react'; import { useLocation, useNavigate } from 'react-router-dom'; import { downloadLearningCourse, fetchMyLearningCourses, LEARNING_LIBRARY_CHANGED_EVENT, listInstalledLearningCourses, } from '@/lib/learning'; import { cn } from '@/lib/utils'; import type { InstalledLearningCourse, LearningCourse } from '../../../shared/learning'; type LearningSidebarProps = { sidebarCollapsed: boolean; }; export function LearningSidebar({ sidebarCollapsed }: LearningSidebarProps) { const navigate = useNavigate(); const location = useLocation(); const [installed, setInstalled] = useState>({}); const [ownedCourses, setOwnedCourses] = useState([]); const [loading, setLoading] = useState(true); const [downloading, setDownloading] = useState(null); const [error, setError] = useState(null); const loadCourses = useCallback(async () => { setLoading(true); setError(null); try { const [localCourses, personalCourses] = await Promise.all([ listInstalledLearningCourses(), fetchMyLearningCourses().catch(() => []), ]); setInstalled(Object.fromEntries(localCourses.map((record) => [record.course.id, record]))); setOwnedCourses(personalCourses); } catch (cause) { setError(cause instanceof Error ? cause.message : '个人课程暂时无法读取'); } finally { setLoading(false); } }, []); useEffect(() => { void loadCourses(); const refresh = () => void loadCourses(); window.addEventListener(LEARNING_LIBRARY_CHANGED_EVENT, refresh); return () => window.removeEventListener(LEARNING_LIBRARY_CHANGED_EVENT, refresh); }, [loadCourses]); const personalCourses = (() => { const courses = new Map(); for (const record of Object.values(installed)) courses.set(record.course.id, record.course); for (const course of ownedCourses) courses.set(course.id, course); return [...courses.values()]; })(); const openCourse = useCallback(async (course: LearningCourse) => { if (installed[course.id]) { navigate(`/learning/course/${encodeURIComponent(course.id)}`); return; } setDownloading(course.id); setError(null); try { const record = await downloadLearningCourse(course.id); setInstalled((current) => ({ ...current, [course.id]: record })); navigate(`/learning/course/${encodeURIComponent(course.id)}`); } catch (cause) { setError(cause instanceof Error ? cause.message : '课程下载失败'); } finally { setDownloading(null); } }, [installed, navigate]); const catalogActive = location.pathname === '/learning'; return (
{!sidebarCollapsed ? (
我的课程 {!loading ? {personalCourses.length} : null}
{loading ? (
正在读取课程
) : error ? ( ) : personalCourses.length === 0 ? (

下载或生成课程后,会显示在这里。

) : (
{personalCourses.map((course) => { const isInstalled = Boolean(installed[course.id]); const isActive = location.pathname === `/learning/course/${encodeURIComponent(course.id)}`; const courseMeta = course.origin === 'user_single' ? isInstalled ? '我生成的,已下载' : '我生成的,待下载' : '已下载到本机'; return ( ); })}
)}
) : null}
); }