diff --git a/README.md b/README.md index d172d11..3ff3baa 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,4 @@ You will need some environment variables set to properly self-host aidxnCC. They ## To-Do - [ ] Dockerize for easier deployment +- [ ] Use MusicBrainz recording collections for displaying data about music diff --git a/app/music/page.tsx b/app/music/page.tsx index f10a013..dacb362 100644 --- a/app/music/page.tsx +++ b/app/music/page.tsx @@ -2,14 +2,23 @@ import Header from '@/components/Header' import MusicWidget from '@/components/widgets/Music' import MusicInfo from '@/components/objects/MusicInfo' import Footer from '@/components/Footer' +import { Music as MusicNote } from "lucide-react"; export default function Music() { return (
- - +
+ +
+

+ Music and Me +

+
+ + +
diff --git a/components/objects/MusicInfo.tsx b/components/objects/MusicInfo.tsx index eb1e9ca..450cf8e 100644 --- a/components/objects/MusicInfo.tsx +++ b/components/objects/MusicInfo.tsx @@ -13,16 +13,14 @@ const timePeriods: TimePeriod[] = [ const MusicInfo: React.FC = () => { return ( -
+
{timePeriods.map((period) => (

{period.title}

-
-
+
))}
diff --git a/components/objects/SeekBar.tsx b/components/objects/SeekBar.tsx new file mode 100644 index 0000000..df0ab75 --- /dev/null +++ b/components/objects/SeekBar.tsx @@ -0,0 +1,52 @@ +"use client" + +import * as React from "react" + +interface SeekBarProps { + duration: string + startPos: number +} + +export function SeekBar({ duration, startPos }: SeekBarProps) { + const getDurationInSeconds = (timeStr: string) => { + const parts = timeStr.split(":").map(Number) + if (parts.length === 3) { + return parts[0] * 3600 + parts[1] * 60 + parts[2] + } else { + return parts[0] * 60 + parts[1] + } + } + + const formatTime = (seconds: number) => { + const hours = Math.floor(seconds / 3600) + const minutes = Math.floor((seconds % 3600) / 60) + const remainingSeconds = seconds % 60 + + if (hours > 0) { + return `${hours}:${minutes.toString().padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}` + } + return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}` + } + + const totalSeconds = getDurationInSeconds(duration) + const [currentSeconds] = React.useState(startPos) + const progress = (currentSeconds / totalSeconds) * 100 + + return ( +
+
+
{formatTime(currentSeconds)}
+
{duration}
+ +
+
+
+
+
+
+ ) +} + diff --git a/components/widgets/Music.tsx b/components/widgets/Music.tsx index 047ccd2..ca3dce3 100644 --- a/components/widgets/Music.tsx +++ b/components/widgets/Music.tsx @@ -1,101 +1,127 @@ -"use client"; +"use client" -import { useState, useEffect } from 'react'; -import Image from 'next/image'; -import { Play, SkipBack, SkipForward } from 'lucide-react'; -import LoadingSpinner from '../objects/LoadingSpinner'; +import { useState, useEffect } from "react" +import Image from "next/image" +import { Play, SkipBack, SkipForward } from "lucide-react" +import LoadingSpinner from "../objects/LoadingSpinner" +import { SeekBar } from "@/components/objects/SeekBar" interface Song { - albumArt: string; - name: string; - artist: string; - duration: string; - link?: string; + albumArt: string + name: string + artist: string + duration: string + link?: string } interface Period { - timePeriod: string; - songs: Song[]; + timePeriod: string + songs: Song[] } export default function Home() { - const [timePeriod, setTimePeriod] = useState('Early Summer 2024'); - const [songs, setSongs] = useState([]); - const [currentIndex, setCurrentIndex] = useState(0); - const [isLoading, setIsLoading] = useState(true); + const [timePeriod, setTimePeriod] = useState("Early Summer 2024") + const [songs, setSongs] = useState([]) + const [currentIndex, setCurrentIndex] = useState(0) + const [isLoading, setIsLoading] = useState(true) + const [currentPosition, setCurrentPosition] = useState(0) useEffect(() => { - setIsLoading(true); - fetch('/data/music.json') - .then(response => response.json()) + setIsLoading(true) + fetch("/data/music.json") + .then((response) => response.json()) .then((data: Period[]) => { - const selectedPeriod = data.find((period) => period.timePeriod === timePeriod); - const songsList = selectedPeriod ? selectedPeriod.songs : []; - setSongs(songsList); - setCurrentIndex(Math.floor(Math.random() * songsList.length)); - setIsLoading(false); + const selectedPeriod = data.find((period) => period.timePeriod === timePeriod) + const songsList = selectedPeriod ? selectedPeriod.songs : [] + setSongs(songsList) + const newIndex = Math.floor(Math.random() * songsList.length) + setCurrentIndex(newIndex) + // Set initial random position for the selected song + if (songsList.length > 0) { + const durationInSeconds = parseDuration(songsList[newIndex]?.duration || "0:00") + setCurrentPosition(Math.floor(Math.random() * durationInSeconds)) + } + setIsLoading(false) }) - .catch(error => { - console.error('Error fetching music data:', error); - setIsLoading(false); - }); - }, [timePeriod]); + .catch((error) => { + console.error("Error fetching music data:", error) + setIsLoading(false) + }) + }, [timePeriod]) const handleNext = () => { - setCurrentIndex((currentIndex + 1) % songs.length); - }; + setCurrentIndex((prevIndex) => { + const nextIndex = (prevIndex + 1) % songs.length + const durationInSeconds = parseDuration(songs[nextIndex].duration) + setCurrentPosition(Math.floor(Math.random() * durationInSeconds)) + return nextIndex + }) + } const handlePrevious = () => { - setCurrentIndex((currentIndex - 1 + songs.length) % songs.length); - }; + setCurrentIndex((prevIndex) => { + const nextIndex = (prevIndex - 1 + songs.length) % songs.length + const durationInSeconds = parseDuration(songs[nextIndex].duration) + setCurrentPosition(Math.floor(Math.random() * durationInSeconds)) + return nextIndex + }) + } + + const parseDuration = (duration: string): number => { + const [minutes, seconds] = duration.split(":").map(Number) + return minutes * 60 + seconds + } return ( -
+
- ); + ) }