parent
5df3567529
commit
702c8cb088
@ -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…
Reference in new issue