ionian/packages/client/src/components/gallery/GalleryCard.tsx
monoid 8fece9090f BREAKING: Rework (#6)
다시 작업. 디자인도 바꾸고 서버도 바꿈.

Co-authored-by: monoid <jaeung@prelude.duckdns.org>
Reviewed-on: https://git.prelude.duckdns.org/monoid/ionian/pulls/6
2024-04-17 01:45:36 +09:00

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);