Files

175 lines
5.1 KiB
TypeScript

// ---- 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: {} });
},
};
};