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>
41 lines
1.6 KiB
TypeScript
41 lines
1.6 KiB
TypeScript
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>
|
|
)
|
|
}
|