feat: refactoring
This commit is contained in:
45
furumi-node-player/client/src/components/Header.tsx
Normal file
45
furumi-node-player/client/src/components/Header.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { SearchDropdown } from './SearchDropdown'
|
||||
|
||||
type SearchResultItem = {
|
||||
result_type: string
|
||||
slug: string
|
||||
name: string
|
||||
detail?: string
|
||||
}
|
||||
|
||||
type HeaderProps = {
|
||||
searchOpen: boolean
|
||||
searchResults: SearchResultItem[]
|
||||
onSearchSelect: (type: string, slug: string) => void
|
||||
}
|
||||
|
||||
export function Header({
|
||||
searchOpen,
|
||||
searchResults,
|
||||
onSearchSelect,
|
||||
}: HeaderProps) {
|
||||
return (
|
||||
<header className="header">
|
||||
<div className="header-logo">
|
||||
<button className="btn-menu">☰</button>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<circle cx="9" cy="18" r="3" />
|
||||
<circle cx="18" cy="15" r="3" />
|
||||
<path d="M12 18V6l9-3v3" />
|
||||
</svg>
|
||||
Furumi
|
||||
<span className="header-version">v</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
||||
<div className="search-wrap">
|
||||
<input id="searchInput" placeholder="Search..." />
|
||||
<SearchDropdown
|
||||
isOpen={searchOpen}
|
||||
results={searchResults}
|
||||
onSelect={onSearchSelect}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
86
furumi-node-player/client/src/components/MainPanel.tsx
Normal file
86
furumi-node-player/client/src/components/MainPanel.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import type { MouseEvent as ReactMouseEvent } from 'react'
|
||||
import { Breadcrumbs } from './Breadcrumbs'
|
||||
import { LibraryList } from './LibraryList'
|
||||
import { QueueList, type QueueItem } from './QueueList'
|
||||
|
||||
export type Crumb = { label: string; action?: () => void }
|
||||
|
||||
export type LibraryListItem = {
|
||||
key: string
|
||||
className: string
|
||||
icon: string
|
||||
name: string
|
||||
detail?: string
|
||||
nameClassName?: string
|
||||
onClick: () => void
|
||||
button?: { title: string; onClick: (ev: ReactMouseEvent<HTMLButtonElement>) => void }
|
||||
}
|
||||
|
||||
type MainPanelProps = {
|
||||
breadcrumbs: Crumb[]
|
||||
libraryLoading: boolean
|
||||
libraryError: string | null
|
||||
libraryItems: LibraryListItem[]
|
||||
queueItemsView: QueueItem[]
|
||||
queueOrderView: number[]
|
||||
queuePlayingOrigIdxView: number
|
||||
queueScrollSignal: number
|
||||
onQueuePlay: (origIdx: number) => void
|
||||
onQueueRemove: (origIdx: number) => void
|
||||
onQueueMove: (fromPos: number, toPos: number) => void
|
||||
}
|
||||
|
||||
export function MainPanel({
|
||||
breadcrumbs,
|
||||
libraryLoading,
|
||||
libraryError,
|
||||
libraryItems,
|
||||
queueItemsView,
|
||||
queueOrderView,
|
||||
queuePlayingOrigIdxView,
|
||||
queueScrollSignal,
|
||||
onQueuePlay,
|
||||
onQueueRemove,
|
||||
onQueueMove,
|
||||
}: MainPanelProps) {
|
||||
return (
|
||||
<div className="main">
|
||||
<div className="sidebar-overlay" id="sidebarOverlay" />
|
||||
<aside className="sidebar" id="sidebar">
|
||||
<div className="sidebar-header">Library</div>
|
||||
<Breadcrumbs items={breadcrumbs} />
|
||||
<div className="file-list" id="fileList">
|
||||
<LibraryList loading={libraryLoading} error={libraryError} items={libraryItems} />
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<section className="queue-panel">
|
||||
<div className="queue-header">
|
||||
<span>Queue</span>
|
||||
<div className="queue-actions">
|
||||
<button className="queue-btn active" id="btnShuffle">
|
||||
Shuffle
|
||||
</button>
|
||||
<button className="queue-btn active" id="btnRepeat">
|
||||
Repeat
|
||||
</button>
|
||||
<button className="queue-btn" id="btnClearQueue">
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="queue-list" id="queueList">
|
||||
<QueueList
|
||||
queue={queueItemsView}
|
||||
order={queueOrderView}
|
||||
playingOrigIdx={queuePlayingOrigIdxView}
|
||||
scrollSignal={queueScrollSignal}
|
||||
onPlay={onQueuePlay}
|
||||
onRemove={onQueueRemove}
|
||||
onMove={onQueueMove}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
47
furumi-node-player/client/src/components/PlayerBar.tsx
Normal file
47
furumi-node-player/client/src/components/PlayerBar.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { NowPlaying } from './NowPlaying'
|
||||
import type { QueueItem } from './QueueList'
|
||||
|
||||
export function PlayerBar({ track }: { track: QueueItem | null }) {
|
||||
return (
|
||||
<div className="player-bar">
|
||||
<NowPlaying track={track} />
|
||||
<div className="controls">
|
||||
<div className="ctrl-btns">
|
||||
<button className="ctrl-btn" id="btnPrev">
|
||||
⏮
|
||||
</button>
|
||||
<button className="ctrl-btn ctrl-btn-main" id="btnPlayPause">
|
||||
▶
|
||||
</button>
|
||||
<button className="ctrl-btn" id="btnNext">
|
||||
⏭
|
||||
</button>
|
||||
</div>
|
||||
<div className="progress-row">
|
||||
<span className="time" id="timeElapsed">
|
||||
0:00
|
||||
</span>
|
||||
<div className="progress-bar" id="progressBar">
|
||||
<div className="progress-fill" id="progressFill" style={{ width: '0%' }} />
|
||||
</div>
|
||||
<span className="time" id="timeDuration">
|
||||
0:00
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="volume-row">
|
||||
<span className="vol-icon" id="volIcon">
|
||||
🔊
|
||||
</span>
|
||||
<input
|
||||
type="range"
|
||||
className="volume-slider"
|
||||
id="volSlider"
|
||||
min={0}
|
||||
max={100}
|
||||
defaultValue={80}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user