soulseek: wire sections into the view, default to dashboard, persist zoom

- render dashboard/uploads/rooms/chat/users/system from the section channel
- nav + view both default to the dashboard section
- register the view panel's zoom header; read the persisted zoom via useDashboardState

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 21:27:30 +00:00
co-authored by Claude Opus 4.8
parent 5ea06e2d8c
commit f4a47a035a
3 changed files with 55 additions and 8 deletions
@@ -18,7 +18,7 @@ const ICONS: Record<SoulseekSectionId, LucideIcon> = {
};
export const SoulseekNav = () => {
const [section, setSection] = usePanelChannel<SoulseekSectionId>(SOULSEEK_SECTION_CHANNEL, 'search');
const [section, setSection] = usePanelChannel<SoulseekSectionId>(SOULSEEK_SECTION_CHANNEL, 'dashboard');
return (
<div className="flex h-full flex-col overflow-y-auto bg-muted/30">
@@ -1,11 +1,20 @@
import { Construction } from 'lucide-react';
import { usePanelChannel } from 'hooks/usePanelChannel';
import { SOULSEEK_SECTION_CHANNEL, SOULSEEK_SECTIONS, type SoulseekSectionId } from './shared';
import { useDashboardState } from 'state/useDashboardState';
import { SOULSEEK_SECTION_CHANNEL, SOULSEEK_SECTIONS, soulseekZoomKey, type SoulseekSectionId } from './shared';
import { SearchView } from './SearchView';
import { SoulseekTransfers } from './SoulseekTransfers';
import { SoulseekUploads } from './SoulseekUploads';
import { SoulseekRooms } from './SoulseekRooms';
import { SoulseekDashboard } from './SoulseekDashboard';
import { SoulseekChat } from './SoulseekChat';
import { SoulseekUsers } from './SoulseekUsers';
import { SoulseekSystem } from './SoulseekSystem';
// Right panel of the /soulseek workspace — renders the UI for the section the nav selected. Only Search
// (history + input) and Downloads (transfers) are built so far; the rest are placeholders.
// Right panel of the /soulseek workspace — renders the UI for the section the nav selected. The panel
// header's +/- controls set a per-panel zoom factor, persisted via useDashboardState (same store as the
// layout config); we read it here and apply it as a CSS scale, with a compensating width/height so the
// scaled content still fills the panel and inner scroll keeps working.
const Placeholder = ({ id }: { id: SoulseekSectionId }) => {
const label = SOULSEEK_SECTIONS.find((s) => s.id === id)?.label ?? id;
@@ -22,15 +31,45 @@ const Placeholder = ({ id }: { id: SoulseekSectionId }) => {
);
};
export const SoulseekView = () => {
const [section] = usePanelChannel<SoulseekSectionId>(SOULSEEK_SECTION_CHANNEL, 'search');
const sectionView = (section: SoulseekSectionId) => {
switch (section) {
case 'dashboard':
return <SoulseekDashboard />;
case 'search':
return <SearchView />;
case 'downloads':
return <SoulseekTransfers />;
case 'uploads':
return <SoulseekUploads />;
case 'rooms':
return <SoulseekRooms />;
case 'chat':
return <SoulseekChat />;
case 'users':
return <SoulseekUsers />;
case 'system':
return <SoulseekSystem />;
default:
return <Placeholder id={section} />;
}
};
type SoulseekViewProps = { panelId: string };
export const SoulseekView = ({ panelId }: SoulseekViewProps) => {
const [section] = usePanelChannel<SoulseekSectionId>(SOULSEEK_SECTION_CHANNEL, 'dashboard');
const { value: zoom } = useDashboardState<number>(soulseekZoomKey(panelId), 1);
const z = zoom ?? 1;
if (z === 1) return <div className="h-full w-full">{sectionView(section)}</div>;
// transform: scale doesn't reflow, so size the box to 1/z and let the scale bring it back to 100%.
const inv = `${100 / z}%`;
return (
<div className="h-full w-full overflow-hidden">
<div style={{ width: inv, height: inv, transform: `scale(${z})`, transformOrigin: 'top left' }}>
{sectionView(section)}
</div>
</div>
);
};
@@ -2,10 +2,18 @@ import type { AppRegistryMeta } from '../../AppRegistry';
import { PanelLeft, LayoutGrid } from 'lucide-react';
import { SoulseekNav } from './SoulseekNav';
import { SoulseekView } from './SoulseekView';
import { SoulseekViewHeader } from './SoulseekViewHeader';
export { SoulseekNav, SoulseekView };
export const appRegistryMetas: AppRegistryMeta[] = [
{ key: 'soulseek-nav', name: 'Soulseek', icon: PanelLeft, component: SoulseekNav, availableOnPanel: false },
{ key: 'soulseek-view', name: 'Soulseek', icon: LayoutGrid, component: SoulseekView, availableOnPanel: false },
{
key: 'soulseek-view',
name: 'Soulseek',
icon: LayoutGrid,
component: SoulseekView,
header: SoulseekViewHeader,
availableOnPanel: false,
},
];