fix: loadChildren + loadScripts + loadDataResources
This commit is contained in:
@@ -283,28 +283,41 @@ export const createScriptsSlice: StateCreator<
|
||||
|
||||
loadDataResources: async (parentPath = "", ownerUserId) => {
|
||||
const api = requireApi();
|
||||
const cacheKey = ownerCacheKey(ownerUserId, parentPath);
|
||||
// 命中缓存:listResources 是非递归的,同一 (owner, parent_path) 拉过的
|
||||
// 内容不会自己变化;省去 toggleExpanded 重复展开同一目录时的网络往返。
|
||||
if (get().loadedDataResourcePaths.has(cacheKey)) return;
|
||||
set({ dataResourcesLoading: true });
|
||||
try {
|
||||
const list = await api.listResources(parentPath, { ownerUserId });
|
||||
const fresh = Array.isArray(list) ? list : [];
|
||||
set((state) => {
|
||||
// 按 owner 范围合并:丢弃该 owner 的旧资源再并入 fresh(fresh 覆盖
|
||||
// 同 id)。owner 缺省=我,故根加载/刷新我的数据时替换我的一级资源。
|
||||
// 按 (owner, parent_path) 局部替换:丢掉该 owner 在 parentPath 下的
|
||||
// 旧条目,保留该 owner 在其它路径下的条目,再并入 fresh。
|
||||
// 这样 toggleExpanded 在子目录展开时按需拉取不会把根已加载的数据
|
||||
// 资源擦掉(修"根加载后子目录展开丢数据 / 子目录数据本来不显示")。
|
||||
const targetOwner = ownerUserId ?? getCurrentUserId() ?? null;
|
||||
const kept = state.dataResources.filter(
|
||||
(r) => r.owner_user_id !== targetOwner,
|
||||
);
|
||||
const kept = state.dataResources.filter((r) => {
|
||||
if (r.owner_user_id !== targetOwner) return true;
|
||||
return parentPathOf(r.jupyter_accessible_path) !== parentPath;
|
||||
});
|
||||
const byId = new Map(kept.map((r) => [r.resource_id, r]));
|
||||
for (const item of fresh) byId.set(item.resource_id, item);
|
||||
return { dataResources: Array.from(byId.values()) };
|
||||
const nextLoaded = new Set(state.loadedDataResourcePaths);
|
||||
nextLoaded.add(cacheKey);
|
||||
return {
|
||||
dataResources: Array.from(byId.values()),
|
||||
loadedDataResourcePaths: nextLoaded,
|
||||
};
|
||||
});
|
||||
} catch {
|
||||
set((state) => {
|
||||
const targetOwner = ownerUserId ?? getCurrentUserId() ?? null;
|
||||
return {
|
||||
dataResources: state.dataResources.filter(
|
||||
(r) => r.owner_user_id !== targetOwner,
|
||||
),
|
||||
dataResources: state.dataResources.filter((r) => {
|
||||
if (r.owner_user_id !== targetOwner) return true;
|
||||
return parentPathOf(r.jupyter_accessible_path) !== parentPath;
|
||||
}),
|
||||
};
|
||||
});
|
||||
} finally {
|
||||
@@ -336,4 +349,13 @@ export const createScriptsSlice: StateCreator<
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// 提取 jupyter-accessible 路径的父目录;用于 `loadDataResources` 局部替换时
|
||||
// 判断一条缓存资源是否落在目标 parent_path 下(list-resources 按 parent_path
|
||||
// 精确匹配,非递归)。
|
||||
function parentPathOf(path: string): string {
|
||||
const parts = path.split("/");
|
||||
parts.pop();
|
||||
return parts.join("/");
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
// ---- treeSlice ----
|
||||
//
|
||||
// 拥有 expandedPaths / loadingChildrenPaths / loadedChildPaths /
|
||||
// loadedScriptPaths / loadingScriptPaths (5 个 directory-tree 缓存集合)。
|
||||
// 负责 toggleExpanded 和 loadChildren。
|
||||
// loadedScriptPaths / loadingScriptPaths / loadedDataResourcePaths
|
||||
// (6 个 directory-tree 缓存集合)。负责 toggleExpanded 和 loadChildren。
|
||||
//
|
||||
// 注意:
|
||||
// - loadedScriptPaths/loadingScriptPaths 也由 scriptsSlice 写 (loadScripts /
|
||||
// loadOwnerGroup),但 ownership 在 treeSlice 里 (因为是 cache set,不是数据)
|
||||
// - scriptsSlice.load 也会写这俩,所以这里只保留这两个 setter (toggleExpanded
|
||||
// 也要写 expandedPaths)。
|
||||
// - loadedDataResourcePaths 由 scriptsSlice.loadDataResources 写(同样的 cache
|
||||
// 不放数据原则),toggleExpanded 在真实目录分支按需触发。
|
||||
// - scriptsSlice.load 也会写 cached script paths,所以这里只保留 toggleExpanded
|
||||
// (写 expandedPaths) 和 loadChildren (写目录缓存)。
|
||||
|
||||
import type { StateCreator } from "zustand";
|
||||
|
||||
@@ -34,6 +36,7 @@ export const createTreeSlice: StateCreator<
|
||||
loadedChildPaths: new Set<string>(),
|
||||
loadedScriptPaths: new Set<string>(),
|
||||
loadingScriptPaths: new Set<string>(),
|
||||
loadedDataResourcePaths: new Set<string>(),
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -110,15 +113,24 @@ export const createTreeSlice: StateCreator<
|
||||
}
|
||||
} else {
|
||||
// 真实目录展开:loadChildren(owner 限定的显式目录行)+ loadScripts
|
||||
// 并行。两者都 idempotent + 缓存;ownerUserId 缺省=我。他人目录同样
|
||||
// 调 loadChildren(owner) 拉取其目录结构,否则嵌套子目录无法被发现
|
||||
// (list_scripts 非递归,只能看到直接子脚本)。
|
||||
// + loadDataResources 并行。三者都 idempotent + 缓存;ownerUserId
|
||||
// 缺省=我。他人目录同样调 loadChildren(owner) 拉取其目录结构,否则
|
||||
// 嵌套子目录无法被发现(list_scripts 非递归,只能看到直接子脚本)。
|
||||
// 数据资源也是非递归的——不按需拉取,子目录里的 csv/xlsx/json 等
|
||||
// 都不会出现(修"目录树子目录里的数据文件不显示")。
|
||||
if (!state.loadedChildPaths.has(ownerCacheKey(ownerUserId, loadPath))) {
|
||||
void get().loadChildren(loadPath, ownerUserId);
|
||||
}
|
||||
if (!state.loadedScriptPaths.has(ownerCacheKey(ownerUserId, loadPath))) {
|
||||
void get().loadScripts(loadPath, ownerUserId);
|
||||
}
|
||||
if (
|
||||
!state.loadedDataResourcePaths.has(
|
||||
ownerCacheKey(ownerUserId, loadPath),
|
||||
)
|
||||
) {
|
||||
void get().loadDataResources(loadPath, ownerUserId);
|
||||
}
|
||||
}
|
||||
}
|
||||
set({ expandedPaths: next });
|
||||
|
||||
@@ -62,6 +62,10 @@ export type TreeSliceState = {
|
||||
// `${owner_user_id}:${parent_path}` (see ownerCacheKey).
|
||||
loadedScriptPaths: Set<string>;
|
||||
loadingScriptPaths: Set<string>;
|
||||
// 与 loadedScriptPaths 同形:按 (owner, parent_path) 缓存已拉取的数据资源,
|
||||
// 让 toggleExpanded 在子目录展开时也按需请求 listDataResources,而不是
|
||||
// 只在根加载一次(修"子目录下的数据文件不展示")。
|
||||
loadedDataResourcePaths: Set<string>;
|
||||
};
|
||||
|
||||
export type TreeSliceActions = {
|
||||
|
||||
@@ -112,6 +112,7 @@ const INITIAL: ScriptWorkspaceState = {
|
||||
loadedChildPaths: new Set<string>(),
|
||||
loadedScriptPaths: new Set<string>(),
|
||||
loadingScriptPaths: new Set<string>(),
|
||||
loadedDataResourcePaths: new Set<string>(),
|
||||
// selectionSlice
|
||||
selectedId: null,
|
||||
openTabIds: [],
|
||||
|
||||
Reference in New Issue
Block a user