Initial commit: Shelfless – alternative Audiobookshelf frontend
React + Vite + TypeScript SPA covering the full ABS feature set (library browsing, item detail, metadata/cover editing, podcasts, player with session sync, admin: users/libraries/scanner/server settings). Dev uses a dynamic CORS proxy; production is served by server/index.mjs (static + reverse proxy to ABS_URL). Includes systemd unit and installer under deploy/. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
40
src/components/detail/FilesTab.tsx
Normal file
40
src/components/detail/FilesTab.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { FileAudio } from 'lucide-react'
|
||||
import { formatBytes, formatDuration } from '@/lib/format'
|
||||
import type { AudioFile, Book, LibraryItem, Podcast } from '@/types/abs'
|
||||
|
||||
function collectAudioFiles(item: LibraryItem): AudioFile[] {
|
||||
if (item.mediaType === 'book') return (item.media as Book).audioFiles ?? []
|
||||
const eps = (item.media as Podcast).episodes ?? []
|
||||
return eps.map((e) => e.audioFile).filter((f): f is AudioFile => !!f)
|
||||
}
|
||||
|
||||
export function FilesTab({ item }: { item: LibraryItem }) {
|
||||
const files = collectAudioFiles(item)
|
||||
|
||||
if (files.length === 0) {
|
||||
return <p className="text-sm text-text-muted">Keine Audiodateien vorhanden.</p>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden rounded-lg border border-border">
|
||||
{files.map((f, i) => (
|
||||
<div
|
||||
key={`${f.ino}-${i}`}
|
||||
className="flex items-center gap-3 border-b border-border px-3 py-2.5 last:border-b-0"
|
||||
>
|
||||
<FileAudio size={18} className="shrink-0 text-text-muted" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm text-text">{f.metadata.filename}</p>
|
||||
<p className="text-xs text-text-muted">
|
||||
{f.codec?.toUpperCase()} {f.bitRate ? `· ${Math.round(f.bitRate / 1000)} kbps` : ''}
|
||||
</p>
|
||||
</div>
|
||||
<span className="tnum shrink-0 text-xs text-text-muted">{formatDuration(f.duration)}</span>
|
||||
<span className="tnum hidden shrink-0 text-xs text-text-muted sm:block">
|
||||
{formatBytes(f.metadata.size)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user