Files
Aurora-SpotiRip/components/AppShell.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

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>
);
}