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);
}
.active-breadcrumb {
font-weight: bold;
text-decoration: underline;
text-underline-offset: 2px; /* makes it more visible */
}
/*
Toastify customizations

View File

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

View File

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

View File

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

View File

@@ -38,7 +38,11 @@ export default function RequestManagement() {
setRequests(Array.isArray(data.jobs) ? data.jobs : []);
} catch (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]);
useEffect(() => {
let filtered = [...requests];
if (filterType) filtered = filtered.filter((r) => r.type === filterType);
if (filterStatus) filtered = filtered.filter((r) => r.status === filterStatus);
const filtered = requests.filter((r) => {
const typeMatch = !filterType || r.type === filterType;
const statusMatch = filterStatus === "all" || filterStatus === null || r.status === filterStatus;
return typeMatch && statusMatch;
});
setFilteredRequests(filtered);
}, [filterType, filterStatus, requests]);
const getStatusColorClass = (status) => {
switch (status) {
case "queued": return "bg-yellow-500 text-black";
case "started":
case "compressing": return "bg-blue-500 text-white";
case "started": return "bg-blue-500 text-white";
case "compressing": return "bg-orange-500 text-white";
case "finished": return "bg-green-500 text-white";
case "failed": return "bg-red-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 "—";
const num = Number(p);
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}%`;
};
@@ -255,7 +262,7 @@ export default function RequestManagement() {
<div className="flex flex-wrap gap-6 mb-6">
<Dropdown
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)}
placeholder="Filter by Status"
className="min-w-[180px]"
@@ -273,7 +280,9 @@ export default function RequestManagement() {
responsiveLayout="scroll"
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="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="progress" header="Progress" body={(row) => formatProgress(row.progress)} style={{ width: "8rem", textAlign: "center" }} sortable />
<Column
@@ -314,16 +323,23 @@ export default function RequestManagement() {
>
{selectedRequest ? (
<div className="space-y-4 text-sm">
<p><strong>Target:</strong> {selectedRequest.target}</p>
<p>
{selectedRequest.id && (
<p><strong>ID:</strong> {selectedRequest.id}</p>
)}
{selectedRequest.target && (
<p><strong>Target:</strong> {selectedRequest.target}</p>
)}
{selectedRequest.tracks && (
<p><strong># Tracks:</strong> {selectedRequest.tracks}</p>
)}
{selectedRequest.status && (<p>
<strong>Status:</strong>{" "}
<span
className={`px-2 py-0.5 rounded-full text-xs font-bold ${getStatusColorClass(selectedRequest.status)}`}
>
{selectedRequest.status}
</span>
</p>
</p>)}
{selectedRequest.progress !== undefined && selectedRequest.progress !== null && (
<p><strong>Progress:</strong> {formatProgress(selectedRequest.progress)}</p>
@@ -352,21 +368,6 @@ export default function RequestManagement() {
</a>
</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>
) : (
<p>Loading...</p>

View File

@@ -12,6 +12,6 @@ export const API_URL = "https://api.codey.lol";
export const socialLinks = {
};
export const MAJOR_VERSION = "0.0"
export const RELEASE_FLAG = "Alpha";
export const MAJOR_VERSION = "0.1"
export const RELEASE_FLAG = null;
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 isAuthedPath = item.auth ?? false;
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 shouldShowThinBar = nextItem //&& !nextItem.blockSeparator;

View File

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