Files
Psychotoxical-psysonic/src/features/playlist/components/PlaylistsFolderView.tsx
T
Psychotoxical 7ad196711e refactor(lib): move generic hooks, DnD engine, shortcut contract to lib
Continue M4 lib/ de-flattening with domain-agnostic infra:
- lib/hooks/: 9 pure generic hooks (useDebouncedValue, useIsMobile,
  useLongPressAction, useRangeSelection, useResizeClientHeight,
  useWindowVisibility, useSystemPrefersDark, useVirtualizerScrollMargin,
  useRemeasureGridVirtualizer) — all PURE (only react/zustand/tanstack deps).
- lib/dnd/DragDropContext.tsx: the generic mouse-event DnD engine (WebKitGTK
  HTML5-DnD workaround, ~24 importers, self-contained) — empties src/contexts/.
- lib/shortcuts/: shortcutActions + shortcutTypes (the action-id + binding
  contract; registry/dispatch/bindings stay app-level and import the contract,
  app->lib direction).

useWindowFullscreenState deliberately NOT moved — kept in hooks/ as app-shell
per the handoff iron-rule list (overrides its pure-helper appearance).

Pure move via deep @/lib/* specifiers. tsc 0, lint 0/0, full suite 319 files /
2353 tests green.
2026-06-30 08:20:11 +02:00

53 lines
2.1 KiB
TypeScript

import React from 'react';
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
import { EMPTY_SERVER_FOLDERS, usePlaylistFolderStore } from '@/features/playlist/store/playlistFolderStore';
import { groupPlaylistsByFolder } from '@/features/playlist/utils/playlistFolders';
import { useDragDrop } from '@/lib/dnd/DragDropContext';
import PlaylistFolderSection from '@/features/playlist/components/PlaylistFolderSection';
interface Props {
serverId: string;
playlists: SubsonicPlaylist[];
renderCard: (pl: SubsonicPlaylist) => React.ReactNode;
disableVirtualization: boolean;
}
/**
* Playlists page rendered as collapsible folder sections + an ungrouped
* remainder. Each section reuses `VirtualCardGrid`, so the card layout and
* virtualisation match the flat grid; only the grouping differs. Rendered only
* when at least one folder exists (the page falls back to the plain grid).
*/
export default function PlaylistsFolderView({ serverId, playlists, renderCard, disableVirtualization }: Props) {
const bucket = usePlaylistFolderStore(s => s.byServer[serverId]) ?? EMPTY_SERVER_FOLDERS;
const { isDragging } = useDragDrop();
const grouped = groupPlaylistsByFolder(playlists, bucket.folders, bucket.assignments);
// Keep the ungrouped section as a drop target during a drag even when empty,
// so a playlist filed into a folder can always be dragged back out to root.
const showUngrouped = grouped.ungrouped.length > 0 || isDragging;
return (
<div className="playlist-folder-view">
{grouped.folders.map(({ folder, playlists: items }) => (
<PlaylistFolderSection
key={folder.id}
serverId={serverId}
folder={folder}
items={items}
renderCard={renderCard}
disableVirtualization={disableVirtualization}
/>
))}
{showUngrouped && (
<PlaylistFolderSection
serverId={serverId}
folder={null}
items={grouped.ungrouped}
renderCard={renderCard}
disableVirtualization={disableVirtualization}
/>
)}
</div>
);
}