import React, { useState, useEffect, useCallback } from "react"; import { invoke } from "@tauri-apps/api/core"; import { X, Save, FileText, AlertCircle, Check, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import { useTranslation } from "react-i18next"; import Editor from "@monaco-editor/react"; import { motion, AnimatePresence } from "framer-motion"; interface FileEditorProps { filePath: string; onClose: () => void; className?: string; } // 根据文件扩展名获取语言 const getLanguageFromPath = (path: string): string => { const ext = path.split(".").pop()?.toLowerCase(); const languageMap: Record = { // JavaScript/TypeScript js: "javascript", jsx: "javascript", ts: "typescript", tsx: "typescript", // Web html: "html", htm: "html", css: "css", scss: "scss", sass: "sass", less: "less", // Programming Languages py: "python", java: "java", c: "c", cpp: "cpp", cc: "cpp", cxx: "cpp", cs: "csharp", php: "php", rb: "ruby", go: "go", rs: "rust", kt: "kotlin", swift: "swift", m: "objective-c", scala: "scala", sh: "shell", bash: "shell", zsh: "shell", fish: "shell", ps1: "powershell", r: "r", lua: "lua", perl: "perl", // Data/Config json: "json", xml: "xml", yaml: "yaml", yml: "yaml", toml: "toml", ini: "ini", cfg: "ini", conf: "ini", // Documentation md: "markdown", markdown: "markdown", rst: "restructuredtext", tex: "latex", // Database sql: "sql", // Others dockerfile: "dockerfile", makefile: "makefile", cmake: "cmake", gradle: "gradle", }; return languageMap[ext || ""] || "plaintext"; }; export const FileEditor: React.FC = ({ filePath, onClose, className, }) => { const { t } = useTranslation(); const [content, setContent] = useState(""); const [originalContent, setOriginalContent] = useState(""); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [saved, setSaved] = useState(false); const [hasChanges, setHasChanges] = useState(false); const fileName = filePath.split("/").pop() || filePath; const language = getLanguageFromPath(filePath); // 加载文件内容 const loadFile = useCallback(async () => { if (!filePath) return; try { setLoading(true); setError(null); const fileContent = await invoke("read_file", { path: filePath, }); setContent(fileContent); setOriginalContent(fileContent); setHasChanges(false); } catch (err) { console.error("Failed to load file:", err); setError(err instanceof Error ? err.message : "Failed to load file"); } finally { setLoading(false); } }, [filePath]); // 保存文件 const saveFile = useCallback(async () => { if (!filePath || !hasChanges) return; try { setSaving(true); setError(null); await invoke("write_file", { path: filePath, content: content, }); setOriginalContent(content); setHasChanges(false); setSaved(true); // 显示保存成功提示 setTimeout(() => setSaved(false), 2000); } catch (err) { console.error("Failed to save file:", err); setError(err instanceof Error ? err.message : "Failed to save file"); } finally { setSaving(false); } }, [filePath, content, hasChanges]); // 处理内容变化 const handleContentChange = (value: string | undefined) => { if (value !== undefined) { setContent(value); setHasChanges(value !== originalContent); } }; // 处理关闭 const handleClose = () => { if (hasChanges) { if (confirm(t("app.unsavedChangesConfirm"))) { onClose(); } } else { onClose(); } }; // 快捷键处理 useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { // Ctrl/Cmd + S 保存 if ((e.ctrlKey || e.metaKey) && e.key === "s" && hasChanges) { e.preventDefault(); saveFile(); } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [hasChanges, saveFile]); // 加载文件 useEffect(() => { if (filePath) { loadFile(); } }, [filePath, loadFile]); return (
{/* Header */}
{fileName} {hasChanges && ( {t("app.modified")} )} {saved && ( {t("app.saved")} )}
{hasChanges && ( )}
{/* Editor */} {error ? (

{t("app.error")}

{error}

) : loading ? (
) : (
)}
); }; export default FileEditor;