Add new gallery variant
This commit is contained in:
62
src/components/artworks/ArtworkGalleryVariantProcessor.tsx
Normal file
62
src/components/artworks/ArtworkGalleryVariantProcessor.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
"use client";
|
||||
|
||||
import { generateGalleryVariantsMissing } from "@/actions/artworks/generateGalleryVariant";
|
||||
import { getGalleryVariantStats } from "@/actions/artworks/getGalleryVariantStats";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import * as React from "react";
|
||||
|
||||
export function ArtworkGalleryVariantProcessor() {
|
||||
const [stats, setStats] = React.useState<Awaited<
|
||||
ReturnType<typeof getGalleryVariantStats>
|
||||
> | null>(null);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [msg, setMsg] = React.useState<string | null>(null);
|
||||
|
||||
const refreshStats = React.useCallback(async () => {
|
||||
const s = await getGalleryVariantStats();
|
||||
setStats(s);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
void refreshStats();
|
||||
}, [refreshStats]);
|
||||
|
||||
const run = async () => {
|
||||
setLoading(true);
|
||||
setMsg(null);
|
||||
try {
|
||||
const res = await generateGalleryVariantsMissing({ limit: 50 });
|
||||
setMsg(`Processed ${res.processed}: ${res.ok} ok, ${res.failed} failed`);
|
||||
await refreshStats();
|
||||
} catch (e) {
|
||||
setMsg(e instanceof Error ? e.message : "Failed");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const done = !!stats && stats.missing === 0;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button onClick={run} disabled={loading || done}>
|
||||
{done
|
||||
? "All gallery variants present"
|
||||
: loading
|
||||
? "Generating…"
|
||||
: "Generate missing gallery variants"}
|
||||
</Button>
|
||||
|
||||
{stats && (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Ready {stats.withGallery}/{stats.total}
|
||||
{stats.missing > 0 && ` · Missing ${stats.missing}`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{msg && <p className="text-sm text-muted-foreground">{msg}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,12 +1,19 @@
|
||||
import { FileVariant } from "@/generated/prisma/client";
|
||||
"use client";
|
||||
|
||||
import { generateGalleryVariant } from "@/actions/artworks/generateGalleryVariant";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { FileVariant } from "@/generated/prisma/client";
|
||||
import { formatFileSize } from "@/utils/formatFileSize";
|
||||
import NextImage from "next/image";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useTransition } from "react";
|
||||
|
||||
const ORDER: Record<string, number> = {
|
||||
thumbnail: 0,
|
||||
resized: 1,
|
||||
modified: 2,
|
||||
original: 3,
|
||||
gallery: 1,
|
||||
resized: 2,
|
||||
modified: 3,
|
||||
original: 4,
|
||||
};
|
||||
|
||||
function byVariantOrder(a: FileVariant, b: FileVariant) {
|
||||
@ -16,22 +23,58 @@ function byVariantOrder(a: FileVariant, b: FileVariant) {
|
||||
return a.type.localeCompare(b.type);
|
||||
}
|
||||
|
||||
export default function ArtworkVariants({ variants }: { variants: FileVariant[] }) {
|
||||
export default function ArtworkVariants({
|
||||
artworkId,
|
||||
variants,
|
||||
}: {
|
||||
artworkId: string;
|
||||
variants: FileVariant[];
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const hasGallery = variants.some((v) => v.type === "gallery");
|
||||
const sorted = [...variants].sort(byVariantOrder);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className="font-semibold text-lg mb-2">Variants</h2>
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
<h2 className="font-semibold text-lg">Variants</h2>
|
||||
{!hasGallery ? (
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={isPending}
|
||||
onClick={() =>
|
||||
startTransition(async () => {
|
||||
await generateGalleryVariant(artworkId);
|
||||
router.refresh();
|
||||
})
|
||||
}
|
||||
>
|
||||
{isPending ? "Generating..." : "Generate gallery"}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
<div>
|
||||
{sorted.map((variant) => (
|
||||
<div key={variant.id}>
|
||||
<div className="text-sm mb-1">{variant.type} | {variant.width}x{variant.height}px | {variant.sizeBytes ? formatFileSize(variant.sizeBytes) : "-"}</div>
|
||||
<div className="text-sm mb-1">
|
||||
{variant.type} | {variant.width}x{variant.height}px |{" "}
|
||||
{variant.sizeBytes ? formatFileSize(variant.sizeBytes) : "-"}
|
||||
</div>
|
||||
{variant.s3Key && (
|
||||
<NextImage src={`/api/image/${variant.s3Key}`} alt={variant.s3Key} width={variant.width} height={variant.height} className="rounded shadow max-w-md" />
|
||||
<NextImage
|
||||
src={`/api/image/${variant.s3Key}`}
|
||||
alt={variant.s3Key}
|
||||
width={variant.width}
|
||||
height={variant.height}
|
||||
className="rounded shadow max-w-md"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user