misc
This commit is contained in:
@@ -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")
|
||||||
|
@@ -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;
|
||||||
|
@@ -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";
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@@ -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,6 +331,7 @@ 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>{`
|
||||||
|
/* Accordion tab backgrounds & text */
|
||||||
.p-accordion-tab {
|
.p-accordion-tab {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
@@ -318,14 +340,65 @@ export default function MediaRequestForm() {
|
|||||||
background-color: #1e1e1e;
|
background-color: #1e1e1e;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Accordion header link */
|
||||||
|
.p-accordion-header .p-accordion-header-link {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
[data-theme="dark"] .p-accordion-header .p-accordion-header-link {
|
[data-theme="dark"] .p-accordion-header .p-accordion-header-link {
|
||||||
background-color: #1e1e1e !important;
|
background-color: #1e1e1e !important;
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Accordion content panel */
|
||||||
|
.p-accordion-content {
|
||||||
|
background-color: #fafafa;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
[data-theme="dark"] .p-accordion-content {
|
[data-theme="dark"] .p-accordion-content {
|
||||||
background-color: #2a2a2a;
|
background-color: #2a2a2a;
|
||||||
color: #ffffff;
|
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>
|
`}</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>
|
||||||
|
@@ -14,3 +14,4 @@ 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";
|
@@ -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;
|
||||||
|
@@ -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");
|
||||||
|
Reference in New Issue
Block a user