flattentree
filip mertens 11 months ago
parent 5df3567529
commit 702c8cb088
  1. 96
      libs/remix-ui/workspace/src/lib/components/file-label.tsx
  2. 66
      libs/remix-ui/workspace/src/lib/components/file-recursive-tree-item.tsx
  3. 194
      libs/remix-ui/workspace/src/lib/components/file-recursive-tree.tsx
  4. 175
      libs/remix-ui/workspace/src/lib/components/file-render.tsx

@ -1,96 +0,0 @@
// eslint-disable-next-line no-use-before-define
import {fileDecoration} from '@remix-ui/file-decorators'
import {CustomTooltip} from '@remix-ui/helper'
import {FormattedMessage} from 'react-intl'
import React, {useEffect, useRef, useState} from 'react'
import {FileType} from '../types'
export interface FileLabelProps {
file: FileType
focusEdit: {
element: string
type: string
isNew: boolean
lastEdit: string
}
fileDecorations: fileDecoration[]
editModeOff: (content: string) => void
dragStatus: boolean
}
export const FileLabel = (props: FileLabelProps) => {
const {file, focusEdit, editModeOff, fileDecorations} = props
const [isEditable, setIsEditable] = useState<boolean>(false)
const [fileStateClasses, setFileStateClasses] = useState<string>('')
const labelRef = useRef(null)
useEffect(() => {
if (focusEdit.element && file.path) {
setIsEditable(focusEdit.element === file.path)
}
}, [file.path, focusEdit])
useEffect(() => {
const state = props.fileDecorations.find((state: fileDecoration) => {
if (state.path === props.file.path) return true
if (state.bubble && props.file.isDirectory && state.path.startsWith(props.file.path)) return true
})
if (state && state.fileStateLabelClass) {
setFileStateClasses(state.fileStateLabelClass)
} else {
setFileStateClasses('')
}
}, [fileDecorations])
useEffect(() => {
if (labelRef.current) {
setTimeout(() => {
labelRef.current.focus()
}, 0)
}
}, [isEditable])
const handleEditInput = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.which === 13) {
event.preventDefault()
editModeOff(labelRef.current.innerText)
labelRef.current.innerText = file.name
}
if (event.which === 27) {
event.preventDefault()
// don't change it
editModeOff(file.name)
}
}
const handleEditBlur = (event: React.SyntheticEvent) => {
event.stopPropagation()
editModeOff(labelRef.current.innerText)
labelRef.current.innerText = file.name
}
// The tooltip is setted up on the label and not the whole line to avoid unnecessary tooltips on the short filenames.
// It has the delay for the same reason.
return (
<div
className="remixui_items d-inline-block w-100"
ref={isEditable ? labelRef : null}
suppressContentEditableWarning={true}
contentEditable={isEditable}
onKeyDown={handleEditInput}
onBlur={handleEditBlur}
>
<CustomTooltip
placement="top"
delay={{show: 1000, hide: 0}}
tooltipText={`${file.path}`}
tooltipId={`fileExplorer.${file.path}`}
tooltipClasses="text-nowrap"
hide={props.dragStatus}
>
<span className={`remixui_label ${fileStateClasses} ` + (file.isDirectory ? 'folder' : 'remixui_leaf')} data-path={file.path}>
{file.name}
</span>
</CustomTooltip>
</div>
)
}

@ -1,66 +0,0 @@
import { getPathIcon } from "@remix-ui/helper";
import React, { useEffect, useState } from "react";
import { FileType, WorkspaceElement } from "../types";
import { FlatTreeItemInput } from "./flat-tree-item-input";
interface RecursiveTreeItemProps {
file: 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}
}
export const RecursiveTreeItem = (props: RecursiveTreeItemProps) => {
const [hover, setHover] = useState<boolean>(false)
const { file, expandPath, focusEdit, editModeOff } = props
useEffect(() => {
console.log('item render')
},[])
const labelClass =
props.focusEdit.element === file.path
? 'bg-light'
: props.focusElement.findIndex((item) => item.key === file.path) !== -1
? 'bg-secondary'
: hover
? 'bg-light border-no-shift'
: props.focusContext.element === file.path && props.focusEdit.element !== file.path
? 'bg-light border-no-shift'
: ''
return (
<>
<li ref={null} key={`treeViewLi${file.path}`} data-type={file.isDirectory ? 'folder' : 'file'} data-path={`${file.path}`} data-id={`treeViewLi${file.path}`} className="li_tv">
<div
key={`treeViewDiv${file.path}`}
data-id={`treeViewDiv${file.path}`}
className={`d-flex flex-row align-items-center ${labelClass}`}
onMouseOver={() => setHover(true)}
onMouseOut={() => setHover(false)}
>
<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 ?
<FlatTreeItemInput 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" >
{
expandPath && expandPath.includes(file.path) &&
file.child && Object.keys(file.child).map((key, index) => {
//console.log('recursive tree', file.child[key])
return (<RecursiveTreeItem
editModeOff={editModeOff}
focusElement={props.focusElement}
focusContext={props.focusContext}
focusEdit={props.focusEdit}
expandPath={expandPath} key={index} file={file.child[key]} />)
})
}
</ul>
</li>
</>)
}

@ -1,194 +0,0 @@
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 { RecursiveTreeItem } from './file-recursive-tree-item'
interface RecursiveTreeProps {
files: { [x: string]: Record<string, FileType> },
expandPath: string[],
handleContextMenu: (pageX: number, pageY: number, path: string, content: string, type: string) => void
focusEdit: {element: string; type: string; isNew: boolean; lastEdit: string}
focusElement: {key: string; type: WorkspaceElement}[]
focusContext: {element: string; x: number; y: number; type: string}
editModeOff: (content: string) => void
}
let mouseTimer: any = {
path: null,
timer: null
}
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,
type: string,
content: string,
position: {
top: number,
left: number
}
}>(null)
const [showMouseOverTarget, setShowMouseOverTarget] = useState<boolean>(false)
useEffect(() => {
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) => {
// console.log('recursive tree', files[ROOT_PATH][key])
//})
}, [props.focusContext, props.focusElement, props.focusEdit, expandPath])
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('show mouse over target', showMouseOverTarget)
}, [showMouseOverTarget])
return (
<div 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>
}
<ul className="ul_tv ml-0 pl-1" >{files[ROOT_PATH] &&
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>
)
}

@ -1,175 +0,0 @@
// eslint-disable-next-line no-use-before-define
import React, {SyntheticEvent, useEffect, useState} from 'react'
import {FileType, WorkspaceElement} from '../types'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import {TreeView, TreeViewItem} from '@remix-ui/tree-view'
import {getPathIcon} from '@remix-ui/helper'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import {FileLabel} from './file-label'
import {fileDecoration, FileDecorationIcons} from '@remix-ui/file-decorators'
import {Draggable} from '@remix-ui/drag-n-drop'
import { fileKeySort } from '../utils'
export interface RenderFileProps {
file: FileType
index: number
focusEdit: {element: string; type: string; isNew: boolean; lastEdit: string}
focusElement: {key: string; type: WorkspaceElement}[]
focusContext: {element: string; x: number; y: number; type: string}
ctrlKey: boolean
expandPath: string[]
hideIconsMenu?: React.Dispatch<React.SetStateAction<boolean>>
showIconsMenu?: boolean
editModeOff: (content: string) => void
handleClickFolder: (path: string, type: string) => void
handleClickFile: (path: string, type: string) => void
handleContextMenu: (pageX: number, pageY: number, path: string, content: string, type: string) => void
fileDecorations: fileDecoration[]
dragStatus: boolean
}
export const FileRender = (props: RenderFileProps) => {
const [file, setFile] = useState<FileType>({} as FileType)
const [hover, setHover] = useState<boolean>(false)
const [icon, setIcon] = useState<string>('')
const [childrenKeys, setChildrenKeys] = useState<string[]>([])
useEffect(() => {
if (props.file && props.file.path && props.file.type) {
setFile(props.file)
setIcon(getPathIcon(props.file.path))
}
}, [props.file])
useEffect(() => {
if (file.child) {
try {
const children: FileType[] = file.child as any
setChildrenKeys(fileKeySort(children))
} catch (e) {
setChildrenKeys(Object.keys(file.child))
}
} else {
setChildrenKeys([])
}
}, [file.child, props.expandPath, props.file])
const labelClass =
props.focusEdit.element === file.path
? 'bg-light'
: props.focusElement.findIndex((item) => item.key === file.path) !== -1
? 'bg-secondary'
: hover
? 'bg-light border-no-shift'
: props.focusContext.element === file.path && props.focusEdit.element !== file.path
? 'bg-light border-no-shift'
: ''
const spreadProps = {
onClick: (e) => e.stopPropagation()
}
const handleFolderClick = (event: SyntheticEvent) => {
event.stopPropagation()
if (props.focusEdit.element !== file.path) props.handleClickFolder(file.path, file.type)
if (props.showIconsMenu === true) props.hideIconsMenu(!props.showIconsMenu)
}
const handleFileClick = (event: SyntheticEvent) => {
event.stopPropagation()
if (props.focusEdit.element !== file.path) props.handleClickFile(file.path, file.type)
if (props.showIconsMenu === true) props.hideIconsMenu(!props.showIconsMenu)
}
const handleContextMenu = (event: PointerEvent) => {
event.preventDefault()
event.stopPropagation()
props.handleContextMenu(event.pageX, event.pageY, file.path, (event.target as HTMLElement).textContent, file.type)
}
const handleMouseOut = (event: SyntheticEvent) => {
event.stopPropagation()
setHover(false)
}
const handleMouseOver = (event: SyntheticEvent) => {
event.stopPropagation()
setHover(true)
}
if (file.isDirectory) {
return (
<TreeViewItem
id={`treeViewItem${file.path}`}
iconX="mr-2 fa fa-folder"
iconY={props.expandPath.includes(file.path) ? 'fa fa-folder-open' : 'fa fa-folder'}
key={`${file.path + props.index}`}
label={
<>
<Draggable isDraggable={props.focusEdit.element !== null} file={file} expandedPath={props.expandPath} handleClickFolder={props.handleClickFolder}>
<div className="d-flex flex-row">
<FileLabel fileDecorations={props.fileDecorations} file={file} focusEdit={props.focusEdit} editModeOff={props.editModeOff} dragStatus={props.dragStatus} />
<FileDecorationIcons file={file} fileDecorations={props.fileDecorations} />
</div>
</Draggable>
</>
}
onClick={handleFolderClick}
onContextMenu={handleContextMenu}
labelClass={labelClass}
controlBehaviour={props.ctrlKey}
expand={props.expandPath.includes(file.path)}
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
>
{file.child ? (
<TreeView id={`treeView${file.path}`} key={`treeView${file.path}`} {...spreadProps}>
{childrenKeys.map((key, index) => (
<FileRender
file={file.child[key]}
fileDecorations={props.fileDecorations}
index={index}
focusContext={props.focusContext}
focusEdit={props.focusEdit}
focusElement={props.focusElement}
ctrlKey={props.ctrlKey}
editModeOff={props.editModeOff}
handleClickFile={props.handleClickFile}
handleClickFolder={props.handleClickFolder}
handleContextMenu={props.handleContextMenu}
expandPath={props.expandPath}
key={index}
dragStatus={props.dragStatus}
/>
))}
</TreeView>
) : (
<TreeView id={`treeView${file.path}`} key={`treeView${file.path}`} {...spreadProps} />
)}
</TreeViewItem>
)
} else {
return (
<TreeViewItem
id={`treeViewItem${file.path}`}
key={`treeView${file.path}`}
label={
<>
<Draggable isDraggable={props.focusEdit.element !== null} file={file} expandedPath={props.expandPath} handleClickFolder={props.handleClickFolder}>
<div className="d-flex flex-row">
<FileLabel fileDecorations={props.fileDecorations} file={file} focusEdit={props.focusEdit} editModeOff={props.editModeOff} dragStatus={props.dragStatus} />
<FileDecorationIcons file={file} fileDecorations={props.fileDecorations} />
</div>
</Draggable>
</>
}
onClick={handleFileClick}
onContextMenu={handleContextMenu}
icon={icon}
labelClass={labelClass}
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
/>
)
}
}
Loading…
Cancel
Save