From 9c4981b9cb88ae39e7ecefca416294abfa6a6530 Mon Sep 17 00:00:00 2001 From: James Magahern Date: Sat, 15 Feb 2025 15:22:07 -0800 Subject: [PATCH] frontend: add AddSongPanel --- frontend/src/components/AddSongPanel.tsx | 42 ++++++++++++ frontend/src/components/App.tsx | 82 +++++++++++------------- frontend/src/components/NowPlaying.tsx | 40 ++++++++++++ frontend/src/components/SongRow.tsx | 68 ++++++++++++++++++++ frontend/src/components/SongTable.tsx | 74 ++++----------------- 5 files changed, 203 insertions(+), 103 deletions(-) create mode 100644 frontend/src/components/AddSongPanel.tsx create mode 100644 frontend/src/components/NowPlaying.tsx create mode 100644 frontend/src/components/SongRow.tsx diff --git a/frontend/src/components/AddSongPanel.tsx b/frontend/src/components/AddSongPanel.tsx new file mode 100644 index 0000000..f6517b8 --- /dev/null +++ b/frontend/src/components/AddSongPanel.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; + +interface AddSongPanelProps { + onAddURL: (url: string) => void; +} + +const AddSongPanel: React.FC = ({ onAddURL }) => { + const [url, setUrl] = useState(''); + + const handleAddURL = () => { + onAddURL(url); + setUrl(''); + } + + return ( +
+
+ setUrl(e.target.value)} + placeholder="Add any URL..." + className="p-2 rounded-lg border-2 border-violet-500 flex-grow" + onKeyDown={(e) => { + if (e.key === 'Enter') { + handleAddURL(); + } + }} + /> + + +
+
+ ); +}; + +export default AddSongPanel; \ No newline at end of file diff --git a/frontend/src/components/App.tsx b/frontend/src/components/App.tsx index f306f9b..b5b612c 100644 --- a/frontend/src/components/App.tsx +++ b/frontend/src/components/App.tsx @@ -1,60 +1,56 @@ -import React, { HTMLAttributes, useState } from 'react'; +import React, { useState } from 'react'; import SongTable from './SongTable'; -import classNames from 'classnames'; -import { FaPlay, FaPause, FaStepForward, FaStepBackward } from 'react-icons/fa'; - -interface NowPlayingProps extends HTMLAttributes { - songName: string; - fileName: string; - isPlaying: boolean; - onPlayPause: () => void; - onStepForward: () => void; - onStepBackward: () => void; -} - -const NowPlaying: React.FC = (props) => { - return ( -
-
-
-
-
{props.songName}
-
{props.fileName}
-
-
- - - -
-
-
-
- ); -}; - - +import NowPlaying from './NowPlaying'; +import AddSongPanel from './AddSongPanel'; const App: React.FC = () => { const [isPlaying, setIsPlaying] = useState(false); + const [nowPlayingSong, setNowPlayingSong] = useState(null); + const [nowPlayingFileName, setNowPlayingFileName] = useState(null); + const [songs, setSongs] = useState([]); + + const handleAddURL = (url: string) => { + const urlToAdd = url.trim(); + if (urlToAdd) { + setSongs([...songs, urlToAdd]); + } + }; + + const handleDelete = (index: number) => { + setSongs(songs.filter((_, i) => i !== index)); + }; + + const handleSkipTo = (index: number) => { + setNowPlayingSong(songs[index]); + setNowPlayingFileName(songs[index].split('/').pop() || null); + setIsPlaying(true); + }; return (
setIsPlaying(!isPlaying)} onStepForward={() => {}} onStepBackward={() => {}} /> - + + {songs.length > 0 ? ( + + ) : ( +
+
Playlist is empty
+
+ )} + +
); diff --git a/frontend/src/components/NowPlaying.tsx b/frontend/src/components/NowPlaying.tsx new file mode 100644 index 0000000..de42757 --- /dev/null +++ b/frontend/src/components/NowPlaying.tsx @@ -0,0 +1,40 @@ +import React, { HTMLAttributes } from 'react'; +import classNames from 'classnames'; +import { FaPlay, FaPause, FaStepForward, FaStepBackward } from 'react-icons/fa'; + +interface NowPlayingProps extends HTMLAttributes { + songName: string; + fileName: string; + isPlaying: boolean; + onPlayPause: () => void; + onStepForward: () => void; + onStepBackward: () => void; +} + +const NowPlaying: React.FC = (props) => { + return ( +
+
+
+
+
{props.songName}
+
{props.fileName}
+
+
+ + + +
+
+
+
+ ); +}; + +export default NowPlaying; diff --git a/frontend/src/components/SongRow.tsx b/frontend/src/components/SongRow.tsx new file mode 100644 index 0000000..d01060e --- /dev/null +++ b/frontend/src/components/SongRow.tsx @@ -0,0 +1,68 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { FaPlay } from 'react-icons/fa'; + +interface SongRowProps { + song: string; + onDelete: () => void; + onPlay: () => void; +} + +const SongRow: React.FC = ({ song, onDelete, onPlay }) => { + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + const buttonRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (buttonRef.current && !buttonRef.current.contains(event.target as Node)) { + setShowDeleteConfirm(false); + } + }; + + if (showDeleteConfirm) { + document.addEventListener('click', handleClickOutside); + } + + return () => { + document.removeEventListener('click', handleClickOutside); + }; + }, [showDeleteConfirm]); + + return ( +
+
+ +
+ +
+
+ {song} +
+
+ +
+ +
+
+ ); +}; + +export default SongRow; diff --git a/frontend/src/components/SongTable.tsx b/frontend/src/components/SongTable.tsx index 3cdb93d..a3dc955 100644 --- a/frontend/src/components/SongTable.tsx +++ b/frontend/src/components/SongTable.tsx @@ -1,68 +1,22 @@ -import React, { useEffect, useRef, useState } from "react"; -import { FaPlay } from 'react-icons/fa'; +import React from "react"; +import SongRow from "./SongRow"; -const SongRow: React.FC<{ song: string }> = ({ song }) => { - const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); - const buttonRef = useRef(null); - - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (buttonRef.current && !buttonRef.current.contains(event.target as Node)) { - setShowDeleteConfirm(false); - } - }; - - if (showDeleteConfirm) { - document.addEventListener('click', handleClickOutside); - } - - return () => { - document.removeEventListener('click', handleClickOutside); - }; - }, [showDeleteConfirm]); - - return ( -
-
- -
- -
-
- {song} -
-
- -
- -
-
- ); -}; +interface SongTableProps { + songs: string[]; + onDelete: (index: number) => void; + onSkipTo: (index: number) => void; +} -const SongTable: React.FC = () => { - const songs = Array.from({ length: 20 }, (_, index) => `Song ${index + 1}`); - +const SongTable: React.FC = ({ songs, onDelete, onSkipTo }) => { return (
{songs.map((song, index) => ( - + onDelete(index)} + onPlay={() => onSkipTo(index)} + /> ))}
);