"use client"; import React, { useMemo, useCallback, useState, useEffect } from "react"; import { FileText, Copy, Download, Edit, Save, X, Loader2 } from "lucide-react"; import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism"; import { toast } from "sonner"; import { MarkdownContent } from "@/app/components/MarkdownContent"; import type { FileItem } from "@/app/types/types"; import useSWRMutation from "swr/mutation"; const LANGUAGE_MAP: Record = { js: "javascript", jsx: "javascript", ts: "typescript", tsx: "typescript", py: "python", rb: "ruby", go: "go", rs: "rust", java: "java", cpp: "cpp", c: "c", cs: "csharp", php: "php", swift: "swift", kt: "kotlin", scala: "scala", sh: "bash", bash: "bash", zsh: "bash", json: "json", xml: "xml", html: "html", css: "css", scss: "scss", sass: "sass", less: "less", sql: "sql", yaml: "yaml", yml: "yaml", toml: "toml", ini: "ini", dockerfile: "dockerfile", makefile: "makefile", }; export const FileViewDialog = React.memo<{ file: FileItem | null; onSaveFile: (fileName: string, content: string) => Promise; onClose: () => void; editDisabled: boolean; }>(({ file, onSaveFile, onClose, editDisabled }) => { const [isEditingMode, setIsEditingMode] = useState(file === null); const [fileName, setFileName] = useState(String(file?.path || "")); const [fileContent, setFileContent] = useState(String(file?.content || "")); const fileUpdate = useSWRMutation( { kind: "files-update", fileName, fileContent }, async ({ fileName, fileContent }) => { if (!fileName || !fileContent) return; return await onSaveFile(fileName, fileContent); }, { onSuccess: () => setIsEditingMode(false), onError: (error) => toast.error(`Failed to save file: ${error}`), } ); useEffect(() => { setFileName(String(file?.path || "")); setFileContent(String(file?.content || "")); setIsEditingMode(file === null); }, [file]); const fileExtension = useMemo(() => { const fileNameStr = String(fileName || ""); return fileNameStr.split(".").pop()?.toLowerCase() || ""; }, [fileName]); const isMarkdown = useMemo(() => { return fileExtension === "md" || fileExtension === "markdown"; }, [fileExtension]); const language = useMemo(() => { return LANGUAGE_MAP[fileExtension] || "text"; }, [fileExtension]); const handleCopy = useCallback(() => { if (fileContent) { navigator.clipboard.writeText(fileContent); } }, [fileContent]); const handleDownload = useCallback(() => { if (fileContent && fileName) { const blob = new Blob([fileContent], { type: "text/plain" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = fileName; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } }, [fileContent, fileName]); const handleEdit = useCallback(() => { setIsEditingMode(true); }, []); const handleCancel = useCallback(() => { if (file === null) { onClose(); } else { setFileName(String(file.path)); setFileContent(String(file.content)); setIsEditingMode(false); } }, [file, onClose]); const fileNameIsValid = useMemo(() => { return ( fileName.trim() !== "" && !fileName.includes("/") && !fileName.includes(" ") ); }, [fileName]); return ( {file?.path || "New File"}
{isEditingMode && file === null ? ( setFileName(e.target.value)} placeholder="Enter filename..." className="text-base font-medium" aria-invalid={!fileNameIsValid} /> ) : ( {file?.path} )}
{!isEditingMode && ( <> )}
{isEditingMode ? (