import { useCallback, useEffect, useRef, useState } from "react"; import { apiGet, apiPost, apiPostImage } from "../../api"; import { PageHead, Empty, Loading } from "../../components/ui"; import { IconCheck, IconLayers, IconSearch, IconWand } from "../../components/icons"; import { useToast } from "../../lib/toast"; interface Collection { id: string; name: string; child_count: number; poster_url: string | null; } interface Opts { target_type: string; text: string; text_color: string; text_align: string; text_position: string; text_scale: number; darkness: number; } const DEFAULTS: Opts = { target_type: "Thumb", text: "", text_color: "#FFFFFF", text_align: "center", text_position: "bottom", text_scale: 1.0, darkness: 0.18, }; export default function Collections() { const toast = useToast(); const [query, setQuery] = useState(""); const [list, setList] = useState([]); const [loading, setLoading] = useState(true); const [selected, setSelected] = useState(null); const [opts, setOpts] = useState(DEFAULTS); const [preview, setPreview] = useState(null); const [rendering, setRendering] = useState(false); const [applying, setApplying] = useState(false); const debounceRef = useRef(); const set = (k: K, v: Opts[K]) => setOpts((o) => ({ ...o, [k]: v })); const load = useCallback(() => { setLoading(true); apiGet<{ items: Collection[] }>(`/api/collections?q=${encodeURIComponent(query.trim())}&limit=60`) .then((d) => setList(d.items)) .catch((e) => toast(e.message, "err")) .finally(() => setLoading(false)); }, [query, toast]); useEffect(() => { load(); }, []); // eslint-disable-line react-hooks/exhaustive-deps function select(c: Collection) { setSelected(c); setPreview(null); setOpts({ ...DEFAULTS, text: c.name }); } const generate = useCallback(async () => { if (!selected) return; setRendering(true); try { const { url } = await apiPostImage("/api/collections/generate", { item_id: selected.id, ...opts }); setPreview((p) => { if (p) URL.revokeObjectURL(p); return url; }); } catch (e: any) { toast(e.message, "err"); } finally { setRendering(false); } }, [selected, opts, toast]); useEffect(() => { if (!selected) return; window.clearTimeout(debounceRef.current); debounceRef.current = window.setTimeout(generate, 350); return () => window.clearTimeout(debounceRef.current); }, [selected, opts]); // eslint-disable-line react-hooks/exhaustive-deps async function apply() { if (!selected) return; setApplying(true); try { await apiPost("/api/collections/apply", { item_id: selected.id, ...opts }); toast(`Applied ${opts.target_type} to ${selected.name}`, "ok"); } catch (e: any) { toast(e.message, "err"); } finally { setApplying(false); } } return ( <> Generate cover artwork for your Emby collections with custom titling.
{ e.preventDefault(); load(); }} > setQuery(e.target.value)} />
{loading ? ( ) : !list.length ? ( }>No collections found. ) : ( list.map((c) => (
select(c)}> {c.poster_url ? :
}
{c.name}
{c.child_count} items
)) )}
{!selected ? ( }>Select a collection to design its artwork. ) : ( <>
{preview ? preview : }
)}

Controls

{!selected ? (

Pick a collection first.

) : ( <>
set("text", e.target.value)} />
{["left", "center", "right"].map((a) => ( ))}
{["top", "center", "bottom"].map((p) => ( ))}
set("text_scale", +e.target.value)} />
set("darkness", +e.target.value)} />
set("text_color", e.target.value)} /> set("text_color", e.target.value)} />
)}
); }