import { useRef, useState } from 'react'; import { AlignLeft, Book, BookOpen, Braces, Code2, Download, Eye, Hash, Image as ImageIcon, Loader2, Pencil, Save, SlidersHorizontal, Terminal, X, } from 'lucide-react'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import CodeMirror, { EditorView } from '@uiw/react-codemirror'; import { oneDark } from '@codemirror/theme-one-dark'; import HexEditor from './HexEditor'; import CodeEditor from './CodeEditor'; import ConfigEditor from './ConfigEditor'; import type { EntryInfo } from '../webdav'; export type ViewMode = 'text' | 'markdown' | 'json' | 'xml' | 'hex' | 'image' | 'config' | 'code' | 'doc'; const VIEWER_LABEL: Record = { code: 'Code', text: 'Text', markdown: 'Markdown', json: 'JSON', xml: 'HTML/XML', hex: 'Hex', image: 'Image', config: 'Config', doc: 'Document', }; const EXT_TO_LANG: Record = { asm: 'nasm', s: 'nasm', bas: 'basic', js: 'javascript', ts: 'typescript', jsx: 'jsx', tsx: 'tsx', css: 'css', scss: 'scss', py: 'python', c: 'c', cpp: 'cpp', h: 'cpp', hpp: 'cpp', lua: 'lua', sh: 'bash', bash: 'bash', php: 'php', rb: 'ruby', rs: 'rust', go: 'go', java: 'java', cs: 'csharp', kt: 'kotlin', sql: 'sql', pl: 'perl', }; function ViewerModeIcon({ mode, className }: { mode: ViewMode; className?: string }) { const cls = className ?? 'w-4 h-4'; switch (mode) { case 'text': return ; case 'markdown': return ; case 'json': return ; case 'code': return ; case 'xml': return ; case 'hex': return ; case 'image': return ; case 'config': return ; case 'doc': return ; } } function MarkdownViewer({ text }: { text: string }) { return (

{children}

, h2: ({ children }) =>

{children}

, h3: ({ children }) =>

{children}

, p: ({ children }) =>

{children}

, a: ({ href, children }) => {children}, code: ({ className, children, ...props }) => { const match = /language-(\w+)/.exec(className ?? ''); const inline = !match && !String(children).includes('\n'); return inline ? {children} : (
{String(children).replace(/\n$/, '')}
); }, pre: ({ children }) => <>{children}, blockquote: ({ children }) =>
{children}
, ul: ({ children }) =>
    {children}
, ol: ({ children }) =>
    {children}
, li: ({ children }) =>
  • {children}
  • , hr: () =>
    , table: ({ children }) => {children}
    , th: ({ children }) => {children}, td: ({ children }) => {children}, strong: ({ children }) => {children}, img: ({ src, alt }) => {alt}, }} > {text}
    ); } const CM_THEME = EditorView.theme({ '&': { height: '100%', background: '#0a0a0a' }, '.cm-scroller': { overflow: 'auto', fontFamily: 'ui-monospace,monospace', fontSize: '12px', lineHeight: '1.5' }, '.cm-content': { padding: '12px 0' }, '.cm-focused': { outline: 'none' }, }); function MarkdownEditor({ text, onSave }: { text: string; onSave?: (s: string) => Promise }) { const [editMode, setEditMode] = useState(false); const [editInitText, setEditInitText] = useState(''); const [saving, setSaving] = useState(false); const editorViewRef = useRef(null); const save = async () => { if (!editorViewRef.current || !onSave) return; setSaving(true); try { await onSave(editorViewRef.current.state.doc.toString()); } finally { setSaving(false); } }; return (
    {onSave && ( )} {editMode && onSave && ( )} {editMode && Ctrl+Z/Y undo · Ctrl+F search}
    {editMode ? (
    { editorViewRef.current = v; }} />
    ) : ( )}
    ); } export interface MediaViewerEditorProps { entry: EntryInfo; mode: ViewMode; availableModes: ViewMode[]; text: string | null; imgUrl: string | null; hexData: Uint8Array | null; loading: boolean; onClose: () => void; onSwitchMode: (mode: ViewMode) => void; onSave: (content: string | Uint8Array) => Promise; onDownload: () => void; } export default function MediaViewerEditor({ entry, mode, availableModes, text, imgUrl, hexData, loading, onClose, onSwitchMode, onSave, onDownload, }: MediaViewerEditorProps) { return (
    {/* Title + mode switcher */}
    {entry.name}
    {availableModes.map(m => ( ))}
    {loading && (
    Loading…
    )} {!loading && mode === 'image' && imgUrl && (
    {entry.name}
    )} {!loading && mode === 'hex' && hexData && ( void onSave(d)} /> )} {!loading && mode === 'markdown' && text !== null && ( onSave(s)} /> )} {!loading && (mode === 'text' || mode === 'json' || mode === 'xml') && text !== null && ( onSave(s)} /> )} {!loading && mode === 'config' && text !== null && ( onSave(s)} /> )} {!loading && mode === 'code' && text !== null && ( onSave(s)} /> )}
    ); }