import { Check, ChevronRight, Copy, Loader2, RefreshCw, Search } from "lucide-react"; import { useEffect, useState } from "react"; import { api, errorMessage } from "../api"; import { StaticLottie } from "../components/StaticLottie"; import { Alert, Metric, PageFrame, QueryPanel } from "../components/ui"; import { useI18n } from "../i18n"; import type { EmojiListResponse, EmojiRow } from "../types"; function formatBytes(value: number): string { if (value < 1024) return `${value} B`; if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`; return `${(value / (1024 * 1024)).toFixed(1)} MB`; } function isAnimated(mime: string): boolean { const m = mime.toLowerCase(); return m.includes("tgsticker") || m.includes("lottie") || m.includes("json"); } function EmojiPreview({ row }: { row: EmojiRow }) { const [failed, setFailed] = useState(!isAnimated(row.MimeType)); useEffect(() => { setFailed(!isAnimated(row.MimeType)); }, [row.DocumentID, row.MimeType]); if (failed) { return
{row.Alt || "๐Ÿ™‚"}
; } // Render a static first frame (plays only on hover) so a full grid of emoji // does not keep every Lottie canvas animating and lag the page. return ( api.emojiAnimation(row.DocumentID)} onError={() => setFailed(true)} /> ); } function EmojiCard({ row }: { row: EmojiRow }) { const { t } = useI18n(); const [copied, setCopied] = useState(false); async function copy() { try { await navigator.clipboard.writeText(row.DocumentID); setCopied(true); setTimeout(() => setCopied(false), 1200); } catch { // Clipboard is best-effort. } } return (
{row.Alt || "โ€”"} {row.SetTitle || t("emoji.noSet")} ยท {formatBytes(row.Size)}
); } export function EmojiPage() { const { t } = useI18n(); const [q, setQ] = useState(""); const [data, setData] = useState(null); const [cursor, setCursor] = useState(0); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); async function load(next = false) { setBusy(true); setError(""); const params = new URLSearchParams(); if (q.trim()) { params.set("q", q.trim()); } else if (next) { params.set("before_id", String(cursor)); } try { const result = await api.emoji(params); setData(result); setCursor(result.next_before_id); } catch (err) { setError(errorMessage(err)); } finally { setBusy(false); } } useEffect(() => { void load(false); }, []); const rows = data?.rows ?? []; return ( load(false)} disabled={busy}> {t("common.refresh")} } > {error && {error}}
{ event.preventDefault(); void load(false); }}> {data?.listing && data.has_more && ( )}

{t("emoji.hint")}

{rows.length === 0 ? (
{t("common.noResults")}
) : (
{rows.map((row) => )}
)}
); }