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
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user