use flat tree

flattentree
bunsenstraat 11 months ago
parent c46792e04f
commit c5cfb5cb27
  1. 4
      apps/remix-ide/webpack.config.js
  2. 48
      libs/remix-ui/workspace/src/lib/components/file-explorer.tsx
  3. 4
      libs/remix-ui/workspace/src/lib/components/file-recursive-tree-item.tsx
  4. 36
      libs/remix-ui/workspace/src/lib/components/file-recursive-tree.tsx
  5. 229
      libs/remix-ui/workspace/src/lib/components/flat-tree.tsx
  6. 6
      libs/remix-ui/workspace/src/lib/reducers/workspace.ts
  7. 4
      package.json
  8. 55
      yarn.lock

@ -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

@ -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<HTMLDivElement>(null)
const [childrenKeys, setChildrenKeys] = useState<string[]>([])
useEffect(() => {
if (contextMenuItems) {
addMenuItems(contextMenuItems)
}
}, [contextMenuItems])
useEffect(() => {
if (removedContextMenuItems) {
removeMenuItems(removedContextMenuItems)
@ -390,9 +394,14 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
return (
<div ref={treeRef} tabIndex={0} style={{ outline: 'none' }}>
<div ref={treeRef} tabIndex={0} style={{ outline: 'none',
display: 'flex',
flexDirection: 'column',
height: '100%',
}}>
<TreeView id="treeView">
<li key={`treeViewLiMenu`} data-id={`treeViewLiMenu`} className="li_tv">
<div
@ -404,6 +413,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
<div onClick={handleFileExplorerMenuClick}>
<FileExplorerMenu
title={''}
menuItems={props.menuItems}
createNewFile={handleNewFileInput}
createNewFolder={handleNewFolderInput}
@ -415,19 +425,22 @@ export const FileExplorer = (props: FileExplorerProps) => {
</span>
</div>
</li>
<div style={{flexGrow: 2}}>
<div>
<div onClick={handleTreeClick}>
<RecursiveTree
focusEdit={state.focusEdit}
focusElement={props.focusElement}
focusContext={state.focusContext}
editModeOff={editModeOff}
handleContextMenu={handleContextMenu}
expandPath={props.expandPath}
files={files} />
</div>
<FlatTree
treeRef={treeRef}
handleTreeClick={handleTreeClick}
focusEdit={state.focusEdit}
focusElement={props.focusElement}
focusContext={state.focusContext}
editModeOff={editModeOff}
files={files}
expandPath={props.expandPath}
handleContextMenu={handleContextMenu}
/>
</div>
</div>
<div className='d-block w-100 pb-4 mb-4'></div>
</TreeView>
</div>
@ -436,3 +449,14 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
export default FileExplorer
/*
<RecursiveTree
focusEdit={state.focusEdit}
focusElement={props.focusElement}
focusContext={state.focusContext}
editModeOff={editModeOff}
handleContextMenu={handleContextMenu}
expandPath={props.expandPath}
files={files} />*/

@ -44,8 +44,8 @@ export const RecursiveTreeItem = (props: RecursiveTreeItemProps) => {
>
<div className={`pr-2 pl-2 ${file.isDirectory ? expandPath && expandPath.includes(file.path) ? 'fa fa-folder-open' : 'fa fa-folder' : getPathIcon(file.path)} caret caret_tv`}></div>
{focusEdit && file.path && focusEdit.element === file.path ?
<RecursiveItemInput editModeOff={editModeOff} file={file}/>:
<span draggable="true" className="ml-1 pl-2" data-label-type={file.isDirectory ? 'folder' : 'file'} data-label-path={`${file.path}`}>{file.name}</span>}
<RecursiveItemInput editModeOff={editModeOff} file={file}/>:
<span draggable="true" className="ml-1 pl-2" data-label-type={file.isDirectory ? 'folder' : 'file'} data-label-path={`${file.path}`}>{file.name}</span>}
</div>
<ul className="ul_tv ml-0 pl-1" >
{

@ -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<string, FileType> }>({})
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) => {
}
<ul className="ul_tv ml-0 pl-1" >{files[ROOT_PATH] &&
Object.keys(files[ROOT_PATH]).map((key, index) => {
//console.log('recursive tree', files[ROOT_PATH][key])
return (<RecursiveTreeItem
editModeOff={editModeOff}
focusEdit={props.focusEdit}
focusElement={props.focusElement}
focusContext={props.focusContext}
expandPath={expandPath}
key={index}
file={files[ROOT_PATH][key]}
/>)
})}
internalFiles && internalFiles[ROOT_PATH] && Object.keys(internalFiles[ROOT_PATH]).map((key, index) => {
//console.log('recursive tree', files[ROOT_PATH][key])
return (<RecursiveTreeItem
editModeOff={editModeOff}
focusEdit={props.focusEdit}
focusElement={props.focusElement}
focusContext={props.focusContext}
expandPath={expandPath}
key={index}
file={internalFiles[ROOT_PATH][key]}
/>)
})}
</ul>
</div>

@ -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<string, FileType> },
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<HTMLDivElement>
}
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<string>('')
const [mouseOverTarget, setMouseOverTarget] = useState<{
path: string,
type: string,
content: string,
position: {
top: number,
left: number
}
}>(null)
const [showMouseOverTarget, setShowMouseOverTarget] = useState<boolean>(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 (<div style={{ width: `${indent}px` }}></div>)
}
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 (<div
className={`d-flex flex-row align-items-center ${labelClass(file)}`}
onMouseOver={() => 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)}
<div className={`pr-2 pl-2 ${file.isDirectory ? expandPath && expandPath.includes(file.path) ? 'fa fa-folder-open' : 'fa fa-folder' : getPathIcon(file.path)} caret caret_tv`}></div>
{focusEdit && file.path && focusEdit.element === file.path ?
<RecursiveItemInput editModeOff={editModeOff} file={file}/>:
<div draggable={true} className="ml-1 pl-2" data-label-type={file.isDirectory ? 'folder' : 'file'} data-label-path={`${file.path}`} key={index}>{file.name}
</div>}
</div>)
}
return (<>
<div onClick={handleTreeClick} onMouseLeave={onMouseLeave} onMouseMove={onMouseMove} onDrop={onDrop} onDragOver={onDragOver} onContextMenu={handleContextMenu}>
{showMouseOverTarget && mouseOverTarget &&
<Popover id='popover-basic'
placement='top'
ref={ref}
style={
{
position: 'fixed',
top: `${mouseOverTarget.position.top}px`,
left: `${mouseOverTarget.position.left}px`,
minWidth: 'fit-content'
}
}>
<Popover.Content
className='text-wrap p-1 px-2 bg-secondary w-100'
>
{mouseOverTarget && mouseOverTarget.path}
</Popover.Content>
</Popover>
}
<Virtuoso
style={{ height: `${size}px` }}
totalCount={Object.keys(flatTree).length}
itemContent={index => Row(index)}
/>
</div>
</>)
}

@ -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

@ -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",

@ -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"

Loading…
Cancel
Save