"use client" import { type MouseEvent as ReactMouseEvent, type ReactNode, useState } from "react" import { createPortal } from "react-dom" import { Film, Loader2, Trash2 } from "lucide-react" type MediaAssetAction = { key: string label: string icon: ReactNode onClick: () => void disabled?: boolean busy?: boolean tone?: "neutral" | "cyan" | "rose" } type MediaAssetTileProps = { kind?: "image" | "video" src?: string poster?: string href?: string alt?: string title?: string label?: ReactNode meta?: ReactNode previewDetail?: ReactNode className?: string mediaClassName?: string objectFit?: "contain" | "cover" previewObjectFit?: "contain" | "cover" previewClassName?: string previewSize?: "normal" | "large" selected?: boolean disabled?: boolean busy?: boolean topLeft?: ReactNode topRight?: ReactNode bottom?: ReactNode emptyText?: string onClick?: () => void onDelete?: () => void deleteLabel?: string deleting?: boolean deleteDisabled?: boolean actions?: MediaAssetAction[] disablePreview?: boolean } const actionToneClass: Record, string> = { neutral: "border-white/18 bg-black/78 text-white/82 hover:border-white/42 hover:bg-white/12", cyan: "border-cyan-100/35 bg-black/78 text-cyan-100 hover:border-cyan-100/70 hover:bg-cyan-500/25", rose: "border-rose-100/35 bg-black/78 text-rose-100 hover:border-rose-100/70 hover:bg-rose-500/25", } function mediaObjectClass(fit: "contain" | "cover") { return fit === "cover" ? "object-cover" : "object-contain" } function previewPosition(event: ReactMouseEvent, size: "normal" | "large" = "normal") { const margin = 16 const previewWidth = Math.min(size === "large" ? 720 : 520, window.innerWidth - margin * 2) const previewHeight = Math.min(size === "large" ? 880 : 760, window.innerHeight - margin * 2) let left = event.clientX + 18 let top = event.clientY + 18 if (left + previewWidth > window.innerWidth - margin) left = event.clientX - previewWidth - 18 if (top + previewHeight > window.innerHeight - margin) top = window.innerHeight - previewHeight - margin return { left: Math.max(margin, left), top: Math.max(margin, top) } } export function MediaAssetTile({ kind = "image", src, poster, href, alt = "", title, label, meta, previewDetail, className = "", mediaClassName = "", objectFit = "contain", previewObjectFit, previewClassName = "", previewSize = "normal", selected = false, disabled = false, busy = false, topLeft, topRight, bottom, emptyText, onClick, onDelete, deleteLabel = "删除素材", deleting = false, deleteDisabled = false, actions = [], disablePreview = false, }: MediaAssetTileProps) { const [position, setPosition] = useState<{ left: number; top: number } | null>(null) const mediaSrc = src || poster || "" const canPreview = !!mediaSrc && !disablePreview const fit = mediaObjectClass(objectFit) const previewFit = mediaObjectClass(previewObjectFit ?? objectFit) const previewWidthClass = previewSize === "large" ? "w-[min(720px,calc(100vw-32px))]" : "w-[min(520px,calc(100vw-32px))]" const previewMaxHeightClass = previewSize === "large" ? "max-h-[min(84vh,860px)]" : "max-h-[min(76vh,720px)]" const previewMediaHeightClass = previewSize === "large" ? "max-h-[min(82vh,840px)]" : "max-h-[min(74vh,700px)]" const updatePreview = (event: ReactMouseEvent) => { if (!canPreview) return setPosition(previewPosition(event, previewSize)) } const media = kind === "video" && src ? (