// ---- pythonEditorSlice ---- // // 拥有 pythonEditorBuffers (per-scriptId 临时编辑缓冲)。 // Actions: openPythonEditor / setPythonEditorContent / savePythonEditor / // exitPythonEditor / exitAllPythonEditors。 // 模块级 _pythonEditorOpeningIds (去重 in-flight open) 通过 helpers 访问。 import type { StateCreator } from "zustand"; import type { ScriptItem } from "~/services/api"; import { getPythonEditorOpeningIds, pushToast, requireApi, } from "./helpers"; import type { PythonEditorSliceActions, PythonEditorSliceState, } from "./types"; import type { ScriptWorkspaceStore } from "./useScriptWorkspaceStore"; export const createPythonEditorSlice: StateCreator< ScriptWorkspaceStore, [], [], PythonEditorSliceState & PythonEditorSliceActions > = (set, get) => { const initial: PythonEditorSliceState = { pythonEditorBuffers: {}, }; return { ...initial, openPythonEditor: async (script: ScriptItem, showToast = true) => { if (!script) return; const opening = getPythonEditorOpeningIds(); if (opening.has(script.script_id)) return; opening.add(script.script_id); try { const workspaceId = script.workspace_id; const url = `/jupyter/${workspaceId}/api/contents/${script.jupyter_path}` + `?type=file&content=1&hash=1&format=text`; const response = await fetch(url, { credentials: "include", cache: "no-store", }); if (!response.ok) { throw new Error(`加载文件失败: ${response.status}`); } const data = await response.json(); const content = typeof data.content === "string" ? data.content : ""; set((state) => ({ pythonEditorBuffers: { ...state.pythonEditorBuffers, [script.script_id]: { initialContent: content, content, dirty: false, saving: false, initial: true, loadError: null, }, }, })); } catch (error) { const message = error instanceof Error ? error.message : "打开 Python 编辑器失败"; set((state) => ({ pythonEditorBuffers: { ...state.pythonEditorBuffers, [script.script_id]: { initialContent: null, content: null, dirty: false, saving: false, initial: false, loadError: message, }, }, })); if (showToast) { pushToast("error", message); } } finally { opening.delete(script.script_id); } }, setPythonEditorContent: (scriptId, value) => { set((state) => { const buffer = state.pythonEditorBuffers[scriptId]; if (!buffer) return state; return { pythonEditorBuffers: { ...state.pythonEditorBuffers, [scriptId]: { ...buffer, content: value, dirty: value !== buffer.initialContent, }, }, }; }); }, savePythonEditor: async (scriptId) => { const buffer = get().pythonEditorBuffers[scriptId]; if (!buffer || buffer.content === null || !buffer.dirty || buffer.saving) { return; } const script = get().scripts.find((s) => s.script_id === scriptId); if (!script) return; const savedContent = buffer.content; set((state) => ({ pythonEditorBuffers: { ...state.pythonEditorBuffers, [scriptId]: { ...buffer, saving: true }, }, })); try { const api = requireApi(); await api.updateScript(scriptId, { content: savedContent }); set((state) => { const current = state.pythonEditorBuffers[scriptId]; if (!current) return state; return { pythonEditorBuffers: { ...state.pythonEditorBuffers, [scriptId]: { ...current, initialContent: savedContent, dirty: current.content !== savedContent, saving: false, initial: false, }, }, }; }); pushToast("success", "已保存"); } catch (error) { set((state) => { const current = state.pythonEditorBuffers[scriptId]; if (!current) return state; return { pythonEditorBuffers: { ...state.pythonEditorBuffers, [scriptId]: { ...current, saving: false }, }, }; }); pushToast( "error", error instanceof Error ? error.message : "保存失败", ); } }, exitPythonEditor: (scriptId) => { set((state) => { const next = { ...state.pythonEditorBuffers }; delete next[scriptId]; return { pythonEditorBuffers: next }; }); }, exitAllPythonEditors: () => { const opening = getPythonEditorOpeningIds(); opening.clear(); set({ pythonEditorBuffers: {} }); }, }; };