import { useEffect, useState, useRef } from "react"; import { Button } from "@/components/ui/button"; import { Trash2, ExternalLink, Search, ArrowUpDown, History, Play, Pause, Database, CloudUpload, Music2, Disc3, ListMusic, UserRound } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from "@/components/ui/pagination"; import { GetDownloadHistory, ClearDownloadHistory, GetPreviewURL, GetFetchHistory, DeleteDownloadHistoryItem, DeleteFetchHistoryItem, ClearFetchHistoryByType } from "../../wailsjs/go/main/App"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { openExternal } from "@/lib/utils"; const formatDate = (timestamp: number) => { const date = new Date(timestamp * 1000); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; interface DownloadHistoryItem { id: string; spotify_id: string; title: string; artists: string; album: string; duration_str: string; cover_url: string; quality: string; format: string; path: string; timestamp: number; } interface FetchHistoryItem { id: string; url: string; type: string; name: string; info: string; image: string; data: string; timestamp: number; } interface HistoryPageProps { onHistorySelect?: (cachedData: string) => void; } export function HistoryPage({ onHistorySelect }: HistoryPageProps) { const [activeTab, setActiveTab] = useState("downloads"); const [downloadHistory, setDownloadHistory] = useState([]); const [filteredDownloadHistory, setFilteredDownloadHistory] = useState([]); const [showClearDownloadConfirm, setShowClearDownloadConfirm] = useState(false); const [downloadSearchQuery, setDownloadSearchQuery] = useState(""); const [downloadSortBy, setDownloadSortBy] = useState("default"); const [downloadCurrentPage, setDownloadCurrentPage] = useState(1); const [playingPreviewId, setPlayingPreviewId] = useState(null); const audioRef = useRef(null); const [fetchHistory, setFetchHistory] = useState([]); const [filteredFetchHistory, setFilteredFetchHistory] = useState([]); const [activeFetchTab, setActiveFetchTab] = useState("track"); const [showClearFetchConfirm, setShowClearFetchConfirm] = useState(false); const [fetchSearchQuery, setFetchSearchQuery] = useState(""); const [fetchCurrentPage, setFetchCurrentPage] = useState(1); const ITEMS_PER_PAGE = 50; const fetchDownloadHistory = async () => { try { const items = await GetDownloadHistory(); setDownloadHistory(items || []); } catch (err) { console.error("Failed to fetch download history:", err); } }; const fetchFetchHistory = async () => { try { const items = await GetFetchHistory(); setFetchHistory(items || []); } catch (err) { console.error("Failed to fetch fetch history:", err); } }; useEffect(() => { if (activeTab === "downloads") { fetchDownloadHistory(); const interval = setInterval(fetchDownloadHistory, 5000); return () => clearInterval(interval); } else { fetchFetchHistory(); const interval = setInterval(fetchFetchHistory, 5000); return () => clearInterval(interval); } }, [activeTab]); useEffect(() => { return () => { if (audioRef.current) { audioRef.current.pause(); } }; }, []); useEffect(() => { let result = [...downloadHistory]; if (downloadSearchQuery) { const query = downloadSearchQuery.toLowerCase(); result = result.filter(item => item.title.toLowerCase().includes(query) || item.artists.toLowerCase().includes(query) || item.album.toLowerCase().includes(query)); } const parseDuration = (str: string) => { const parts = str.split(':').map(Number); if (parts.length === 2) return parts[0] * 60 + parts[1]; if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2]; return 0; }; result.sort((a, b) => { switch (downloadSortBy) { case "default": case "date_desc": return b.timestamp - a.timestamp; case "date_asc": return a.timestamp - b.timestamp; case "title_asc": return a.title.localeCompare(b.title); case "title_desc": return b.title.localeCompare(a.title); case "artist_asc": return a.artists.localeCompare(b.artists); case "artist_desc": return b.artists.localeCompare(a.artists); case "duration_asc": return parseDuration(a.duration_str) - parseDuration(b.duration_str); case "duration_desc": return parseDuration(b.duration_str) - parseDuration(a.duration_str); default: return 0; } }); setFilteredDownloadHistory(result); }, [downloadHistory, downloadSearchQuery, downloadSortBy]); useEffect(() => { setDownloadCurrentPage(1); }, [downloadSearchQuery, downloadSortBy]); useEffect(() => { let result = [...fetchHistory]; if (activeFetchTab !== "all") { result = result.filter(item => item.type.toLowerCase() === activeFetchTab.toLowerCase()); } if (fetchSearchQuery) { const query = fetchSearchQuery.toLowerCase(); result = result.filter(item => item.name.toLowerCase().includes(query) || item.info.toLowerCase().includes(query)); } result.sort((a, b) => b.timestamp - a.timestamp); setFilteredFetchHistory(result); }, [fetchHistory, fetchSearchQuery, activeFetchTab]); useEffect(() => { setFetchCurrentPage(1); }, [fetchSearchQuery, activeFetchTab]); const handlePreview = async (id: string, spotifyId: string) => { if (playingPreviewId === id) { audioRef.current?.pause(); setPlayingPreviewId(null); return; } if (audioRef.current) { audioRef.current.pause(); } try { const url = await GetPreviewURL(spotifyId); if (url) { const audio = new Audio(url); audioRef.current = audio; audio.volume = 0.5; audio.onended = () => setPlayingPreviewId(null); audio.play(); setPlayingPreviewId(id); } } catch (e) { console.error("Failed to play preview:", e); } }; const handleClearDownloadHistory = async () => { await ClearDownloadHistory(); fetchDownloadHistory(); setShowClearDownloadConfirm(false); }; const handleDeleteDownloadItem = async (id: string) => { await DeleteDownloadHistoryItem(id); setDownloadHistory(prev => prev.filter(item => item.id !== id)); }; const handleClearFetchHistory = async () => { await ClearFetchHistoryByType(activeFetchTab); fetchFetchHistory(); setShowClearFetchConfirm(false); }; const handleDeleteFetchItem = async (id: string, e: React.MouseEvent) => { e.stopPropagation(); await DeleteFetchHistoryItem(id); setFetchHistory(prev => prev.filter(item => item.id !== id)); }; const getPaginationPages = (current: number, total: number): (number | 'ellipsis')[] => { if (total <= 10) return Array.from({ length: total }, (_, i) => i + 1); const pages: (number | 'ellipsis')[] = []; pages.push(1); if (current <= 7) { for (let i = 2; i <= 10; i++) pages.push(i); pages.push('ellipsis'); pages.push(total); } else if (current >= total - 7) { pages.push('ellipsis'); for (let i = total - 9; i <= total; i++) pages.push(i); } else { pages.push('ellipsis'); pages.push(current - 1); pages.push(current); pages.push(current + 1); pages.push('ellipsis'); pages.push(total); } return pages; }; const renderDownloadHistory = () => { const totalPages = Math.ceil(filteredDownloadHistory.length / ITEMS_PER_PAGE); const startIndex = (downloadCurrentPage - 1) * ITEMS_PER_PAGE; const paginated = filteredDownloadHistory.slice(startIndex, startIndex + ITEMS_PER_PAGE); return (

Downloads

{downloadHistory.length > 0 && ( {downloadHistory.length.toLocaleString('en-US')} )}
setDownloadSearchQuery(e.target.value)} className="pl-8 h-9"/>
{paginated.length === 0 ? (

No download history

Your downloaded tracks will appear here.

) : ( {paginated.map((item, index) => ())}
# Title Album Format Dur Downloaded At Actions
{startIndex + index + 1}
{item.album} { (e.target as HTMLImageElement).src = "https://placehold.co/300?text=No+Cover"; }}/>
{item.title} {item.artists}
{item.album}
{['HI_RES_LOSSLESS', 'LOSSLESS', 'flac', '6', '7', '27'].includes(item.format?.toLowerCase() || '') ? 'FLAC' : item.format?.toUpperCase()} {item.quality && {item.quality}}
{item.duration_str}
{formatDate(item.timestamp).split(' ')[0]} {formatDate(item.timestamp).split(' ')[1]}

{playingPreviewId === item.id ? "Pause Preview" : "Play Preview"}

Open in Spotify

Delete

)}
{totalPages > 1 && ( { e.preventDefault(); if (downloadCurrentPage > 1) setDownloadCurrentPage(downloadCurrentPage - 1); }} className={downloadCurrentPage === 1 ? "pointer-events-none opacity-50" : "cursor-pointer"}/> {getPaginationPages(downloadCurrentPage, totalPages).map((page, index) => (page === 'ellipsis' ? ( ) : ( { e.preventDefault(); setDownloadCurrentPage(page as number); }} isActive={downloadCurrentPage === page} className="cursor-pointer"> {page} )))} { e.preventDefault(); if (downloadCurrentPage < totalPages) setDownloadCurrentPage(downloadCurrentPage + 1); }} className={downloadCurrentPage === totalPages ? "pointer-events-none opacity-50" : "cursor-pointer"}/> )}
); }; const renderFetchHistory = () => { const totalPages = Math.ceil(filteredFetchHistory.length / ITEMS_PER_PAGE); const startIndex = (fetchCurrentPage - 1) * ITEMS_PER_PAGE; const paginated = filteredFetchHistory.slice(startIndex, startIndex + ITEMS_PER_PAGE); return (

Fetches

{fetchHistory.length > 0 && ( {fetchHistory.length.toLocaleString('en-US')} )}
setFetchSearchQuery(e.target.value)} className="pl-8 h-9"/>
{paginated.length === 0 ? (

No fetch history

Fetched metadata will appear here.

) : ( {paginated.map((item, index) => ())}
# {activeFetchTab === 'artist' ? 'Name' : 'Title'} Details Fetched At Actions
{startIndex + index + 1}
{item.image ? ({item.name}) : (
{item.type.slice(0, 2).toUpperCase()}
)}
{item.name}
{item.info}
{formatDate(item.timestamp).split(' ')[0]} {formatDate(item.timestamp).split(' ')[1]}

Load

Delete

)}
{totalPages > 1 && ( { e.preventDefault(); if (fetchCurrentPage > 1) setFetchCurrentPage(fetchCurrentPage - 1); }} className={fetchCurrentPage === 1 ? "pointer-events-none opacity-50" : "cursor-pointer"}/> {getPaginationPages(fetchCurrentPage, totalPages).map((page, index) => (page === 'ellipsis' ? ( ) : ( { e.preventDefault(); setFetchCurrentPage(page as number); }} isActive={fetchCurrentPage === page} className="cursor-pointer"> {page} )))} { e.preventDefault(); if (fetchCurrentPage < totalPages) setFetchCurrentPage(fetchCurrentPage + 1); }} className={fetchCurrentPage === totalPages ? "pointer-events-none opacity-50" : "cursor-pointer"}/> )}
); }; return (

History

{activeTab === "downloads" && (
{renderDownloadHistory()}
)} {activeTab === "fetches" && (
{renderFetchHistory()}
)} Clear Download History? This will remove all entries from your download history. This action cannot be undone. Note: The actual downloaded files will NOT be deleted. Clear {activeFetchTab.charAt(0).toUpperCase() + activeFetchTab.slice(1)} History? This will remove all {activeFetchTab} entries from your fetch history cache.
); }