feat: workspace 目录树服务端懒加载
- api.ts: listWorkspaceDirectories 支持 parent_path 查询; WorkspaceDirectory 增加 has_children; 同步 WorkspaceBoundApi 签名 - AuthContext: 绑定透传 parentPath - scriptWorkspaceStore: 新增 expandedPaths/loadingChildrenPaths/loadedChildPaths, loadChildren/toggleExpanded, 局部刷新 createFolder/deleteDirectory - WorkspaceTree: 移除 useState, 改为受控展开/加载状态 - ScriptExplorer: 从 store 读取并透传展开状态与 toggle
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { type MouseEvent as ReactMouseEvent, useState } from "react";
|
||||
import { type MouseEvent as ReactMouseEvent } from "react";
|
||||
|
||||
import Icon from "../../components/common/Icon";
|
||||
import type {
|
||||
@@ -23,6 +23,9 @@ type WorkspaceTreeProps = {
|
||||
target: WorkspaceTreeTarget,
|
||||
) => void;
|
||||
readOnly?: boolean;
|
||||
expandedPaths: Set<string>;
|
||||
onToggle: (path: string) => void;
|
||||
loadingChildrenPaths: Set<string>;
|
||||
};
|
||||
|
||||
type WorkspaceTreeItemsProps = Omit<WorkspaceTreeProps, "title" | "readOnly"> & {
|
||||
@@ -62,15 +65,19 @@ export function WorkspaceTreeGroup({
|
||||
onSelect,
|
||||
onContextMenu,
|
||||
readOnly = false,
|
||||
expandedPaths,
|
||||
onToggle,
|
||||
loadingChildrenPaths,
|
||||
}: WorkspaceTreeProps) {
|
||||
const [open, setOpen] = useState(true);
|
||||
const open = expandedPaths.has("");
|
||||
const childrenLoading = loadingChildrenPaths.has("");
|
||||
return (
|
||||
<div className="tree-group">
|
||||
<button
|
||||
className={`tree-group__title${open ? " is-open" : ""}`}
|
||||
type="button"
|
||||
aria-expanded={open}
|
||||
onClick={() => setOpen((current) => !current)}
|
||||
onClick={() => onToggle("")}
|
||||
onContextMenu={onContextMenu
|
||||
? (event) => onContextMenu(event, { kind: "root", path: "" })
|
||||
: undefined}
|
||||
@@ -79,6 +86,7 @@ export function WorkspaceTreeGroup({
|
||||
<Icon name="folder" size={17} />
|
||||
<span>{title}</span>
|
||||
<em>{scripts.length}</em>
|
||||
{childrenLoading && <span className="loading-spinner" />}
|
||||
</button>
|
||||
{open && (
|
||||
<div className="tree-group__items">
|
||||
@@ -90,6 +98,9 @@ export function WorkspaceTreeGroup({
|
||||
selectedId={selectedId}
|
||||
onSelect={onSelect}
|
||||
onContextMenu={onContextMenu}
|
||||
expandedPaths={expandedPaths}
|
||||
onToggle={onToggle}
|
||||
loadingChildrenPaths={loadingChildrenPaths}
|
||||
/>
|
||||
{scripts.length === 0 && directories.length === 0 && (
|
||||
<p className="tree-group__empty">
|
||||
@@ -110,6 +121,9 @@ function WorkspaceTreeItems({
|
||||
selectedId,
|
||||
onSelect,
|
||||
onContextMenu,
|
||||
expandedPaths,
|
||||
onToggle,
|
||||
loadingChildrenPaths,
|
||||
}: WorkspaceTreeItemsProps) {
|
||||
const childDirectories = directories.filter(
|
||||
(item) => item.parent_path === path,
|
||||
@@ -129,6 +143,9 @@ function WorkspaceTreeItems({
|
||||
selectedId={selectedId}
|
||||
onSelect={onSelect}
|
||||
onContextMenu={onContextMenu}
|
||||
expandedPaths={expandedPaths}
|
||||
onToggle={onToggle}
|
||||
loadingChildrenPaths={loadingChildrenPaths}
|
||||
/>
|
||||
))}
|
||||
{childScripts.map((item) => (
|
||||
@@ -177,17 +194,21 @@ function DirectoryBranch({
|
||||
selectedId,
|
||||
onSelect,
|
||||
onContextMenu,
|
||||
expandedPaths,
|
||||
onToggle,
|
||||
loadingChildrenPaths,
|
||||
}: Omit<WorkspaceTreeItemsProps, "path"> & {
|
||||
directory: WorkspaceDirectory;
|
||||
}) {
|
||||
const [open, setOpen] = useState(true);
|
||||
const open = expandedPaths.has(directory.path);
|
||||
const childrenLoading = loadingChildrenPaths.has(directory.path);
|
||||
return (
|
||||
<div className="directory-branch">
|
||||
<button
|
||||
className="directory-row"
|
||||
style={{ paddingLeft: 10 + depth * 16 }}
|
||||
type="button"
|
||||
onClick={() => setOpen((current) => !current)}
|
||||
onClick={() => onToggle(directory.path)}
|
||||
onContextMenu={onContextMenu
|
||||
? (event) => onContextMenu(event, {
|
||||
kind: "directory",
|
||||
@@ -200,6 +221,7 @@ function DirectoryBranch({
|
||||
</span>
|
||||
<Icon name="folder" size={17} />
|
||||
<strong title={directory.path}>{directory.name}</strong>
|
||||
{childrenLoading && <span className="loading-spinner" />}
|
||||
</button>
|
||||
{open && (
|
||||
<WorkspaceTreeItems
|
||||
@@ -210,6 +232,9 @@ function DirectoryBranch({
|
||||
selectedId={selectedId}
|
||||
onSelect={onSelect}
|
||||
onContextMenu={onContextMenu}
|
||||
expandedPaths={expandedPaths}
|
||||
onToggle={onToggle}
|
||||
loadingChildrenPaths={loadingChildrenPaths}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user