- 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
87 lines
2.8 KiB
TypeScript
87 lines
2.8 KiB
TypeScript
import { type ReactNode, useEffect, useState } from "react";
|
|
import { NavLink, useLocation } from "react-router-dom";
|
|
import {
|
|
Activity,
|
|
AudioLines,
|
|
Clock3,
|
|
Database,
|
|
Download,
|
|
FolderOpen,
|
|
Heart,
|
|
Library,
|
|
Settings2,
|
|
ShieldCheck,
|
|
} from "lucide-react";
|
|
import { getDesktopBridge } from "../helpers/desktopBridge";
|
|
import styles from "./AppShell.module.css";
|
|
|
|
const NAV_ITEMS = [
|
|
{ to: "/", label: "Playlist", icon: Library, end: true },
|
|
{ to: "/spotify-library", label: "My Spotify", icon: Heart, also: ["/artist/", "/album/"] },
|
|
{ to: "/identify", label: "Identify Files", icon: AudioLines },
|
|
{ to: "/convert", label: "Convert Audio", icon: Download },
|
|
{ to: "/library", label: "Local Library", icon: Database },
|
|
{ to: "/analyze", label: "Audio Analyzer", icon: Activity },
|
|
{ to: "/history", label: "History", icon: Clock3 },
|
|
{ to: "/settings", label: "Settings", icon: Settings2 },
|
|
];
|
|
|
|
export function AppShell({ children }: { children: ReactNode }) {
|
|
const { pathname } = useLocation();
|
|
const desktop = getDesktopBridge();
|
|
const [downloadFolder, setDownloadFolder] = useState<string>();
|
|
|
|
useEffect(() => {
|
|
desktop?.getDownloadFolder().then(setDownloadFolder).catch(() => undefined);
|
|
}, [pathname]);
|
|
|
|
async function chooseDownloadFolder() {
|
|
const chosen = await desktop?.chooseDownloadFolder();
|
|
if (chosen) setDownloadFolder(chosen);
|
|
}
|
|
|
|
return (
|
|
<div className={styles.shell}>
|
|
<aside className={styles.sidebar}>
|
|
<NavLink to="/" className={styles.logoWrap} aria-label="Aurora SpotiRip home">
|
|
<img className={styles.logo} src="/SpotiRip.png" alt="Aurora SpotiRip" />
|
|
</NavLink>
|
|
|
|
<nav className={styles.nav} aria-label="Main navigation">
|
|
{NAV_ITEMS.map(({ to, label, icon: Icon, end, also }) => (
|
|
<NavLink
|
|
key={to}
|
|
to={to}
|
|
end={end}
|
|
className={({ isActive }) =>
|
|
styles.navItem +
|
|
(isActive || also?.some((prefix) => pathname.startsWith(prefix)) ? " " + styles.active : "")
|
|
}
|
|
>
|
|
<Icon size={17} />
|
|
<span>{label}</span>
|
|
</NavLink>
|
|
))}
|
|
</nav>
|
|
|
|
{desktop ? (
|
|
<div className={styles.section}>
|
|
<span className={styles.label}>DOWNLOADS</span>
|
|
<button className={styles.folder} onClick={chooseDownloadFolder} title="Change download folder">
|
|
<FolderOpen size={15} />
|
|
<span>{downloadFolder || "Choose folder…"}</span>
|
|
</button>
|
|
</div>
|
|
) : null}
|
|
|
|
<div className={styles.footer}>
|
|
<ShieldCheck size={15} />
|
|
<span>Local-first workflow</span>
|
|
</div>
|
|
</aside>
|
|
|
|
<div className={styles.content}>{children}</div>
|
|
</div>
|
|
);
|
|
}
|