mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 22:45:41 +00:00
refactor(album-detail): I.7 — split AlbumDetail.tsx 511 → 369 LOC across 5 files (#679)
* refactor(album-detail): extract sanitizeFilename + useAlbumDetailData Move sanitizeFilename into utils/albumDetailHelpers.ts. Pull the album + related-albums fetch and the starred state seeds (isStarred, starredSongs) into hooks/useAlbumDetailData.ts. AlbumDetail.tsx: 511 → 483 LOC. * refactor(album-detail): extract useAlbumOfflineState hook Move the four primitive-selector offline status reads (cache map + job-status filters + progress totals) into hooks/useAlbumOfflineState.ts. Keeps the re-render minimisation comment with the code that needs it. AlbumDetail.tsx: 483 → 461 LOC. * refactor(album-detail): extract useAlbumDetailSort hook Pull sortKey/Dir/clickCount state, the 3-click natural-reset cycle in handleSort, and the displayedSongs memo (filter + sort comparator) into hooks/useAlbumDetailSort.ts. Rating comparator keeps the same priority chain as the row renderer. AlbumDetail.tsx: 461 → 414 LOC. * refactor(album-detail): extract AlbumDetailToolbar subcomponent Pull the search input + bulk-action cluster (selection count, add-to- playlist popover, clear-selection button) into components/albumDetail/AlbumDetailToolbar.tsx. Parent retains showPlPicker to coordinate the popover close with selection clears. AlbumDetail.tsx: 414 → 369 LOC.
This commit is contained in:
committed by
GitHub
parent
3dc50a2ef0
commit
ff99b10faa
@@ -0,0 +1,90 @@
|
||||
import React from 'react';
|
||||
import { ListPlus, Search, X } from 'lucide-react';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { useSelectionStore } from '../../store/selectionStore';
|
||||
import { AddToPlaylistSubmenu } from '../ContextMenu';
|
||||
|
||||
interface Props {
|
||||
filterText: string;
|
||||
setFilterText: (v: string) => void;
|
||||
inSelectMode: boolean;
|
||||
selectedCount: number;
|
||||
showPlPicker: boolean;
|
||||
setShowPlPicker: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
t: TFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toolbar above the album tracklist. The filter input narrows the
|
||||
* visible songs by title/artist; the bulk-action cluster only appears
|
||||
* while the global selection store has at least one item picked.
|
||||
*
|
||||
* The "Add to playlist" picker is a portal-aware popover (closes on
|
||||
* outside mousedown, see AlbumDetail page effect) so the parent owns
|
||||
* `showPlPicker` to coordinate that close with selection clears.
|
||||
*/
|
||||
export function AlbumDetailToolbar({
|
||||
filterText,
|
||||
setFilterText,
|
||||
inSelectMode,
|
||||
selectedCount,
|
||||
showPlPicker,
|
||||
setShowPlPicker,
|
||||
t,
|
||||
}: Props) {
|
||||
return (
|
||||
<div className="album-track-toolbar">
|
||||
<div className="album-track-toolbar-filter">
|
||||
<Search size={16} style={{ position: 'absolute', left: 10, top: '50%', transform: 'translateY(-50%)', color: 'var(--text-muted)', pointerEvents: 'none' }} />
|
||||
<input
|
||||
className="input-search"
|
||||
style={{ width: '100%', paddingRight: filterText ? 28 : undefined }}
|
||||
placeholder={t('albumDetail.filterSongs')}
|
||||
value={filterText}
|
||||
onChange={e => setFilterText(e.target.value)}
|
||||
/>
|
||||
{filterText && (
|
||||
<button
|
||||
style={{ position: 'absolute', right: 6, top: '50%', transform: 'translateY(-50%)', background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', padding: 2, display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
||||
onClick={() => setFilterText('')}
|
||||
aria-label="Clear filter"
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="album-track-toolbar-actions">
|
||||
{inSelectMode && (
|
||||
<>
|
||||
<span className="bulk-action-count">
|
||||
{t('common.bulkSelected', { count: selectedCount })}
|
||||
</span>
|
||||
<div className="bulk-pl-picker-wrap">
|
||||
<button
|
||||
className="btn btn-surface btn-sm"
|
||||
onClick={() => setShowPlPicker(v => !v)}
|
||||
>
|
||||
<ListPlus size={14} />
|
||||
{t('common.bulkAddToPlaylist')}
|
||||
</button>
|
||||
{showPlPicker && (
|
||||
<AddToPlaylistSubmenu
|
||||
songIds={[...useSelectionStore.getState().selectedIds]}
|
||||
onDone={() => { setShowPlPicker(false); useSelectionStore.getState().clearAll(); }}
|
||||
dropDown
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
className="btn btn-ghost btn-sm"
|
||||
onClick={() => useSelectionStore.getState().clearAll()}
|
||||
>
|
||||
<X size={13} />
|
||||
{t('common.bulkClear')}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user