124 lines
4.0 KiB
TypeScript
124 lines
4.0 KiB
TypeScript
import React, { useMemo, memo } from "react";
|
|
import { Box, VStack, Text, Divider } from "@chakra-ui/react";
|
|
import { Song } from "../types/interfaces";
|
|
import { formatDuration } from '../utils/formatters';
|
|
|
|
interface SongDetailsProps {
|
|
song: Song | null;
|
|
}
|
|
|
|
const calculateBitrate = (size: string, totalTime: string): number | null => {
|
|
if (!size || !totalTime) return null;
|
|
|
|
// Convert size from bytes to bits
|
|
const bits = parseInt(size) * 8;
|
|
|
|
// Convert duration to seconds (handle both milliseconds and seconds format)
|
|
const seconds = parseInt(totalTime) / (totalTime.length > 4 ? 1000 : 1);
|
|
|
|
if (seconds <= 0) return null;
|
|
|
|
// Calculate bitrate in kbps
|
|
return Math.round(bits / seconds / 1000);
|
|
};
|
|
|
|
export const SongDetails: React.FC<SongDetailsProps> = memo(({ song }) => {
|
|
// Memoize expensive calculations
|
|
const songDetails = useMemo(() => {
|
|
if (!song) return null;
|
|
|
|
// Calculate bitrate only if imported value isn't available
|
|
const calculatedBitrate = song.size && song.totalTime ? calculateBitrate(song.size, song.totalTime) : null;
|
|
const displayBitrate = song.bitRate ?
|
|
`${song.bitRate} kbps` :
|
|
(calculatedBitrate ? `${calculatedBitrate} kbps (calculated)` : undefined);
|
|
|
|
const details = [
|
|
{ label: "Title", value: song.title },
|
|
{ label: "Artist", value: song.artist },
|
|
{ label: "Duration", value: formatDuration(song.totalTime || '') },
|
|
{ label: "Album", value: song.album },
|
|
{ label: "Genre", value: song.genre },
|
|
{ label: "BPM", value: song.averageBpm },
|
|
{ label: "Key", value: song.tonality },
|
|
{ label: "Year", value: song.year },
|
|
{ label: "Label", value: song.label },
|
|
{ label: "Mix", value: song.mix },
|
|
{ label: "Rating", value: song.rating },
|
|
{ label: "Bitrate", value: displayBitrate },
|
|
{ label: "Comments", value: song.comments },
|
|
].filter(detail => detail.value);
|
|
|
|
return { details, displayBitrate };
|
|
}, [song]);
|
|
|
|
if (!song) {
|
|
return (
|
|
<Box p={4} bg="gray.800" borderRadius="md">
|
|
<Text color="gray.400">Select a song to view details</Text>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (!songDetails) {
|
|
return (
|
|
<Box p={4} bg="gray.800" borderRadius="md">
|
|
<Text color="gray.400">Loading song details...</Text>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Box p={4} bg="gray.800" borderRadius="md">
|
|
<VStack align="stretch" spacing={4}>
|
|
<Box>
|
|
<Text fontSize="lg" fontWeight="bold" color="white">
|
|
{song.title}
|
|
</Text>
|
|
<Text fontSize="md" color="gray.400">
|
|
{song.artist}
|
|
</Text>
|
|
</Box>
|
|
<Divider borderColor="gray.700" />
|
|
<VStack align="stretch" spacing={3}>
|
|
{songDetails.details.map(({ label, value }) => (
|
|
<Box key={label}>
|
|
<Text fontSize="xs" color="gray.500" mb={1}>
|
|
{label}
|
|
</Text>
|
|
<Text fontSize="sm" color="gray.300">
|
|
{value}
|
|
</Text>
|
|
</Box>
|
|
))}
|
|
</VStack>
|
|
{song.tempo && (
|
|
<>
|
|
<Divider borderColor="gray.700" />
|
|
<Box>
|
|
<Text fontSize="xs" color="gray.500" mb={2}>
|
|
Tempo Details
|
|
</Text>
|
|
<VStack align="stretch" spacing={2}>
|
|
<Box>
|
|
<Text fontSize="xs" color="gray.500">BPM</Text>
|
|
<Text fontSize="sm" color="gray.300">{song.tempo.bpm}</Text>
|
|
</Box>
|
|
<Box>
|
|
<Text fontSize="xs" color="gray.500">Beat</Text>
|
|
<Text fontSize="sm" color="gray.300">{song.tempo.battito}</Text>
|
|
</Box>
|
|
<Box>
|
|
<Text fontSize="xs" color="gray.500">Time Signature</Text>
|
|
<Text fontSize="sm" color="gray.300">{song.tempo.metro}</Text>
|
|
</Box>
|
|
</VStack>
|
|
</Box>
|
|
</>
|
|
)}
|
|
</VStack>
|
|
</Box>
|
|
);
|
|
});
|
|
|
|
SongDetails.displayName = 'SongDetails';
|