Files
Aurora-SpotiRip/pages/settings.tsx
T
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

199 lines
6.7 KiB
TypeScript

import { useEffect, useState } from "react";
import {
CopyCheck,
Download,
FolderTree,
Image,
Music2,
Save,
Settings2,
Tags,
} from "lucide-react";
import { Button } from "../components/Button";
import { Checkbox } from "../components/Checkbox";
import { Input } from "../components/Input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../components/Select";
import {
loadSpotiRipSettings,
saveSpotiRipSettings,
type SpotiRipSettings,
} from "../helpers/spotiripSettings";
import { getDesktopBridge } from "../helpers/desktopBridge";
import styles from "./settings.module.css";
import { MobileNav } from "../components/MobileNav";
export default function SettingsPage() {
const [settings, setSettings] = useState<SpotiRipSettings>(() => loadSpotiRipSettings());
const [saved, setSaved] = useState(false);
const desktop = getDesktopBridge();
const [downloadFolder, setDownloadFolder] = useState<string>();
useEffect(() => {
desktop?.getDownloadFolder().then(setDownloadFolder).catch(() => undefined);
}, []);
async function chooseDownloadFolder() {
const chosen = await desktop?.chooseDownloadFolder();
if (chosen) setDownloadFolder(chosen);
}
function update(changes: Partial<SpotiRipSettings>) {
setSaved(false);
setSettings((current) => ({ ...current, ...changes }));
}
function save() {
saveSpotiRipSettings(settings);
setSaved(true);
}
return (
<main className={styles.page}>
<header className={styles.topbar}>
<span>Preferences</span>
</header>
<section className={styles.heading}>
<Settings2 size={22} />
<div>
<h1>Settings</h1>
<p>Defaults for local file repair and metadata cleanup.</p>
</div>
</section>
<section className={styles.panel}>
{desktop ? (
<div className={styles.row}>
<Download size={18} />
<div className={styles.rowContent}>
<label>Download folder</label>
<div className={styles.folderRow}>
<Input value={downloadFolder || ""} readOnly aria-label="Download folder" />
<Button variant="outline" onClick={chooseDownloadFolder}>Change…</Button>
</div>
<small>Songs are saved here; each playlist gets its own subfolder. Changes apply immediately.</small>
</div>
</div>
) : null}
<div className={styles.row}>
<Tags size={18} />
<div className={styles.rowContent}>
<label htmlFor="filename-template">Filename template</label>
<Input
id="filename-template"
value={settings.filenameTemplate}
onChange={(event) => update({ filenameTemplate: event.target.value })}
/>
<small>Available tokens: {"{artist}"} {"{title}"} {"{album}"} {"{year}"} {"{track}"}</small>
</div>
</div>
<div className={styles.row}>
<FolderTree size={18} />
<div className={styles.rowContent}>
<label htmlFor="folder-template">Folder structure</label>
<Input
id="folder-template"
value={settings.folderTemplate}
onChange={(event) => update({ folderTemplate: event.target.value })}
/>
<small>
Tokens: {"{album_artist}"} {"{artist}"} {"{album}"} {"{year}"} {"{genre}"} {"{disc}"}
</small>
</div>
</div>
<div className={styles.row}>
<Music2 size={18} />
<div className={styles.rowContent}>
<label>Multi-disc numbering</label>
<Select
value={settings.multiDiscMode}
onValueChange={(value) =>
update({ multiDiscMode: value as SpotiRipSettings["multiDiscMode"] })
}
>
<SelectTrigger aria-label="Multi-disc numbering style"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="disc-prefix">Disc prefix · 2-05</SelectItem>
<SelectItem value="offset">Disc offset · 205</SelectItem>
<SelectItem value="track-only">Track only · 05</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className={styles.row}>
<Music2 size={18} />
<div className={styles.rowContent}>
<label>MP3 conversion bitrate</label>
<Select
value={String(settings.mp3Bitrate)}
onValueChange={(value) =>
update({ mp3Bitrate: Number(value) as SpotiRipSettings["mp3Bitrate"] })
}
>
<SelectTrigger aria-label="Default MP3 bitrate"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="128">128 kbps</SelectItem>
<SelectItem value="192">192 kbps</SelectItem>
<SelectItem value="256">256 kbps</SelectItem>
<SelectItem value="320">320 kbps</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<label className={styles.checkRow}>
<Checkbox
checked={settings.embedArtwork}
onChange={(event) => update({ embedArtwork: event.target.checked })}
/>
<Image size={17} />
<div>
<strong>Embed cover artwork</strong>
<span>Add recognized album artwork to repaired MP3 tags when available.</span>
</div>
</label>
<label className={styles.checkRow}>
<Checkbox
checked={settings.smartDedup}
onChange={(event) => update({ smartDedup: event.target.checked })}
/>
<CopyCheck size={17} />
<div>
<strong>Smart deduplication</strong>
<span>Warn when an ISRC or normalized artist/title already exists in the local SpotiRip index.</span>
</div>
</label>
<label className={styles.checkRow}>
<Checkbox
checked={settings.autoSelectMatches}
onChange={(event) => update({ autoSelectMatches: event.target.checked })}
/>
<Tags size={17} />
<div>
<strong>Auto-select confident matches</strong>
<span>Newly recognized tracks are selected for batch apply by default.</span>
</div>
</label>
<div className={styles.footer}>
<span>{saved ? "Settings saved on this device." : "Original source files are always preserved."}</span>
<Button onClick={save}><Save size={16} /> Save settings</Button>
</div>
</section>
<MobileNav />
</main>
);
}