Enhance AudioPlayer functionality with improved error handling, toast notifications, and refined queue management. Update typeahead options handling, adjust track metadata defaults, and optimize DJ controls layout. Introduce new methods for handling song requests and queue operations.
This commit is contained in:
@@ -7,7 +7,8 @@ import { Dialog } from "primereact/dialog";
|
|||||||
import { AutoComplete } from "primereact/autocomplete";
|
import { AutoComplete } from "primereact/autocomplete";
|
||||||
import { DataTable } from "primereact/datatable";
|
import { DataTable } from "primereact/datatable";
|
||||||
import { Column } from "primereact/column";
|
import { Column } from "primereact/column";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { ENVIRONMENT } from "../config";
|
||||||
import { API_URL } from "@/config";
|
import { API_URL } from "@/config";
|
||||||
import { authFetch } from "@/utils/authFetch";
|
import { authFetch } from "@/utils/authFetch";
|
||||||
import { requireAuthHook } from "@/hooks/requireAuthHook";
|
import { requireAuthHook } from "@/hooks/requireAuthHook";
|
||||||
@@ -65,8 +66,9 @@ export default function Player({ user }) {
|
|||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.debug('Typeahead: response data', data);
|
console.debug('Typeahead: response data', data);
|
||||||
setTypeaheadOptions(data.options || []);
|
// Accept bare array or { options: [...] }
|
||||||
console.debug('Typeahead: setTypeaheadOptions', data.options || []);
|
setTypeaheadOptions(Array.isArray(data) ? data : data.options || []);
|
||||||
|
console.debug('Typeahead: setTypeaheadOptions', Array.isArray(data) ? data : data.options || []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Typeahead: error', error);
|
console.error('Typeahead: error', error);
|
||||||
setTypeaheadOptions([]);
|
setTypeaheadOptions([]);
|
||||||
@@ -74,9 +76,9 @@ export default function Player({ user }) {
|
|||||||
};
|
};
|
||||||
console.debug('AudioPlayer user:', user);
|
console.debug('AudioPlayer user:', user);
|
||||||
if (user) {
|
if (user) {
|
||||||
console.debug('isAuthenticated:', user.isAuthenticated);
|
console.debug('isAuthenticated:', user);
|
||||||
console.debug('roles:', user.roles);
|
console.debug('roles:', user.roles);
|
||||||
console.debug('isDJ:', user.isAuthenticated && Array.isArray(user.roles) && user.roles.includes('dj'));
|
console.debug('isDJ:', user && Array.isArray(user.roles) && user.roles.includes('dj'));
|
||||||
}
|
}
|
||||||
const theme = useHtmlThemeAttr();
|
const theme = useHtmlThemeAttr();
|
||||||
|
|
||||||
@@ -219,6 +221,7 @@ export default function Player({ user }) {
|
|||||||
|
|
||||||
// ...existing code...
|
// ...existing code...
|
||||||
|
|
||||||
|
|
||||||
// ...existing code...
|
// ...existing code...
|
||||||
|
|
||||||
|
|
||||||
@@ -274,8 +277,8 @@ export default function Player({ user }) {
|
|||||||
// If station changed while request was in-flight, ignore the response
|
// If station changed while request was in-flight, ignore the response
|
||||||
if (requestStation !== activeStationRef.current) return;
|
if (requestStation !== activeStationRef.current) return;
|
||||||
|
|
||||||
if (trackData.artist === 'N/A') {
|
if (!trackData.song || trackData.song === 'N/A') {
|
||||||
setTrackTitle('Offline');
|
setTrackTitle('No track playing');
|
||||||
setLyrics([]);
|
setLyrics([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -285,6 +288,9 @@ export default function Player({ user }) {
|
|||||||
|
|
||||||
setTrackMetadata(trackData, requestStation);
|
setTrackMetadata(trackData, requestStation);
|
||||||
|
|
||||||
|
// Refresh queue when track changes
|
||||||
|
fetchQueue();
|
||||||
|
|
||||||
const lyricsResponse = await fetch(`${API_URL}/lyric/search`, {
|
const lyricsResponse = await fetch(`${API_URL}/lyric/search`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
@@ -317,16 +323,16 @@ export default function Player({ user }) {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching metadata:', error);
|
console.error('Error fetching metadata:', error);
|
||||||
setTrackTitle('Offline');
|
setTrackTitle('Error fetching track');
|
||||||
setLyrics([]);
|
setLyrics([]);
|
||||||
}
|
}
|
||||||
}, [activeStation]);
|
}, [activeStation]);
|
||||||
|
|
||||||
const setTrackMetadata = useCallback((trackData, requestStation) => {
|
const setTrackMetadata = useCallback((trackData, requestStation) => {
|
||||||
setTrackTitle(trackData.song);
|
setTrackTitle(trackData.song || 'Unknown Title');
|
||||||
setTrackArtist(trackData.artist);
|
setTrackArtist(trackData.artist || 'Unknown Artist');
|
||||||
setTrackGenre(trackData.genre || '');
|
setTrackGenre(trackData.genre || '');
|
||||||
setTrackAlbum(trackData.album);
|
setTrackAlbum(trackData.album || 'Unknown Album');
|
||||||
setCoverArt(`${API_URL}/radio/album_art?station=${requestStation}&_=${Date.now()}`);
|
setCoverArt(`${API_URL}/radio/album_art?station=${requestStation}&_=${Date.now()}`);
|
||||||
|
|
||||||
baseTrackElapsed.current = trackData.elapsed;
|
baseTrackElapsed.current = trackData.elapsed;
|
||||||
@@ -358,19 +364,26 @@ export default function Player({ user }) {
|
|||||||
// ...existing code...
|
// ...existing code...
|
||||||
|
|
||||||
|
|
||||||
const handleSkip = async (skipTo = null) => {
|
const handleSkip = async (uuid = null) => {
|
||||||
try {
|
try {
|
||||||
const response = await authFetch(`${API_URL}/radio/skip`, {
|
const response = await authFetch(`${API_URL}/radio/skip`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ station: activeStation, skipTo }),
|
body: JSON.stringify({
|
||||||
|
skipTo: uuid,
|
||||||
|
station: activeStation,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (!data.success) {
|
if (data.success) {
|
||||||
console.error("Failed to skip track.", data);
|
toast.success("OK!");
|
||||||
|
fetchQueue();
|
||||||
|
} else {
|
||||||
|
toast.error("Skip failed.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error skipping track:", error);
|
console.error("Error skipping track:", error);
|
||||||
|
toast.error("Skip failed.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -379,62 +392,127 @@ export default function Player({ user }) {
|
|||||||
const response = await authFetch(`${API_URL}/radio/reshuffle`, {
|
const response = await authFetch(`${API_URL}/radio/reshuffle`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ station: activeStation }),
|
body: JSON.stringify({
|
||||||
|
station: activeStation,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (!data.ok) {
|
if (data.ok) {
|
||||||
console.error("Failed to reshuffle queue.", data);
|
toast.success("OK!");
|
||||||
|
fetchQueue();
|
||||||
|
} else {
|
||||||
|
toast.error("Reshuffle failed.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error reshuffling queue:", error);
|
console.error("Error reshuffling queue:", error);
|
||||||
|
toast.error("Reshuffle failed.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleQueueShift = async (uuid, playNow = false) => {
|
const handleQueueShift = async (uuid, next = false) => {
|
||||||
try {
|
try {
|
||||||
const response = await authFetch(`${API_URL}/radio/queue_shift`, {
|
const response = await authFetch(`${API_URL}/radio/queue_shift`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ station: activeStation, uuid, playNow }),
|
body: JSON.stringify({
|
||||||
|
uuid,
|
||||||
|
next,
|
||||||
|
station: activeStation,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (!data.ok) {
|
if (!data.err) {
|
||||||
console.error("Failed to shift queue.", data);
|
toast.success("OK!");
|
||||||
|
fetchQueue();
|
||||||
|
} else {
|
||||||
|
toast.error("Queue shift failed.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error shifting queue:", error);
|
console.error("Error shifting queue:", error);
|
||||||
|
toast.error("Queue shift failed.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleQueueRemove = async (uuid) => {
|
const handlePlayNow = async (artistSong, next = false) => {
|
||||||
try {
|
const toastId = "playNowToast"; // Unique ID for this toast
|
||||||
const response = await authFetch(`${API_URL}/radio/queue_remove`, {
|
if (!toast.isActive(toastId)) {
|
||||||
method: "POST",
|
toast.info("Trying...", { toastId });
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ station: activeStation, uuid }),
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
if (!data.ok) {
|
|
||||||
console.error("Failed to remove from queue.", data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error removing from queue:", error);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const handleSongRequest = async (artist, song) => {
|
|
||||||
try {
|
try {
|
||||||
const response = await authFetch(`${API_URL}/radio/request`, {
|
const response = await authFetch(`${API_URL}/radio/request`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ station: activeStation, artist, song }),
|
body: JSON.stringify({
|
||||||
|
artistsong: artistSong,
|
||||||
|
alsoSkip: !next,
|
||||||
|
station: activeStation,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (!data.result) {
|
if (data.result) {
|
||||||
console.error("Failed to request song.", data);
|
setRequestInput(""); // Clear the input immediately
|
||||||
|
toast.update(toastId, { render: "OK!", type: "success", autoClose: 2000 });
|
||||||
|
fetchQueue();
|
||||||
|
} else {
|
||||||
|
toast.update(toastId, { render: "Play Now failed.", type: "error", autoClose: 3000 });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error playing song immediately:", error);
|
||||||
|
toast.update(toastId, { render: "Play Now failed.", type: "error", autoClose: 3000 });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSongRequest = async (artistSong) => {
|
||||||
|
const toastId = "songRequestToast"; // Unique ID for this toast
|
||||||
|
if (!toast.isActive(toastId)) {
|
||||||
|
toast.info("Trying...", { toastId });
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await authFetch(`${API_URL}/radio/request`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
artistsong: artistSong,
|
||||||
|
alsoSkip: false, // Ensure alsoSkip is false for requests
|
||||||
|
station: activeStation,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.result) {
|
||||||
|
setRequestInput(""); // Clear the input immediately
|
||||||
|
toast.update(toastId, { render: "OK!", type: "success", autoClose: 2000 });
|
||||||
|
fetchQueue();
|
||||||
|
} else {
|
||||||
|
toast.update(toastId, { render: "Song request failed.", type: "error", autoClose: 3000 });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error requesting song:", error);
|
console.error("Error requesting song:", error);
|
||||||
|
toast.update(toastId, { render: "Song request failed.", type: "error", autoClose: 3000 });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveFromQueue = async (uuid) => {
|
||||||
|
console.debug("handleRemoveFromQueue called with uuid:", uuid); // Debugging log
|
||||||
|
try {
|
||||||
|
const response = await authFetch(`${API_URL}/radio/queue_remove`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
uuid,
|
||||||
|
station: activeStation,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
console.debug("handleRemoveFromQueue response:", data); // Debugging log
|
||||||
|
if (!data.err) {
|
||||||
|
toast.success("OK!");
|
||||||
|
fetchQueue();
|
||||||
|
} else {
|
||||||
|
toast.error("Remove from queue failed.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error removing from queue:", error);
|
||||||
|
toast.error("Remove from queue failed.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -454,7 +532,12 @@ export default function Player({ user }) {
|
|||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setQueueData(data.items || []);
|
setQueueData(data.items || []);
|
||||||
setQueueTotalRecords(data.total || data.totalRecords || 0);
|
// Use recordsFiltered for search, otherwise recordsTotal
|
||||||
|
setQueueTotalRecords(
|
||||||
|
typeof search === 'string' && search.length > 0
|
||||||
|
? data.recordsFiltered ?? data.recordsTotal ?? 0
|
||||||
|
: data.recordsTotal ?? 0
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching queue:", error);
|
console.error("Error fetching queue:", error);
|
||||||
}
|
}
|
||||||
@@ -467,7 +550,7 @@ export default function Player({ user }) {
|
|||||||
}, [isQueueVisible, queuePage, queueRows, queueSearch]);
|
}, [isQueueVisible, queuePage, queueRows, queueSearch]);
|
||||||
|
|
||||||
// Always define queueFooter, fallback to Close button if user is not available
|
// Always define queueFooter, fallback to Close button if user is not available
|
||||||
const isDJ = user && Array.isArray(user.roles) && user.roles.includes('dj');
|
const isDJ = (user && user.roles.includes('dj')) || ENVIRONMENT === "Dev";
|
||||||
const queueFooter = isDJ
|
const queueFooter = isDJ
|
||||||
? (
|
? (
|
||||||
<div className="flex gap-2 justify-end">
|
<div className="flex gap-2 justify-end">
|
||||||
@@ -514,7 +597,7 @@ export default function Player({ user }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="c-container">
|
<div className="c-container">
|
||||||
{user && <span className="text-lg font-semibold">Hello, {user.user}</span>}
|
{/* {user && <span className="text-lg font-semibold">Hello, {user.user}</span>} */}
|
||||||
|
|
||||||
<div className="music-container mt-8">
|
<div className="music-container mt-8">
|
||||||
<section className="album-cover">
|
<section className="album-cover">
|
||||||
@@ -554,69 +637,74 @@ export default function Player({ user }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isDJ && (
|
{isDJ && (
|
||||||
<div className="dj-controls mt-6 flex gap-1 justify-center items-center flex-wrap">
|
<div className="dj-controls mt-6 flex flex-wrap justify-center items-center gap-2" style={{ minWidth: '24rem', maxWidth: '100%', alignItems: 'flex-start' }}>
|
||||||
<AutoComplete
|
<div className="flex flex-col items-center gap-2 w-full" style={{ minWidth: '24rem' }}>
|
||||||
value={requestInput}
|
<AutoComplete
|
||||||
suggestions={typeaheadOptions}
|
value={requestInput}
|
||||||
completeMethod={(e) => {
|
suggestions={typeaheadOptions}
|
||||||
console.debug('AutoComplete: completeMethod called', e);
|
completeMethod={(e) => {
|
||||||
fetchTypeahead(e.query);
|
console.debug('AutoComplete: completeMethod called', e);
|
||||||
}}
|
fetchTypeahead(e.query);
|
||||||
onChange={e => {
|
}}
|
||||||
console.debug('AutoComplete: onChange', e);
|
onChange={e => {
|
||||||
setRequestInput(e.target.value);
|
console.debug('AutoComplete: onChange', e);
|
||||||
}}
|
setRequestInput(e.target.value);
|
||||||
onShow={() => {
|
}}
|
||||||
setTimeout(() => {
|
onShow={() => {
|
||||||
const panel = document.querySelector('.p-autocomplete-panel');
|
setTimeout(() => {
|
||||||
const items = panel?.querySelector('.p-autocomplete-items');
|
const panel = document.querySelector('.p-autocomplete-panel');
|
||||||
console.debug('AutoComplete: onShow panel', panel);
|
const items = panel?.querySelector('.p-autocomplete-items');
|
||||||
if (items) {
|
console.debug('AutoComplete: onShow panel', panel);
|
||||||
items.style.maxHeight = '200px';
|
if (items) {
|
||||||
items.style.overflowY = 'auto';
|
items.style.maxHeight = '200px';
|
||||||
items.style.overscrollBehavior = 'contain';
|
items.style.overflowY = 'auto';
|
||||||
const wheelHandler = (e) => {
|
items.style.overscrollBehavior = 'contain';
|
||||||
const delta = e.deltaY;
|
const wheelHandler = (e) => {
|
||||||
const atTop = items.scrollTop === 0;
|
const delta = e.deltaY;
|
||||||
const atBottom = items.scrollTop + items.clientHeight >= items.scrollHeight;
|
const atTop = items.scrollTop === 0;
|
||||||
if ((delta < 0 && atTop) || (delta > 0 && atBottom)) {
|
const atBottom = items.scrollTop + items.clientHeight >= items.scrollHeight;
|
||||||
e.preventDefault();
|
if ((delta < 0 && atTop) || (delta > 0 && atBottom)) {
|
||||||
} else {
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
} else {
|
||||||
}
|
e.stopPropagation();
|
||||||
};
|
}
|
||||||
items.removeEventListener('wheel', wheelHandler);
|
};
|
||||||
items.addEventListener('wheel', wheelHandler, { passive: false });
|
items.removeEventListener('wheel', wheelHandler);
|
||||||
}
|
items.addEventListener('wheel', wheelHandler, { passive: false });
|
||||||
}, 0);
|
}
|
||||||
}}
|
}, 0);
|
||||||
placeholder="Request a song..."
|
}}
|
||||||
inputStyle={{
|
placeholder="Request a song..."
|
||||||
width: '14rem',
|
inputStyle={{
|
||||||
padding: '0.25rem 0.5rem',
|
width: '24rem',
|
||||||
borderRadius: '9999px',
|
minWidth: '24rem',
|
||||||
border: '1px solid #d1d5db',
|
padding: '0.35rem 0.75rem',
|
||||||
fontSize: '0.85rem',
|
borderRadius: '9999px',
|
||||||
fontWeight: 'bold',
|
border: '1px solid #d1d5db',
|
||||||
}}
|
fontSize: '1rem',
|
||||||
className="typeahead-input"
|
fontWeight: 'bold',
|
||||||
forceSelection={false}
|
}}
|
||||||
/>
|
className="typeahead-input"
|
||||||
<button className="px-3 py-1 rounded-full bg-blue-400 text-white hover:bg-blue-500 text-xs font-bold"
|
forceSelection={false}
|
||||||
onClick={() => handleSongRequest(requestInputArtist, requestInputSong)}>
|
/>
|
||||||
Request
|
<div className="flex flex-row flex-wrap justify-center gap-2 items-center" style={{ minWidth: '24rem' }}>
|
||||||
</button>
|
<button className="px-3 py-1 rounded-full bg-blue-400 text-white hover:bg-blue-500 text-xs font-bold"
|
||||||
<button className="px-3 py-1 rounded-full bg-green-400 text-white hover:bg-green-500 text-xs font-bold"
|
onClick={() => handleSongRequest(requestInput)}>
|
||||||
onClick={() => handleQueueShift(requestInputUuid, true)}>
|
Request
|
||||||
Play Now
|
</button>
|
||||||
</button>
|
<button className="px-3 py-1 rounded-full bg-green-400 text-white hover:bg-green-500 text-xs font-bold"
|
||||||
<button className="px-3 py-1 rounded-full bg-red-400 text-white hover:bg-red-500 text-xs font-bold" onClick={() => handleSkip()}>Skip</button>
|
onClick={() => handlePlayNow(requestInput)}>
|
||||||
<button
|
Play Now
|
||||||
className="px-3 py-1 rounded-full bg-gray-400 text-white hover:bg-gray-500 text-xs font-bold"
|
</button>
|
||||||
onClick={() => setQueueVisible(true)}
|
<button className="px-3 py-1 rounded-full bg-red-400 text-white hover:bg-red-500 text-xs font-bold" onClick={() => handleSkip()}>Skip</button>
|
||||||
>
|
<button
|
||||||
Queue
|
className="px-3 py-1 rounded-full bg-gray-400 text-white hover:bg-gray-500 text-xs font-bold"
|
||||||
</button>
|
onClick={() => setQueueVisible(true)}
|
||||||
|
>
|
||||||
|
Queue
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Always show play/pause button */}
|
{/* Always show play/pause button */}
|
||||||
@@ -646,12 +734,13 @@ export default function Player({ user }) {
|
|||||||
<audio ref={audioElement} preload="none" />
|
<audio ref={audioElement} preload="none" />
|
||||||
|
|
||||||
<Dialog
|
<Dialog
|
||||||
header="Queue"
|
header={`Queue - ${activeStation}`}
|
||||||
visible={isQueueVisible}
|
visible={isQueueVisible}
|
||||||
style={{ width: "80vw", maxWidth: "1200px", height: "auto", maxHeight: "90vh" }}
|
style={{ width: "80vw", maxWidth: "1200px", height: "auto", maxHeight: "90vh" }}
|
||||||
footer={queueFooter}
|
footer={queueFooter}
|
||||||
onHide={() => setQueueVisible(false)}
|
onHide={() => setQueueVisible(false)}
|
||||||
className={theme === "dark" ? "dark-theme" : "light-theme"}
|
className={theme === "dark" ? "dark-theme" : "light-theme"}
|
||||||
|
dismissableMask={true}
|
||||||
>
|
>
|
||||||
<div style={{ maxHeight: "calc(90vh - 100px)", overflow: "visible" }}>
|
<div style={{ maxHeight: "calc(90vh - 100px)", overflow: "visible" }}>
|
||||||
<div className="mb-2 flex justify-end">
|
<div className="mb-2 flex justify-end">
|
||||||
@@ -726,21 +815,24 @@ export default function Player({ user }) {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="px-2 py-1 rounded bg-yellow-400 text-neutral-900 hover:bg-yellow-500 text-xs shadow-md cursor-pointer font-bold font-sans"
|
className="px-2 py-1 rounded bg-yellow-400 text-neutral-900 hover:bg-yellow-500 text-xs shadow-md cursor-pointer font-bold font-sans"
|
||||||
onClick={() => handleQueueShift(rowData.uuid, true)}
|
onClick={() => handleQueueShift(rowData.uuid, true, false)}
|
||||||
title="Play"
|
title="Play"
|
||||||
>
|
>
|
||||||
Play
|
Play
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="px-2 py-1 rounded bg-green-400 text-neutral-900 hover:bg-green-500 text-xs shadow-md cursor-pointer font-bold font-sans"
|
className="px-2 py-1 rounded bg-green-400 text-neutral-900 hover:bg-green-500 text-xs shadow-md cursor-pointer font-bold font-sans"
|
||||||
onClick={() => handleQueueShift(rowData.uuid)}
|
onClick={() => handleQueueShift(rowData.uuid, false, true)}
|
||||||
title="Play Next"
|
title="Play Next"
|
||||||
>
|
>
|
||||||
Play Next
|
Play Next
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="px-2 py-1 rounded bg-red-400 text-neutral-900 hover:bg-red-500 text-xs shadow-md cursor-pointer font-bold font-sans"
|
className="px-2 py-1 rounded bg-red-400 text-neutral-900 hover:bg-red-500 text-xs shadow-md cursor-pointer font-bold font-sans"
|
||||||
onClick={() => handleQueueRemove(rowData.uuid)}
|
onClick={() => {
|
||||||
|
console.debug("Remove button clicked for uuid:", rowData.uuid); // Debugging log
|
||||||
|
handleRemoveFromQueue(rowData.uuid);
|
||||||
|
}}
|
||||||
title="Remove"
|
title="Remove"
|
||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
|
|||||||
Reference in New Issue
Block a user