Initial commit

This commit is contained in:
2024-12-30 11:42:12 -07:00
commit 09ba4114c1
86 changed files with 7522 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
import { useWebSocket } from "../contexts/useWebSocket";
import { Slider } from "./Slider";
export const CurrentSong = () => {
const { ws, playbackInfo, sendMessage } = useWebSocket();
return (
<>
{playbackInfo && (
<div className="rounded border p-3 my-5 bg-body-tertiary bg-opacity-50">
<h2>Playing Song</h2>
<h5>{playbackInfo.file_name}</h5>
{ws && (
<Slider
min={0}
max={playbackInfo.duration}
current={playbackInfo.current_position}
onChange={(v) => {
sendMessage({ action: "set_playback", position: v });
}}
/>
)}
</div>
)}
</>
);
};

View File

@@ -0,0 +1,22 @@
import React from "react";
import { useInfoTask } from "./useInfoTask";
import { useWebSocket } from "../contexts/useWebSocket";
export const PlaybackInfo: React.FC = () => {
const { ws, error, message, botStatus } = useWebSocket();
useInfoTask(ws);
return (
<div className="row justify-content-end my-3">
<div className="col-auto">
<div className="border rounded-3 p-3 bg-secondary-subtle">
<h5 className="text-center">Status Messages</h5>
{botStatus && <div>status: {botStatus}</div>}
{error && <div>error: {error}</div>}
{message && <div>message: {message}</div>}
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,38 @@
@import "../../node_modules/bootstrap/scss/bootstrap.scss";
:root {
--slider-color: var(--bs-primary);
--slider-background-color: var(--bs-primary-bg-subtle);
}
.slider {
height: 15px;
border-radius: 5px;
background: var(--slider-background-color);
outline: none;
transition: opacity 0.2s;
opacity: .5;
&:hover {
opacity: 1;
}
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 25px;
height: 25px;
border-radius: 50%;
background: var(--slider-color);
cursor: pointer;
}
&::-moz-range-thumb {
width: 25px;
height: 25px;
border-radius: 50%;
background: var(--slider-color);
cursor: pointer;
}
}

View File

@@ -0,0 +1,44 @@
import { ChangeEvent, FC, useEffect, useState } from "react";
import "./Slider.scss";
interface SliderProps {
min: number;
max: number;
current: number;
onChange: (value: number) => void;
}
export const Slider: FC<SliderProps> = ({ min, max, current, onChange }) => {
const [localValue, setLocalValue] = useState<number>(current);
const [isDragging, setIsDragging] = useState<boolean>(false);
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setLocalValue(Number(e.target.value));
};
const handleMouseDown = () => {
setIsDragging(true);
};
const handleMouseUp = () => {
setIsDragging(false);
onChange(localValue);
};
useEffect(() => {
if (!isDragging) setLocalValue(current);
}, [current, isDragging]);
return (
<div className="w-100">
<input
type="range"
min={min}
max={max}
value={localValue}
onChange={handleChange}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
className="slider w-100"
/>
</div>
);
};

View File

@@ -0,0 +1,8 @@
@import "../../node_modules/bootstrap/scss/bootstrap.scss";
.songListItem {
height: 3em;
&:hover {
@extend .bg-dark-subtle;
}
}

View File

@@ -0,0 +1,63 @@
import { useWebSocket } from "../contexts/useWebSocket";
import classes from "./SongQueue.module.scss";
export const SongQueue = () => {
const { songQueue, sendMessage } = useWebSocket();
return (
<div>
{songQueue && (
<div>
<ul className="list-group">
{songQueue.song_file_list.map((s, i) => {
const isCurrent = i === songQueue.position;
return (
<li
key={i}
className={` list-group-item m-0 p-0 ${
isCurrent && "bg-primary-subtle"
} ${classes.songListItem}`}
>
<div className="row h-100">
<div className="col-1 text-end my-auto">
{!isCurrent && (
<i
className="bi bi-play-circle text-primary fs-3 "
role="button"
onClick={() => {
sendMessage({
action: "set_position",
position: i,
});
}}
></i>
)}
{isCurrent && (
<i
className="bi bi-pause-circle text-primary fs-3 "
role="button"
onClick={() => {
// send pause message
// sendMessage({
// action: "set_position",
// position: i,
// });
}}
></i>
)}
</div>
<div className="col my-auto">
{s.filename
.substring(s.filename.lastIndexOf("/") + 1)
.replace(".mp3", "")}
</div>
</div>
</li>
);
})}
</ul>
</div>
)}
</div>
);
};

View File

@@ -0,0 +1,17 @@
import { useEffect } from "react";
const updateInterval = 100;
const getPlaybackInfo = (ws: WebSocket) => {
ws.send(JSON.stringify({ action: "get_playback_info" }));
};
export const useInfoTask = (websocket?: WebSocket) => {
useEffect(() => {
const interval = setInterval(() => {
if(websocket)
getPlaybackInfo(websocket);
}, updateInterval);
return () => clearInterval(interval);
}, [websocket]);
};