"use client"; import { useEffect, useMemo, useState } from "react"; import { useMutation } from "convex/react"; import { AlertCircle, Box, ImageIcon, Loader2, Video } from "lucide-react"; import { api } from "@/convex/_generated/api"; import type { Id } from "@/convex/_generated/dataModel"; import { useAuthQuery } from "@/hooks/use-auth-query"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { collectMediaStorageIdsForResolution, resolveMediaPreviewUrl, } from "@/components/media/media-preview-utils"; const DEFAULT_PAGE_SIZE = 8; export type MediaLibraryMetadataItem = { kind: "image" | "video" | "asset"; source: "upload" | "ai-image" | "ai-video" | "freepik-asset" | "pexels-video"; storageId?: Id<"_storage">; previewStorageId?: Id<"_storage">; previewUrl?: string; originalUrl?: string; sourceUrl?: string; filename?: string; mimeType?: string; width?: number; height?: number; previewWidth?: number; previewHeight?: number; durationSeconds?: number; sourceCanvasId?: Id<"canvases">; sourceNodeId?: Id<"nodes">; createdAt: number; }; export type MediaLibraryItem = MediaLibraryMetadataItem & { url?: string; }; export type MediaLibraryDialogProps = { open: boolean; onOpenChange: (open: boolean) => void; onPick?: (item: MediaLibraryItem) => void | Promise; title?: string; description?: string; pageSize?: number; kindFilter?: "image" | "video" | "asset"; pickCtaLabel?: string; }; type MediaLibraryResponse = { items: MediaLibraryMetadataItem[]; page: number; pageSize: number; totalPages: number; totalCount: number; }; function formatDimensions(width: number | undefined, height: number | undefined): string | null { if (typeof width !== "number" || typeof height !== "number") { return null; } return `${width} x ${height}px`; } function formatMediaMeta(item: MediaLibraryItem): string { if (item.kind === "video") { if (typeof item.durationSeconds === "number" && Number.isFinite(item.durationSeconds)) { return `${Math.max(1, Math.round(item.durationSeconds))}s`; } return "Videodatei"; } return formatDimensions(item.width, item.height) ?? "Groesse unbekannt"; } function getItemKey(item: MediaLibraryItem): string { if (item.storageId) { return item.storageId; } if (item.originalUrl) { return `url:${item.originalUrl}`; } if (item.previewUrl) { return `preview:${item.previewUrl}`; } if (item.sourceUrl) { return `source:${item.sourceUrl}`; } return `${item.kind}:${item.createdAt}:${item.filename ?? "unnamed"}`; } function getItemLabel(item: MediaLibraryItem): string { if (item.filename) { return item.filename; } if (item.kind === "video") { return "Unbenanntes Video"; } if (item.kind === "asset") { return "Unbenanntes Asset"; } return "Unbenanntes Bild"; } export function MediaLibraryDialog({ open, onOpenChange, onPick, title = "Mediathek", description, pageSize = DEFAULT_PAGE_SIZE, kindFilter, pickCtaLabel = "Auswaehlen", }: MediaLibraryDialogProps) { const [page, setPage] = useState(1); const normalizedPageSize = useMemo(() => { if (typeof pageSize !== "number" || !Number.isFinite(pageSize)) { return DEFAULT_PAGE_SIZE; } return Math.max(1, Math.floor(pageSize)); }, [pageSize]); useEffect(() => { if (!open) { setPage(1); } }, [open]); useEffect(() => { setPage(1); }, [kindFilter]); const metadata = useAuthQuery( api.dashboard.listMediaLibrary, open ? { page, pageSize: normalizedPageSize, ...(kindFilter ? { kindFilter } : {}), } : "skip", ) as MediaLibraryResponse | undefined; const resolveUrls = useMutation(api.storage.batchGetUrlsForUserMedia); const [urlMap, setUrlMap] = useState>({}); const [isResolvingUrls, setIsResolvingUrls] = useState(false); const [urlError, setUrlError] = useState(null); const [pendingPickItemKey, setPendingPickItemKey] = useState(null); useEffect(() => { let isCancelled = false; async function run() { if (!open) { setUrlMap({}); setUrlError(null); setIsResolvingUrls(false); return; } if (!metadata) { return; } const storageIds = collectMediaStorageIdsForResolution(metadata.items); if (storageIds.length === 0) { setUrlMap({}); setUrlError(null); setIsResolvingUrls(false); return; } setIsResolvingUrls(true); setUrlError(null); try { const resolved = await resolveUrls({ storageIds }); if (isCancelled) { return; } setUrlMap(resolved); } catch (error) { if (isCancelled) { return; } setUrlMap({}); setUrlError(error instanceof Error ? error.message : "URLs konnten nicht geladen werden."); } finally { if (!isCancelled) { setIsResolvingUrls(false); } } } void run(); return () => { isCancelled = true; }; }, [metadata, open, resolveUrls]); const items: MediaLibraryItem[] = useMemo(() => { if (!metadata) { return []; } return metadata.items.map((item) => ({ ...item, url: resolveMediaPreviewUrl(item, urlMap), })); }, [metadata, urlMap]); const visibleItems = useMemo(() => items.slice(0, DEFAULT_PAGE_SIZE), [items]); const isMetadataLoading = open && metadata === undefined; const isInitialLoading = isMetadataLoading || (metadata !== undefined && isResolvingUrls); const isPreviewMode = typeof onPick !== "function"; const effectiveDescription = description ?? (kindFilter === "image" ? "Waehle ein Bild aus deiner LemonSpace-Mediathek." : "Durchsuche deine Medien aus Uploads, KI-Generierung und Archivquellen."); async function handlePick(item: MediaLibraryItem): Promise { if (!onPick || pendingPickItemKey) { return; } setPendingPickItemKey(getItemKey(item)); try { await onPick(item); } finally { setPendingPickItemKey(null); } } return ( {title} {effectiveDescription}
{isInitialLoading ? (
{Array.from({ length: DEFAULT_PAGE_SIZE }).map((_, index) => (
))}
) : urlError ? (

Mediathek konnte nicht geladen werden

{urlError}

) : items.length === 0 ? (

Keine Medien vorhanden

Sobald du Medien hochlaedst oder generierst, erscheinen sie hier.

) : (
{visibleItems.map((item) => { const itemKey = getItemKey(item); const isPickingThis = pendingPickItemKey === itemKey; const itemLabel = getItemLabel(item); const metaLabel = formatMediaMeta(item); return (
{item.url && item.kind === "video" ? (

{itemLabel}

{metaLabel}

{isPreviewMode ? (

Nur Vorschau

) : ( )}
); })}
)}
{metadata && !isInitialLoading && !urlError && items.length > 0 ? (
Page {metadata.page} of {metadata.totalPages}
) : null}
); }