diff --git a/package-lock.json b/package-lock.json index 78506f18..3b20a36e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,6 +49,7 @@ "react-select": "^5.7.3", "react-toastify": "^9.0.8", "react-tooltip": "^5.10.4", + "scroll-into-view-if-needed": "^3.1.0", "sequelize": "^6.23.2", "sequelize-cli": "^6.5.1", "sqlite3": "^5.1.6", @@ -9116,6 +9117,11 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz", + "integrity": "sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -25500,6 +25506,14 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", diff --git a/package.json b/package.json index df40ec07..b5c505c6 100644 --- a/package.json +++ b/package.json @@ -118,6 +118,7 @@ "react-select": "^5.7.3", "react-toastify": "^9.0.8", "react-tooltip": "^5.10.4", + "scroll-into-view-if-needed": "^3.1.0", "sequelize": "^6.23.2", "sequelize-cli": "^6.5.1", "sqlite3": "^5.1.6", diff --git a/src/components/FileTree/FileTree.css b/src/components/FileTree/FileTree.css index 4d8d2d5b..8803e721 100644 --- a/src/components/FileTree/FileTree.css +++ b/src/components/FileTree/FileTree.css @@ -2,7 +2,6 @@ padding: 15px; border: 0; min-height: 80vh !important; - /* border-right: 2px solid black; */ } .file-tree-loader { @@ -18,9 +17,7 @@ } .filetree-node-wrapper { - scroll-margin-left: 50px; - scroll-margin-top: 150px; - scroll-margin-bottom: 150px; + scroll-margin-left: 20px; } .rc-tree-child-tree { diff --git a/src/components/FileTree/FileTree.tsx b/src/components/FileTree/FileTree.tsx index a2abd1ab..d33874c7 100644 --- a/src/components/FileTree/FileTree.tsx +++ b/src/components/FileTree/FileTree.tsx @@ -2,6 +2,7 @@ import RcTree from "rc-tree"; import { Key } from "rc-tree/lib/interface"; import React, { useEffect, useLayoutEffect, useState } from "react"; import { Element } from "react-scroll"; +import scrollIntoView from "scroll-into-view-if-needed"; import EllipticLoader from "../EllipticLoader"; import SwitcherIcon from "./SwitcherIcon"; @@ -18,6 +19,8 @@ const FileTree = (props: React.HTMLProps) => { initialized, importedSqliteFilePath, currentPath, + startProcessing, + endProcessing, updateCurrentPath, } = useWorkbenchDB(); @@ -27,27 +30,49 @@ const FileTree = (props: React.HTMLProps) => { useLayoutEffect(() => { if (currentPath.length === 0) return; + // Show working indicator while the FileTree Node is being rendered and focused + startProcessing(); + setExpandedKeys((keys) => { return [...keys, currentPath.substring(0, currentPath.lastIndexOf("/"))]; }); + function scrollTreeNode(targetNode: HTMLElement) { + scrollIntoView(targetNode, { + scrollMode: "if-needed", + behavior: "smooth", + block: "center", + inline: "start", + }); + } + // Timeout ensures that targetNode is accessed only after its rendered - let pendingTimeoutId: NodeJS.Timeout; - pendingTimeoutId = setTimeout(() => { - const targetNode = document.getElementsByName(currentPath)[0]; - if (targetNode) { - pendingTimeoutId = setTimeout(() => { - targetNode.scrollIntoView({ - behavior: "smooth", - block: "nearest", - inline: "start", - }); - }, FOCUS_ATTEMPT_DELAY); - } - }); + let pendingScrollerTimeoutId: NodeJS.Timeout; + + const alreadyRenderedTargetNode = + document.getElementsByName(currentPath)[0]; + + if (alreadyRenderedTargetNode) { + // Immediate scroll possible + scrollTreeNode(alreadyRenderedTargetNode); + } else { + // Wait for target node to render + pendingScrollerTimeoutId = setTimeout(() => { + const targetNode = document.getElementsByName(currentPath)[0]; + + if (targetNode) { + pendingScrollerTimeoutId = setTimeout(() => { + scrollTreeNode(targetNode); + }, FOCUS_ATTEMPT_DELAY); + + // Hide working indicator after the FileTree Node is focused + endProcessing(); + } + }); + } return () => { - clearTimeout(pendingTimeoutId); + clearTimeout(pendingScrollerTimeoutId); }; }, [currentPath]);