From d595ce4a3a553233c6b5e8f736093819f031a90b Mon Sep 17 00:00:00 2001 From: codetypes Date: Sat, 22 Mar 2025 11:13:15 +0800 Subject: [PATCH] reveal file or node def in explorer --- src/components/explorer.tsx | 541 +++++++++++++++++------------- src/contexts/workspace-context.ts | 10 +- 2 files changed, 311 insertions(+), 240 deletions(-) diff --git a/src/components/explorer.tsx b/src/components/explorer.tsx index 435d706..80bb885 100644 --- a/src/components/explorer.tsx +++ b/src/components/explorer.tsx @@ -22,6 +22,8 @@ import Path from "../misc/path"; const { DirectoryTree } = Tree; +let timeout: NodeJS.Timeout; + type MenuInfo = Parameters>[0]; type MenuEvent = | "open" @@ -273,6 +275,280 @@ const createFolderContextMenu = (copiedPath: string) => { return arr; }; +const createNodeTree = (rootName: string, nodeDefs: b3util.NodeDefs) => { + const workspace = useWorkspace.getState(); + const data: NodeTreeType = { + title: i18n.t("nodeDefinition"), + path: rootName, + icon: ( + + + + ), + children: [], + style: { + fontWeight: "bold", + fontSize: "13px", + }, + }; + nodeDefs.forEach((nodeDef) => { + (nodeDef.group || [""]).forEach((g) => { + const typeGroup = !g ? nodeDef.type : `${nodeDef.type} (${g})`; + let catalog = data.children?.find((nt) => nt.title === typeGroup); + if (!catalog) { + const type = getNodeType(nodeDef); + catalog = { + title: typeGroup, + path: `nodeTree.catalog.${typeGroup}`, + children: [], + icon: ( + + + + ), + }; + data.children?.push(catalog); + } + catalog.children?.push({ + title: `${nodeDef.name}(${nodeDef.desc})`, + isLeaf: true, + def: nodeDef, + path: `${nodeDef.name}(${g})`, + icon: ( + + {nodeDef.icon ? ( + + ) : ( + + )} + + ), + }); + }); + }); + data.children?.sort((a, b) => a.title.localeCompare(b.title)); + data.children?.forEach((child) => child.children?.sort((a, b) => a.title.localeCompare(b.title))); + return data; +}; + +const isElementInViewport = (element: Element, container: Element) => { + const elementRect = element.getBoundingClientRect(); + const containerRect = container.getBoundingClientRect(); + return elementRect.top >= containerRect.top && elementRect.bottom <= containerRect.bottom; +}; + +const scrollIntoView = (path: string) => { + clearTimeout(timeout); + timeout = setTimeout(() => { + const element = document.querySelector(`[title="${path}"]`); + const container = element?.closest(".b3-overflow") || element?.closest(".b3-explorer"); + if (element && container && !isElementInViewport(element, container)) { + element.scrollIntoView({ + behavior: "auto", + block: "center", + }); + } + }, 300); +}; + +const alertReplaceFile = (path: string, newPath: string) => { + const workspace = useWorkspace.getState(); + const alert = modal.confirm({ + centered: true, + content: ( + +
+ +
+
{i18n.t("explorer.replaceFile", { name: Path.basename(newPath) })}
+
+ ), + footer: ( + + + + + + + ), + }); +}; + +const doMoveFile = (path: string, newPath: string) => { + const workspace = useWorkspace.getState(); + const title = Path.basename(path); + const destDir = Path.dirname(newPath); + const doMove = () => { + fs.renameSync(path, newPath); + for (const editor of workspace.editors) { + if (editor.path.startsWith(path)) { + editor.dispatch("rename", destDir + "/" + Path.basename(editor.path)); + } + console.log("editor move", editor.path === newPath, editor.path, newPath); + if (editor.path.startsWith(newPath)) { + console.log("editor reload", editor.path === newPath, editor.path, newPath); + editor.dispatch("refresh"); + } + } + }; + + if (!fs.existsSync(newPath)) { + doMove(); + return; + } + + const alert = modal.confirm({ + centered: true, + content: ( + +
+ +
+
{i18n.t("explorer.replaceFile", { name: title })}
+
+ ), + footer: ( + + + + + + + ), + }); +}; + +const alertDeleteFile = (path: string) => { + const workspace = useWorkspace.getState(); + const title = Path.basename(path); + const alert = modal.confirm({ + centered: true, + content: ( + +
+ +
+
{i18n.t("explorer.deleteFile", { name: title })}
+
+ ), + footer: ( + + + + + +
+ {i18n.t("explorer.restoreFileInfo")} +
+
+ ), + }); +}; + +const alertDeleteFolder = (path: string) => { + const workspace = useWorkspace.getState(); + const title = Path.basename(path); + const alert = modal.confirm({ + centered: true, + content: ( + +
+ +
+
{i18n.t("explorer.deleteFolder", { name: title })}
+
+ ), + footer: ( + + + + + +
+ {i18n.t("explorer.restoreFileInfo")} +
+
+ ), + }); +}; + export const Explorer: FC = () => { const workspace = useWorkspace( useShallow((state) => ({ @@ -300,6 +576,7 @@ export const Explorer: FC = () => { const [selectedNodedefKeys, setSelectedNodedefKeys] = useState([]); const [expandedNodedefKeys, setExpandedNodedefKeys] = useState([rootNodedefName]); + // set the icon of the root node if (workspace.fileTree) { workspace.fileTree.icon = ( @@ -308,71 +585,10 @@ export const Explorer: FC = () => { ); } - const nodeTree = useMemo(() => { - const data: NodeTreeType = { - title: t("nodeDefinition"), - path: rootNodedefName, - icon: ( - - - - ), - children: [], - style: { - fontWeight: "bold", - fontSize: "13px", - }, - }; - workspace.nodeDefs.forEach((nodeDef) => { - (nodeDef.group || [""]).forEach((g) => { - const typeGroup = !g ? nodeDef.type : `${nodeDef.type} (${g})`; - let catalog = data.children?.find((nt) => nt.title === typeGroup); - if (!catalog) { - const type = getNodeType(nodeDef); - catalog = { - title: typeGroup, - path: `nodeTree.catalog.${typeGroup}`, - children: [], - icon: ( - - - - ), - }; - data.children?.push(catalog); - } - catalog.children?.push({ - title: `${nodeDef.name}(${nodeDef.desc})`, - isLeaf: true, - def: nodeDef, - path: `${nodeDef.name}(${g})`, - icon: nodeDef.icon ? ( - - - - ) : ( - - {" "} - - ), - }); - }); - }); - data.children?.sort((a, b) => a.title.localeCompare(b.title)); - data.children?.forEach((child) => - child.children?.sort((a, b) => a.title.localeCompare(b.title)) - ); - return data; - }, [t, workspace.nodeDefs]); + const nodeTree = useMemo( + () => createNodeTree(rootNodedefName, workspace.nodeDefs), + [t, workspace.nodeDefs] + ); // expand the selected tree useEffect(() => { @@ -384,6 +600,9 @@ export const Explorer: FC = () => { keys.push(k); } } + if (!selectedNodedefKeys.includes(workspace.editing.path)) { + scrollIntoView(workspace.editing.path); + } setExpandedKeys(keys); setSelectedKeys([workspace.editing.path]); } @@ -402,6 +621,9 @@ export const Explorer: FC = () => { keys.push(k); } } + if (!workspace.editingNodeDef.path) { + scrollIntoView(path); + } setExpandedNodedefKeys(keys); setSelectedNodedefKeys([path]); } @@ -537,41 +759,7 @@ export const Explorer: FC = () => { if (node.path === newPath) { dispatch("duplicate", node); } else { - const alert = modal.confirm({ - centered: true, - content: ( - -
- -
-
{t("explorer.replaceFile", { name: Path.basename(copyFile) })}
-
- ), - footer: ( - - - - - - - ), - }); + alertReplaceFile(node.path, newPath); } } else { fs.copyFileSync(copyFile, newPath); @@ -604,87 +792,9 @@ export const Explorer: FC = () => { return; } if (node.isLeaf) { - const alert = modal.confirm({ - centered: true, - content: ( - -
- -
-
{t("explorer.deleteFile", { name: node.title })}
-
- ), - footer: ( - - - - - -
- {t("explorer.restoreFileInfo")} -
-
- ), - }); + alertDeleteFile(node.path); } else { - const alert = modal.confirm({ - centered: true, - content: ( - -
- -
-
{t("explorer.deleteFolder", { name: node.title })}
-
- ), - footer: ( - - - - - -
- {t("explorer.restoreFileInfo")} -
-
- ), - }); + alertDeleteFolder(node.path); } break; } @@ -695,59 +805,7 @@ export const Explorer: FC = () => { return; } const newPath = destDir + "/" + Path.basename(node.path); - const doMove = () => { - fs.renameSync(node.path, newPath); - for (const editor of workspace.editors) { - if (editor.path.startsWith(node.path)) { - editor.dispatch("rename", destDir + "/" + Path.basename(editor.path)); - } - console.log("editor move", editor.path === newPath, editor.path, newPath); - if (editor.path.startsWith(newPath)) { - console.log("editor reload", editor.path === newPath, editor.path, newPath); - editor.dispatch("refresh"); - } - } - }; - if (fs.existsSync(newPath)) { - const alert = modal.confirm({ - centered: true, - content: ( - -
- -
-
{t("explorer.replaceFile", { name: Path.basename(node.path) })}
-
- ), - footer: ( - - - - - - - ), - }); - } else { - doMove(); - } + doMoveFile(node.path, newPath); } catch (error) { console.error("move file:", error); } @@ -801,7 +859,7 @@ export const Explorer: FC = () => { { @@ -818,6 +876,18 @@ export const Explorer: FC = () => { if (node && !node.editing) { dispatch("open", node); setSelectedKeys([node.path]); + + setTimeout(() => { + const element = document.querySelector(`[title="${node.title}"]`); + const container = + element?.closest(".b3-overflow") || element?.closest(".b3-explorer"); + if (element && container && !isElementInViewport(element, container)) { + element.scrollIntoView({ + behavior: "smooth", + block: "center", + }); + } + }, 100); } }} onDrop={(info) => { @@ -898,7 +968,7 @@ export const Explorer: FC = () => { { path: node.path, }); } - console.info("select node:", node.path, node.def); setSelectedNodedefKeys([node.path]); } }} diff --git a/src/contexts/workspace-context.ts b/src/contexts/workspace-context.ts index e4f9922..5d4ed8f 100644 --- a/src/contexts/workspace-context.ts +++ b/src/contexts/workspace-context.ts @@ -634,10 +634,11 @@ export const useWorkspace = create((set, get) => ({ if (!fileMeta) { fileMeta = { path: fileNode.path }; allFiles.set(path, fileMeta); + console.log("add file meta:", path); } else { fileMeta.path = fileNode.path; } - fileMeta.exists = true; + fileMeta.exists = fs.existsSync(fileNode.path); if (fileMeta.desc === undefined) { const file = readJson(fileNode.path) as TreeModel; fileMeta.desc = file.desc ?? ""; @@ -650,11 +651,12 @@ export const useWorkspace = create((set, get) => ({ allFiles.forEach((file, key) => { if (!file.exists) { allFiles.delete(key); - delete b3util.files[key]; updated = true; + console.log("delete file meta:", key); + delete b3util.files[key]; + } else { + b3util.files[key] = fs.statSync(file.path).mtimeMs; } - const modified = fs.statSync(file.path).mtimeMs; - b3util.files[key] = modified; }); set({ allFiles }); if (updated) {