This commit is contained in:
2025-08-20 15:57:59 -04:00
parent a13cbabdb4
commit 1528931a29
8 changed files with 68 additions and 52 deletions

View File

@@ -266,6 +266,11 @@ Custom
background-color: rgba(255, 255, 255, 0.9); background-color: rgba(255, 255, 255, 0.9);
} }
.active-breadcrumb {
font-weight: bold;
text-decoration: underline;
text-underline-offset: 2px; /* makes it more visible */
}
/* /*
Toastify customizations Toastify customizations

View File

@@ -22,7 +22,7 @@
} }
body { body {
font-family: "Mukta", sans-serif; font-family: sans-serif;
width: 100%; width: 100%;
height: 100%; height: 100%;
margin: 0; margin: 0;
@@ -264,7 +264,7 @@ body {
margin-top: 1rem; margin-top: 1rem;
padding: 0 1rem 1rem; padding: 0 1rem 1rem;
text-align: center; text-align: center;
font-family: "Mukta", sans-serif; font-family: sans-serif;
font-size: 0.85rem; font-size: 0.85rem;
line-height: 1.4; line-height: 1.4;
color: var(--lrc-text-color); color: var(--lrc-text-color);

View File

@@ -9,20 +9,25 @@ export default function BreadcrumbNav({ currentPage }) {
return ( return (
<div> <div>
<nav aria-label="breadcrumb" className="mb-6 flex gap-4 text-sm font-medium text-blue-600 dark:text-blue-400"> <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) => ( {pages.map(({ key, label, href }, i) => {
return (
<React.Fragment key={key}> <React.Fragment key={key}>
<a <a
href={href} href={href}
className={`hover:underline ${currentPage === key ? "font-bold underline" : ""}`} className={`${currentPage === key
? "!font-bold underline" // active: always underlined + bold
: "hover:underline" // inactive: underline only on hover
}`}
aria-current={currentPage === key ? "page" : undefined} aria-current={currentPage === key ? "page" : undefined}
> >
{label} {label}
</a> </a>
{i < pages.length - 1 && <span aria-hidden="true">/</span>} {i < pages.length - 1 && <span aria-hidden="true">/</span>}
</React.Fragment> </React.Fragment>
))} );
})}
</nav > </nav >
<div class="mb-2"> <div className="mb-2">
Self Service Self Service
</div> </div>
</div> </div>

View File

@@ -271,7 +271,7 @@ export default function MediaRequestForm() {
if (albumsToFetch.length === 0) return; if (albumsToFetch.length === 0) return;
const fetchTracksSequentially = async () => { const fetchTracksSequentially = async () => {
const minDelay = 400; // ms between API requests const minDelay = 200; // ms between API requests
setIsFetching(true); setIsFetching(true);
const totalAlbums = albumsToFetch.length; const totalAlbums = albumsToFetch.length;
@@ -588,8 +588,8 @@ export default function MediaRequestForm() {
className="cursor-pointer" className="cursor-pointer"
aria-label={`Select all tracks for album ${album}`} aria-label={`Select all tracks for album ${album}`}
/> />
<span className="flex items-center"> <span className="flex items-center" title={album}>
{album} {truncate(album, 32)}
{loadingAlbumId === id && <Spinner />} {loadingAlbumId === id && <Spinner />}
</span> </span>
<small className="ml-2 text-neutral-500 dark:text-neutral-400">({release_date})</small> <small className="ml-2 text-neutral-500 dark:text-neutral-400">({release_date})</small>
@@ -614,7 +614,7 @@ export default function MediaRequestForm() {
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} {truncate(track.title, 80)}
</button> </button>
<span className="text-xs text-neutral-500">{track.audioQuality}</span> <span className="text-xs text-neutral-500">{track.audioQuality}</span>
{track.version && ( {track.version && (
@@ -650,7 +650,8 @@ export default function MediaRequestForm() {
</Button> </Button>
</div> </div>
</> </>
)} )
}
</div > </div >
</div > </div >
); );

View File

@@ -38,7 +38,11 @@ export default function RequestManagement() {
setRequests(Array.isArray(data.jobs) ? data.jobs : []); setRequests(Array.isArray(data.jobs) ? data.jobs : []);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
toast.error("Failed to fetch jobs list"); if (!toast.isActive('fetch-fail-toast')) {
toast.error("Failed to fetch jobs list", {
toastId: 'fetch-fail-toast',
});
}
} }
}; };
@@ -84,17 +88,20 @@ export default function RequestManagement() {
}, [requests]); }, [requests]);
useEffect(() => { useEffect(() => {
let filtered = [...requests]; const filtered = requests.filter((r) => {
if (filterType) filtered = filtered.filter((r) => r.type === filterType); const typeMatch = !filterType || r.type === filterType;
if (filterStatus) filtered = filtered.filter((r) => r.status === filterStatus); const statusMatch = filterStatus === "all" || filterStatus === null || r.status === filterStatus;
return typeMatch && statusMatch;
});
setFilteredRequests(filtered); setFilteredRequests(filtered);
}, [filterType, filterStatus, requests]); }, [filterType, filterStatus, requests]);
const getStatusColorClass = (status) => { const getStatusColorClass = (status) => {
switch (status) { switch (status) {
case "queued": return "bg-yellow-500 text-black"; case "queued": return "bg-yellow-500 text-black";
case "started": case "started": return "bg-blue-500 text-white";
case "compressing": return "bg-blue-500 text-white"; case "compressing": return "bg-orange-500 text-white";
case "finished": return "bg-green-500 text-white"; case "finished": return "bg-green-500 text-white";
case "failed": return "bg-red-500 text-white"; case "failed": return "bg-red-500 text-white";
default: return "bg-gray-500 text-white"; default: return "bg-gray-500 text-white";
@@ -118,7 +125,7 @@ export default function RequestManagement() {
if (p === null || p === undefined || p === "") return "—"; if (p === null || p === undefined || p === "") return "—";
const num = Number(p); const num = Number(p);
if (Number.isNaN(num)) return "—"; if (Number.isNaN(num)) return "—";
const pct = num > 1 ? Math.round(num) : Math.round(num * 100); const pct = num > 1 ? Math.round(num) : num;
return `${pct}%`; return `${pct}%`;
}; };
@@ -255,7 +262,7 @@ export default function RequestManagement() {
<div className="flex flex-wrap gap-6 mb-6"> <div className="flex flex-wrap gap-6 mb-6">
<Dropdown <Dropdown
value={filterStatus} value={filterStatus}
options={[{ label: "All Statuses", value: null }, ...STATUS_OPTIONS.map((s) => ({ label: s, value: s }))]} options={[{ label: "All Statuses", value: "all" }, ...STATUS_OPTIONS.map((s) => ({ label: s, value: s }))]}
onChange={(e) => setFilterStatus(e.value)} onChange={(e) => setFilterStatus(e.value)}
placeholder="Filter by Status" placeholder="Filter by Status"
className="min-w-[180px]" className="min-w-[180px]"
@@ -273,7 +280,9 @@ export default function RequestManagement() {
responsiveLayout="scroll" responsiveLayout="scroll"
onRowClick={handleRowClick} onRowClick={handleRowClick}
> >
<Column field="id" header="ID" sortable style={{ width: "10rem" }} body={(row) => textWithEllipsis(row.id, "8rem")} />
<Column field="target" header="Target" sortable style={{ width: "12rem" }} body={(row) => textWithEllipsis(row.target, "10rem")} /> <Column field="target" header="Target" sortable style={{ width: "12rem" }} body={(row) => textWithEllipsis(row.target, "10rem")} />
<Column field="tracks" header="# Tracks" sortable style={{ width: "10rem" }} body={(row) => row.tracks} />
<Column field="status" header="Status" body={statusBodyTemplate} style={{ width: "10rem", textAlign: "center" }} sortable /> <Column field="status" header="Status" body={statusBodyTemplate} style={{ width: "10rem", textAlign: "center" }} sortable />
<Column field="progress" header="Progress" body={(row) => formatProgress(row.progress)} style={{ width: "8rem", textAlign: "center" }} sortable /> <Column field="progress" header="Progress" body={(row) => formatProgress(row.progress)} style={{ width: "8rem", textAlign: "center" }} sortable />
<Column <Column
@@ -314,16 +323,23 @@ export default function RequestManagement() {
> >
{selectedRequest ? ( {selectedRequest ? (
<div className="space-y-4 text-sm"> <div className="space-y-4 text-sm">
{selectedRequest.id && (
<p><strong>ID:</strong> {selectedRequest.id}</p>
)}
{selectedRequest.target && (
<p><strong>Target:</strong> {selectedRequest.target}</p> <p><strong>Target:</strong> {selectedRequest.target}</p>
)}
<p> {selectedRequest.tracks && (
<p><strong># Tracks:</strong> {selectedRequest.tracks}</p>
)}
{selectedRequest.status && (<p>
<strong>Status:</strong>{" "} <strong>Status:</strong>{" "}
<span <span
className={`px-2 py-0.5 rounded-full text-xs font-bold ${getStatusColorClass(selectedRequest.status)}`} className={`px-2 py-0.5 rounded-full text-xs font-bold ${getStatusColorClass(selectedRequest.status)}`}
> >
{selectedRequest.status} {selectedRequest.status}
</span> </span>
</p> </p>)}
{selectedRequest.progress !== undefined && selectedRequest.progress !== null && ( {selectedRequest.progress !== undefined && selectedRequest.progress !== null && (
<p><strong>Progress:</strong> {formatProgress(selectedRequest.progress)}</p> <p><strong>Progress:</strong> {formatProgress(selectedRequest.progress)}</p>
@@ -352,21 +368,6 @@ export default function RequestManagement() {
</a> </a>
</p> </p>
)} )}
{Array.isArray(selectedRequest.tracks) && selectedRequest.tracks.length > 0 && (
<div>
<strong>Tracks:</strong>
<ul className="list-disc pl-5">
{selectedRequest.tracks.map((t, idx) => {
if (t && typeof t === "object") {
const tid = "track_id" in t ? t.track_id : t.id ?? idx;
const st = t.status ?? "—";
return <li key={idx}>{`${tid}${st}`}</li>;
}
return <li key={idx}>{String(t)}</li>;
})}
</ul>
</div>
)}
</div> </div>
) : ( ) : (
<p>Loading...</p> <p>Loading...</p>

View File

@@ -12,6 +12,6 @@ export const API_URL = "https://api.codey.lol";
export const socialLinks = { export const socialLinks = {
}; };
export const MAJOR_VERSION = "0.0" export const MAJOR_VERSION = "0.1"
export const RELEASE_FLAG = "Alpha"; export const RELEASE_FLAG = null;
export const ENVIRONMENT = import.meta.env.DEV ? "Dev" : "Prod"; export const ENVIRONMENT = import.meta.env.DEV ? "Dev" : "Prod";

View File

@@ -41,7 +41,11 @@ const currentPath = Astro.url.pathname;
const isExternal = item.href?.startsWith("http"); const isExternal = item.href?.startsWith("http");
const isAuthedPath = item.auth ?? false; const isAuthedPath = item.auth ?? false;
const normalize = (url) => url?.replace(/\/+$/, '') || '/'; const normalize = (url) => url?.replace(/\/+$/, '') || '/';
const isActive = !isExternal && normalize(item.href) === normalize(currentPath); const normalizedCurrent = normalize(currentPath).replace(/\/$/, ""); // remove trailing slash
const normalizedHref = normalize(item.href).replace(/\/$/, "");
const isActive = !isExternal && (
normalizedCurrent === normalizedHref ||
normalizedCurrent.startsWith(normalizedHref + "/"));
const nextItem = navItems[index + 1]; const nextItem = navItems[index + 1];
const shouldShowThinBar = nextItem //&& !nextItem.blockSeparator; const shouldShowThinBar = nextItem //&& !nextItem.blockSeparator;

View File

@@ -5,6 +5,6 @@ export const buildTime = new Date().toLocaleString(undefined, {
}); });
export var buildNumber = generate(MAJOR_VERSION); export var buildNumber = generate(MAJOR_VERSION);
if (RELEASE_FLAG.length) { if (RELEASE_FLAG && RELEASE_FLAG.length) {
buildNumber = `${buildNumber} (${RELEASE_FLAG})`; buildNumber = `${buildNumber} (${RELEASE_FLAG})`;
} }