update: PythonEditor.tsx
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import Editor from "@monaco-editor/react";
|
||||
|
||||
import Icon from "../../components/common/Icon";
|
||||
|
||||
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<HTMLDivElement | null>(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 (
|
||||
<div className="python-editor" ref={containerRef}>
|
||||
<section className="editor-opening-state" aria-live="polite" style={{ display: "block" }}>
|
||||
<div className="editor-opening-state__icon">
|
||||
<span className="button-spinner button-spinner--blue" />
|
||||
</div>
|
||||
<strong>正在打开 Python 编辑器</strong>
|
||||
<p>正在加载文件内容…</p>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (loadError) {
|
||||
return (
|
||||
<div className="python-editor" ref={containerRef}>
|
||||
<section className="editor-opening-state has-error" aria-live="polite" style={{ display: "block" }}>
|
||||
<div className="editor-opening-state__icon">
|
||||
<Icon name="info" size={28} />
|
||||
</div>
|
||||
<strong>Python 编辑器打开失败</strong>
|
||||
<p>{loadError}</p>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="python-editor" ref={containerRef}>
|
||||
{dirty && <div className="editor-dirty-banner">未保存修改</div>}
|
||||
<div className="python-editor__status">
|
||||
<span>
|
||||
<i /> Workspace Python Editor
|
||||
</span>
|
||||
<span>{script.relative_path}</span>
|
||||
</div>
|
||||
<div className="python-editor__canvas">
|
||||
<Editor
|
||||
height="100%"
|
||||
language="python"
|
||||
theme="vs"
|
||||
value={value}
|
||||
onChange={(v) => {
|
||||
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,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user