import type { FilePreview } from "@umt/contracts"; import { Download, ExternalLink } from "lucide-react"; import { type FileEntry, type FileRoot, fileDownloadUrl, filesErrorText, filesMockEnabled, useFilePreview, } from "~/api/files.ts"; import { DetailSection } from "~/components/console/detail-sheet.tsx"; import { LoadingState } from "~/components/console/empty-state.tsx"; import { KvList } from "~/components/console/kv-list.tsx"; import { StatusBadge } from "~/components/console/status-badge.tsx"; import { TechInfo } from "~/components/console/tech-info.tsx"; import { buttonVariants } from "~/components/ui/button.tsx"; import { formatBytes, formatDateTime } from "~/lib/utils.ts"; import { cueTime, prettyJson, previewKindOf } from "./model.ts"; /** 图片原图经 /files/download 内联,上限与下载一致 */ const IMAGE_INLINE_MAX = 20 * 1024 * 1024; const ENCODING_LABEL: Record = { "utf-8": "UTF-8", "utf-16le": "UTF-16 LE", "utf-16be": "UTF-16 BE", gb18030: "GBK / GB18030", big5: "Big5", "windows-1252": "Latin-1", }; const encLabel = (e: string | null) => (e ? (ENCODING_LABEL[e] ?? e) : null); const preBox = "max-h-[28rem] overflow-auto rounded-xl border border-border bg-foreground/[0.03] p-3 font-mono text-meta whitespace-pre-wrap break-words"; function Meta({ items }: { items: (string | null | false | undefined)[] }) { return

{items.filter(Boolean).join(" · ")}

; } function TextView({ p }: { p: Extract }) { const body = p.format === "json" ? prettyJson(p.text, p.truncated) : p.text; return ( <> {body.trim() ? (
{body}
) : (

空文件。

)} {p.truncated ?

只显示前 1 MB

: null} ); } function SubtitleView({ p }: { p: Extract }) { const graphic = p.format === "sup"; const count = p.parsed > p.cues.length ? `共 ${p.parsed}${p.truncated ? "+" : ""} 条,显示前 ${p.cues.length} 条` : `${p.cues.length} 条`; return ( <> {graphic ?

PGS 图形字幕没有文字,只列时间轴。

: null} {p.cues.length ? (
    {p.cues.map((c, i) => (
  1. {cueTime(c.start)} {graphic ? ` → ${cueTime(c.end)}` : ""} {graphic ? null : {c.text}}
  2. ))}
) : (

没有读到字幕条目。

)} {p.truncated ?

只读了文件前 1 MB

: null} ); } function TorrentView({ p }: { p: Extract }) { const prefix = `${p.name}/`; return (
私有 : "否" }, ...(p.comment ? [{ label: "注释", value: p.comment, wide: true }] : []), ]} />

文件 {p.fileCount} {p.fileCount > p.files.length ? `,显示前 ${p.files.length} 个` : ""}

    {p.files.map((f) => (
  • {f.path.startsWith(prefix) ? f.path.slice(prefix.length) : f.path} {formatBytes(f.length, 1)}
  • ))}

Tracker {p.trackers.length}(passkey 已隐藏)

{p.trackers.length ? (
    {p.trackers.map((t) => (
  • {t}
  • ))}
) : (

没有 tracker,只能走 DHT。

)}
); } function ImageView({ root, entry, p, }: { root: FileRoot; entry: FileEntry; p: Extract; }) { const dims = p.width && p.height ? `${p.width} × ${p.height}` : null; const meta = ; if (filesMockEnabled()) return ( <> {meta}

演示数据没有图片内容。

); if (p.size > IMAGE_INLINE_MAX) return ( <> {meta}

图片超过 20 MB,不在这里显示。

); const src = fileDownloadUrl(root.id, entry.path, true); return ( <> {meta} {entry.name} 在新窗口打开原图 ); } /** 详情抽屉的「预览」节:文本/NFO、字幕、.torrent、图片;其它类型不渲染 */ export function FilePreviewSection({ root, entry }: { root: FileRoot; entry: FileEntry }) { const kind = previewKindOf(entry); const q = useFilePreview(root.id, entry.path, !!kind && !entry.escape); if (!kind || entry.escape) return null; return ( IMAGE_INLINE_MAX ? null : ( 下载 ) } > {q.isPending ? ( ) : q.error ? (

{filesErrorText(q.error, "预览失败")}

) : q.data.type === "text" ? ( ) : q.data.type === "subtitle" ? ( ) : q.data.type === "torrent" ? ( ) : ( )}
); }