added gifs preview

This commit is contained in:
onysd 2026-08-07 10:51:43 +03:00
parent ff1d5ae361
commit 15f2d6e925
11 changed files with 141 additions and 8 deletions

View file

@ -225,6 +225,7 @@ export const api = {
stickerSets: (kind: string) => request<StickerSetListResponse>(`/api/stickers?kind=${encodeURIComponent(kind)}`),
stickerSetDocuments: (setID: string) => request<{ document_ids: string[] }>(`/api/stickers/${encodeURIComponent(setID)}/documents`),
stickerDocumentAnimationURL: (documentID: string) => `/api/stickers/documents/${encodeURIComponent(documentID)}/animation`,
gifCatalogDocumentPreviewURL: (documentID: string) => `/api/gif-catalog/documents/${encodeURIComponent(documentID)}/preview`,
createStickerSet: (form: FormData) => request<CommandResult>("/api/actions/create-sticker-set", { method: "POST", body: form }),
setAccountAvatar: (form: FormData) => request<CommandResult>("/api/actions/set-account-avatar", { method: "POST", body: form }),
setChannelAvatar: (form: FormData) => request<CommandResult>("/api/actions/set-channel-avatar", { method: "POST", body: form }),

View file

@ -115,7 +115,14 @@ export function Dashboard({ navigate }: { navigate: Navigate }) {
href="/emoji"
navigate={navigate}
/>
<StatTile icon={<Film />} label="GIFs" value={counts ? formatQuantity(String(counts.Gifs)) : "…"} sub="saved by users" />
<StatTile
icon={<Film />}
label="GIFs"
value={counts ? formatQuantity(String(counts.Gifs)) : "…"}
sub="saved by users"
href="/gif-catalog"
navigate={navigate}
/>
<StatTile
icon={<Database />}
label="Media storage used"

View file

@ -1,4 +1,4 @@
import { ChevronLeft, ChevronRight, Loader2, Plus, RefreshCw, Search, Upload, X } from "lucide-react";
import { ChevronLeft, ChevronRight, ImageOff, Loader2, Plus, RefreshCw, Search, Upload, X } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import { createPortal } from "react-dom";
import { api, errorMessage } from "../api";
@ -8,6 +8,33 @@ import type { GifCatalogRow } from "../types";
type GifPageSize = 10 | 20 | 50 | 100 | "all";
// One list-row preview cell. The backend always stores a plain H.264 MP4 (see
// files.Service.AdminUploadGifMaterial), so this is just a small looping
// <video> -- no Lottie/canvas work needed the way StickerDocumentPreview
// needs for TGS. onError falls back to a placeholder rather than a broken
// player if the document is somehow missing.
function GifPreviewThumb({ documentID }: { documentID: string }) {
const [broken, setBroken] = useState(false);
if (broken) {
return (
<div className="sticker-list-thumb-empty">
<ImageOff size={14} />
</div>
);
}
return (
<video
className="gif-catalog-thumb"
src={api.gifCatalogDocumentPreviewURL(documentID)}
muted
loop
autoPlay
playsInline
onError={() => setBroken(true)}
/>
);
}
// Manage view for the admin-curated GIF catalog the built-in @gif inline bot
// serves for the client's GIF picker trending/search panel.
export function GifCatalogPage() {
@ -105,6 +132,7 @@ export function GifCatalogPage() {
<table className="data-table">
<thead>
<tr>
<th>{"Preview"}</th>
<th>{"ID"}</th>
<th>{"Title"}</th>
<th>{"Document ID"}</th>
@ -117,6 +145,7 @@ export function GifCatalogPage() {
<tbody>
{paged.map((row) => (
<tr className={row.Enabled ? "" : "gift-row-disabled"} key={row.ID}>
<td><GifPreviewThumb documentID={row.DocumentID} /></td>
<td className="mono">{row.ID}</td>
<td>{row.Title || <span className="muted-cell">{"Untitled"}</span>}</td>
<td className="mono">{row.DocumentID}</td>
@ -162,7 +191,7 @@ export function GifCatalogPage() {
</td>
</tr>
))}
{paged.length === 0 && <EmptyRow colSpan={7} />}
{paged.length === 0 && <EmptyRow colSpan={8} />}
</tbody>
</table>
</div>

View file

@ -587,6 +587,7 @@
.sticker-doc-image { width: 100%; height: 100%; object-fit: contain; }
.sticker-doc-cell.list-thumb { width: 40px; flex: 0 0 40px; }
.sticker-list-thumb-empty { display: grid; place-items: center; width: 40px; height: 40px; background: var(--panel-strong); border: 1px solid var(--line); border-radius: 9px; color: var(--muted); }
.gif-catalog-thumb { width: 40px; height: 40px; object-fit: cover; background: var(--panel-strong); border: 1px solid var(--line); border-radius: 9px; }
.sticker-doc-grid-cell { display: grid; gap: 4px; }
.sticker-doc-grid-cell .btn { width: 100%; justify-content: center; }
.sticker-add-form { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 14px; padding: 10px; background: var(--panel-strong); border: 1px solid var(--line); border-radius: 10px; }