다시 작업. 디자인도 바꾸고 서버도 바꿈. Co-authored-by: monoid <jaeung@prelude.duckdns.org> Reviewed-on: https://git.prelude.duckdns.org/monoid/ionian/pulls/6
90 lines
No EOL
3.9 KiB
TypeScript
90 lines
No EOL
3.9 KiB
TypeScript
import type { Document } from "dbtype/api";
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card.tsx";
|
|
import TagBadge from "@/components/gallery/TagBadge.tsx";
|
|
import { Fragment, useLayoutEffect, useRef, useState } from "react";
|
|
import { LazyImage } from "./LazyImage.tsx";
|
|
import StyledLink from "./StyledLink.tsx";
|
|
import React from "react";
|
|
|
|
function clipTagsWhenOverflow(tags: string[], limit: number) {
|
|
let l = 0;
|
|
for (let i = 0; i < tags.length; i++) {
|
|
l += tags[i].length;
|
|
if (l > limit) {
|
|
return tags.slice(0, i);
|
|
}
|
|
l += 1; // for space
|
|
}
|
|
return tags;
|
|
}
|
|
|
|
function GalleryCardImpl({
|
|
doc: x
|
|
}: { doc: Document; }) {
|
|
const ref = useRef<HTMLUListElement>(null);
|
|
const [clipCharCount, setClipCharCount] = useState(200);
|
|
const isDeleted = x.deleted_at !== null;
|
|
|
|
const artists = x.tags.filter(x => x.startsWith("artist:")).map(x => x.replace("artist:", ""));
|
|
const groups = x.tags.filter(x => x.startsWith("group:")).map(x => x.replace("group:", ""));
|
|
|
|
const originalTags = x.tags.filter(x => !x.startsWith("artist:") && !x.startsWith("group:"));
|
|
const clippedTags = clipTagsWhenOverflow(originalTags, clipCharCount);
|
|
|
|
useLayoutEffect(() => {
|
|
const listener = () => {
|
|
if (ref.current) {
|
|
const { width } = ref.current.getBoundingClientRect();
|
|
const charWidth = 7; // rough estimate
|
|
const newClipCharCount = Math.floor(width / charWidth) * 3;
|
|
setClipCharCount(newClipCharCount);
|
|
}
|
|
};
|
|
listener();
|
|
window.addEventListener("resize", listener);
|
|
return () => {
|
|
window.removeEventListener("resize", listener);
|
|
};
|
|
}, []);
|
|
|
|
return <Card className="flex h-[200px]">
|
|
{isDeleted ? <div className="bg-primary border flex items-center justify-center h-[200px] w-[142px] rounded-xl">
|
|
<span className="text-primary-foreground text-lg font-bold">Deleted</span>
|
|
</div> : <div className="rounded-xl overflow-hidden h-[200px] w-[142px] flex-none bg-[#272733] flex items-center justify-center">
|
|
<LazyImage src={`/api/doc/${x.id}/comic/thumbnail`}
|
|
alt={x.title}
|
|
className="max-h-full max-w-full object-cover object-center"
|
|
/>
|
|
</div>
|
|
}
|
|
<div className="flex-1 flex flex-col">
|
|
<CardHeader className="flex-none">
|
|
<CardTitle>
|
|
<StyledLink className="line-clamp-2" to={`/doc/${x.id}`}>
|
|
{x.title}
|
|
</StyledLink>
|
|
</CardTitle>
|
|
<CardDescription>
|
|
{artists.map((x, i) => <Fragment key={`artist:${x}`}>
|
|
<StyledLink to={`/search?allow_tag=artist:${x}`}>{x}</StyledLink>
|
|
{i + 1 < artists.length && <span className="opacity-50">, </span>}
|
|
</Fragment>)}
|
|
{groups.length > 0 && <span key={"sep"}>{" | "}</span>}
|
|
{groups.map((x, i) => <Fragment key={`group:${x}`}>
|
|
<StyledLink to={`/search?allow_tag=group:${x}`}>{x}</StyledLink>
|
|
{i + 1 < groups.length && <span className="opacity-50">, </span>}
|
|
</Fragment>
|
|
)}
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="flex-1 overflow-hidden">
|
|
<ul ref={ref} className="flex flex-wrap gap-2 items-baseline content-start">
|
|
{clippedTags.map(tag => <TagBadge key={tag} tagname={tag} className="" />)}
|
|
{clippedTags.length < originalTags.length && <TagBadge key={"..."} tagname="..." className="inline-block" disabled />}
|
|
</ul>
|
|
</CardContent>
|
|
</div>
|
|
</Card>;
|
|
}
|
|
|
|
export const GalleryCard = React.memo(GalleryCardImpl); |