import { useEffect, useRef, useState } from "react"; import Editor from "@monaco-editor/react"; import { Info } from "lucide-react"; import type { ScriptItem } from "../../services/api"; interface PythonEditorProps { script: ScriptItem; scriptId: string; initialContent: string; saving: boolean; dirty: boolean; loading: boolean; loadError?: string | null; onChange: (scriptId: string, value: string) => void; onSave: (scriptId: string) => void; onEndEditing: (scriptId: string) => void; onCloseTab: (scriptId: string) => void; } export function PythonEditor({ script, scriptId, initialContent, saving, dirty, loading, loadError, onChange, onSave, onEndEditing, onCloseTab, }: PythonEditorProps) { const containerRef = useRef(null); const [value, setValue] = useState(initialContent); useEffect(() => { const onKey = (e: KeyboardEvent) => { const mod = e.metaKey || e.ctrlKey; if (!mod) return; if (e.key.toLowerCase() === "s") { e.preventDefault(); if (dirty && !saving) onSave(scriptId); } else if (e.key.toLowerCase() === "w") { e.preventDefault(); onCloseTab(scriptId); } }; containerRef.current?.addEventListener("keydown", onKey); return () => containerRef.current?.removeEventListener("keydown", onKey); }, [dirty, saving, onSave, onCloseTab, scriptId]); useEffect(() => { if (!dirty || saving) return; const t = window.setTimeout(() => onSave(scriptId), 30_000); return () => window.clearTimeout(t); }, [value, dirty, saving, onSave, scriptId]); const hasSyncedRef = useRef(false); useEffect(() => { if (!loading && initialContent !== null && !hasSyncedRef.current) { setValue(initialContent); hasSyncedRef.current = true; } }, [loading, initialContent]); if (loading) { return (
正在打开 Python 编辑器

正在加载文件内容…

); } if (loadError) { return (
Python 编辑器打开失败

{loadError}

); } return (
{dirty && (
未保存修改
)}
Workspace Python Editor {script.relative_path}
{ setValue(v ?? ""); onChange(scriptId, v ?? ""); }} options={{ minimap: { enabled: initialContent.length > 5000 }, wordWrap: "on", fontSize: 13, automaticLayout: true, renderLineHighlight: "gutter", contextmenu: false, dragAndDrop: false, scrollBeyondLastLine: false, }} />
); }