This commit is contained in:
2025-08-14 11:40:02 -04:00
parent ccea5db9e9
commit dbb70fc743
7 changed files with 112 additions and 38 deletions

View File

@@ -14,7 +14,6 @@ const MediaRequestForm = lazy(() => import('./TRip/MediaRequestForm.jsx'));
const RequestManagement = lazy(() => import('./TRip/RequestManagement.jsx')); const RequestManagement = lazy(() => import('./TRip/RequestManagement.jsx'));
const Player = lazy(() => import('./AudioPlayer.jsx')); const Player = lazy(() => import('./AudioPlayer.jsx'));
export default function Root({ child }) { export default function Root({ child }) {
window.toast = toast; window.toast = toast;
const theme = document.documentElement.getAttribute("data-theme") const theme = document.documentElement.getAttribute("data-theme")

View File

@@ -11,7 +11,6 @@ const STATIONS = {
rock: { label: "Rock" }, rock: { label: "Rock" },
rap: { label: "Rap" }, rap: { label: "Rap" },
electronic: { label: "Electronic" }, electronic: { label: "Electronic" },
classical: { label: "Classical" },
pop: { label: "Pop" }, pop: { label: "Pop" },
}; };
@@ -84,8 +83,8 @@ export default function Player() {
const hls = new Hls({ const hls = new Hls({
lowLatencyMode: true, lowLatencyMode: true,
abrEnabled: false, abrEnabled: false,
liveSyncDuration: 2.5, // seconds behind live edge target liveSyncDuration: 1.0, // seconds behind live edge target
liveMaxLatencyDuration: 3.5, // max allowed latency before catchup liveMaxLatencyDuration: 2.0, // max allowed latency before catchup
liveCatchUpPlaybackRate: 1.05, // playback speed when catching up liveCatchUpPlaybackRate: 1.05, // playback speed when catching up
}); });
@@ -111,8 +110,6 @@ export default function Player() {
// Update elapsed time smoothly // Update elapsed time smoothly
useEffect(() => { useEffect(() => {
if (!isPlaying) return;
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
const now = Date.now(); const now = Date.now();
const deltaSec = (now - lastUpdateTimestamp.current) / 1000; const deltaSec = (now - lastUpdateTimestamp.current) / 1000;

View File

@@ -1,10 +1,10 @@
--- ---
import { metaData, API_URL } from "../config"; import { metaData, ENVIRONMENT } from "../config";
import RandomMsg from "../components/RandomMsg"; import RandomMsg from "../components/RandomMsg";
import { buildTime, buildNumber } from '../utils/buildTime.js'; import { buildTime, buildNumber } from '../utils/buildTime.js';
const YEAR = new Date().getFullYear(); const YEAR = new Date().getFullYear();
var ENVIRONMENT = (Astro.url.hostname === "localhost") ? "Dev": "Prod";
--- ---

View File

@@ -4,7 +4,7 @@ import { Button } from "@mui/joy";
import { Accordion, AccordionTab } from "primereact/accordion"; import { Accordion, AccordionTab } from "primereact/accordion";
import { AutoComplete } from "primereact/autocomplete"; import { AutoComplete } from "primereact/autocomplete";
import BreadcrumbNav from "./BreadcrumbNav"; import BreadcrumbNav from "./BreadcrumbNav";
import { API_URL } from "@/config"; import { API_URL, ENVIRONMENT } from "@/config";
export default function MediaRequestForm() { export default function MediaRequestForm() {
const [type, setType] = useState("artist"); const [type, setType] = useState("artist");
@@ -25,6 +25,8 @@ export default function MediaRequestForm() {
const debounceTimeout = useRef(null); const debounceTimeout = useRef(null);
const autoCompleteRef = useRef(null); const autoCompleteRef = useRef(null);
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); // Helper for delays
// Helper fetch wrapper that includes cookies automatically // Helper fetch wrapper that includes cookies automatically
const authFetch = async (url, options = {}) => { const authFetch = async (url, options = {}) => {
const opts = { const opts = {
@@ -55,10 +57,17 @@ export default function MediaRequestForm() {
debounceTimeout.current = setTimeout(async () => { debounceTimeout.current = setTimeout(async () => {
try { try {
// Ensure at least 600ms between actual requests
const now = Date.now();
if (!searchArtists.lastCall) searchArtists.lastCall = 0;
const elapsed = now - searchArtists.lastCall;
const minDelay = 600; // ms
if (elapsed < minDelay) await delay(minDelay - elapsed);
searchArtists.lastCall = Date.now();
const res = await authFetch( const res = await authFetch(
`${API_URL}/trip/get_artists_by_name?artist=${encodeURIComponent( `${API_URL}/trip/get_artists_by_name?artist=${encodeURIComponent(query)}`
query
)}`,
); );
if (!res.ok) throw new Error("API error"); if (!res.ok) throw new Error("API error");
const data = await res.json(); const data = await res.json();
@@ -67,9 +76,11 @@ export default function MediaRequestForm() {
toast.error("Failed to fetch artist suggestions."); toast.error("Failed to fetch artist suggestions.");
setArtistSuggestions([]); setArtistSuggestions([]);
} }
}, 300); }, 500); // debounce 500ms
}; };
//helpers for string truncation //helpers for string truncation
const truncate = (text, maxLen) => const truncate = (text, maxLen) =>
maxLen <= 3 maxLen <= 3
@@ -205,18 +216,23 @@ export default function MediaRequestForm() {
if (type !== "artist" || albums.length === 0) return; if (type !== "artist" || albums.length === 0) return;
let isCancelled = false; let isCancelled = false;
const albumsToFetch = albums.filter((a) => !tracksByAlbum[a.id]); const albumsToFetch = albums.filter((a) => !tracksByAlbum[a.id]);
if (albumsToFetch.length === 0) return; if (albumsToFetch.length === 0) return;
const fetchTracksSequentially = async () => { const fetchTracksSequentially = async () => {
const minDelay = 600; // ms between API requests
for (const album of albumsToFetch) { for (const album of albumsToFetch) {
if (isCancelled) break; if (isCancelled) break;
setLoadingAlbumId(album.id); setLoadingAlbumId(album.id);
try { try {
const now = Date.now();
if (!fetchTracksSequentially.lastCall) fetchTracksSequentially.lastCall = 0;
const elapsed = now - fetchTracksSequentially.lastCall;
if (elapsed < minDelay) await delay(minDelay - elapsed);
fetchTracksSequentially.lastCall = Date.now();
const res = await authFetch(`${API_URL}/trip/get_tracks_by_album_id/${album.id}`); const res = await authFetch(`${API_URL}/trip/get_tracks_by_album_id/${album.id}`);
if (!res.ok) throw new Error("API error"); if (!res.ok) throw new Error("API error");
const data = await res.json(); const data = await res.json();
@@ -299,7 +315,12 @@ export default function MediaRequestForm() {
try { try {
// Example: simulate submission delay // Example: simulate submission delay
await new Promise((resolve) => setTimeout(resolve, 1500)); await new Promise((resolve) => setTimeout(resolve, 1500));
toast.success("Request submitted!"); const allSelectedIds = Object.values(selectedTracks)
.filter(arr => Array.isArray(arr)) // skip null entries
.flat();
toast.success(`Request submitted! (${allSelectedIds.length} tracks)`);
console.debug("Requested: ", selectedTracks);
console.debug("Flattened: ", allSelectedIds);
} catch (err) { } catch (err) {
toast.error("Failed to submit request."); toast.error("Failed to submit request.");
} finally { } finally {
@@ -310,23 +331,75 @@ export default function MediaRequestForm() {
return ( return (
<div className="max-w-3xl mx-auto my-10 p-6 rounded-xl shadow-md bg-white dark:bg-neutral-900 text-neutral-900 dark:text-neutral-100 border border-neutral-200 dark:border-neutral-700"> <div className="max-w-3xl mx-auto my-10 p-6 rounded-xl shadow-md bg-white dark:bg-neutral-900 text-neutral-900 dark:text-neutral-100 border border-neutral-200 dark:border-neutral-700">
<style>{` <style>{`
.p-accordion-tab { /* Accordion tab backgrounds & text */
background-color: #ffffff; .p-accordion-tab {
color: #000000; background-color: #ffffff;
} color: #000000;
[data-theme="dark"] .p-accordion-tab { }
background-color: #1e1e1e; [data-theme="dark"] .p-accordion-tab {
color: #ffffff; background-color: #1e1e1e;
} color: #ffffff;
[data-theme="dark"] .p-accordion-header .p-accordion-header-link { }
background-color: #1e1e1e !important;
color: #ffffff !important; /* Accordion header link */
} .p-accordion-header .p-accordion-header-link {
[data-theme="dark"] .p-accordion-content { background-color: #f9f9f9;
background-color: #2a2a2a; color: #000000;
color: #ffffff; }
} [data-theme="dark"] .p-accordion-header .p-accordion-header-link {
`}</style> background-color: #1e1e1e !important;
color: #ffffff !important;
}
/* Accordion content panel */
.p-accordion-content {
background-color: #fafafa;
color: #000000;
}
[data-theme="dark"] .p-accordion-content {
background-color: #2a2a2a;
color: #ffffff;
}
/* Track list UL/LI styling */
.p-accordion-content ul {
padding-left: 0;
margin: 0;
list-style: none;
}
[data-theme="dark"] .p-accordion-content ul {
color: #ffffff;
}
/* Track items */
.p-accordion-content li {
background-color: #fff;
border-bottom: 1px solid #e5e5e5;
}
[data-theme="dark"] .p-accordion-content li {
background-color: #2a2a2a;
border-bottom: 1px solid #444;
}
/* Checkboxes inside track list */
.p-accordion-content input[type="checkbox"] {
accent-color: #1d4ed8; /* optional for consistent dark mode styling */
}
/* Loading spinner (optional darker style) */
[data-theme="dark"] .animate-spin {
border-color: #555;
border-top-color: #1d4ed8;
}
/* Small text like audio quality, duration, version */
.p-accordion-content span {
color: #555;
}
[data-theme="dark"] .p-accordion-content span {
color: #aaa;
}
`}</style>
<BreadcrumbNav currentPage="request" /> <BreadcrumbNav currentPage="request" />
@@ -369,6 +442,7 @@ export default function MediaRequestForm() {
field="artist" field="artist"
completeMethod={searchArtists} completeMethod={searchArtists}
onChange={handleArtistChange} onChange={handleArtistChange}
minLength={3}
placeholder="Artist" placeholder="Artist"
dropdown dropdown
className="w-full" className="w-full"
@@ -454,13 +528,13 @@ export default function MediaRequestForm() {
checked={selected?.includes(String(track.id))} checked={selected?.includes(String(track.id))}
onChange={() => toggleTrack(id, track.id)} onChange={() => toggleTrack(id, track.id)}
className="cursor-pointer" className="cursor-pointer"
aria-label={`Select track ${track.title}`} aria-label={`Select track ${track.title} `}
/> />
<button <button
type="button" type="button"
onClick={() => handleTrackClick(track.id, selectedArtist.artist, track.title)} onClick={() => handleTrackClick(track.id, selectedArtist.artist, track.title)}
className="font-medium text-blue-600 hover:underline cursor-pointer bg-transparent border-none p-0" className="font-medium text-blue-600 hover:underline cursor-pointer bg-transparent border-none p-0"
aria-label={`Download track ${track.title}`} aria-label={`Download track ${track.title} `}
> >
{track.title} {track.title}
</button> </button>

View File

@@ -13,4 +13,5 @@ export const socialLinks = {
}; };
export const MAJOR_VERSION = "0.0" export const MAJOR_VERSION = "0.0"
export const RELEASE_FLAG = "Alpha"; export const RELEASE_FLAG = "Alpha";
export const ENVIRONMENT = import.meta.env.DEV ? "Dev" : "Prod";

View File

@@ -1,8 +1,9 @@
--- ---
import MediaRequestForm from "@/components/qs2/MediaRequestForm" import MediaRequestForm from "@/components/TRip/MediaRequestForm"
import Base from "@/layouts/Base.astro"; import Base from "@/layouts/Base.astro";
import Root from "@/components/AppLayout.jsx"; import Root from "@/components/AppLayout.jsx";
import { verifyToken } from "@/utils/jwt"; import { verifyToken } from "@/utils/jwt";
import { ENVIRONMENT } from "@/config";
const token = Astro.cookies.get("access_token")?.value; const token = Astro.cookies.get("access_token")?.value;
let user = null; let user = null;

View File

@@ -3,14 +3,16 @@ import MediaRequestForm from "@/components/TRip/MediaRequestForm"
import Base from "@/layouts/Base.astro"; import Base from "@/layouts/Base.astro";
import Root from "@/components/AppLayout.jsx"; import Root from "@/components/AppLayout.jsx";
import { verifyToken } from "@/utils/jwt"; import { verifyToken } from "@/utils/jwt";
import { ENVIRONMENT } from "@/config";
const token = Astro.cookies.get("access_token")?.value; const token = Astro.cookies.get("access_token")?.value;
let user = null; let user = null;
try { try {
const IS_DEV = (ENVIRONMENT==="Dev");
if (token) { if (token) {
user = verifyToken(token); user = verifyToken(token);
if (user) { if (IS_DEV) {
console.log("Verified!", user); console.log("Verified!", user);
} else { } else {
throw Error("Authentication required"); throw Error("Authentication required");