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. 110
      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,15 +402,23 @@ 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 <TreeViewItem
{ ...provided.droppableProps }
innerRef={ provided.innerRef }
id={`treeViewItem${file.path}`} id={`treeViewItem${file.path}`}
iconX='pr-3 far fa-folder' iconX='pr-3 far fa-folder'
iconY='pr-3 far fa-folder-open' iconY='pr-3 far fa-folder-open'
@ -379,7 +433,9 @@ export const FileExplorer = (props: FileExplorerProps) => {
e.stopPropagation() e.stopPropagation()
handleContextMenuFolder(e.pageX, e.pageY, file.path) handleContextMenuFolder(e.pageX, e.pageY, file.path)
}} }}
labelClass={ state.focusElement.findIndex(item => item.key === file.path) !== -1 ? 'bg-secondary' : '' } labelClass={ state.focusEdit.element === file.path ? 'bg-light' : state.focusElement.findIndex(item => item.key === file.path) !== -1 ? 'bg-secondary' : '' }
editable={state.focusEdit.element === file.path}
onBlur={() => editModeOff(file.path)}
controlBehaviour={ state.ctrlKey } controlBehaviour={ state.ctrlKey }
> >
{ {
@ -390,7 +446,6 @@ export const FileExplorer = (props: FileExplorerProps) => {
} }
</TreeView> : <TreeView id={`treeView${file.path}`} key={index} /> </TreeView> : <TreeView id={`treeView${file.path}`} key={index} />
} }
{ provided.placeholder }
</TreeViewItem> </TreeViewItem>
{ (state.focusContext.element === file.path) && { (state.focusContext.element === file.path) &&
<FileExplorerContextMenu <FileExplorerContextMenu
@ -398,30 +453,25 @@ export const FileExplorer = (props: FileExplorerProps) => {
hideContextMenu={hideContextMenu} hideContextMenu={hideContextMenu}
createNewFile={createNewFile} createNewFile={createNewFile}
createNewFolder={createNewFolder} createNewFolder={createNewFolder}
deletePath={deletePath}
renamePath={renamePath}
pageX={state.focusContext.x} pageX={state.focusContext.x}
pageY={state.focusContext.y} pageY={state.focusContext.y}
folder={file.path} path={file.path}
/> />
} }
</> </>
)}
</Droppable>
) )
} else { } else {
return ( return (
<Draggable draggableId={file.path} index={index} key={index}>
{(provided) => (
<> <>
<TreeViewItem <TreeViewItem
{...provided.draggableProps}
{...provided.dragHandleProps}
innerRef={provided.innerRef}
id={`treeViewItem${file.path}`} id={`treeViewItem${file.path}`}
key={index} key={index}
label={label(file)} label={label(file)}
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
handleClickFile(file.path) if (state.focusEdit.element !== file.path) handleClickFile(file.path)
}} }}
onContextMenu={(e) => { onContextMenu={(e) => {
e.preventDefault() e.preventDefault()
@ -429,7 +479,9 @@ export const FileExplorer = (props: FileExplorerProps) => {
handleContextMenuFile(e.pageX, e.pageY, file.path) handleContextMenuFile(e.pageX, e.pageY, file.path)
}} }}
icon='fa fa-file' icon='fa fa-file'
labelClass={ state.focusElement.findIndex(item => item.key === file.path) !== -1 ? 'bg-secondary' : '' } labelClass={ state.focusEdit.element === file.path ? 'bg-light' : state.focusElement.findIndex(item => item.key === file.path) !== -1 ? 'bg-secondary' : '' }
editable={state.focusEdit.element === file.path}
onBlur={() => editModeOff(file.path)}
/> />
{ (state.focusContext.element === file.path) && { (state.focusContext.element === file.path) &&
<FileExplorerContextMenu <FileExplorerContextMenu
@ -437,13 +489,14 @@ export const FileExplorer = (props: FileExplorerProps) => {
hideContextMenu={hideContextMenu} hideContextMenu={hideContextMenu}
createNewFile={createNewFile} createNewFile={createNewFile}
createNewFolder={createNewFolder} createNewFolder={createNewFolder}
deletePath={deletePath}
renamePath={renamePath}
pageX={state.focusContext.x} pageX={state.focusContext.x}
pageY={state.focusContext.y} pageY={state.focusContext.y}
path={file.path}
/> />
} }
</> </>
)}
</Draggable>
) )
} }
} }
@ -480,12 +533,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
/> />
} }
expand={true}> expand={true}>
<DragDropContext onDragEnd={onDragEnd}> <div>
<Droppable droppableId='droppableTreeView'>
{(provided) => (
<div
{ ...provided.droppableProps }
ref={ provided.innerRef }>
<TreeView id='treeViewMenu'> <TreeView id='treeViewMenu'>
{ {
state.files.map((file, index) => { state.files.map((file, index) => {
@ -493,11 +541,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
}) })
} }
</TreeView> </TreeView>
{ provided.placeholder }
</div> </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