@ -5,29 +5,76 @@ import { FileExplorerProps } from './types'
import './css/file-explorer.css'
function extractData ( value , tree , key ) {
const newValue = { }
let isFile = false
Object . keys ( value ) . filter ( ( x ) = > {
if ( x === '/content' ) isFile = true
if ( x [ 0 ] !== '/' ) return true
} ) . forEach ( ( x ) = > { newValue [ x ] = value [ x ] } )
return {
path : ( tree || { } ) . path ? tree . path + '/' + key : key ,
children : isFile ? undefined
: value instanceof Array ? value . map ( ( item , index ) = > ( {
key : index , value : item
} ) ) : value instanceof Object ? Object . keys ( value ) . map ( subkey = > ( {
key : subkey , value : value [ subkey ]
} ) ) : undefined
export const FileExplorer = ( props : FileExplorerProps ) = > {
const { files , name , registry } = props
const uploadFile = ( target ) = > {
// TODO The file explorer is merely a view on the current state of
// the files module. Please ask the user here if they want to overwrite
// a file and then just use `files.add`. The file explorer will
// pick that up via the 'fileAdded' event from the files module.
[ . . . target . files ] . forEach ( ( file ) = > {
const files = props . files
function loadFile ( ) {
const fileReader = new FileReader ( )
fileReader . onload = async function ( event ) {
if ( helper . checkSpecialChars ( file . name ) ) {
// modalDialogCustom.alert('Special characters are not allowed')
return
}
const success = await files . set ( name , event . target . result )
if ( ! success ) {
// modalDialogCustom.alert('Failed to create file ' + name)
} else {
// self.events.trigger('focus', [name])
}
}
fileReader . readAsText ( file )
}
const name = files . type + '/' + file . name
files . exists ( name , ( error , exist ) = > {
if ( error ) console . log ( error )
if ( ! exist ) {
loadFile ( )
} else {
// modalDialogCustom.confirm('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, () => { loadFile() })
}
} )
} )
}
const publishToGist = ( ) = > {
// modalDialogCustom.confirm(
// 'Create a public gist',
// 'Are you sure you want to publish all your files in browser directory anonymously as a public gist on github.com? Note: this will not include directories.',
// () => { this.toGist() }
// )
}
const createNewFile = ( parentFolder = 'browser' ) = > {
// const self = this
// modalDialogCustom.prompt('Create new file', 'File Name (e.g Untitled.sol)', 'Untitled.sol', (input) => {
// if (!input) input = 'New file'
// helper.createNonClashingName(parentFolder + '/' + input, self.files, async (error, newName) => {
// if (error) return tooltip('Failed to create file ' + newName + ' ' + error)
// const fileManager = self._deps.fileManager
// const createFile = await fileManager.writeFile(newName, '')
// if (!createFile) {
// tooltip('Failed to create file ' + newName)
// } else {
// await fileManager.open(newName)
// if (newName.includes('_test.sol')) {
// self.events.trigger('newTestFileCreated', [newName])
// }
// }
// })
// }, null, true)
}
}
export const FileExplorer = ( props : FileExplorerProps ) = > {
const [ state , setState ] = useState ( {
files : props.files ,
focusElement : null ,
focusPath : null ,
menuItems : [
@ -51,34 +98,184 @@ export const FileExplorer = (props: FileExplorerProps) => {
title : 'Update the current [gist] explorer' ,
icon : 'fab fa-github'
}
] . filter ( item = > props . menuItems && props . menuItems . find ( ( name ) = > { return name === item . action } ) )
] . filter ( item = > props . menuItems && props . menuItems . find ( ( name ) = > { return name === item . action } ) ) ,
files : [ ] ,
actions : {
updateGist : ( ) = > { } ,
uploadFile ,
publishToGist ,
createNewFile
} ,
fileManager : null
} )
useEffect ( ( ) = > {
if ( props . files ) {
console . log ( 'props.files.type: ' , props . files . type )
props . files . event . register ( 'fileAdded' , fileAdded )
}
} , [ props . files ] )
const fileManager = registry . get ( 'filemanager' ) . api
setState ( prevState = > {
return { . . . prevState , fileManager }
} )
resolveDirectory ( name )
} , [ ] )
const resolveDirectory = ( folderPath ) = > {
const folderIndex = state . files . findIndex ( ( { path } ) = > path === folderPath )
if ( folderIndex === - 1 ) {
files . resolveDirectory ( folderPath , ( error , fileTree ) = > {
if ( error ) console . error ( error )
const files = normalize ( folderPath , fileTree )
setState ( prevState = > {
return { . . . prevState , files }
} )
} )
} else {
files . resolveDirectory ( folderPath , ( error , fileTree ) = > {
if ( error ) console . error ( error )
const files = state . files
const formatSelf = ( key , data , li ) = > {
const isFolder = ! ! data . children
files [ folderIndex ] . child = normalize ( folderPath , fileTree )
setState ( prevState = > {
return { . . . prevState , files }
} )
} )
}
}
const label = ( data ) = > {
return (
< div className = 'remixui_items' >
< span
title = { data . path }
className = { 'remixui_label ' + ( ! isFolder ? 'remixui_leaf' : 'folder' ) }
className = { 'remixui_label ' + ( data . isDirectory ? 'folder' : 'remixui_leaf ') }
data - path = { data . path }
// onkeydown=${editModeOff}
// onblur=${editModeOff}
>
{ key . split ( '/' ) . pop ( ) }
{ data . path . split ( '/' ) . pop ( ) }
< / span >
< / div >
)
}
const normalize = ( path , filesList ) = > {
const prefix = path . split ( '/' ) [ 0 ]
const files = Object . keys ( filesList ) . map ( key = > {
const path = prefix + '/' + key
return {
path ,
name : extractNameFromKey ( path ) ,
isDirectory : filesList [ key ] . isDirectory
}
} )
return files
}
const extractNameFromKey = ( key ) = > {
const keyPath = key . split ( '/' )
return keyPath [ keyPath . length - 1 ]
}
const toGist = ( id ) = > {
// const proccedResult = function (error, data) {
// if (error) {
// modalDialogCustom.alert('Failed to manage gist: ' + error)
// console.log('Failed to manage gist: ' + error)
// } else {
// if (data.html_url) {
// modalDialogCustom.confirm('Gist is ready', `The gist is at ${data.html_url}. Would you like to open it in a new window?`, () => {
// window.open(data.html_url, '_blank')
// })
// } else {
// modalDialogCustom.alert(data.message + ' ' + data.documentation_url + ' ' + JSON.stringify(data.errors, null, '\t'))
// }
// }
// }
// /**
// * This function is to get the original content of given gist
// * @params id is the gist id to fetch
// */
// async function getOriginalFiles (id) {
// if (!id) {
// return []
// }
// const url = `https://api.github.com/gists/${id}`
// const res = await fetch(url)
// const data = await res.json()
// return data.files || []
// }
// // If 'id' is not defined, it is not a gist update but a creation so we have to take the files from the browser explorer.
// const folder = id ? 'browser/gists/' + id : 'browser/'
// this.packageFiles(this.files, folder, (error, packaged) => {
// if (error) {
// console.log(error)
// modalDialogCustom.alert('Failed to create gist: ' + error.message)
// } else {
// // check for token
// var tokenAccess = this._deps.config.get('settings/gist-access-token')
// if (!tokenAccess) {
// modalDialogCustom.alert(
// 'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.'
// )
// } else {
// const description = 'Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. \n Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=' +
// queryParams.get().version + '&optimize=' + queryParams.get().optimize + '&runs=' + queryParams.get().runs + '&gist='
// const gists = new Gists({ token: tokenAccess })
// if (id) {
// const originalFileList = getOriginalFiles(id)
// // Telling the GIST API to remove files
// const updatedFileList = Object.keys(packaged)
// const allItems = Object.keys(originalFileList)
// .filter(fileName => updatedFileList.indexOf(fileName) === -1)
// .reduce((acc, deleteFileName) => ({
// ...acc,
// [deleteFileName]: null
// }), originalFileList)
// // adding new files
// updatedFileList.forEach((file) => {
// const _items = file.split('/')
// const _fileName = _items[_items.length - 1]
// allItems[_fileName] = packaged[file]
// })
// tooltip('Saving gist (' + id + ') ...')
// gists.edit({
// description: description,
// public: true,
// files: allItems,
// id: id
// }, (error, result) => {
// proccedResult(error, result)
// if (!error) {
// for (const key in allItems) {
// if (allItems[key] === null) delete allItems[key]
// }
// }
// })
// } else {
// // id is not existing, need to create a new gist
// tooltip('Creating a new gist ...')
// gists.create({
// description: description,
// public: true,
// files: packaged
// }, (error, result) => {
// proccedResult(error, result)
// })
// }
// }
// }
// })
}
// self._components = {}
// self._components.registry = localRegistry || globalRegistry
// self._deps = {
@ -87,10 +284,6 @@ export const FileExplorer = (props: FileExplorerProps) => {
// fileManager: self._components.registry.get('filemanager').api
// }
// self.events.register('focus', function (path) {
// self._deps.fileManager.open(path)
// })
// self._components.registry.put({ api: self, name: `fileexplorer/${self.files.type}` })
// warn if file changed outside of Remix
@ -128,33 +321,25 @@ export const FileExplorer = (props: FileExplorerProps) => {
// modalDialogCustom.alert(error)
// }
const fileAdded = ( filepath ) = > {
const folderpath = filepath . split ( '/' ) . slice ( 0 , - 1 ) . join ( '/' )
console . log ( 'filePath: ' , folderpath )
console . log ( 'folderPath: ' , folderpath )
// const currentTree = self.treeView.nodeAt(folderpath)
// if (!self.treeView.isExpanded(folderpath)) self.treeView.expand(folderpath)
// if (currentTree) {
// props.files.resolveDirectory(folderpath, (error, fileTree) => {
// if (error) console.error(error)
// if (!fileTree) return
// fileTree = normalize(folderpath, fileTree)
// self.treeView.updateNodeFromJSON(folderpath, fileTree, true)
// self.focusElement = self.treeView.labelAt(self.focusPath)
// // TODO: here we update the selected file (it applicable)
// // cause we are refreshing the interface of the whole directory when there's a new file.
// if (self.focusElement && !self.focusElement.classList.contains('bg-secondary')) {
// self.focusElement.classList.add('bg-secondary')
// }
// })
// }
}
const extractNameFromKey = ( key ) = > {
const keyPath = key . split ( '/' )
return keyPath [ keyPath . length - 1 ]
}
// const fileAdded = (filepath) => {
// const folderpath = filepath.split('/').slice(0, -1).join('/')
// const currentTree = self.treeView.nodeAt(folderpath)
// if (!self.treeView.isExpanded(folderpath)) self.treeView.expand(folderpath)
// if (currentTree) {
// props.files.resolveDirectory(folderpath, (error, fileTree) => {
// if (error) console.error(error)
// if (!fileTree) return
// fileTree = normalize(folderpath, fileTree)
// self.treeView.updateNodeFromJSON(folderpath, fileTree, true)
// self.focusElement = self.treeView.labelAt(self.focusPath)
// // TODO: here we update the selected file (it applicable)
// // cause we are refreshing the interface of the whole directory when there's a new file.
// if (self.focusElement && !self.focusElement.classList.contains('bg-secondary')) {
// self.focusElement.classList.add('bg-secondary')
// }
// })
// }
// }
const renderMenuItems = ( ) = > {
let items
@ -181,7 +366,10 @@ export const FileExplorer = (props: FileExplorerProps) => {
< span
id = { action }
data - id = { 'fileExplorerNewFile' + action }
// onclick={({ stopPropagation }) => { stopPropagation(); this[action]() }}
onClick = { ( e ) = > {
e . stopPropagation ( )
state . actions [ action ] ( )
} }
className = { 'newFile ' + icon + ' remixui_newFile' }
title = { title }
key = { index }
@ -193,57 +381,49 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
return (
< >
< span className = 'remixui_label' title = { props . name } data - path = { props . name } style = { { fontWeight : 'bold' } } > { props . name } < / span >
< span className = 'remixui_label' title = { name } data - path = { name } style = { { fontWeight : 'bold' } } > { name } < / span >
< span className = "remixui_menu" > { items } < / span >
< / >
)
}
const uploadFile = ( target ) = > {
// TODO The file explorer is merely a view on the current state of
// the files module. Please ask the user here if they want to overwrite
// a file and then just use `files.add`. The file explorer will
// pick that up via the 'fileAdded' event from the files module.
; [ . . . target . files ] . forEach ( ( file ) = > {
const files = state . files
function loadFile ( ) {
const fileReader = new FileReader ( )
fileReader . onload = async function ( event ) {
if ( helper . checkSpecialChars ( file . name ) ) {
// modalDialogCustom.alert('Special characters are not allowed')
return
const renderFiles = ( file , index ) = > {
if ( file . isDirectory ) {
return (
< TreeViewItem id = { ` treeViewItem ${ file . path } ` } onClick = { ( ) = > { resolveDirectory ( file . path ) } } key = { index } label = { label ( file ) } >
{
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 } / >
}
const success = await files . set ( name , event . target . result )
if ( ! success ) {
// modalDialogCustom.alert('Failed to create file ' + name)
} else {
// self.events.trigger('focus', [name])
}
}
fileReader . readAsText ( file )
}
const name = files . type + '/' + file . name
files . exists ( name , ( error , exist ) = > {
if ( error ) console . log ( error )
if ( ! exist ) {
loadFile ( )
} else {
// modalDialogCustom.confirm('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, () => { loadFile() })
}
} )
} )
< / TreeViewItem >
)
} else {
return (
< TreeViewItem
id = { ` treeView ${ file . path } ` }
key = { index }
label = { label ( file ) }
onClick = { ( ) = > { state . fileManager . open ( file . path ) } }
/ >
)
}
}
return (
< div >
< TreeView id = 'treeView' >
< TreeViewItem id = "treeViewItem" label = { renderMenuItems ( ) } >
< TreeViewItem id = "treeViewItem" label = { renderMenuItems ( ) } expand = { true } >
< TreeView id = 'treeViewMenu' >
{
state . files . map ( ( file , index ) = > {
return renderFiles ( file , index )
} )
}
< / TreeView >
< / TreeViewItem >
< / TreeView >
< / div >