misc
This commit is contained in:
@@ -7,6 +7,7 @@ export default function BreadcrumbNav({ currentPage }) {
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<nav aria-label="breadcrumb" className="mb-6 flex gap-4 text-sm font-medium text-blue-600 dark:text-blue-400">
|
||||
{pages.map(({ key, label, href }, i) => (
|
||||
<React.Fragment key={key}>
|
||||
@@ -20,6 +21,10 @@ export default function BreadcrumbNav({ currentPage }) {
|
||||
{i < pages.length - 1 && <span aria-hidden="true">/</span>}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</nav>
|
||||
</nav >
|
||||
<div class="mb-2">
|
||||
Self Service
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -123,6 +123,8 @@ export default function MediaRequestForm() {
|
||||
metadataFetchToastId.current = toast.info("Retrieving metadata...",
|
||||
{
|
||||
autoClose: false,
|
||||
progress: 0,
|
||||
closeOnClick: false,
|
||||
}
|
||||
);
|
||||
if (type === "artist") {
|
||||
@@ -222,6 +224,42 @@ export default function MediaRequestForm() {
|
||||
}
|
||||
};
|
||||
|
||||
const allTracksLoaded = albums.every(({ id }) => Array.isArray(tracksByAlbum[id]) && tracksByAlbum[id].length > 0);
|
||||
|
||||
const handleToggleAllAlbums = () => {
|
||||
const allSelected = albums.every(({ id }) => {
|
||||
const allTracks = tracksByAlbum[id] || [];
|
||||
return selectedTracks[id]?.length === allTracks.length && allTracks.length > 0;
|
||||
});
|
||||
|
||||
const newSelection = {};
|
||||
albums.forEach(({ id }) => {
|
||||
const allTracks = tracksByAlbum[id] || [];
|
||||
if (allSelected) {
|
||||
// Uncheck all
|
||||
newSelection[id] = [];
|
||||
} else {
|
||||
// Check all tracks in the album
|
||||
newSelection[id] = allTracks.map(track => String(track.id));
|
||||
}
|
||||
});
|
||||
setSelectedTracks(newSelection);
|
||||
};
|
||||
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
if (!allTracksLoaded) return; // prevent clicking before data ready
|
||||
handleToggleAllAlbums();
|
||||
}}
|
||||
className={`text-sm hover:underline cursor-pointer ${!allTracksLoaded ? "text-gray-400 dark:text-gray-500 pointer-events-none" : "text-blue-600"
|
||||
}`}
|
||||
>
|
||||
Check / Uncheck All Albums
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
// Sequentially fetch tracks for albums not loaded yet
|
||||
@@ -233,9 +271,14 @@ export default function MediaRequestForm() {
|
||||
if (albumsToFetch.length === 0) return;
|
||||
|
||||
const fetchTracksSequentially = async () => {
|
||||
const minDelay = 600; // ms between API requests
|
||||
const minDelay = 400; // ms between API requests
|
||||
setIsFetching(true);
|
||||
for (const album of albumsToFetch) {
|
||||
|
||||
const totalAlbums = albumsToFetch.length;
|
||||
|
||||
for (let index = 0; index < totalAlbums; index++) {
|
||||
const album = albumsToFetch[index];
|
||||
|
||||
if (isCancelled) break;
|
||||
|
||||
setLoadingAlbumId(album.id);
|
||||
@@ -263,14 +306,24 @@ export default function MediaRequestForm() {
|
||||
setTracksByAlbum((prev) => ({ ...prev, [album.id]: [] }));
|
||||
setSelectedTracks((prev) => ({ ...prev, [album.id]: [] }));
|
||||
}
|
||||
|
||||
// Update progress toast
|
||||
toast.update(metadataFetchToastId.current, {
|
||||
progress: (index + 1) / totalAlbums,
|
||||
render: `Retrieving metadata... (${index + 1} / ${totalAlbums})`,
|
||||
});
|
||||
}
|
||||
|
||||
setLoadingAlbumId(null);
|
||||
setIsFetching(false);
|
||||
try {
|
||||
toast.done(metadataFetchToastId.current);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
};
|
||||
|
||||
// Finish the toast
|
||||
toast.update(metadataFetchToastId.current, {
|
||||
render: "Metadata retrieved!",
|
||||
type: "success",
|
||||
progress: 1,
|
||||
autoClose: 1500,
|
||||
});
|
||||
};
|
||||
|
||||
fetchTracksSequentially();
|
||||
@@ -281,6 +334,7 @@ export default function MediaRequestForm() {
|
||||
}, [albums, type]);
|
||||
|
||||
|
||||
|
||||
// Toggle individual track checkbox
|
||||
const toggleTrack = (albumId, trackId) => {
|
||||
setSelectedTracks((prev) => {
|
||||
@@ -337,21 +391,36 @@ export default function MediaRequestForm() {
|
||||
}
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
// Example: simulate submission delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
const allSelectedIds = Object.values(selectedTracks)
|
||||
.filter(arr => Array.isArray(arr)) // skip null entries
|
||||
.flat();
|
||||
|
||||
const response = await authFetch(`${API_URL}/trip/bulk_fetch`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
body: JSON.stringify({ track_ids: allSelectedIds }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Server error: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
toast.success(`Request submitted! (${allSelectedIds.length} tracks)`);
|
||||
console.debug("Requested: ", selectedTracks);
|
||||
console.debug("Flattened: ", allSelectedIds);
|
||||
console.debug("Server response: ", data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error("Failed to submit request.");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
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">
|
||||
<style>{`
|
||||
@@ -424,42 +493,13 @@ export default function MediaRequestForm() {
|
||||
color: #aaa;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<BreadcrumbNav currentPage="request" />
|
||||
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex gap-4">
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
value="artist"
|
||||
checked={type === "artist"}
|
||||
onChange={() => setType("artist")}
|
||||
/>
|
||||
Artist
|
||||
</label>
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
value="album"
|
||||
checked={type === "album"}
|
||||
onChange={() => setType("album")}
|
||||
/>
|
||||
Album
|
||||
</label>
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
value="track"
|
||||
checked={type === "track"}
|
||||
onChange={() => setType("track")}
|
||||
/>
|
||||
Track
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label for="artistInput">Artist: </label>
|
||||
<AutoComplete
|
||||
id={artistInput}
|
||||
ref={autoCompleteRef}
|
||||
value={selectedArtist || artistInput}
|
||||
suggestions={artistSuggestions}
|
||||
@@ -501,6 +541,20 @@ export default function MediaRequestForm() {
|
||||
|
||||
{type === "artist" && albums.length > 0 && (
|
||||
<>
|
||||
<div className="flex justify-end mb-2">
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault(); // prevent page jump
|
||||
handleToggleAllAlbums();
|
||||
}}
|
||||
className="text-sm text-blue-600 hover:underline cursor-pointer"
|
||||
>
|
||||
Check / Uncheck All Albums
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<Accordion
|
||||
multiple
|
||||
className="mt-4"
|
||||
|
Reference in New Issue
Block a user