Online browser + post detail: SPA talks to e621 directly
Backend:
- POST /api/files/lookup/ reports which MD5s are already in the library
Frontend:
- e621 client extended with post/tag/favorite types and helpers: post
search, post detail, batch posts by id, tag autocomplete, toggle
favorite
- /online: tag search with autocomplete, post grid with rating colors
and in-library badges, numbered pagination, page tag cloud, blacklist
panel and filtered counts from /users/me.json, anonymous hint
- /online/view/🆔 media viewer (sample or original, video support),
taxonomy-colored tags by category, specs sheet, favorite/unfavorite,
description, sources, pools and parent/children thumbnails
- Shared CollapsibleSidebar extracted from the library page; Online
added to the nav, command palette and sidebar toggle
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import { CheckCircle2 } from "lucide-react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { cn } from "@/lib/cn";
|
||||
import type { E621Post } from "@/lib/e621";
|
||||
import { formatBytes } from "@/lib/format";
|
||||
|
||||
const ratingBorder: Record<string, string> = {
|
||||
s: "border-ctp-green/25 hover:border-ctp-green/60",
|
||||
q: "border-ctp-peach/25 hover:border-ctp-peach/60",
|
||||
e: "border-ctp-red/25 hover:border-ctp-red/60",
|
||||
};
|
||||
|
||||
const ratingPill: Record<string, string> = {
|
||||
s: "bg-ctp-green text-ctp-crust",
|
||||
q: "bg-ctp-peach text-ctp-crust",
|
||||
e: "bg-ctp-red text-ctp-crust",
|
||||
};
|
||||
|
||||
export function PostCard({
|
||||
post,
|
||||
inLibrary,
|
||||
}: {
|
||||
post: E621Post;
|
||||
inLibrary: boolean;
|
||||
}) {
|
||||
return (
|
||||
<Link
|
||||
to={`/online/view/${post.id}`}
|
||||
className={cn(
|
||||
"group overflow-hidden rounded-lg border bg-ctp-base transition",
|
||||
ratingBorder[post.rating] ?? "border-ctp-surface0",
|
||||
)}
|
||||
>
|
||||
<div className="relative aspect-square overflow-hidden bg-ctp-mantle">
|
||||
{post.preview.url ? (
|
||||
<img
|
||||
src={post.preview.url}
|
||||
alt={`Post #${post.id}`}
|
||||
loading="lazy"
|
||||
className="h-full w-full object-cover transition duration-200 group-hover:scale-[1.03]"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center text-xs text-ctp-overlay0">
|
||||
No preview
|
||||
</div>
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"absolute left-2 top-2 rounded-full px-1.5 py-0.5 font-mono text-[10px] font-semibold uppercase",
|
||||
ratingPill[post.rating] ?? "bg-ctp-surface1 text-ctp-text",
|
||||
)}
|
||||
>
|
||||
{post.rating}
|
||||
</span>
|
||||
<span className="absolute right-2 top-2 rounded-sm bg-ctp-crust/80 px-1.5 py-0.5 font-mono text-[10px] uppercase text-ctp-overlay1">
|
||||
{post.file.ext}
|
||||
</span>
|
||||
{inLibrary ? (
|
||||
<span
|
||||
className="absolute bottom-2 right-2 flex items-center gap-1 rounded-full bg-ctp-green/90 px-1.5 py-0.5 font-mono text-[10px] font-medium text-ctp-crust"
|
||||
title="Already in your library"
|
||||
>
|
||||
<CheckCircle2 className="h-3 w-3" /> IN
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2 px-2.5 py-2">
|
||||
<span className="font-mono text-xs text-ctp-subtext1">#{post.id}</span>
|
||||
<span className="font-mono text-[11px] text-ctp-overlay0">
|
||||
▲ {post.score.total} · {formatBytes(post.file.size)}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user