misc
This commit is contained in:
@@ -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
|
||||||
|
@@ -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);
|
||||||
|
@@ -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>
|
||||||
|
@@ -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,8 +650,9 @@ export default function MediaRequestForm() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)
|
||||||
</div>
|
}
|
||||||
|
</div >
|
||||||
</div >
|
</div >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -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>
|
||||||
|
@@ -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";
|
@@ -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;
|
||||||
|
@@ -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})`;
|
||||||
}
|
}
|
Reference in New Issue
Block a user