{"version":3,"file":"index-CxyAJGkI.mjs","sources":["../../src/renderers/Subtitle/index.tsx"],"sourcesContent":["import { useState, useEffect, useMemo, forwardRef, useImperativeHandle } from 'react';\nimport {\n  parseSubtitle,\n  formatSubtitleTime,\n  fetchTextUtf8,\n  type SubtitleParseResult,\n  type SubtitleFormat,\n} from '@eternalheart/file-preview-core';\nimport { useTranslator } from '../../i18n/LocaleContext';\nimport { useFetcher } from '../../RequestContext';\nimport { RendererError } from '../RendererError';\nimport type { RendererHandle } from '../base.types';\n\ninterface SubtitleRendererProps {\n  url: string;\n  fileName: string;\n}\n\nconst FORMAT_BY_EXT: Record<string, SubtitleFormat> = {\n  srt: 'srt',\n  vtt: 'vtt',\n  lrc: 'lrc',\n  elrc: 'elrc',\n  ass: 'ass',\n  ssa: 'ssa',\n  ttml: 'ttml',\n  dfxp: 'ttml',\n};\n\nconst getFormat = (fileName: string): SubtitleFormat | undefined => {\n  const ext = fileName.split('.').pop()?.toLowerCase() || '';\n  return FORMAT_BY_EXT[ext];\n};\n\nexport const SubtitleRenderer = forwardRef<RendererHandle, SubtitleRendererProps>(({ url, fileName }, ref) => {\n  const t = useTranslator();\n  const fetcher = useFetcher();\n  const [text, setText] = useState<string>('');\n  const [loading, setLoading] = useState(true);\n  const [error, setError] = useState<string | null>(null);\n\n  useEffect(() => {\n    const controller = new AbortController();\n    const load = async () => {\n      try {\n        setLoading(true);\n        setError(null);\n        setText(await fetchTextUtf8(url, { fetcher, signal: controller.signal }));\n      } catch (err: any) {\n        if (err.name === 'AbortError') return;\n        console.warn('[SubtitleRenderer] Failed to load subtitle:', err instanceof Error ? err.message : String(err));\n        setError(t('subtitle.load_failed'));\n      } finally {\n        setLoading(false);\n      }\n    };\n    load();\n    return () => controller.abort();\n  }, [url]);\n\n  const parsed: SubtitleParseResult | null = useMemo(() => {\n    if (!text) return null;\n    try {\n      return parseSubtitle(text, getFormat(fileName));\n    } catch (err) {\n      // 字幕解析失败通常是格式不支持或文件损坏，用 warn 级别记录\n      console.warn('[SubtitleRenderer] Failed to parse subtitle:', err instanceof Error ? err.message : String(err));\n      return null;\n    }\n  }, [text, fileName]);\n\n  // 暴露接口给父组件\n  useImperativeHandle(ref, () => ({\n    getToolbarGroups: () => [],\n  }), []);\n\n  if (loading) {\n    return (\n      <div className=\"rfp-flex rfp-items-center rfp-justify-center rfp-w-full rfp-h-full rfp-bg-[#0f0f12]\">\n        <div className=\"rfp-w-12 rfp-h-12 rfp-border-4 rfp-border-line-strong rfp-border-t-spinner-head rfp-rounded-full rfp-animate-spin\" />\n      </div>\n    );\n  }\n\n  if (error || !parsed) {\n    return <RendererError message={error || t('subtitle.parse_failed')} />;\n  }\n\n  const isLyric = parsed.format === 'lrc' || parsed.format === 'elrc';\n  const meta = parsed.metadata ?? {};\n  const dotHover = isLyric ? 'group-hover:rfp-bg-violet-400' : 'group-hover:rfp-bg-sky-400';\n\n  return (\n    <div className=\"rfp-relative rfp-w-full rfp-h-full rfp-bg-[#0f0f12]\">\n      {/* 内容滚动区 */}\n      <div className=\"rfp-w-full rfp-h-full rfp-overflow-auto rfp-px-4 rfp-pt-6 rfp-pb-16\">\n        <div className=\"rfp-relative\">\n          {/* vertical line */}\n          <div className=\"rfp-absolute rfp-left-[5px] rfp-top-2 rfp-bottom-2 rfp-w-px rfp-bg-surface-1\" />\n\n          <ol className=\"rfp-space-y-5\">\n            {parsed.cues.map((cue, i) => (\n              <li key={`cue-${i}`} className=\"rfp-relative rfp-pl-6 rfp-group\">\n                {/* dot */}\n                <div\n                  className={`rfp-absolute rfp-left-0 rfp-top-[0.4rem] rfp-w-3 rfp-h-3 rfp-rounded-full rfp-bg-surface-3 rfp-border-2 rfp-border-[#0f0f12] rfp-transition-colors ${dotHover}`}\n                />\n\n                <div className=\"rfp-flex rfp-flex-wrap rfp-items-baseline rfp-gap-x-3 rfp-gap-y-1 rfp-mb-1.5\">\n                  <span className=\"rfp-text-[11px] rfp-font-mono rfp-text-fg-muted rfp-tabular-nums\">\n                    {formatSubtitleTime(cue.start)}\n                  </span>\n                  <span className=\"rfp-text-[11px] rfp-text-fg-disabled\">→</span>\n                  <span className=\"rfp-text-[11px] rfp-font-mono rfp-text-fg-muted rfp-tabular-nums\">\n                    {formatSubtitleTime(cue.end)}\n                  </span>\n                  <span className=\"rfp-text-[10px] rfp-font-mono rfp-text-fg-disabled rfp-tabular-nums\">\n                    #{cue.id ?? i + 1}\n                  </span>\n                  {cue.style && (\n                    <span className=\"rfp-text-[9px] rfp-uppercase rfp-tracking-widest rfp-text-fg-tertiary rfp-px-1.5 rfp-py-0.5 rfp-rounded rfp-bg-surface-1 rfp-border rfp-border-line-weak\">\n                      {cue.style}\n                    </span>\n                  )}\n                </div>\n\n                {cue.words && cue.words.length > 0 ? (\n                  <div className=\"rfp-flex rfp-flex-wrap rfp-gap-x-1.5 rfp-gap-y-1 rfp-text-base rfp-text-fg-primary rfp-leading-relaxed group-hover:rfp-text-fg-primary rfp-transition-colors\">\n                    {cue.words.map((word, wi) => (\n                      <span\n                        key={`w-${wi}`}\n                        className=\"rfp-inline-flex rfp-flex-col rfp-items-start\"\n                        title={formatSubtitleTime(word.start)}\n                      >\n                        <span className=\"rfp-text-[9px] rfp-text-fg-disabled rfp-font-mono rfp-leading-none rfp-tabular-nums\">\n                          {formatSubtitleTime(word.start).slice(3, 8)}\n                        </span>\n                        <span className=\"rfp-leading-snug\">{word.text}</span>\n                      </span>\n                    ))}\n                  </div>\n                ) : (\n                  <p\n                    className={`rfp-whitespace-pre-wrap rfp-break-words rfp-leading-relaxed group-hover:rfp-text-fg-primary rfp-transition-colors rfp-text-fg-primary rfp-min-h-[1.25rem] ${\n                      isLyric ? 'rfp-text-base rfp-font-medium' : 'rfp-text-sm'\n                    }`}\n                  >\n                    {cue.text}\n                  </p>\n                )}\n              </li>\n            ))}\n          </ol>\n        </div>\n      </div>\n\n      {/* 底部状态栏 */}\n      <div className=\"rfp-pointer-events-none rfp-absolute rfp-bottom-3 rfp-right-3 rfp-flex rfp-items-center rfp-gap-2 rfp-px-2.5 rfp-py-1 rfp-rounded-full rfp-bg-surface-nav rfp-backdrop-blur rfp-border rfp-border-line-weak rfp-text-[10px] rfp-text-fg-tertiary rfp-font-mono rfp-tabular-nums\">\n        <span>{parsed.cues.length} {isLyric ? t('subtitle.lines') : t('subtitle.cues')}</span>\n        {meta.length && (\n          <>\n            <span className=\"rfp-text-fg-disabled\">·</span>\n            <span>{meta.length}</span>\n          </>\n        )}\n      </div>\n    </div>\n  );\n});\n"],"names":["FORMAT_BY_EXT","getFormat","fileName","ext","_a","SubtitleRenderer","forwardRef","url","ref","t","useTranslator","fetcher","useFetcher","text","setText","useState","loading","setLoading","error","setError","useEffect","controller","fetchTextUtf8","err","parsed","useMemo","parseSubtitle","useImperativeHandle","jsx","RendererError","isLyric","meta","dotHover","jsxs","cue","i","formatSubtitleTime","word","wi","Fragment"],"mappings":"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"}