"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 MediaAssetPreviewPlacement = "auto" | "left" | "right" 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 previewPlacement?: MediaAssetPreviewPlacement previewMaxWidth?: number videoControls?: boolean 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[] actionsAlwaysVisible?: boolean 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, placement: MediaAssetPreviewPlacement, maxWidth: number) { const margin = 16 const previewWidth = Math.min(maxWidth, window.innerWidth - margin * 2) const previewHeight = Math.min(760, window.innerHeight - margin * 2) let left = placement === "left" ? event.clientX - previewWidth - 18 : event.clientX + 18 let top = event.clientY + 18 if (placement === "auto" && left + previewWidth > window.innerWidth - margin) left = event.clientX - previewWidth - 18 if (placement === "right" && left + previewWidth > window.innerWidth - margin) left = window.innerWidth - previewWidth - margin if (placement === "left" && left < margin) left = margin if (top + previewHeight > window.innerHeight - margin) top = window.innerHeight - previewHeight - margin return { left: Math.max(margin, Math.min(left, window.innerWidth - previewWidth - margin)), top: Math.max(margin, top), width: previewWidth, } } export function MediaAssetTile({ kind = "image", src, poster, href, alt = "", title, label, meta, previewDetail, className = "", mediaClassName = "", objectFit = "contain", previewObjectFit, previewClassName = "", previewPlacement = "auto", previewMaxWidth = 520, videoControls = false, selected = false, disabled = false, busy = false, topLeft, topRight, bottom, emptyText, onClick, onDelete, deleteLabel = "删除素材", deleting = false, deleteDisabled = false, actions = [], actionsAlwaysVisible = false, disablePreview = false, }: MediaAssetTileProps) { const [position, setPosition] = useState<{ left: number; top: number; width: number } | null>(null) const mediaSrc = src || poster || "" const canPreview = !!mediaSrc && !disablePreview const fit = mediaObjectClass(objectFit) const previewFit = mediaObjectClass(previewObjectFit ?? objectFit) const updatePreview = (event: ReactMouseEvent) => { if (!canPreview) return setPosition(previewPosition(event, previewPlacement, previewMaxWidth)) } const media = kind === "video" && src ? (