()
| 46 | } |
| 47 | |
| 48 | export default function FavoritesPage() { |
| 49 | const [favorites, setFavorites] = useState<FavoriteBook[]>([]); |
| 50 | const [loading, setLoading] = useState(true); |
| 51 | const [removingIds, setRemovingIds] = useState<Set<string>>(new Set()); |
| 52 | const [downloadingIds, setDownloadingIds] = useState<Set<string>>(new Set()); |
| 53 | const [selectedBook, setSelectedBook] = useState<any>(null); |
| 54 | const [limitError, setLimitError] = useState<string | null>(null); |
| 55 | const [searchQuery, setSearchQuery] = useState(""); |
| 56 | |
| 57 | const { getCoverUrl: getCoverUrlRaw, handleCoverError } = useCoverCache(); |
| 58 | |
| 59 | useEffect(() => { |
| 60 | loadFavorites(); |
| 61 | }, []); |
| 62 | |
| 63 | |
| 64 | |
| 65 | const getCoverUrl = (book: FavoriteBook): string | undefined => { |
| 66 | return getCoverUrlRaw(book.book_id, book.cover); |
| 67 | }; |
| 68 | |
| 69 | const loadFavorites = async () => { |
| 70 | setLoading(true); |
| 71 | try { |
| 72 | const result: FavoriteBook[] = await invoke("get_favorites", { page: 1, limit: 200 }); |
| 73 | setFavorites(result); |
| 74 | } catch (err) { |
| 75 | console.error("Failed to load favorites:", err); |
| 76 | } |
| 77 | setLoading(false); |
| 78 | }; |
| 79 | |
| 80 | const handleRemove = async (bookId: string) => { |
| 81 | setRemovingIds(prev => new Set(prev).add(bookId)); |
| 82 | try { |
| 83 | await invoke("remove_favorite", { bookId }); |
| 84 | setFavorites(prev => prev.filter(f => f.book_id !== bookId)); |
| 85 | toast.success("已取消收藏"); |
| 86 | } catch (err) { |
| 87 | console.error("Failed to remove favorite:", err); |
| 88 | toast.error("取消收藏失败"); |
| 89 | } |
| 90 | setRemovingIds(prev => { const n = new Set(prev); n.delete(bookId); return n; }); |
| 91 | }; |
| 92 | |
| 93 | const handleDownload = async (book: FavoriteBook) => { |
| 94 | if (downloadingIds.has(book.book_id)) return; |
| 95 | setDownloadingIds(prev => new Set(prev).add(book.book_id)); |
| 96 | try { |
| 97 | const result = await invoke("download_book", { |
| 98 | bookId: book.book_id, hashId: book.hash || "", |
| 99 | title: book.title || "Unknown", extension: book.extension || "pdf", |
| 100 | }) as string; |
| 101 | if (result.startsWith("dispatched:")) { |
| 102 | const method = result.split(":")[1]; |
| 103 | const labels: Record<string, string> = { |
| 104 | browser: "已发送到浏览器", |
| 105 | idm: "已发送到 IDM", |
nothing calls this directly
no test coverage detected