Files
R3ap3r 3178b8f4f7 Aurora SpotiRip desktop and web app
- Spotify playlist, library, album and artist browsing with in-app playback
- Song, playlist and album downloads via spotdl with YouTube and Bandcamp fallbacks
- Electron desktop app (castLabs Widevine) with a permanent sidebar layout
- Linux and Windows packaging scripts
2026-09-25 23:20:57 -05:00

97 lines
3.5 KiB
TypeScript

import { useEffect, useMemo, useState } from "react";
import {
Clock3,
CopyCheck,
FileAudio,
Search,
Trash2,
} from "lucide-react";
import { Button } from "../components/Button";
import { Input } from "../components/Input";
import {
clearLibraryHistory,
listLibraryHistory,
type LibraryTrackRecord,
} from "../helpers/libraryIndex";
import styles from "./history.module.css";
import { MobileNav } from "../components/MobileNav";
export default function HistoryPage() {
const [records, setRecords] = useState<LibraryTrackRecord[]>([]);
const [filter, setFilter] = useState("");
useEffect(() => {
listLibraryHistory().then(setRecords).catch(() => setRecords([]));
}, []);
const visible = useMemo(() => {
const query = filter.trim().toLowerCase();
if (!query) return records;
return records.filter((record) =>
[record.title, record.artist, record.album || "", record.isrc || ""]
.some((value) => value.toLowerCase().includes(query)),
);
}, [records, filter]);
async function clearHistory() {
await clearLibraryHistory();
setRecords([]);
}
return (
<main className={styles.page}>
<header className={styles.topbar}>
<span>Library History</span>
</header>
<section className={styles.heading}>
<div>
<span>LOCAL LIBRARY</span>
<h1>History & Dedup Index</h1>
<p>Tracks SpotiRip has repaired or saved on this device, including ISRC identities used for duplicate detection.</p>
</div>
<div className={styles.actions}>
<div className={styles.searchBox}><Search size={15} /><Input value={filter} onChange={(event) => setFilter(event.target.value)} placeholder="Filter history" /></div>
<Button variant="outline" onClick={clearHistory} disabled={!records.length}><Trash2 size={15} /> Clear history</Button>
</div>
</section>
<section className={styles.panel}>
<div className={styles.summary}>
<div><strong>{records.length}</strong><span>Indexed tracks</span></div>
<div><strong>{records.filter((record) => record.isrc).length}</strong><span>ISRC identified</span></div>
<div><strong>{new Set(records.map((record) => record.artist)).size}</strong><span>Artists</span></div>
</div>
<div className={styles.tableHeader}>
<span>Track</span><span>Album</span><span>ISRC</span><span>Saved</span>
</div>
{visible.map((record) => (
<div className={styles.row} key={record.id}>
<div className={styles.track}>
<div className={styles.art}>
{record.artworkUrl ? <img src={record.artworkUrl} alt="" /> : <FileAudio size={16} />}
</div>
<div><strong>{record.title}</strong><span>{record.artist}</span></div>
</div>
<span>{record.album || "—"}</span>
<span className={styles.mono}>{record.isrc || "artist/title key"}</span>
<div className={styles.saved}>
<Clock3 size={13} />
<span>{new Date(record.addedAt).toLocaleString()}</span>
</div>
</div>
))}
{!visible.length ? (
<div className={styles.empty}>
<CopyCheck size={24} />
<strong>{records.length ? "No matching history" : "No local history yet"}</strong>
<span>Repaired files will appear here and become part of smart duplicate detection.</span>
</div>
) : null}
</section>
<MobileNav />
</main>
);
}