misc
This commit is contained in:
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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) => (
|
||||
{pages.map(({ key, label, href }, i) => {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
<a
|
||||
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}
|
||||
>
|
||||
{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>
|
||||
|
@@ -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 >
|
||||
);
|
||||
}
|
||||
|
@@ -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">
|
||||
{selectedRequest.id && (
|
||||
<p><strong>ID:</strong> {selectedRequest.id}</p>
|
||||
)}
|
||||
{selectedRequest.target && (
|
||||
<p><strong>Target:</strong> {selectedRequest.target}</p>
|
||||
|
||||
<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>
|
||||
|
@@ -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";
|
@@ -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;
|
||||
|
@@ -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})`;
|
||||
}
|
Reference in New Issue
Block a user