import React, { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { X } from 'lucide-react'; import { usePlayerStore } from '../store/playerStore'; import { useShallow } from 'zustand/react/shallow'; import { getSong, SubsonicSong } from '../api/subsonic'; import { useTranslation } from 'react-i18next'; function formatDuration(s: number): string { const m = Math.floor(s / 60); const sec = s % 60; return `${m}:${sec.toString().padStart(2, '0')}`; } function formatSize(bytes?: number): string | null { if (!bytes) return null; if (bytes >= 1_000_000) return `${(bytes / 1_000_000).toFixed(2)} MB`; return `${(bytes / 1_000).toFixed(0)} KB`; } function Row({ label, value }: { label: string; value: React.ReactNode }) { if (value === null || value === undefined || value === '' || value === '—') return null; return ( {label} {value} ); } function Divider() { return ; } export default function SongInfoModal() { const { t } = useTranslation(); const { songInfoModal, closeSongInfo } = usePlayerStore( useShallow(s => ({ songInfoModal: s.songInfoModal, closeSongInfo: s.closeSongInfo })) ); const [song, setSong] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { if (!songInfoModal.isOpen || !songInfoModal.songId) { setSong(null); return; } let cancelled = false; setLoading(true); getSong(songInfoModal.songId).then(s => { if (!cancelled) { setSong(s); setLoading(false); } }); return () => { cancelled = true; }; }, [songInfoModal.isOpen, songInfoModal.songId]); useEffect(() => { if (!songInfoModal.isOpen) return; const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') closeSongInfo(); }; document.addEventListener('keydown', handler); return () => document.removeEventListener('keydown', handler); }, [songInfoModal.isOpen, closeSongInfo]); if (!songInfoModal.isOpen) return null; const channels = song?.channelCount === 1 ? t('songInfo.mono') : song?.channelCount === 2 ? t('songInfo.stereo') : song?.channelCount ? `${song.channelCount} ch` : null; const trackLabel = song?.discNumber && song.discNumber > 1 ? `${song.discNumber} – ${song.track}` : song?.track != null ? String(song.track) : null; const hasReplayGain = song?.replayGain && (song.replayGain.trackGain !== undefined || song.replayGain.albumGain !== undefined); return createPortal( <>
{t('songInfo.title')}
{loading &&
{t('common.loading')}
} {!loading && song && ( {song.albumArtist && song.albumArtist !== song.artist && ( )} {song.path && ( <> {song.path}} /> )} {hasReplayGain && ( <> {song.replayGain!.trackGain !== undefined && ( = 0 ? '+' : ''}${song.replayGain!.trackGain.toFixed(2)} dB`} /> )} {song.replayGain!.albumGain !== undefined && ( = 0 ? '+' : ''}${song.replayGain!.albumGain.toFixed(2)} dB`} /> )} {song.replayGain!.trackPeak !== undefined && ( )} )}
)}
, document.body ); }