import Icon from "../../components/common/Icon"; import type { ActiveEditSession, LatestVersion, ScriptItem, ScriptType, } from "../../services/api"; import { scriptIcon } from "./WorkspaceTree"; import type { MouseEvent as ReactMouseEvent } from "react"; import { useRef, useEffect } from "react"; type ToastState = { tone: "success" | "error" | "info"; message: string; }; type ScriptWorkspaceProps = { script: ScriptItem; editSession: ActiveEditSession | null; jupyterUrl: string | null; editBusy: boolean; openError: string | null; latestVersion: LatestVersion | null; versionsLoading: boolean; openTabs: Array<{ scriptId: string; scriptName: string; scriptType: ScriptType }>; onOpenEditor: () => void; onEndEditing: () => void; onClose: (scriptId: string, event?: ReactMouseEvent) => void; onSwitchTab: (scriptId: string) => void; onNewTab: () => void; onPublish: () => void; onInfo: (toast: ToastState) => void; }; function formatTime(value: string) { return new Intl.DateTimeFormat("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false, }).format(new Date(value)); } function formatBytes(value: number) { if (value < 1024) return `${value} B`; return `${(value / 1024).toFixed(1)} KB`; } function shortHash(value: string) { return value ? `${value.slice(0, 8)}…${value.slice(-6)}` : "—"; } function confineJupyterFrame(frame: HTMLIFrameElement): void { try { const document = frame.contentDocument; if (!document?.documentElement) return; const keepInside = (): void => { document.querySelectorAll("button,[role='button']").forEach( (element) => { const label = `${element.getAttribute("aria-label") ?? ""} ${ element.getAttribute("title") ?? "" } ${element.textContent ?? ""}`.trim(); if (/^open in\b/i.test(label) || /\bopen in\.\.\./i.test(label)) { element.style.setProperty("display", "none", "important"); } }, ); document.querySelectorAll("a[target]").forEach((link) => { if (["_blank", "_top", "_parent"].includes(link.target)) { link.target = "_self"; } }); }; keepInside(); new MutationObserver(keepInside).observe(document.documentElement, { childList: true, subtree: true, }); document.addEventListener("click", (event) => { const target = event.target as HTMLElement | null; const link = target?.closest?.("a") as HTMLAnchorElement | null; if (link && ["_blank", "_top", "_parent"].includes(link.target)) { link.target = "_self"; } }, true); } catch { // The iframe remains sandboxed even if its document is not yet accessible. } } export function ScriptWorkspace({ script, editSession, jupyterUrl, editBusy, openError, latestVersion, versionsLoading, openTabs, onOpenEditor, onEndEditing, onClose, onSwitchTab, onNewTab, onPublish, onInfo, }: ScriptWorkspaceProps) { const tabbarRef = useRef(null); const isNotebook = script.script_type === "notebook"; const isEditing = editSession?.session_status === "active"; const scroll = (direction: "left" | "right") => { const tabbar = tabbarRef.current; if (!tabbar) return; const scrollAmount = 200; tabbar.scrollBy({ left: direction === "left" ? -scrollAmount : scrollAmount, behavior: "smooth", }); }; useEffect(() => { const tabbar = tabbarRef.current; if (!tabbar) return; const activeTab = tabbar.querySelector(".editor-tab--active") as HTMLElement | null; if (activeTab) { const tabbarRect = tabbar.getBoundingClientRect(); const tabRect = activeTab.getBoundingClientRect(); if (tabRect.right > tabbarRect.right || tabRect.left < tabbarRect.left) { activeTab.scrollIntoView({ behavior: "smooth", inline: "center" }); } } }, [script.script_id]); return ( <>
{openTabs.map((tab) => (
onSwitchTab(tab.scriptId)} > {tab.scriptName}
))}
工作副本 {script.script_name}
{isEditing ? ( ) : ( )} {isEditing ? isNotebook ? "Demo 无锁模式 · Kernel 已连接" : "Demo 无锁模式 · 编辑中" : latestVersion ? `最新 ${latestVersion.version_label}` : "工作副本已就绪"}
{isEditing && jupyterUrl ? (
Workspace Jupyter Server {isNotebook ? "Notebook Session · Python 3 Kernel" : "Jupyter 文本编辑器"} Runtime {editSession.runtime_id.slice(-8)}