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:
@@ -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,
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user