frontend: implements volume slider
This commit is contained in:
@@ -13,7 +13,7 @@ const AddSongPanel: React.FC<AddSongPanelProps> = ({ onAddURL }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-fit bg-black/50 rounded-b-2xl text-white">
|
<div className="flex items-center justify-center h-fit bg-black/50 md:rounded-b-2xl text-white">
|
||||||
<div className="flex flex-row items-center gap-4 w-full px-8 py-4">
|
<div className="flex flex-row items-center gap-4 w-full px-8 py-4">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import SongTable from './SongTable';
|
import SongTable from './SongTable';
|
||||||
import NowPlaying from './NowPlaying';
|
import NowPlaying from './NowPlaying';
|
||||||
import AddSongPanel from './AddSongPanel';
|
import AddSongPanel from './AddSongPanel';
|
||||||
@@ -8,20 +8,26 @@ const App: React.FC = () => {
|
|||||||
const [isPlaying, setIsPlaying] = useState(false);
|
const [isPlaying, setIsPlaying] = useState(false);
|
||||||
const [nowPlayingSong, setNowPlayingSong] = useState<string | null>(null);
|
const [nowPlayingSong, setNowPlayingSong] = useState<string | null>(null);
|
||||||
const [nowPlayingFileName, setNowPlayingFileName] = useState<string | null>(null);
|
const [nowPlayingFileName, setNowPlayingFileName] = useState<string | null>(null);
|
||||||
|
const [volume, setVolume] = useState(100);
|
||||||
|
const [volumeSettingIsLocked, setVolumeSettingIsLocked] = useState(false);
|
||||||
const [songs, setSongs] = useState<PlaylistItem[]>([]);
|
const [songs, setSongs] = useState<PlaylistItem[]>([]);
|
||||||
const [ws, setWs] = useState<WebSocket | null>(null);
|
const [_, setWs] = useState<WebSocket | null>(null);
|
||||||
|
|
||||||
const fetchPlaylist = async () => {
|
const fetchPlaylist = useCallback(async () => {
|
||||||
const playlist = await API.getPlaylist();
|
const playlist = await API.getPlaylist();
|
||||||
setSongs(playlist);
|
setSongs(playlist);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const fetchNowPlaying = async () => {
|
const fetchNowPlaying = useCallback(async () => {
|
||||||
const nowPlaying = await API.getNowPlaying();
|
const nowPlaying = await API.getNowPlaying();
|
||||||
setNowPlayingSong(nowPlaying.nowPlaying);
|
setNowPlayingSong(nowPlaying.nowPlaying);
|
||||||
setNowPlayingFileName(nowPlaying.currentFile);
|
setNowPlayingFileName(nowPlaying.currentFile);
|
||||||
setIsPlaying(!nowPlaying.isPaused);
|
setIsPlaying(!nowPlaying.isPaused);
|
||||||
};
|
|
||||||
|
if (!volumeSettingIsLocked) {
|
||||||
|
setVolume(nowPlaying.volume);
|
||||||
|
}
|
||||||
|
}, [volumeSettingIsLocked]);
|
||||||
|
|
||||||
const handleAddURL = (url: string) => {
|
const handleAddURL = (url: string) => {
|
||||||
const urlToAdd = url.trim();
|
const urlToAdd = url.trim();
|
||||||
@@ -71,36 +77,43 @@ const App: React.FC = () => {
|
|||||||
fetchNowPlaying();
|
fetchNowPlaying();
|
||||||
};
|
};
|
||||||
|
|
||||||
const watchForEvents = () => {
|
const handleVolumeSettingChange = async (volume: number) => {
|
||||||
const ws = API.subscribeToEvents((event) => {
|
setVolume(volume);
|
||||||
switch (event.event) {
|
await API.setVolume(volume);
|
||||||
case 'user_modify':
|
|
||||||
case 'end-file':
|
|
||||||
case 'playback-restart':
|
|
||||||
fetchPlaylist();
|
|
||||||
fetchNowPlaying();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return ws;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleWebSocketEvent = useCallback((event: any) => {
|
||||||
|
switch (event.event) {
|
||||||
|
case 'user_modify':
|
||||||
|
case 'end-file':
|
||||||
|
case 'playback-restart':
|
||||||
|
fetchPlaylist();
|
||||||
|
fetchNowPlaying();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, [fetchPlaylist, fetchNowPlaying]);
|
||||||
|
|
||||||
|
// Initial data fetch
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchPlaylist();
|
fetchPlaylist();
|
||||||
fetchNowPlaying();
|
fetchNowPlaying();
|
||||||
setWs(watchForEvents());
|
}, [fetchPlaylist, fetchNowPlaying]);
|
||||||
|
|
||||||
|
// WebSocket connection
|
||||||
|
useEffect(() => {
|
||||||
|
const ws = API.subscribeToEvents(handleWebSocketEvent);
|
||||||
|
setWs(ws);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (ws) {
|
if (ws) {
|
||||||
ws.close();
|
ws.close();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, [handleWebSocketEvent]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-screen w-screen bg-black py-10">
|
<div className="flex items-center justify-center h-screen w-screen bg-black md:py-10">
|
||||||
<div className="bg-violet-900 w-full md:max-w-xl h-full md:max-h-xl md:border md:rounded-2xl flex flex-col">
|
<div className="bg-violet-900 w-full md:max-w-2xl h-full md:max-h-xl md:border md:rounded-2xl flex flex-col">
|
||||||
<NowPlaying
|
<NowPlaying
|
||||||
className="flex flex-row md:rounded-t-2xl"
|
className="flex flex-row md:rounded-t-2xl"
|
||||||
songName={nowPlayingSong || "(Not Playing)"}
|
songName={nowPlayingSong || "(Not Playing)"}
|
||||||
@@ -109,6 +122,10 @@ const App: React.FC = () => {
|
|||||||
onPlayPause={togglePlayPause}
|
onPlayPause={togglePlayPause}
|
||||||
onSkip={handleSkip}
|
onSkip={handleSkip}
|
||||||
onPrevious={handlePrevious}
|
onPrevious={handlePrevious}
|
||||||
|
volume={volume}
|
||||||
|
onVolumeSettingChange={handleVolumeSettingChange}
|
||||||
|
onVolumeWillChange={() => setVolumeSettingIsLocked(true)}
|
||||||
|
onVolumeDidChange={() => setVolumeSettingIsLocked(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{songs.length > 0 ? (
|
{songs.length > 0 ? (
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
import React, { HTMLAttributes } from 'react';
|
import React, { HTMLAttributes } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { FaPlay, FaPause, FaStepForward, FaStepBackward } from 'react-icons/fa';
|
import { FaPlay, FaPause, FaStepForward, FaStepBackward, FaVolumeUp } from 'react-icons/fa';
|
||||||
|
|
||||||
interface NowPlayingProps extends HTMLAttributes<HTMLDivElement> {
|
interface NowPlayingProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
songName: string;
|
songName: string;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
isPlaying: boolean;
|
isPlaying: boolean;
|
||||||
|
volume: number;
|
||||||
onPlayPause: () => void;
|
onPlayPause: () => void;
|
||||||
onSkip: () => void;
|
onSkip: () => void;
|
||||||
onPrevious: () => void;
|
onPrevious: () => void;
|
||||||
|
|
||||||
|
// Sent when the volume setting actually changes value
|
||||||
|
onVolumeSettingChange: (volume: number) => void;
|
||||||
|
|
||||||
|
// Sent when the volume is about to start changing
|
||||||
|
onVolumeWillChange: (volume: number) => void;
|
||||||
|
|
||||||
|
// Sent when the volume has changed
|
||||||
|
onVolumeDidChange: (volume: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NowPlaying: React.FC<NowPlayingProps> = (props) => {
|
const NowPlaying: React.FC<NowPlayingProps> = (props) => {
|
||||||
@@ -20,13 +30,30 @@ const NowPlaying: React.FC<NowPlayingProps> = (props) => {
|
|||||||
<div className="text-white text-lg font-bold truncate">{props.songName}</div>
|
<div className="text-white text-lg font-bold truncate">{props.songName}</div>
|
||||||
<div className="text-white text-sm truncate">{props.fileName}</div>
|
<div className="text-white text-sm truncate">{props.fileName}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row gap-4">
|
<div className="flex flex-row items-center gap-4">
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 text-white">
|
||||||
|
<FaVolumeUp size={20} />
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
value={props.volume}
|
||||||
|
onMouseDown={() => props.onVolumeWillChange(props.volume)}
|
||||||
|
onMouseUp={() => props.onVolumeDidChange(props.volume)}
|
||||||
|
onChange={(e) => props.onVolumeSettingChange(Number(e.target.value))}
|
||||||
|
className="w-24 h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:rounded-full hover:[&::-webkit-slider-thumb]:bg-violet-300"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button className="text-white hover:text-violet-300 transition-colors" onClick={props.onPrevious}>
|
<button className="text-white hover:text-violet-300 transition-colors" onClick={props.onPrevious}>
|
||||||
<FaStepBackward size={24} />
|
<FaStepBackward size={24} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button className="text-white hover:text-violet-300 transition-colors" onClick={props.onPlayPause}>
|
<button className="text-white hover:text-violet-300 transition-colors" onClick={props.onPlayPause}>
|
||||||
{props.isPlaying ? <FaPause size={24} /> : <FaPlay size={24} />}
|
{props.isPlaying ? <FaPause size={24} /> : <FaPlay size={24} />}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button className="text-white hover:text-violet-300 transition-colors" onClick={props.onSkip}>
|
<button className="text-white hover:text-violet-300 transition-colors" onClick={props.onSkip}>
|
||||||
<FaStepForward size={24} />
|
<FaStepForward size={24} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user