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

@ -27,6 +27,9 @@ export const FileExplorer = (props: FileExplorerProps) => {
element: null,
x: null,
y: null
},
focusEdit: {
element: null
}
})
@ -127,9 +130,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
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
console.log('parentFolderAfter: ', parentFolder)
// const self = this
// modalDialogCustom.prompt('Create new file', 'File Name (e.g Untitled.sol)', 'Untitled.sol', (input) => {
// if (!input) input = 'New file'
@ -152,10 +153,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
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
else if (parentFolder.indexOf('.sol') !== -1) parentFolder = extractParentFromKey(parentFolder)
console.log('parentFolderAfter: ', parentFolder)
// const self = this
// modalDialogCustom.prompt('Create new folder', '', 'New folder', (input) => {
// if (!input) {
@ -178,6 +177,39 @@ export const FileExplorer = (props: FileExplorerProps) => {
// }, 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) => {
if (parentFolder === name) {
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.registry = localRegistry || globalRegistry
// self._deps = {
@ -344,7 +391,6 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
const handleContextMenuFolder = (pageX: number, pageY: number, path: string) => {
console.log('path: ', path)
setState(prevState => {
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) => {
if (file.isDirectory) {
return (
<Droppable droppableId={file.path} key={index}>
{(provided) => (
<>
<TreeViewItem
{ ...provided.droppableProps }
innerRef={ provided.innerRef }
id={`treeViewItem${file.path}`}
iconX='pr-3 far fa-folder'
iconY='pr-3 far fa-folder-open'
key={`${file.path + index}`}
label={label(file)}
onClick={(e) => {
e.stopPropagation()
handleClickFolder(file.path)
}}
onContextMenu={(e) => {
e.preventDefault()
e.stopPropagation()
handleContextMenuFolder(e.pageX, e.pageY, file.path)
}}
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) => {
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}
/>
<>
<TreeViewItem
id={`treeViewItem${file.path}`}
iconX='pr-3 far fa-folder'
iconY='pr-3 far fa-folder-open'
key={`${file.path + index}`}
label={label(file)}
onClick={(e) => {
e.stopPropagation()
handleClickFolder(file.path)
}}
onContextMenu={(e) => {
e.preventDefault()
e.stopPropagation()
handleContextMenuFolder(e.pageX, e.pageY, file.path)
}}
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 }
>
{
file.child ? <TreeView id={`treeView${file.path}`} key={index}>{
file.child.map((file, index) => {
return renderFiles(file, index)
})
}
</>
)}
</Droppable>
</TreeView> : <TreeView id={`treeView${file.path}`} key={index} />
}
</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 {
return (
<Draggable draggableId={file.path} index={index} key={index}>
{(provided) => (
<>
<TreeViewItem
{...provided.draggableProps}
{...provided.dragHandleProps}
innerRef={provided.innerRef}
id={`treeViewItem${file.path}`}
key={index}
label={label(file)}
onClick={(e) => {
e.stopPropagation()
handleClickFile(file.path)
}}
onContextMenu={(e) => {
e.preventDefault()
e.stopPropagation()
handleContextMenuFile(e.pageX, e.pageY, file.path)
}}
icon='fa fa-file'
labelClass={ state.focusElement.findIndex(item => item.key === file.path) !== -1 ? 'bg-secondary' : '' }
/>
{ (state.focusContext.element === file.path) &&
<FileExplorerContextMenu
actions={ state.actions.filter(item => item.type.findIndex(name => name === 'file') !== -1) }
hideContextMenu={hideContextMenu}
createNewFile={createNewFile}
createNewFolder={createNewFolder}
pageX={state.focusContext.x}
pageY={state.focusContext.y}
/>
}
</>
)}
</Draggable>
<>
<TreeViewItem
id={`treeViewItem${file.path}`}
key={index}
label={label(file)}
onClick={(e) => {
e.stopPropagation()
if (state.focusEdit.element !== file.path) handleClickFile(file.path)
}}
onContextMenu={(e) => {
e.preventDefault()
e.stopPropagation()
handleContextMenuFile(e.pageX, e.pageY, file.path)
}}
icon='fa fa-file'
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) &&
<FileExplorerContextMenu
actions={ state.actions.filter(item => item.type.findIndex(name => name === 'file') !== -1) }
hideContextMenu={hideContextMenu}
createNewFile={createNewFile}
createNewFolder={createNewFolder}
deletePath={deletePath}
renamePath={renamePath}
pageX={state.focusContext.x}
pageY={state.focusContext.y}
path={file.path}
/>
}
</>
)
}
}
@ -480,24 +533,15 @@ export const FileExplorer = (props: FileExplorerProps) => {
/>
}
expand={true}>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId='droppableTreeView'>
{(provided) => (
<div
{ ...provided.droppableProps }
ref={ provided.innerRef }>
<TreeView id='treeViewMenu'>
{
state.files.map((file, index) => {
return renderFiles(file, index)
})
}
</TreeView>
{ provided.placeholder }
</div>
)}
</Droppable>
</DragDropContext>
<div>
<TreeView id='treeViewMenu'>
{
state.files.map((file, index) => {
return renderFiles(file, index)
})
}
</TreeView>
</div>
</TreeViewItem>
</TreeView>
</div>

@ -29,8 +29,10 @@ export interface FileExplorerContextMenuProps {
actions: { name: string, type: string[] }[],
createNewFile: (folder?: string) => void,
createNewFolder: (parentFolder?: string) => void,
deletePath: (path: string) => void,
renamePath: (path: string) => void
hideContextMenu: () => void,
pageX: 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 './tree-view-item.css'
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 contentRef = useRef(null)
useEffect(() => {
setIsExpanded(expand)
}, [expand])
useEffect(() => {
if (editable) {
contentRef.current.focus()
}
}, [editable])
return (
<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)}>
{ 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 }
</span>
</div>

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

Loading…
Cancel
Save