update:css样式拆分
This commit is contained in:
@@ -15,6 +15,30 @@ import { useAuth } from "../../context/AuthContext";
|
||||
import { getScriptContent } from "../../services/api";
|
||||
import {PythonEditor} from "~/features/platform/PythonEditor";
|
||||
|
||||
function fileIconTone(type: ScriptType | "data") {
|
||||
if (type === "notebook") return "text-[#e15e50] bg-[#fff0ed]";
|
||||
if (type === "data") return "text-[#5a8f6a] bg-[#eef8f1]";
|
||||
return "text-[#2e73c6] bg-[#eaf3ff]";
|
||||
}
|
||||
|
||||
const toolbarBtnClass =
|
||||
"h-[31px] cursor-pointer rounded-[5px] border border-[#d9e3ec] bg-white px-[11px] text-[11px] text-[#52708d] hover:border-[#9dbbd8] hover:bg-[#f7fbff] disabled:cursor-wait disabled:opacity-70";
|
||||
|
||||
const endEditBtnClass =
|
||||
"h-[31px] cursor-pointer rounded-[5px] border border-[#e1b8b8] bg-[#fff8f8] px-[11px] text-[11px] text-[#a34c4c] hover:border-[#9dbbd8] hover:bg-[#f7fbff] disabled:cursor-wait disabled:opacity-70";
|
||||
|
||||
const releaseBtnClass =
|
||||
"h-[31px] cursor-pointer rounded-[5px] border border-[#e7c689] bg-[#fffbf3] px-[11px] text-[11px] text-[#9b6a18] hover:border-[#9dbbd8] hover:bg-[#f7fbff]";
|
||||
|
||||
const openEditorBtnClass =
|
||||
"inline-flex h-[37px] cursor-pointer items-center justify-center gap-[7px] rounded-md border border-[#b9d4ef] bg-[#f4f9ff] px-3.5 text-[11px] font-[650] text-[#176cc0] hover:border-[#7eafe0] hover:bg-[#eaf4ff] disabled:cursor-wait disabled:opacity-70";
|
||||
|
||||
const openEditorEditingBtnClass =
|
||||
"inline-flex h-[37px] cursor-pointer items-center justify-center gap-[7px] rounded-md border border-[#69b79a] bg-[#effaf5] px-3.5 text-[11px] font-[650] text-[#137a55] hover:border-[#7eafe0] hover:bg-[#eaf4ff] disabled:cursor-wait disabled:opacity-70";
|
||||
|
||||
const saveBtnClass =
|
||||
"inline-flex h-[31px] cursor-pointer items-center justify-center gap-1.5 rounded-[5px] border border-[#b8dec1] bg-[#f1faf3] px-[11px] text-[11px] font-semibold text-[#1f6d3a] disabled:cursor-not-allowed disabled:opacity-50";
|
||||
|
||||
// 模块级变量存储刷新版本号,用于检测只读内容刷新
|
||||
let _lastRefreshVersion = 0;
|
||||
|
||||
@@ -101,7 +125,7 @@ function NotebookViewer({ content }: { content: object }) {
|
||||
const cells = notebook.cells ?? [];
|
||||
|
||||
return (
|
||||
<div className="notebook-read-only">
|
||||
<div className="flex h-full flex-col overflow-y-auto bg-white">
|
||||
{cells.map((cell, index) => {
|
||||
const sourceText = Array.isArray(cell.source)
|
||||
? cell.source.join("")
|
||||
@@ -109,8 +133,8 @@ function NotebookViewer({ content }: { content: object }) {
|
||||
|
||||
if (cell.cell_type === "markdown") {
|
||||
return (
|
||||
<div key={index} className="notebook-cell notebook-cell--markdown">
|
||||
<div className="notebook-cell__content">
|
||||
<div key={index} className="flex w-full shrink-0 px-6 py-1">
|
||||
<div className="notebook-md flex min-w-0 flex-1 flex-col">
|
||||
{renderMarkdown(sourceText)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -120,36 +144,50 @@ function NotebookViewer({ content }: { content: object }) {
|
||||
if (cell.cell_type === "code") {
|
||||
const hasSource = sourceText.trim().length > 0;
|
||||
return (
|
||||
<div key={index} className="notebook-cell notebook-cell--code">
|
||||
<div className="notebook-cell__prompt">
|
||||
<div key={index} className="flex w-full min-h-11 shrink-0 py-2">
|
||||
<div className="w-[90px] shrink-0 select-none px-4 text-right font-mono text-[11px] leading-[1.6] text-[#8b949e]">
|
||||
In [{cell.execution_count ?? " "}]:
|
||||
</div>
|
||||
<div className="notebook-cell__content">
|
||||
<div className={`notebook-cell__input${!hasSource ? ' notebook-cell__input--empty' : ''}`}>
|
||||
<pre><code>{sourceText}</code></pre>
|
||||
<div className="flex min-w-0 flex-1 flex-col">
|
||||
<div
|
||||
className={`mr-3.5 min-h-6 rounded-[1px] border border-[#ccc] bg-[#f6f8fa] ${
|
||||
hasSource ? "p-1.5" : "flex items-center px-3.5"
|
||||
}`}
|
||||
>
|
||||
<pre className="m-0 bg-transparent font-mono text-[13px] leading-normal break-words whitespace-pre-wrap text-[#24292f]">
|
||||
<code className="bg-transparent font-mono text-[13px]">{sourceText}</code>
|
||||
</pre>
|
||||
</div>
|
||||
{cell.outputs && cell.outputs.length > 0 && (
|
||||
<div className="notebook-cell__outputs">
|
||||
<div className="mt-2">
|
||||
{cell.outputs.map((output, outputIndex) => {
|
||||
// 处理 stream 类型的输出
|
||||
if (output.output_type === "stream" && output.text) {
|
||||
const text = Array.isArray(output.text)
|
||||
? output.text.join("")
|
||||
: output.text;
|
||||
return (
|
||||
<div key={outputIndex} className={`notebook-output notebook-output--${output.name}`}>
|
||||
<pre>{text}</pre>
|
||||
<div
|
||||
key={outputIndex}
|
||||
className="px-1.5 text-[13px] leading-normal text-[#24292f]"
|
||||
>
|
||||
<pre className="m-0 font-mono text-[13px] break-words whitespace-pre-wrap">
|
||||
{text}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// 处理 execute_result / display_data 类型
|
||||
if (output.output_type === "execute_result" || output.output_type === "display_data") {
|
||||
const text = output.data?.["text/plain"];
|
||||
if (text) {
|
||||
const textStr = Array.isArray(text) ? text.join("") : String(text);
|
||||
return (
|
||||
<div key={outputIndex} className="notebook-output">
|
||||
<pre>{textStr}</pre>
|
||||
<div
|
||||
key={outputIndex}
|
||||
className="px-1.5 text-[13px] leading-normal text-[#24292f]"
|
||||
>
|
||||
<pre className="m-0 font-mono text-[13px] break-words whitespace-pre-wrap">
|
||||
{textStr}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -309,7 +347,7 @@ export function ScriptWorkspace({
|
||||
useLayoutEffect(() => {
|
||||
const tabbar = tabbarRef.current;
|
||||
if (!tabbar) return;
|
||||
const activeTab = tabbar.querySelector(".editor-tab--active") as HTMLElement | null;
|
||||
const activeTab = tabbar.querySelector('[data-active="true"]') as HTMLElement | null;
|
||||
if (activeTab) {
|
||||
const tabbarRect = tabbar.getBoundingClientRect();
|
||||
const tabRect = activeTab.getBoundingClientRect();
|
||||
@@ -365,37 +403,54 @@ export function ScriptWorkspace({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="tabbar">
|
||||
<div className="relative flex h-[43px] min-h-[43px] items-stretch overflow-hidden border-b border-[#e5ebf1] bg-[#f7f9fb]">
|
||||
<button
|
||||
className="tabbar-scroll-btn tabbar-scroll-btn--left"
|
||||
className="z-[1] grid w-7 shrink-0 rotate-180 cursor-pointer place-items-center border-0 border-r border-[#e4eaf0] bg-[#f7f9fb] text-[#6b7c8f] transition-colors duration-150 hover:bg-[#edf1f5] hover:text-[#3d4c5c]"
|
||||
type="button"
|
||||
aria-label="向左滚动"
|
||||
onClick={() => scroll("left")}
|
||||
>
|
||||
<Icon name="chevron" size={16} />
|
||||
</button>
|
||||
<div className="tabbar-scroll-content" ref={tabbarRef}>
|
||||
{openTabs.map((tab) => (
|
||||
<div
|
||||
key={tab.scriptId}
|
||||
className={`editor-tab${tab.scriptId === script.script_id ? " editor-tab--active" : ""}`}
|
||||
onClick={() => onSwitchTab(tab.scriptId)}
|
||||
>
|
||||
<span className={`file-icon file-icon--${tab.scriptType}`}>
|
||||
<Icon name={tab.scriptType === "notebook" ? "notebook" : "python"} size={16} />
|
||||
</span>
|
||||
<span>{tab.scriptName}</span>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="关闭标签"
|
||||
onClick={(event) => onClose(tab.scriptId, event)}
|
||||
<div
|
||||
className="tabbar-scroll flex flex-1 overflow-x-auto overflow-y-hidden scroll-smooth"
|
||||
ref={tabbarRef}
|
||||
>
|
||||
{openTabs.map((tab) => {
|
||||
const isActive = tab.scriptId === script.script_id;
|
||||
return (
|
||||
<div
|
||||
key={tab.scriptId}
|
||||
data-active={isActive ? "true" : undefined}
|
||||
className={`relative flex max-w-[290px] min-w-[100px] shrink-0 cursor-pointer items-center gap-1.5 border-r border-[#e4eaf0] px-1.5 text-xs text-[#4b5c70] ${
|
||||
isActive
|
||||
? "bg-white before:absolute before:inset-x-0 before:top-0 before:h-0.5 before:bg-[#2381e5] before:content-['']"
|
||||
: "bg-white hover:bg-[#f0f4f8]"
|
||||
}`}
|
||||
onClick={() => onSwitchTab(tab.scriptId)}
|
||||
>
|
||||
<Icon name="close" size={14} />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<span
|
||||
className={`grid size-[25px] shrink-0 place-items-center rounded-[5px] ${fileIconTone(tab.scriptType)}`}
|
||||
>
|
||||
<Icon
|
||||
name={tab.scriptType === "notebook" ? "notebook" : "python"}
|
||||
size={16}
|
||||
/>
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate">{tab.scriptName}</span>
|
||||
<button
|
||||
type="button"
|
||||
className="grid size-[25px] place-items-center border-0 bg-transparent text-[#8e9baa] hover:bg-[#edf1f5] hover:text-[#43566c]"
|
||||
aria-label="关闭标签"
|
||||
onClick={(event) => onClose(tab.scriptId, event)}
|
||||
>
|
||||
<Icon name="close" size={14} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<button
|
||||
className="new-tab"
|
||||
className="ml-[5px] grid size-[25px] shrink-0 cursor-pointer place-items-center self-center rounded border-0 bg-transparent text-[#8e9baa] hover:bg-[#edf1f5] hover:text-[#43566c]"
|
||||
type="button"
|
||||
onClick={onNewTab}
|
||||
>
|
||||
@@ -403,7 +458,7 @@ export function ScriptWorkspace({
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
className="tabbar-scroll-btn tabbar-scroll-btn--right"
|
||||
className="z-[1] grid w-7 shrink-0 cursor-pointer place-items-center border-0 border-l border-[#e4eaf0] bg-[#f7f9fb] text-[#6b7c8f] transition-colors duration-150 hover:bg-[#edf1f5] hover:text-[#3d4c5c]"
|
||||
type="button"
|
||||
aria-label="向右滚动"
|
||||
onClick={() => scroll("right")}
|
||||
@@ -412,36 +467,46 @@ export function ScriptWorkspace({
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="editor-toolbar">
|
||||
<div className="editor-toolbar__path">
|
||||
<span className={`file-icon file-icon--${script.script_type}`}>
|
||||
<div className="flex min-h-[53px] items-center justify-between border-b border-[#e6ebf0] bg-white px-3.5">
|
||||
<div className="flex min-w-0 items-center gap-[7px] text-[11px] text-[#8794a3]">
|
||||
<span
|
||||
className={`grid size-[25px] shrink-0 place-items-center rounded-[5px] ${fileIconTone(script.script_type)}`}
|
||||
>
|
||||
<Icon name={scriptIcon(script)} size={17} />
|
||||
</span>
|
||||
<span>工作副本</span>
|
||||
<Icon name="chevron" size={13} />
|
||||
<strong>{script.script_name}</strong>
|
||||
<strong className="max-w-[300px] truncate text-[#45576b]">
|
||||
{script.script_name}
|
||||
</strong>
|
||||
</div>
|
||||
{!isReadOnlyMode && (
|
||||
<div className="editor-toolbar__actions">
|
||||
<div className="flex items-center gap-[7px]">
|
||||
{isPythonEditing ? (
|
||||
<>
|
||||
{showSaveButton && (
|
||||
<button
|
||||
type="button"
|
||||
className={`editor-save-button${activePythonBuf?.saving ? " is-saving" : ""}`}
|
||||
className={`${saveBtnClass}${activePythonBuf?.saving ? " opacity-85" : ""}`}
|
||||
disabled={saveDisabled}
|
||||
onClick={() => onSavePythonEditor(script.script_id)}
|
||||
>
|
||||
{activePythonBuf?.saving
|
||||
? <><span className="button-spinner button-spinner--blue" /> 保存中</>
|
||||
? <><span className="button-spinner button-spinner--blue size-3" /> 保存中</>
|
||||
: "保存"}
|
||||
</button>
|
||||
)}
|
||||
<button type="button" className="end-edit-button" onClick={() => onExitPythonEditor(script.script_id)}>结束编辑</button>
|
||||
<button
|
||||
type="button"
|
||||
className={endEditBtnClass}
|
||||
onClick={() => onExitPythonEditor(script.script_id)}
|
||||
>
|
||||
结束编辑
|
||||
</button>
|
||||
</>
|
||||
) : isEditing ? (
|
||||
<button
|
||||
className="end-edit-button"
|
||||
className={endEditBtnClass}
|
||||
type="button"
|
||||
disabled={editBusy}
|
||||
onClick={onEndEditing}
|
||||
@@ -451,6 +516,7 @@ export function ScriptWorkspace({
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className={toolbarBtnClass}
|
||||
onClick={() => onInfo({
|
||||
tone: "info",
|
||||
message: "Jupyter 中保存后会直接写入 Workspace 工作副本",
|
||||
@@ -460,14 +526,26 @@ export function ScriptWorkspace({
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="release-button"
|
||||
className={releaseBtnClass}
|
||||
type="button"
|
||||
onClick={onPublish}
|
||||
>
|
||||
发布稳定版
|
||||
</button>
|
||||
<span className={`stage-badge${isEditing ? " is-editing" : ""}`}>
|
||||
<span />
|
||||
<span
|
||||
className={`inline-flex h-[29px] items-center gap-1.5 rounded-[15px] px-2.5 text-[10px] ${
|
||||
isEditing
|
||||
? "bg-[#e9f5fd] text-[#126b9d]"
|
||||
: "bg-[#eaf9f3] text-[#16845c]"
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`size-1.5 rounded-full ${
|
||||
isEditing
|
||||
? "stage-dot-editing bg-[#2495d3]"
|
||||
: "bg-[#20bc7f]"
|
||||
}`}
|
||||
/>
|
||||
{
|
||||
latestVersion
|
||||
? `最新 ${latestVersion.version_label}`
|
||||
@@ -482,7 +560,7 @@ export function ScriptWorkspace({
|
||||
因为 Python 编辑走 pythonEditorBuffers,不走文件锁。
|
||||
这个锁只在本浏览器当前 tab 内有效,不能阻止隐身模式 / 其它浏览器同时编辑。 */}
|
||||
{isEditing && (
|
||||
<div className="local-lock-banner">
|
||||
<div className="flex shrink-0 items-center justify-center gap-2 border-b border-[#c9def9] bg-[#e7f1ff] px-4 py-1.5 text-xs font-medium text-[#1f4e8a]">
|
||||
<Icon name="info" size={14} />
|
||||
<span>
|
||||
本地编辑锁——关闭标签页、刷新页面或换浏览器后失效,不会阻止他人同时编辑。
|
||||
@@ -491,25 +569,25 @@ export function ScriptWorkspace({
|
||||
)}
|
||||
|
||||
{/* 编辑器画布区域 - 始终渲染,保证 iframe 不重新加载 */}
|
||||
<div className="editor-canvas-wrapper" style={{ position: 'relative', width: '100%', height: '100%', overflow: 'hidden' }}>
|
||||
<div className="relative h-full w-full overflow-hidden">
|
||||
{/* 只读模式内容 - 用 CSS 控制显示/隐藏 */}
|
||||
<div style={{ display: isReadOnlyMode ? 'block' : 'none', height: '100%' }}>
|
||||
<div className="readonly-editor-container">
|
||||
<div className="readonly-editor-banner">
|
||||
<div className="flex h-full flex-col overflow-hidden bg-[#f8f9fa]">
|
||||
<div className="mb-1.5 flex shrink-0 items-center justify-center gap-2 border-b border-[#e0e0e0] bg-[#fff3cd] px-4 py-2 text-[13px] font-medium text-[#856404]">
|
||||
<Icon name="lock" size={16} />
|
||||
<span>此文件已被锁定,您当前处于只读模式</span>
|
||||
</div>
|
||||
<div className="readonly-editor-content">
|
||||
<div className="min-h-0 flex-1 overflow-y-auto">
|
||||
{readOnlyLoading ? (
|
||||
<div className="readonly-editor-loading">
|
||||
<div className="flex min-h-[200px] flex-col items-center justify-center gap-4 text-sm text-[#666]">
|
||||
<span className="button-spinner button-spinner--blue" />
|
||||
<span>加载内容中...</span>
|
||||
</div>
|
||||
) : readOnlyError ? (
|
||||
<div className="readonly-editor-error">
|
||||
<div className="flex min-h-[200px] flex-col items-center justify-center gap-3 p-5 text-center text-sm text-[#dc3545]">
|
||||
<Icon name="info" size={28} />
|
||||
<strong>加载失败</strong>
|
||||
<p>{readOnlyError}</p>
|
||||
<strong className="text-base">加载失败</strong>
|
||||
<p className="m-0 text-[#666]">{readOnlyError}</p>
|
||||
</div>
|
||||
) : script.script_type === 'notebook' && readOnlyContent && typeof readOnlyContent === 'object' ? (
|
||||
<NotebookViewer content={readOnlyContent} />
|
||||
@@ -536,7 +614,14 @@ export function ScriptWorkspace({
|
||||
</div>
|
||||
|
||||
{/* 正常编辑模式 - 始终渲染,用 CSS 控制显示/隐藏 */}
|
||||
<div className={`editor-canvas ${sessionCache.size > 0 ? 'is-embedded' : ''}`} style={{ display: isReadOnlyMode ? 'none' : 'block', position: 'relative', minHeight: '0', height: '100%' }}>
|
||||
<div
|
||||
className={`relative min-h-0 flex-1 ${
|
||||
sessionCache.size > 0
|
||||
? "overflow-hidden bg-white"
|
||||
: "editor-canvas-shell overflow-auto"
|
||||
}`}
|
||||
style={{ display: isReadOnlyMode ? 'none' : 'block', height: '100%' }}
|
||||
>
|
||||
{/* 多 PythonEditor 实例:每个有 buffer 的 python tab 都挂载,仅 active 可见 */}
|
||||
{pythonTabBuffers.map((tab) => {
|
||||
const tabScript = scripts.find((s) => s.script_id === tab.scriptId);
|
||||
@@ -546,7 +631,7 @@ export function ScriptWorkspace({
|
||||
return (
|
||||
<section
|
||||
key={tab.scriptId}
|
||||
className="python-editor-mount"
|
||||
className="flex h-full min-h-0 w-full flex-col bg-white"
|
||||
style={{
|
||||
position: isActive ? "relative" : "absolute",
|
||||
visibility: isActive ? "visible" : "hidden",
|
||||
@@ -577,29 +662,27 @@ export function ScriptWorkspace({
|
||||
return (
|
||||
<section
|
||||
key={scriptId}
|
||||
className="embedded-jupyter"
|
||||
className="flex h-full min-h-0 w-full flex-col overflow-hidden bg-white"
|
||||
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 />
|
||||
<div className="flex min-h-[34px] items-center gap-[18px] border-b border-[#dfe6ec] bg-[#f8fafc] px-3.5 text-[9px] text-[#718195]">
|
||||
<span className="inline-flex items-center gap-1.5 font-[650] text-[#197b59]">
|
||||
<i className="size-[7px] rounded-full bg-[#20b77d] shadow-[0_0_0_3px_rgb(32_183_125/12%)]" />
|
||||
Workspace Jupyter Server
|
||||
</span>
|
||||
<span>
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
{cached.session.session_status === "active" ? "Notebook Session · Python 3 Kernel" : "Jupyter 文本编辑器"}
|
||||
</span>
|
||||
<code title={cached.session.runtime_id}>
|
||||
<code className="ml-auto text-[9px] text-[#72859a]" title={cached.session.runtime_id}>
|
||||
Runtime {cached.session.runtime_id.slice(-8)}
|
||||
</code>
|
||||
</div>
|
||||
<iframe
|
||||
className="min-h-0 w-full flex-1 border-0 bg-white"
|
||||
src={cached.jupyterUrl}
|
||||
title={`${scriptId} Jupyter 编辑器`}
|
||||
allow="clipboard-read; clipboard-write"
|
||||
@@ -614,26 +697,35 @@ export function ScriptWorkspace({
|
||||
{!sessionCache.has(script.script_id) ? (
|
||||
isNotebook ? (
|
||||
<section
|
||||
className={`editor-opening-state${openError ? " has-error" : ""}`}
|
||||
className={`mx-auto my-16 flex w-[min(520px,calc(100%-48px))] min-h-[260px] flex-col items-center justify-center rounded-[10px] border p-9 text-center text-[#66788d] shadow-[0_12px_34px_rgb(31_64_98/7%)] ${
|
||||
openError
|
||||
? "border-[#f0cfcc] bg-white/95"
|
||||
: "border-[#dce5ed] bg-white/95"
|
||||
}`}
|
||||
aria-live="polite"
|
||||
style={{ display: 'block' }}
|
||||
>
|
||||
<div className="editor-opening-state__icon">
|
||||
<div
|
||||
className={`mb-4 grid size-[54px] place-items-center rounded-full ${
|
||||
openError
|
||||
? "bg-[#fff1f0] text-[#c84f49]"
|
||||
: "bg-[#edf6ff] text-[#247bc5]"
|
||||
}`}
|
||||
>
|
||||
{openError
|
||||
? <Icon name="info" size={28} />
|
||||
: <span className="button-spinner button-spinner--blue" />}
|
||||
: <span className="button-spinner button-spinner--blue size-6 border-[3px]" />}
|
||||
</div>
|
||||
<strong>
|
||||
<strong className="text-base text-[#1d344d]">
|
||||
{openError ? "Notebook 打开失败" : "正在打开 Notebook"}
|
||||
</strong>
|
||||
<p>
|
||||
<p className="mt-[9px] max-w-[430px] text-xs leading-[1.7] text-[#7a8a9b] [overflow-wrap:anywhere]">
|
||||
{openError
|
||||
? openError
|
||||
: "正在获取编辑锁并连接 Workspace Jupyter Server…"}
|
||||
</p>
|
||||
{openError && (
|
||||
<button
|
||||
className="open-editor-button"
|
||||
className={`${openEditorBtnClass} mt-[18px]`}
|
||||
type="button"
|
||||
disabled={editBusy}
|
||||
onClick={onOpenEditor}
|
||||
@@ -646,15 +738,25 @@ export function ScriptWorkspace({
|
||||
)}
|
||||
</section>
|
||||
) : isPython && !activePythonBuf ? (
|
||||
<section className="script-overview">
|
||||
<div className="script-overview__header">
|
||||
<section className="mx-auto mb-[38px] mt-6 w-[min(1020px,calc(100%-48px))] max-xl:w-[calc(100%-30px)]">
|
||||
<div className="flex items-start justify-between gap-5 rounded-t-[9px] border border-[#dce5ed] bg-white px-[26px] py-[23px]">
|
||||
<div>
|
||||
<span className="section-kicker">PYTHON SCRIPT</span>
|
||||
<h2>{script.script_name}</h2>
|
||||
<p>{script.relative_path}</p>
|
||||
<span className="text-[9px] font-extrabold tracking-[0.13em] text-[#2b7fca]">
|
||||
PYTHON SCRIPT
|
||||
</span>
|
||||
<h2 className="mb-1 mt-1.5 text-[22px] text-[#1b2d43]">
|
||||
{script.script_name}
|
||||
</h2>
|
||||
<p className="m-0 font-mono text-[10px] text-[#8997a6]">
|
||||
{script.relative_path}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
className={`open-editor-button${isEditing ? " is-editing" : ""}`}
|
||||
className={
|
||||
isEditing
|
||||
? openEditorEditingBtnClass
|
||||
: openEditorBtnClass
|
||||
}
|
||||
type="button"
|
||||
disabled={editBusy}
|
||||
onClick={onOpenEditor}
|
||||
@@ -670,37 +772,42 @@ export function ScriptWorkspace({
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="metadata-grid">
|
||||
<div>
|
||||
<span>脚本类型</span>
|
||||
<strong>Python</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>可见范围</span>
|
||||
<strong>
|
||||
{script.visibility === "workspace"
|
||||
? "Workspace"
|
||||
: script.visibility === "public" ? "公开" : "私有"}
|
||||
</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>文件大小</span>
|
||||
<strong>{formatBytes(script.size_bytes)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>最近更新</span>
|
||||
<strong>{formatTime(script.updated_at)}</strong>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 border-x border-b border-[#dce5ed] bg-[#f9fbfd]">
|
||||
{[
|
||||
["脚本类型", "Python"],
|
||||
[
|
||||
"可见范围",
|
||||
script.visibility === "workspace"
|
||||
? "Workspace"
|
||||
: script.visibility === "public"
|
||||
? "公开"
|
||||
: "私有",
|
||||
],
|
||||
["文件大小", formatBytes(script.size_bytes)],
|
||||
["最近更新", formatTime(script.updated_at)],
|
||||
].map(([label, value], index) => (
|
||||
<div
|
||||
key={label}
|
||||
className={`flex min-h-16 flex-col justify-center px-[21px] ${
|
||||
index < 3 ? "border-r border-[#e4eaf0]" : ""
|
||||
}`}
|
||||
>
|
||||
<span className="text-[10px] text-[#8a9aaa]">{label}</span>
|
||||
<strong className="text-[13px] text-[#2a3f55]">{value}</strong>
|
||||
</div>
|
||||
<div className="preview-card">
|
||||
<div className="preview-card__bar">
|
||||
<div>
|
||||
<span className="window-dot window-dot--red" />
|
||||
<span className="window-dot window-dot--yellow" />
|
||||
<span className="window-dot window-dot--green" />
|
||||
))}
|
||||
</div>
|
||||
<div className="overflow-hidden rounded-b-[9px] border border-t-0 border-[#dce5ed] bg-white">
|
||||
<div className="flex items-center justify-between border-b border-[#e8edf3] bg-[#f7f9fb] px-3.5 py-2 text-[11px] text-[#6b7c8f]">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="size-2.5 rounded-full bg-[#ef6b62]" />
|
||||
<span className="size-2.5 rounded-full bg-[#e9b949]" />
|
||||
<span className="size-2.5 rounded-full bg-[#49b97b]" />
|
||||
</div>
|
||||
<span>Python 预览</span>
|
||||
<em>{isEditing ? "Jupyter 中可编辑" : "平台只读预览"}</em>
|
||||
<em className="text-[10px] not-italic text-[#97a4b3]">
|
||||
{isEditing ? "Jupyter 中可编辑" : "平台只读预览"}
|
||||
</em>
|
||||
</div>
|
||||
<PythonPreview
|
||||
workspaceId={script.workspace_id}
|
||||
@@ -708,10 +815,9 @@ export function ScriptWorkspace({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="integrity-row">
|
||||
<span>
|
||||
<div className="mt-3 flex flex-wrap items-center gap-x-5 gap-y-2 px-1 text-[11px] text-[#7a8b9c]">
|
||||
<span className="inline-flex items-center gap-[5px] text-[#25875e]">
|
||||
<Icon name="check" size={15} />
|
||||
{/*{isEditing ? "Demo 无锁编辑会话已连接" : "工作副本已同步"}*/}
|
||||
</span>
|
||||
<span>SHA-256 {shortHash(script.content_hash)}</span>
|
||||
<span>
|
||||
|
||||
Reference in New Issue
Block a user