update:多iframe

This commit is contained in:
xiaozhu
2026-08-06 16:32:39 +08:00
parent 245b522d36
commit fcc0964765
3 changed files with 259 additions and 1647 deletions
@@ -6,7 +6,7 @@ import type {
ScriptType,
} from "../../services/api";
import { scriptIcon } from "./WorkspaceTree";
import type { MouseEvent as ReactMouseEvent } from "react";
import type { MouseEvent as ReactMouseEvent, RefObject } from "react";
import { useRef, useEffect } from "react";
type ToastState = {
@@ -14,8 +14,16 @@ type ToastState = {
message: string;
};
// 缓存的会话类型(多 iframe 共存方案)
interface CachedSession {
session: ActiveEditSession;
jupyterUrl: string;
lastActiveTime: number;
}
type ScriptWorkspaceProps = {
script: ScriptItem;
sessionCache: Map<string, CachedSession>;
editSession: ActiveEditSession | null;
jupyterUrl: string | null;
editBusy: boolean;
@@ -91,6 +99,7 @@ function confineJupyterFrame(frame: HTMLIFrameElement): void {
export function ScriptWorkspace({
script,
sessionCache,
editSession,
jupyterUrl,
editBusy,
@@ -108,7 +117,9 @@ export function ScriptWorkspace({
}: ScriptWorkspaceProps) {
const tabbarRef = useRef<HTMLDivElement | null>(null);
const isNotebook = script.script_type === "notebook";
const isEditing = editSession?.session_status === "active";
// 判断当前选中的文件是否正在编辑(需要同时检查 session_status 和 script_id
const isEditing = editSession?.session_status === "active"
&& editSession?.script_id === script.script_id;
const scroll = (direction: "left" | "right") => {
const tabbar = tabbarRef.current;
@@ -232,64 +243,85 @@ export function ScriptWorkspace({
</div>
</div>
<div className={`editor-canvas${isEditing && jupyterUrl ? " is-embedded" : ""}`}>
{isEditing && jupyterUrl ? (
<section className="embedded-jupyter">
<div className="embedded-jupyter__status">
<span>
<i />
Workspace Jupyter Server
</span>
<span>
{isNotebook ? "Notebook Session · Python 3 Kernel" : "Jupyter 文本编辑器"}
</span>
<code title={editSession.runtime_id}>
Runtime {editSession.runtime_id.slice(-8)}
</code>
</div>
<iframe
key={`${editSession.edit_session_id}:${jupyterUrl}`}
src={jupyterUrl}
title={`${script.script_name} Jupyter 编辑器`}
allow="clipboard-read; clipboard-write"
sandbox="allow-same-origin allow-scripts allow-forms allow-downloads allow-modals"
onLoad={(event) => confineJupyterFrame(event.currentTarget)}
/>
</section>
) : isNotebook ? (
<section
className={`editor-opening-state${openError ? " has-error" : ""}`}
aria-live="polite"
>
<div className="editor-opening-state__icon">
{openError
? <Icon name="info" size={28} />
: <span className="button-spinner button-spinner--blue" />}
</div>
<strong>
{openError ? "Notebook 打开失败" : "正在打开 Notebook"}
</strong>
<p>
{openError
? openError
: "正在获取编辑锁并连接 Workspace Jupyter Server…"}
</p>
{openError && (
<button
className="open-editor-button"
type="button"
disabled={editBusy}
onClick={onOpenEditor}
>
{editBusy
? <span className="button-spinner button-spinner--blue" />
: <Icon name="refresh" size={16} />}
{editBusy ? "正在重试…" : "重试打开"}
</button>
)}
</section>
) : (
<section className="script-overview">
{/* 多 iframe 共存方案:为每个缓存的 session 渲染独立的 iframe */}
<div className={`editor-canvas ${sessionCache.size > 0 ? 'is-embedded' : ''}`} style={sessionCache.size > 0 ? { position: 'relative', minHeight: '0', height: '100%' } : undefined}>
{/* 渲染所有缓存的 iframe */}
{Array.from(sessionCache.entries()).map(([scriptId, cached]) => {
// 当前选中的脚本就是激活的
const isActive = scriptId === script.script_id;
return (
<section
key={scriptId}
className="embedded-jupyter"
style={{
position: isActive ? 'relative' : 'absolute',
visibility: isActive ? 'visible' : 'hidden',
pointerEvents: isActive ? 'auto' : 'none',
width: '100%',
height: '100%',
overflow: 'hidden'
}}
>
<div className="embedded-jupyter__status">
<span>
<i />
Workspace Jupyter Server
</span>
<span>
{cached.session.session_status === "active" ? "Notebook Session · Python 3 Kernel" : "Jupyter 文本编辑器"}
</span>
<code title={cached.session.runtime_id}>
Runtime {cached.session.runtime_id.slice(-8)}
</code>
</div>
<iframe
src={cached.jupyterUrl}
title={`${scriptId} Jupyter 编辑器`}
allow="clipboard-read; clipboard-write"
sandbox="allow-same-origin allow-scripts allow-forms allow-downloads allow-modals"
onLoad={(event) => confineJupyterFrame(event.currentTarget)}
/>
</section>
);
})}
{/* 当前脚本没有缓存时的状态显示 */}
{!sessionCache.has(script.script_id) ? (
isNotebook ? (
<section
className={`editor-opening-state${openError ? " has-error" : ""}`}
aria-live="polite"
style={{ display: 'block' }}
>
<div className="editor-opening-state__icon">
{openError
? <Icon name="info" size={28} />
: <span className="button-spinner button-spinner--blue" />}
</div>
<strong>
{openError ? "Notebook 打开失败" : "正在打开 Notebook"}
</strong>
<p>
{openError
? openError
: "正在获取编辑锁并连接 Workspace Jupyter Server…"}
</p>
{openError && (
<button
className="open-editor-button"
type="button"
disabled={editBusy}
onClick={onOpenEditor}
>
{editBusy
? <span className="button-spinner button-spinner--blue" />
: <Icon name="refresh" size={16} />}
{editBusy ? "正在重试…" : "重试打开"}
</button>
)}
</section>
) : (
<section className="script-overview">
<div className="script-overview__header">
<div>
<span className="section-kicker">PYTHON SCRIPT</span>
@@ -365,7 +397,7 @@ export function ScriptWorkspace({
</span>
</div>
</section>
)}
)) : null}
</div>
</>
);