owpengram-server/cmd/telesrv-admin/web/src/components/StickerDocumentPreview.tsx
2026-07-22 12:18:27 +03:00

71 lines
2.7 KiB
TypeScript

import lottie from "lottie-web/build/player/lottie_light_canvas";
import { useEffect, useRef, useState } from "react";
import { api, errorMessage } from "../api";
// One preview cell in a sticker/emoji set's preview grid. Mounted only while
// its modal is open, and only for the one set being viewed — not on the list
// page — so this never repeats the "100 animations on one page" lag.
//
// Documents come back either as Lottie JSON (TGS-animated) or as a raw raster
// image (static webp/png/etc, e.g. the "GestosLol" pack) — branch on the
// response's real Content-Type rather than assuming every document animates.
export function StickerDocumentPreview({ documentID, className = "", showError = true }: { documentID: string; className?: string; showError?: boolean }) {
const host = useRef<HTMLDivElement>(null);
const animation = useRef<ReturnType<typeof lottie.loadAnimation> | null>(null);
const [error, setError] = useState("");
const [imageURL, setImageURL] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
let objectURL: string | null = null;
setError("");
setImageURL(null);
fetch(api.stickerDocumentAnimationURL(documentID), { credentials: "same-origin" })
.then(async (response) => {
if (!response.ok) {
const body = await response.json().catch(() => null);
throw new Error(body?.error || response.statusText);
}
const contentType = response.headers.get("content-type") ?? "";
if (contentType.includes("json")) {
const data = await response.json();
if (cancelled || !host.current) return;
animation.current?.destroy();
animation.current = lottie.loadAnimation({
container: host.current,
renderer: "canvas",
loop: true,
autoplay: true,
animationData: data
});
return;
}
const blob = await response.blob();
if (cancelled) return;
objectURL = URL.createObjectURL(blob);
setImageURL(objectURL);
})
.catch((err) => {
if (!cancelled) setError(errorMessage(err));
});
return () => {
cancelled = true;
animation.current?.destroy();
animation.current = null;
if (objectURL) URL.revokeObjectURL(objectURL);
};
}, [documentID]);
return (
<div className={`sticker-doc-cell ${className}`.trim()}>
{imageURL ? (
<img className="sticker-doc-image" src={imageURL} alt="" />
) : (
<div className="sticker-doc-canvas" ref={host} />
)}
{error && showError && <span className="sticker-doc-error">{error}</span>}
</div>
);
}