Seventh plugin out, and the first that was already dead. Soulseek had been switched off since 2026-08-13: hono.ts had the router import and its mount commented out, so createSidecarProxy never ran, registerProxiedPrefix was never called, the slskd:server announcement fired into a void, and officer-slskd was not in the ecosystem at all. The permission entry had been reduced to api: [], which is why assertPermissionTotality was satisfied and the server booted clean. The UI rendered perfectly and every request 404'd. So this is a resurrection as well as a move. The mount is now /api/soulseek, derived via mountPrefix from the appName — all 47 frontend call sites repointed from /slskd/. The credential key in service_connections stays 'slskd': it names the daemon, not the route. Those two strings differ on purpose and conflating them reads the wrong row. The connection stays in core service_connections, as transmission does, so 'slskd' remains in a platform union this plugin cannot extend. Recorded in PLUGIN.md. Unlike transmission this plugin does own a schema: favourites and the two halves of the cached browse tree, which slskd has no concept of. Those three tables had been dropped from the drizzle barrel, so bun db:push would have deleted them on any server holding data. 313 palette classes converted to the semantic tone set, including a shared PHASE_TONE the three transfer views previously each copied.
78 lines
2.6 KiB
TypeScript
78 lines
2.6 KiB
TypeScript
import type { LucideIcon } from 'lucide-react';
|
|
import { NavLink } from 'react-router';
|
|
import {
|
|
LayoutGrid,
|
|
Search,
|
|
ArrowDownToLine,
|
|
ArrowUpFromLine,
|
|
Hash,
|
|
MessageCircle,
|
|
Users,
|
|
Server,
|
|
Plug,
|
|
} from 'lucide-react';
|
|
import { SOULSEEK_SECTIONS, soulseekSectionPath, type SoulseekSectionId } from './shared';
|
|
|
|
// Left panel of the /soulseek workspace — a vertical section menu mirroring slskd's top nav. Entries are
|
|
// react-router NavLinks to /soulseek/<section>, so active state comes from the router and SoulseekView
|
|
// (right) learns which section is open by reading the same URL rather than by being told.
|
|
|
|
const ICONS: Record<SoulseekSectionId, LucideIcon> = {
|
|
dashboard: LayoutGrid,
|
|
search: Search,
|
|
downloads: ArrowDownToLine,
|
|
uploads: ArrowUpFromLine,
|
|
rooms: Hash,
|
|
chat: MessageCircle,
|
|
users: Users,
|
|
system: Server,
|
|
connection: Plug,
|
|
};
|
|
|
|
export const SoulseekNav = () => {
|
|
return (
|
|
<div className="flex h-full flex-col overflow-y-auto bg-muted/30">
|
|
<div className="flex items-center gap-3 px-4 py-4">
|
|
<div className="flex h-9 w-9 items-center justify-center rounded-xl bg-white shadow-sm ring-1 ring-black/5">
|
|
<img src="/plugins/soulseek/icon.png" alt="" className="h-5 w-5 object-contain" />
|
|
</div>
|
|
<div className="min-w-0">
|
|
<div className="truncate text-sm font-semibold leading-tight">Soulseek</div>
|
|
<div className="truncate text-xs text-muted-foreground">slskd client</div>
|
|
</div>
|
|
</div>
|
|
|
|
<nav className="flex flex-col gap-0.5 px-2 pb-3">
|
|
{SOULSEEK_SECTIONS.map(({ id, label }) => {
|
|
const Icon = ICONS[id];
|
|
return (
|
|
<NavLink
|
|
key={id}
|
|
to={soulseekSectionPath(id)}
|
|
className={({ isActive }) =>
|
|
`group relative flex items-center gap-3 rounded-lg px-3 py-2 text-left text-sm transition-colors ${
|
|
isActive
|
|
? 'bg-primary/10 font-medium text-primary'
|
|
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
|
}`
|
|
}
|
|
>
|
|
{({ isActive }) => (
|
|
<>
|
|
{isActive && (
|
|
<span className="absolute left-0 top-1/2 h-5 w-1 -translate-y-1/2 rounded-r-full bg-primary" />
|
|
)}
|
|
<Icon
|
|
className={`h-4 w-4 shrink-0 ${isActive ? 'text-primary' : 'text-muted-foreground group-hover:text-foreground'}`}
|
|
/>
|
|
{label}
|
|
</>
|
|
)}
|
|
</NavLink>
|
|
);
|
|
})}
|
|
</nav>
|
|
</div>
|
|
);
|
|
};
|