import { type ChangeEvent, type MouseEvent as ReactMouseEvent, type RefObject, useEffect, useMemo, useState } from "react"; import Icon from "../common/Icon"; import { WorkspaceTreeGroup } from "~/features/platform/WorkspaceTree"; import type { ScriptItem, WorkspaceDirectory, WorkspaceMember } from "~/services/api"; import { useApi, useAuth, 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; onHandleUpload: (event: ChangeEvent) => 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 api = useApi(); const { currentWorkspace } = useAuth(); const [members, setMembers] = useState([]); useEffect(() => { if (!currentWorkspace) { setMembers([]); return; } let cancelled = false; api .listWorkspaceMembers(currentWorkspace.workspace_id) .then((list) => { if (!cancelled) setMembers(list); }) .catch(() => { if (!cancelled) setMembers([]); }); return () => { cancelled = true; }; }, [api, currentWorkspace]); const displayNameByUserId = useMemo(() => { const map = new Map(); for (const m of members) { map.set(m.user_id, m.display_name || m.username || m.user_id); } return map; }, [members]); const memberScriptGroups = useMemo(() => { const visibleScripts = user?.is_system_admin === true ? filteredScripts : filteredScripts.filter( (item) => item.owner_user_id === user?.user_id || item.visibility === "workspace" || item.visibility === "public", ); const byOwner = new Map(); for (const item of visibleScripts) { const list = byOwner.get(item.owner_user_id) ?? []; list.push(item); byOwner.set(item.owner_user_id, list); } const groups: { user: AuthUser | null; scripts: ScriptItem[]; directories: WorkspaceDirectory[]; }[] = []; for (const [ownerUserId, groupScripts] of byOwner.entries()) { const displayName = displayNameByUserId.get(ownerUserId) ?? (ownerUserId === user?.user_id ? user?.display_name : null) ?? `${ownerUserId.slice(-6)}…`; const groupUser = ownerUserId === user?.user_id ? user : ({ user_id: ownerUserId, username: ownerUserId, display_name: displayName, email: null, status: "unknown", role_code: null, is_system_admin: false, } as AuthUser); groups.push({ user: groupUser, scripts: groupScripts, directories: inferredDirectories(groupScripts), }); } groups.sort((a, b) => { if (a.user?.user_id === user?.user_id) return -1; if (b.user?.user_id === user?.user_id) return 1; return (a.user?.user_id ?? "").localeCompare(b.user?.user_id ?? ""); }); return groups; }, [filteredScripts, user, displayNameByUserId]); return ( ); } 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(); 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()]; }