From c5cfb5cb27330c7ac647e1316c7af8b62f296a9d Mon Sep 17 00:00:00 2001 From: bunsenstraat Date: Mon, 25 Dec 2023 17:30:56 +0100 Subject: [PATCH] use flat tree --- apps/remix-ide/webpack.config.js | 4 +- .../src/lib/components/file-explorer.tsx | 48 +++- .../components/file-recursive-tree-item.tsx | 4 +- .../lib/components/file-recursive-tree.tsx | 36 +-- .../src/lib/components/flat-tree.tsx | 229 ++++++++++++++++++ .../workspace/src/lib/reducers/workspace.ts | 6 +- package.json | 4 + yarn.lock | 55 ++++- 8 files changed, 347 insertions(+), 39 deletions(-) create mode 100644 libs/remix-ui/workspace/src/lib/components/flat-tree.tsx diff --git a/apps/remix-ide/webpack.config.js b/apps/remix-ide/webpack.config.js index d5109340fb..48d2c4eda1 100644 --- a/apps/remix-ide/webpack.config.js +++ b/apps/remix-ide/webpack.config.js @@ -77,11 +77,13 @@ module.exports = composePlugins(withNx(), withReact(), (config) => { solc: 'solc' } - // set alias + // uncomment this to enable react profiling + /* config.resolve.alias = { ...config.resolve.alias, 'react-dom$': 'react-dom/profiling', } + */ // add public path diff --git a/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx b/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx index 8c42e9cb71..7d54e76a4e 100644 --- a/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx +++ b/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx @@ -13,6 +13,7 @@ import { ROOT_PATH } from '../utils/constants' import { fileKeySort } from '../utils' import { moveFileIsAllowed, moveFolderIsAllowed } from '../actions' import { RecursiveTree } from './file-recursive-tree' +import { FlatTree } from './flat-tree' export const FileExplorer = (props: FileExplorerProps) => { const intl = useIntl() @@ -37,12 +38,15 @@ export const FileExplorer = (props: FileExplorerProps) => { const treeRef = useRef(null) const [childrenKeys, setChildrenKeys] = useState([]) + useEffect(() => { if (contextMenuItems) { addMenuItems(contextMenuItems) } }, [contextMenuItems]) + + useEffect(() => { if (removedContextMenuItems) { removeMenuItems(removedContextMenuItems) @@ -390,9 +394,14 @@ export const FileExplorer = (props: FileExplorerProps) => { } + return ( -
+
  • {
    {
  • +
    -
    - -
    +
    - +
    +
    @@ -436,3 +449,14 @@ export const FileExplorer = (props: FileExplorerProps) => { } export default FileExplorer + +/* + + */ diff --git a/libs/remix-ui/workspace/src/lib/components/file-recursive-tree-item.tsx b/libs/remix-ui/workspace/src/lib/components/file-recursive-tree-item.tsx index 1cc0165f7c..f2f5082c55 100644 --- a/libs/remix-ui/workspace/src/lib/components/file-recursive-tree-item.tsx +++ b/libs/remix-ui/workspace/src/lib/components/file-recursive-tree-item.tsx @@ -44,8 +44,8 @@ export const RecursiveTreeItem = (props: RecursiveTreeItemProps) => { >
    {focusEdit && file.path && focusEdit.element === file.path ? - : - {file.name}} + : + {file.name}}
      { diff --git a/libs/remix-ui/workspace/src/lib/components/file-recursive-tree.tsx b/libs/remix-ui/workspace/src/lib/components/file-recursive-tree.tsx index b92cf83275..635b0749b8 100644 --- a/libs/remix-ui/workspace/src/lib/components/file-recursive-tree.tsx +++ b/libs/remix-ui/workspace/src/lib/components/file-recursive-tree.tsx @@ -1,4 +1,4 @@ -import React, { SyntheticEvent, useEffect, useRef, useState } from 'react' +import React, { SyntheticEvent, startTransition, useEffect, useRef, useState } from 'react' import { Popover } from 'react-bootstrap' import { FileType, WorkspaceElement } from '../types' import { ROOT_PATH } from '../utils/constants' @@ -22,6 +22,7 @@ let mouseTimer: any = { export const RecursiveTree = (props: RecursiveTreeProps) => { const { files, expandPath, editModeOff } = props + const [internalFiles, setInternalFiles] = useState<{ [x: string]: Record }>({}) const ref = useRef(null) const [mouseOverTarget, setMouseOverTarget] = useState<{ path: string, @@ -38,6 +39,13 @@ export const RecursiveTree = (props: RecursiveTreeProps) => { console.log('focus edit', props.focusEdit) },[props.focusEdit]) + useEffect(() => { + console.log('flat files changed', files, ROOT_PATH) + + setInternalFiles(files) + + },[files]) + useEffect(() => { console.log('STATE', props.focusContext, props.focusElement, props.focusEdit, expandPath) //files[ROOT_PATH] && Object.keys(files[ROOT_PATH]).map((key, index) => { @@ -53,7 +61,7 @@ export const RecursiveTree = (props: RecursiveTreeProps) => { if (target && target.getAttribute) { const path = target.getAttribute(useLabel? 'data-label-path' : 'data-path') const type = target.getAttribute(useLabel? 'data-label-type' : 'data-type') - let position = target.getBoundingClientRect() + const position = target.getBoundingClientRect() // get size of element const endPosition = { @@ -162,18 +170,18 @@ export const RecursiveTree = (props: RecursiveTreeProps) => { }
        {files[ROOT_PATH] && - Object.keys(files[ROOT_PATH]).map((key, index) => { - //console.log('recursive tree', files[ROOT_PATH][key]) - return () - })} + internalFiles && internalFiles[ROOT_PATH] && Object.keys(internalFiles[ROOT_PATH]).map((key, index) => { + //console.log('recursive tree', files[ROOT_PATH][key]) + return () + })}
      diff --git a/libs/remix-ui/workspace/src/lib/components/flat-tree.tsx b/libs/remix-ui/workspace/src/lib/components/flat-tree.tsx new file mode 100644 index 0000000000..e922e740cd --- /dev/null +++ b/libs/remix-ui/workspace/src/lib/components/flat-tree.tsx @@ -0,0 +1,229 @@ +import React, { SyntheticEvent, startTransition, useEffect, useRef, useState } from 'react' +import { Popover } from 'react-bootstrap' +import { FileType, WorkspaceElement } from '../types' +import { ROOT_PATH } from '../utils/constants' +import { getPathIcon } from '@remix-ui/helper'; +import { Virtuoso } from 'react-virtuoso' +import { RecursiveItemInput } from './file-recursive-item-input'; + +interface FlatTreeProps { + files: { [x: string]: Record }, + expandPath: string[], + focusEdit: { element: string; type: string; isNew: boolean; lastEdit: string } + editModeOff: (content: string) => void + focusElement: { key: string; type: WorkspaceElement }[] + focusContext: { element: string; x: number; y: number; type: string } + handleContextMenu: (pageX: number, pageY: number, path: string, content: string, type: string) => void + handleTreeClick: (e: SyntheticEvent) => void + treeRef: React.MutableRefObject +} + +let mouseTimer: any = { + path: null, + timer: null +} + +export const FlatTree = (props: FlatTreeProps) => { + const { files, expandPath, focusEdit, editModeOff, handleTreeClick } = props + const [flatTree, setFlatTree] = useState<{ [x: string]: FileType }>({}) + const [hover, setHover] = useState('') + const [mouseOverTarget, setMouseOverTarget] = useState<{ + path: string, + type: string, + content: string, + position: { + top: number, + left: number + } + }>(null) + const [showMouseOverTarget, setShowMouseOverTarget] = useState(false) + const ref = useRef(null) + const [size, setSize] = useState(0) + + + + + + const labelClass = (file: FileType) => + props.focusEdit.element === file.path + ? 'bg-light' + : props.focusElement.findIndex((item) => item.key === file.path) !== -1 + ? 'bg-secondary' + : hover == file.path + ? 'bg-light border-no-shift' + : props.focusContext.element === file.path && props.focusEdit.element !== file.path + ? 'bg-light border-no-shift' + : '' + + useEffect(() => { + console.log('flat files changed', files, ROOT_PATH) + const flatTree = {} + const mapChild = (file: FileType) => { + flatTree[file.path] = file + expandPath && expandPath.includes(file.path) && + file.child && Object.keys(file.child).map((key) => { + mapChild(file.child[key]) + }) + } + files && files[ROOT_PATH] && Object.keys(files[ROOT_PATH]).map((key) => { + mapChild(files[ROOT_PATH][key]) + }) + console.log('flat tree', flatTree) + setFlatTree(flatTree) + }, [props]) + + + + + const getIndentLevelDiv = (path: string) => { + const pathArray = path.split('/') + const level = pathArray.length - 1 + const indent = level * 10 + return (
      ) + } + + const getEventTarget = async (e: any, useLabel: boolean = false) => { + let target = e.target as HTMLElement + while (target && target.getAttribute && !target.getAttribute(useLabel? 'data-label-path' : 'data-path')) { + target = target.parentElement + } + if (target && target.getAttribute) { + const path = target.getAttribute(useLabel? 'data-label-path' : 'data-path') + const type = target.getAttribute(useLabel? 'data-label-type' : 'data-type') + const position = target.getBoundingClientRect() + // get size of element + + const endPosition = { + top: position.top - position.height * 2 + 4, + left: position.left , + } + + const content = target.textContent + return { + path, + type, + content, + position: endPosition + } + } + } + + const handleContextMenu = async (e: any) => { + e.preventDefault() + e.stopPropagation() + const target = await getEventTarget(e) + if (target) { + props.handleContextMenu(e.pageX, e.pageY, target.path, target.content, target.type) + } + } + + const onDragEnd = (event: SyntheticEvent) => { + console.log('drag end', event) + } + + const onDrop = async (event: SyntheticEvent) => { + event.preventDefault() + console.log('drop', event) + const target = await getEventTarget(event) + console.log('drop', target) + } + + const onDragOver = async (e: SyntheticEvent) => { + e.preventDefault() + //console.log('drag over', e) + const target = await getEventTarget(e) + //console.log('drag over', target) + } + + const onMouseMove = async (e: any) => { + ///console.log('mouse move', e) + const target = await getEventTarget(e, true) + if (target && target.path) { + if (mouseTimer.path !== target.path) { + //console.log('set timer', target) + setShowMouseOverTarget(false) + mouseTimer = { + path: target.path, + timer: setTimeout(() => { + if (mouseTimer.path === target.path) { + setShowMouseOverTarget(true) + setMouseOverTarget(target) + } + }, 1000) + } + } + } else { + mouseTimer = { + path: null, + timer: null + } + setShowMouseOverTarget(false) + } + } + + const onMouseLeave = async (e: any) => { + mouseTimer = { + path: null, + timer: null + } + setShowMouseOverTarget(false) + } + + useEffect(() => { + console.log('tree ref', props.treeRef.current) + const boundingRect = props.treeRef.current.getBoundingClientRect() + console.log('bounding rect', boundingRect) + setSize(boundingRect.height - 100) + },[props.treeRef.current]) + + + const Row = (index) => { + const node = Object.keys(flatTree)[index] + const file = flatTree[node] + //console.log('node', node) + return (
      setHover(file.path)} + onMouseOut={() => setHover(file.path)} + data-type={file.isDirectory ? 'folder' : 'file'} data-path={`${file.path}`} data-id={`treeViewLi${file.path}`} + > + {getIndentLevelDiv(file.path)} + +
      + {focusEdit && file.path && focusEdit.element === file.path ? + : +
      {file.name} +
      } +
      ) + } + + return (<> +
      + {showMouseOverTarget && mouseOverTarget && + + + {mouseOverTarget && mouseOverTarget.path} + + + } + Row(index)} + /> +
      + ) + +} \ No newline at end of file diff --git a/libs/remix-ui/workspace/src/lib/reducers/workspace.ts b/libs/remix-ui/workspace/src/lib/reducers/workspace.ts index 448755c1f9..08910ed672 100644 --- a/libs/remix-ui/workspace/src/lib/reducers/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/reducers/workspace.ts @@ -187,7 +187,7 @@ export const browserReducer = (state = browserInitialState, action: Actions) => const startTime = new Date().getTime() const fd = fetchDirectoryContent(state, payload) const endTime = new Date().getTime() - console.log('fetchDirectoryContent tree', endTime - startTime) + console.log('fetchDirectoryContent tree', endTime - startTime, fd) return { ...state, @@ -865,6 +865,10 @@ export const browserReducer = (state = browserInitialState, action: Actions) => } } +const flattenTree = () =>{ + +} + const fileAdded = ( state: BrowserState, path: string diff --git a/package.json b/package.json index 8797b1e092..9a0dbe0cdc 100644 --- a/package.json +++ b/package.json @@ -209,6 +209,9 @@ "react-multi-carousel": "^2.8.2", "react-router-dom": "^6.16.0", "react-tabs": "^6.0.2", + "react-virtualized": "^9.22.5", + "react-virtuoso": "^4.6.2", + "react-window": "^1.8.10", "react-zoom-pan-pinch": "^3.1.0", "regenerator-runtime": "0.13.7", "remark-gfm": "^3.0.1", @@ -276,6 +279,7 @@ "@types/react-dom": "^18.2.0", "@types/react-image-magnifiers": "^1.3.2", "@types/react-router-dom": "^5.3.3", + "@types/react-window": "^1.8.8", "@types/request": "^2.48.7", "@types/semver": "^7.3.10", "@types/tape": "^4.13.0", diff --git a/yarn.lock b/yarn.lock index bae735ac75..fa79dc15e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1649,6 +1649,13 @@ core-js-pure "^3.19.0" regenerator-runtime "^0.13.4" +"@babel/runtime@^7.0.0", "@babel/runtime@^7.13.8": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.6.tgz#c05e610dc228855dc92ef1b53d07389ed8ab521d" + integrity sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.15.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.16.3" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5" @@ -1656,13 +1663,6 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.13.8": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.6.tgz#c05e610dc228855dc92ef1b53d07389ed8ab521d" - integrity sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ== - dependencies: - regenerator-runtime "^0.14.0" - "@babel/runtime@^7.14.8": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259" @@ -6199,6 +6199,13 @@ dependencies: "@types/react" "*" +"@types/react-window@^1.8.8": + version "1.8.8" + resolved "https://registry.yarnpkg.com/@types/react-window/-/react-window-1.8.8.tgz#c20645414d142364fbe735818e1c1e0a145696e3" + integrity sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@16 || 17 || 18", "@types/react@>=16.14.8", "@types/react@>=16.9.11", "@types/react@^18.2.0": version "18.2.45" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.45.tgz#253f4fac288e7e751ab3dc542000fb687422c15c" @@ -10505,6 +10512,11 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" +clsx@^1.0.4: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + clsx@^1.1.0, clsx@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" @@ -12245,7 +12257,7 @@ dom-accessibility-api@^0.5.9: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56" integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg== -dom-helpers@^5.0.1, dom-helpers@^5.2.0, dom-helpers@^5.2.1: +dom-helpers@^5.0.1, dom-helpers@^5.1.3, dom-helpers@^5.2.0, dom-helpers@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== @@ -19554,7 +19566,7 @@ memfs@^3.4.1, memfs@^3.4.3: dependencies: fs-monkey "^1.0.3" -memoize-one@^5.1.1: +"memoize-one@>=3.1.1 <6", memoize-one@^5.1.1: version "5.2.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== @@ -24037,6 +24049,31 @@ react-transition-group@^4.4.1: loose-envify "^1.4.0" prop-types "^15.6.2" +react-virtualized@^9.22.5: + version "9.22.5" + resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.5.tgz#bfb96fed519de378b50d8c0064b92994b3b91620" + integrity sha512-YqQMRzlVANBv1L/7r63OHa2b0ZsAaDp1UhVNEdUaXI8A5u6hTpA5NYtUueLH2rFuY/27mTGIBl7ZhqFKzw18YQ== + dependencies: + "@babel/runtime" "^7.7.2" + clsx "^1.0.4" + dom-helpers "^5.1.3" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-lifecycles-compat "^3.0.4" + +react-virtuoso@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/react-virtuoso/-/react-virtuoso-4.6.2.tgz#74b59ebe3260e1f73e92340ffec84a6853285a12" + integrity sha512-vvlqvzPif+MvBrJ09+hJJrVY0xJK9yran+A+/1iwY78k0YCVKsyoNPqoLxOxzYPggspNBNXqUXEcvckN29OxyQ== + +react-window@^1.8.10: + version "1.8.10" + resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.10.tgz#9e6b08548316814b443f7002b1cf8fd3a1bdde03" + integrity sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg== + dependencies: + "@babel/runtime" "^7.0.0" + memoize-one ">=3.1.1 <6" + react-zoom-pan-pinch@^3.1.0: version "3.3.0" resolved "https://registry.yarnpkg.com/react-zoom-pan-pinch/-/react-zoom-pan-pinch-3.3.0.tgz#873648438c5244d89fcc2127614046928429cbe0"