Make tree view editable

pull/668/head
ioedeveloper 4 years ago
parent 66a0ebb94e
commit c81b5ea79d
  1. 22
      libs/remix-ui/file-explorer/src/lib/file-explorer-context-menu.tsx
  2. 252
      libs/remix-ui/file-explorer/src/lib/file-explorer.tsx
  3. 4
      libs/remix-ui/file-explorer/src/lib/types/index.ts
  4. 13
      libs/remix-ui/tree-view/src/lib/tree-view-item/tree-view-item.tsx
  5. 4
      libs/remix-ui/tree-view/src/types/index.ts

@ -4,8 +4,7 @@ import { FileExplorerContextMenuProps } from './types'
import './css/file-explorer-context-menu.css' import './css/file-explorer-context-menu.css'
export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) => { export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) => {
const { actions, createNewFile, createNewFolder, hideContextMenu, pageX, pageY, folder, ...otherProps } = props const { actions, createNewFile, createNewFolder, deletePath, renamePath, hideContextMenu, pageX, pageY, path, ...otherProps } = props
console.log('folder: ', folder)
const contextMenuRef = useRef(null) const contextMenuRef = useRef(null)
useEffect(() => { useEffect(() => {
@ -30,10 +29,21 @@ export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) =>
key={index} key={index}
className='remixui_liitem' className='remixui_liitem'
onClick={() => { onClick={() => {
if (item.name === 'New File') { switch (item.name) {
createNewFile(folder) case 'New File':
} else if (item.name === 'New Folder') { createNewFile(path)
createNewFolder(folder) break
case 'New Folder':
createNewFolder(path)
break
case 'Rename':
renamePath(path)
break
case 'Delete':
deletePath(path)
break
default:
break
} }
hideContextMenu() hideContextMenu()
}}>{item.name}</li> }}>{item.name}</li>

@ -27,6 +27,9 @@ export const FileExplorer = (props: FileExplorerProps) => {
element: null, element: null,
x: null, x: null,
y: null y: null
},
focusEdit: {
element: null
} }
}) })
@ -127,9 +130,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
} }
const createNewFile = (parentFolder?: string) => { const createNewFile = (parentFolder?: string) => {
console.log('parentFolderBefore: ', parentFolder)
if (!parentFolder) parentFolder = state.focusElement[0] ? state.focusElement[0].type === 'folder' ? state.focusElement[0].key : extractParentFromKey(state.focusElement[0].key) : name if (!parentFolder) parentFolder = state.focusElement[0] ? state.focusElement[0].type === 'folder' ? state.focusElement[0].key : extractParentFromKey(state.focusElement[0].key) : name
console.log('parentFolderAfter: ', parentFolder)
// const self = this // const self = this
// modalDialogCustom.prompt('Create new file', 'File Name (e.g Untitled.sol)', 'Untitled.sol', (input) => { // modalDialogCustom.prompt('Create new file', 'File Name (e.g Untitled.sol)', 'Untitled.sol', (input) => {
// if (!input) input = 'New file' // if (!input) input = 'New file'
@ -152,10 +153,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
} }
const createNewFolder = async (parentFolder?: string) => { const createNewFolder = async (parentFolder?: string) => {
console.log('parentFolderBefore: ', parentFolder)
if (!parentFolder) parentFolder = state.focusElement[0] ? state.focusElement[0].type === 'folder' ? state.focusElement[0].key : extractParentFromKey(state.focusElement[0].key) : name if (!parentFolder) parentFolder = state.focusElement[0] ? state.focusElement[0].type === 'folder' ? state.focusElement[0].key : extractParentFromKey(state.focusElement[0].key) : name
else if (parentFolder.indexOf('.sol') !== -1) parentFolder = extractParentFromKey(parentFolder) else if (parentFolder.indexOf('.sol') !== -1) parentFolder = extractParentFromKey(parentFolder)
console.log('parentFolderAfter: ', parentFolder)
// const self = this // const self = this
// modalDialogCustom.prompt('Create new folder', '', 'New folder', (input) => { // modalDialogCustom.prompt('Create new folder', '', 'New folder', (input) => {
// if (!input) { // if (!input) {
@ -178,6 +177,39 @@ export const FileExplorer = (props: FileExplorerProps) => {
// }, null, true) // }, null, true)
} }
const deletePath = async (path: string) => {
// if (self.files.isReadOnly(key)) { return tooltip('cannot delete file. ' + self.files.type + ' is a read only explorer') }
if (files.isReadOnly(path)) return
// const currentFilename = extractNameFromKey(path)
// modalDialogCustom.confirm(
// 'Delete file', `Are you sure you want to delete ${currentFilename} file?`,
// async () => {
try {
const fileManager = state.fileManager
await fileManager.remove(path)
const files = removePath(path, state.files)
const updatedFiles = files.filter(file => file)
setState(prevState => {
return { ...prevState, files: updatedFiles }
})
} catch (e) {
console.log('e: ', e)
// tooltip(`Failed to remove file ${key}.`)
}
// },
// () => {}
// )
}
const renamePath = async (path: string) => {
// if (self.files.isReadOnly(key)) { return tooltip('cannot rename folder. ' + self.files.type + ' is a read only explorer') }
if (files.isReadOnly(path)) return
editModeOn(path)
}
const addFile = async (parentFolder: string, newFileName: string) => { const addFile = async (parentFolder: string, newFileName: string) => {
if (parentFolder === name) { if (parentFolder === name) {
setState(prevState => { setState(prevState => {
@ -225,6 +257,21 @@ export const FileExplorer = (props: FileExplorerProps) => {
} }
} }
const removePath = (path: string, files: File[]): File[] => {
return files.map(file => {
if (file.path === path) {
return null
} else if (file.child) {
const childFiles = removePath(path, file.child)
file.child = childFiles.filter(file => file)
return file
} else {
return file
}
})
}
// self._components = {} // self._components = {}
// self._components.registry = localRegistry || globalRegistry // self._components.registry = localRegistry || globalRegistry
// self._deps = { // self._deps = {
@ -344,7 +391,6 @@ export const FileExplorer = (props: FileExplorerProps) => {
} }
const handleContextMenuFolder = (pageX: number, pageY: number, path: string) => { const handleContextMenuFolder = (pageX: number, pageY: number, path: string) => {
console.log('path: ', path)
setState(prevState => { setState(prevState => {
return { ...prevState, focusContext: { element: path, x: pageX, y: pageY } } return { ...prevState, focusContext: { element: path, x: pageX, y: pageY } }
}) })
@ -356,94 +402,101 @@ export const FileExplorer = (props: FileExplorerProps) => {
}) })
} }
const editModeOn = (path) => {
setState(prevState => {
return { ...prevState, focusEdit: { element: path } }
})
}
const editModeOff = (path) => {
setState(prevState => {
return { ...prevState, focusEdit: { element: null } }
})
}
const renderFiles = (file: File, index: number) => { const renderFiles = (file: File, index: number) => {
if (file.isDirectory) { if (file.isDirectory) {
return ( return (
<Droppable droppableId={file.path} key={index}> <>
{(provided) => ( <TreeViewItem
<> id={`treeViewItem${file.path}`}
<TreeViewItem iconX='pr-3 far fa-folder'
{ ...provided.droppableProps } iconY='pr-3 far fa-folder-open'
innerRef={ provided.innerRef } key={`${file.path + index}`}
id={`treeViewItem${file.path}`} label={label(file)}
iconX='pr-3 far fa-folder' onClick={(e) => {
iconY='pr-3 far fa-folder-open' e.stopPropagation()
key={`${file.path + index}`} handleClickFolder(file.path)
label={label(file)} }}
onClick={(e) => { onContextMenu={(e) => {
e.stopPropagation() e.preventDefault()
handleClickFolder(file.path) e.stopPropagation()
}} handleContextMenuFolder(e.pageX, e.pageY, file.path)
onContextMenu={(e) => { }}
e.preventDefault() labelClass={ state.focusEdit.element === file.path ? 'bg-light' : state.focusElement.findIndex(item => item.key === file.path) !== -1 ? 'bg-secondary' : '' }
e.stopPropagation() editable={state.focusEdit.element === file.path}
handleContextMenuFolder(e.pageX, e.pageY, file.path) onBlur={() => editModeOff(file.path)}
}} controlBehaviour={ state.ctrlKey }
labelClass={ state.focusElement.findIndex(item => item.key === file.path) !== -1 ? 'bg-secondary' : '' } >
controlBehaviour={ state.ctrlKey } {
> file.child ? <TreeView id={`treeView${file.path}`} key={index}>{
{ file.child.map((file, index) => {
file.child ? <TreeView id={`treeView${file.path}`} key={index}>{ return renderFiles(file, index)
file.child.map((file, index) => { })
return renderFiles(file, index)
})
}
</TreeView> : <TreeView id={`treeView${file.path}`} key={index} />
}
{ provided.placeholder }
</TreeViewItem>
{ (state.focusContext.element === file.path) &&
<FileExplorerContextMenu
actions={ state.actions.filter(item => item.type.findIndex(name => name === 'folder') !== -1) }
hideContextMenu={hideContextMenu}
createNewFile={createNewFile}
createNewFolder={createNewFolder}
pageX={state.focusContext.x}
pageY={state.focusContext.y}
folder={file.path}
/>
} }
</> </TreeView> : <TreeView id={`treeView${file.path}`} key={index} />
)} }
</Droppable> </TreeViewItem>
{ (state.focusContext.element === file.path) &&
<FileExplorerContextMenu
actions={ state.actions.filter(item => item.type.findIndex(name => name === 'folder') !== -1) }
hideContextMenu={hideContextMenu}
createNewFile={createNewFile}
createNewFolder={createNewFolder}
deletePath={deletePath}
renamePath={renamePath}
pageX={state.focusContext.x}
pageY={state.focusContext.y}
path={file.path}
/>
}
</>
) )
} else { } else {
return ( return (
<Draggable draggableId={file.path} index={index} key={index}> <>
{(provided) => ( <TreeViewItem
<> id={`treeViewItem${file.path}`}
<TreeViewItem key={index}
{...provided.draggableProps} label={label(file)}
{...provided.dragHandleProps} onClick={(e) => {
innerRef={provided.innerRef} e.stopPropagation()
id={`treeViewItem${file.path}`} if (state.focusEdit.element !== file.path) handleClickFile(file.path)
key={index} }}
label={label(file)} onContextMenu={(e) => {
onClick={(e) => { e.preventDefault()
e.stopPropagation() e.stopPropagation()
handleClickFile(file.path) handleContextMenuFile(e.pageX, e.pageY, file.path)
}} }}
onContextMenu={(e) => { icon='fa fa-file'
e.preventDefault() labelClass={ state.focusEdit.element === file.path ? 'bg-light' : state.focusElement.findIndex(item => item.key === file.path) !== -1 ? 'bg-secondary' : '' }
e.stopPropagation() editable={state.focusEdit.element === file.path}
handleContextMenuFile(e.pageX, e.pageY, file.path) onBlur={() => editModeOff(file.path)}
}} />
icon='fa fa-file' { (state.focusContext.element === file.path) &&
labelClass={ state.focusElement.findIndex(item => item.key === file.path) !== -1 ? 'bg-secondary' : '' } <FileExplorerContextMenu
/> actions={ state.actions.filter(item => item.type.findIndex(name => name === 'file') !== -1) }
{ (state.focusContext.element === file.path) && hideContextMenu={hideContextMenu}
<FileExplorerContextMenu createNewFile={createNewFile}
actions={ state.actions.filter(item => item.type.findIndex(name => name === 'file') !== -1) } createNewFolder={createNewFolder}
hideContextMenu={hideContextMenu} deletePath={deletePath}
createNewFile={createNewFile} renamePath={renamePath}
createNewFolder={createNewFolder} pageX={state.focusContext.x}
pageX={state.focusContext.x} pageY={state.focusContext.y}
pageY={state.focusContext.y} path={file.path}
/> />
} }
</> </>
)}
</Draggable>
) )
} }
} }
@ -480,24 +533,15 @@ export const FileExplorer = (props: FileExplorerProps) => {
/> />
} }
expand={true}> expand={true}>
<DragDropContext onDragEnd={onDragEnd}> <div>
<Droppable droppableId='droppableTreeView'> <TreeView id='treeViewMenu'>
{(provided) => ( {
<div state.files.map((file, index) => {
{ ...provided.droppableProps } return renderFiles(file, index)
ref={ provided.innerRef }> })
<TreeView id='treeViewMenu'> }
{ </TreeView>
state.files.map((file, index) => { </div>
return renderFiles(file, index)
})
}
</TreeView>
{ provided.placeholder }
</div>
)}
</Droppable>
</DragDropContext>
</TreeViewItem> </TreeViewItem>
</TreeView> </TreeView>
</div> </div>

@ -29,8 +29,10 @@ export interface FileExplorerContextMenuProps {
actions: { name: string, type: string[] }[], actions: { name: string, type: string[] }[],
createNewFile: (folder?: string) => void, createNewFile: (folder?: string) => void,
createNewFolder: (parentFolder?: string) => void, createNewFolder: (parentFolder?: string) => void,
deletePath: (path: string) => void,
renamePath: (path: string) => void
hideContextMenu: () => void, hideContextMenu: () => void,
pageX: number, pageX: number,
pageY: number, pageY: number,
folder?: string path: string
} }

@ -1,21 +1,28 @@
import React, { useState, useEffect } from 'react' // eslint-disable-line import React, { useState, useEffect, useRef } from 'react' // eslint-disable-line
import { TreeViewItemProps } from '../../types' import { TreeViewItemProps } from '../../types'
import './tree-view-item.css' import './tree-view-item.css'
export const TreeViewItem = (props: TreeViewItemProps) => { export const TreeViewItem = (props: TreeViewItemProps) => {
const { id, children, label, labelClass, expand, iconX = 'fas fa-caret-right', iconY = 'fas fa-caret-down', icon, controlBehaviour = false, innerRef, ...otherProps } = props const { id, children, label, labelClass, expand, iconX = 'fas fa-caret-right', iconY = 'fas fa-caret-down', icon, controlBehaviour = false, innerRef, editable, ...otherProps } = props
const [isExpanded, setIsExpanded] = useState(false) const [isExpanded, setIsExpanded] = useState(false)
const contentRef = useRef(null)
useEffect(() => { useEffect(() => {
setIsExpanded(expand) setIsExpanded(expand)
}, [expand]) }, [expand])
useEffect(() => {
if (editable) {
contentRef.current.focus()
}
}, [editable])
return ( return (
<li ref={innerRef} key={`treeViewLi${id}`} data-id={`treeViewLi${id}`} className='li_tv' {...otherProps}> <li ref={innerRef} key={`treeViewLi${id}`} data-id={`treeViewLi${id}`} className='li_tv' {...otherProps}>
<div key={`treeViewDiv${id}`} data-id={`treeViewDiv${id}`} className={`d-flex flex-row align-items-center ${labelClass}`} onClick={() => !controlBehaviour && setIsExpanded(!isExpanded)}> <div key={`treeViewDiv${id}`} data-id={`treeViewDiv${id}`} className={`d-flex flex-row align-items-center ${labelClass}`} onClick={() => !controlBehaviour && setIsExpanded(!isExpanded)}>
{ children ? <div className={isExpanded ? `px-1 ${iconY} caret caret_tv` : `px-1 ${iconX} caret caret_tv`} style={{ visibility: children ? 'visible' : 'hidden' }}></div> : icon ? <div className={`pr-3 pl-1 ${icon} caret caret_tv`}></div> : null } { children ? <div className={isExpanded ? `px-1 ${iconY} caret caret_tv` : `px-1 ${iconX} caret caret_tv`} style={{ visibility: children ? 'visible' : 'hidden' }}></div> : icon ? <div className={`pr-3 pl-1 ${icon} caret caret_tv`}></div> : null }
<span className='w-100 pl-1'> <span className='w-100 pl-1' ref={contentRef} contentEditable={editable} tabIndex={editable ? -3 : null}>
{ label } { label }
</span> </span>
</div> </div>

@ -16,5 +16,7 @@ export interface TreeViewItemProps {
labelClass?: string, labelClass?: string,
controlBehaviour?: boolean controlBehaviour?: boolean
innerRef?: any, innerRef?: any,
onContextMenu?: (...args: any) => void onContextMenu?: (...args: any) => void,
editable?: boolean,
onBlur?: (...args: any) => void
} }

Loading…
Cancel
Save