chore:platform文件拆分
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
import { type ChangeEvent, type MouseEvent as ReactMouseEvent, type RefObject } from "react";
|
||||
import Icon from "../common/Icon";
|
||||
import { WorkspaceTreeGroup } from "../../features/platform/WorkspaceTree";
|
||||
import type { ScriptItem, WorkspaceDirectory } from "../../services/api";
|
||||
import type { AuthUser } from "../../context/AuthContext";
|
||||
|
||||
type ScriptExplorerProps = {
|
||||
scripts: ScriptItem[];
|
||||
filteredScripts: ScriptItem[];
|
||||
directories: WorkspaceDirectory[];
|
||||
user: AuthUser | null;
|
||||
selectedId: string | null;
|
||||
loading: boolean;
|
||||
refreshing: boolean;
|
||||
uploading: boolean;
|
||||
keyword: string;
|
||||
onKeywordChange: (keyword: string) => void;
|
||||
onRefresh: () => void;
|
||||
onUpload: () => void;
|
||||
onOpenCreateDialog: (parentPath?: string, scriptType?: "notebook" | "python") => void;
|
||||
onOpenFolderDialog: (parentPath?: string) => void;
|
||||
onChooseUpload: (parentPath?: string) => void;
|
||||
onContextMenu: (
|
||||
event: ReactMouseEvent,
|
||||
target: { kind: "root" | "directory" | "file"; path: string; script?: ScriptItem }
|
||||
) => void;
|
||||
onSelect: (scriptId: string) => void;
|
||||
uploadInputRef: RefObject<HTMLInputElement | null>;
|
||||
onHandleUpload: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||
};
|
||||
|
||||
export function ScriptExplorer({
|
||||
scripts,
|
||||
filteredScripts,
|
||||
directories,
|
||||
user,
|
||||
selectedId,
|
||||
loading,
|
||||
refreshing,
|
||||
uploading,
|
||||
keyword,
|
||||
onKeywordChange,
|
||||
onRefresh,
|
||||
onUpload,
|
||||
onOpenCreateDialog,
|
||||
onOpenFolderDialog,
|
||||
onChooseUpload,
|
||||
onContextMenu,
|
||||
onSelect,
|
||||
uploadInputRef,
|
||||
onHandleUpload,
|
||||
}: ScriptExplorerProps) {
|
||||
const memberScriptGroups = (() => {
|
||||
const currentUserScripts = filteredScripts.filter(
|
||||
(item) => item.owner_user_id === user?.user_id,
|
||||
);
|
||||
const inferred = inferredDirectories(currentUserScripts);
|
||||
return [{
|
||||
user: user,
|
||||
scripts: currentUserScripts,
|
||||
directories: mergeDirectories(directories, inferred),
|
||||
}];
|
||||
})();
|
||||
|
||||
return (
|
||||
<aside className="explorer">
|
||||
<div className="explorer__header">
|
||||
<div>
|
||||
<h2>脚本目录</h2>
|
||||
<span>{scripts.length} 个工作副本</span>
|
||||
</div>
|
||||
<div className="explorer__actions">
|
||||
<button
|
||||
className="text-button"
|
||||
type="button"
|
||||
disabled={uploading}
|
||||
onClick={onUpload}
|
||||
>
|
||||
<Icon name="upload" size={15} />
|
||||
{uploading ? "上传中…" : "上传"}
|
||||
</button>
|
||||
<input
|
||||
ref={uploadInputRef}
|
||||
className="visually-hidden"
|
||||
type="file"
|
||||
accept=".py,.ipynb"
|
||||
multiple
|
||||
onChange={onHandleUpload}
|
||||
/>
|
||||
<button
|
||||
className="text-button"
|
||||
type="button"
|
||||
onClick={() => onOpenCreateDialog()}
|
||||
>
|
||||
<Icon name="plus" size={16} />
|
||||
新建
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="search-box">
|
||||
<Icon name="search" size={17} />
|
||||
<input
|
||||
aria-label="搜索脚本"
|
||||
placeholder="搜索脚本名称"
|
||||
value={keyword}
|
||||
onChange={(event) => onKeywordChange(event.target.value)}
|
||||
/>
|
||||
<button
|
||||
className={refreshing ? "is-spinning" : ""}
|
||||
type="button"
|
||||
aria-label="刷新脚本"
|
||||
onClick={onRefresh}
|
||||
>
|
||||
<Icon name="refresh" size={16} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="tree-scroll">
|
||||
{loading ? (
|
||||
<div className="tree-skeleton">
|
||||
<span /><span /><span /><span />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{memberScriptGroups.map((group) => (
|
||||
<WorkspaceTreeGroup
|
||||
key={group.user?.user_id ?? "anon"}
|
||||
title={`${group.user?.display_name}的文件`}
|
||||
scripts={group.scripts}
|
||||
directories={group.directories}
|
||||
selectedId={selectedId}
|
||||
onSelect={onSelect}
|
||||
onContextMenu={
|
||||
group.user?.user_id === user?.user_id
|
||||
? onContextMenu
|
||||
: undefined
|
||||
}
|
||||
readOnly={group.user?.user_id !== user?.user_id}
|
||||
/>
|
||||
))}
|
||||
{filteredScripts.length === 0 && (
|
||||
<div className="tree-empty">
|
||||
<span className="tree-empty__icon">
|
||||
<Icon name="script" size={24} />
|
||||
</span>
|
||||
<strong>{keyword ? "没有匹配脚本" : "还没有构建脚本"}</strong>
|
||||
<p>
|
||||
{keyword
|
||||
? "换个关键词试试"
|
||||
: "新建 Notebook 或 Python 脚本开始实验"}
|
||||
</p>
|
||||
{!keyword && (
|
||||
<button type="button" onClick={() => onOpenCreateDialog()}>
|
||||
<Icon name="plus" size={15} />
|
||||
新建脚本
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
function parentOf(path: string) {
|
||||
const parts = path.split("/");
|
||||
parts.pop();
|
||||
return parts.join("/");
|
||||
}
|
||||
|
||||
function ownedScriptPath(item: ScriptItem) {
|
||||
return item.relative_path.replaceAll("\\", "/").split("/").slice(2).join("/");
|
||||
}
|
||||
|
||||
function inferredDirectories(items: ScriptItem[]): WorkspaceDirectory[] {
|
||||
const result = new Map<string, WorkspaceDirectory>();
|
||||
for (const item of items) {
|
||||
const parts = parentOf(ownedScriptPath(item)).split("/").filter(Boolean);
|
||||
let parentPath = "";
|
||||
for (const name of parts) {
|
||||
const path = parentPath ? `${parentPath}/${name}` : name;
|
||||
result.set(path, { path, name, parent_path: parentPath });
|
||||
parentPath = path;
|
||||
}
|
||||
}
|
||||
return [...result.values()];
|
||||
}
|
||||
|
||||
function mergeDirectories(
|
||||
left: WorkspaceDirectory[],
|
||||
right: WorkspaceDirectory[],
|
||||
): WorkspaceDirectory[] {
|
||||
return [...new Map(
|
||||
[...left, ...right].map((item) => [item.path, item]),
|
||||
).values()];
|
||||
}
|
||||
Reference in New Issue
Block a user