@ -1,13 +1,43 @@
import React , { useEffect , useState , useRef } from 'react' // eslint-disable-line
import React , { useEffect , useState , useRef } from 'react' // eslint-disable-line
import { TreeView , TreeViewItem } from '@remix-ui/tree-view' // eslint-disable-line
import { TreeView , TreeViewItem } from '@remix-ui/tree-view' // eslint-disable-line
import Draggable from 'react-draggable' // eslint-disable-line
import { DragDropContext , Droppable , Draggable } from 'react-beautiful-dnd' // eslint-disable-line
import * as async from 'async'
import * as Gists from 'gists'
import * as helper from '../../../../../apps/remix-ide/src/lib/helper'
import * as helper from '../../../../../apps/remix-ide/src/lib/helper'
import QueryParams from '../../../../../apps/remix-ide/src/lib/query-params'
import { FileExplorerProps , File } from './types'
import { FileExplorerProps , File } from './types'
import './css/file-explorer.css'
import './css/file-explorer.css'
const queryParams = new QueryParams ( )
function packageFiles ( filesProvider , directory , callback ) {
const ret = { }
filesProvider . resolveDirectory ( directory , ( error , files ) = > {
if ( error ) callback ( error )
else {
async . eachSeries ( Object . keys ( files ) , ( path , cb ) = > {
if ( filesProvider . isDirectory ( path ) ) {
cb ( )
} else {
filesProvider . get ( path , ( error , content ) = > {
if ( error ) return cb ( error )
if ( /^\s+$/ . test ( content ) || ! content . length ) {
content = '// this line is added to create a gist. Empty file is not allowed.'
}
ret [ path ] = { content }
cb ( )
} )
}
} , ( error ) = > {
callback ( error , ret )
} )
}
} )
}
export const FileExplorer = ( props : FileExplorerProps ) = > {
export const FileExplorer = ( props : FileExplorerProps ) = > {
const { files , name , registry } = props
const { files , name , registry , plugin } = props
const uploadFile = ( target ) = > {
const uploadFile = ( target ) = > {
// TODO The file explorer is merely a view on the current state of
// 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
// the files module. Please ask the user here if they want to overwrite
@ -47,34 +77,6 @@ export const FileExplorer = (props: FileExplorerProps) => {
} )
} )
} )
} )
}
}
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)
}
const containerRef = useRef ( null )
const containerRef = useRef ( null )
const [ state , setState ] = useState ( {
const [ state , setState ] = useState ( {
focusElement : [ ] ,
focusElement : [ ] ,
@ -102,23 +104,28 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
}
] . 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 : [ ] ,
files : [ ] ,
actions : {
actions : { } ,
updateGist : ( ) = > { } ,
uploadFile ,
publishToGist ,
createNewFile
} ,
fileManager : null ,
fileManager : null ,
ctrlKey : false
tokenAccess : null ,
ctrlKey : false ,
newFileName : ''
} )
} )
useEffect ( ( ) = > {
useEffect ( ( ) = > {
( async ( ) = > {
( async ( ) = > {
console . log ( 'registry: ' , registry )
const fileManager = registry . get ( 'filemanager' ) . api
const fileManager = registry . get ( 'filemanager' ) . api
const config = registry . get ( 'config' ) . api
const tokenAccess = config . get ( 'settings/gist-access-token' ) . api
const files = await fetchDirectoryContent ( name )
const files = await fetchDirectoryContent ( name )
const actions = {
updateGist : ( ) = > { } ,
uploadFile ,
publishToGist
}
setState ( prevState = > {
setState ( prevState = > {
return { . . . prevState , fileManager , files }
return { . . . prevState , fileManager , tokenAccess , files , action s }
} )
} )
} ) ( )
} ) ( )
} , [ ] )
} , [ ] )
@ -150,22 +157,6 @@ export const FileExplorer = (props: FileExplorerProps) => {
} )
} )
}
}
const label = ( data ) = > {
return (
< div className = 'remixui_items' >
< span
title = { data . path }
className = { 'remixui_label ' + ( data . isDirectory ? 'folder' : 'remixui_leaf' ) }
data - path = { data . path }
// onkeydown=${editModeOff}
// onblur=${editModeOff}
>
{ data . path . split ( '/' ) . pop ( ) }
< / span >
< / div >
)
}
const normalize = ( path , filesList ) : File [ ] = > {
const normalize = ( path , filesList ) : File [ ] = > {
const folders = [ ]
const folders = [ ]
const files = [ ]
const files = [ ]
@ -198,100 +189,147 @@ export const FileExplorer = (props: FileExplorerProps) => {
return keyPath [ keyPath . length - 1 ]
return keyPath [ keyPath . length - 1 ]
}
}
const toGist = ( id ) = > {
const createNewFile = ( parentFolder = 'browser' ) = > {
// const proccedResult = function (error, data) {
// const self = this
// if (error) {
// modalDialogCustom.prompt('Create new file', 'File Name (e.g Untitled.sol)', 'Untitled.sol', (input) => {
// modalDialogCustom.alert('Failed to manage gist: ' + error)
// if (!input) input = 'New file'
// console.log('Failed to manage gist: ' + error)
// get filename from state (state.newFileName)
// } else {
const fileManager = state . fileManager
// if (data.html_url) {
const newFileName = parentFolder + '/' + 'unnamed' + Math . floor ( Math . random ( ) * 101 )
// 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')
helper . createNonClashingName ( newFileName , files , async ( error , newName ) = > {
// })
// if (error) return tooltip('Failed to create file ' + newName + ' ' + error)
// } else {
if ( error ) return
// modalDialogCustom.alert(data.message + ' ' + data.documentation_url + ' ' + JSON.stringify(data.errors, null, '\t'))
const createFile = await fileManager . writeFile ( newName , '' )
// }
// }
if ( ! createFile ) {
// }
// tooltip('Failed to create file ' + newName)
} else {
if ( parentFolder === name ) {
// const updatedFiles = await resolveDirectory(parentFolder, state.files)
// /**
setState ( prevState = > {
// * This function is to get the original content of given gist
return {
// * @params id is the gist id to fetch
. . . prevState ,
// */
files : [ . . . prevState . files , {
// async function getOriginalFiles (id) {
path : newFileName ,
// if (!id) {
name : extractNameFromKey ( newFileName ) ,
// return []
isDirectory : false
// }
} ]
}
// const url = `https://api.github.com/gists/${id}`
} )
// const res = await fetch(url)
}
// const data = await res.json()
await fileManager . open ( newName )
// return data.files || []
if ( newName . includes ( '_test.sol' ) ) {
// }
plugin . events . trigger ( 'newTestFileCreated' , [ newName ] )
}
// // 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) => {
// }, null, true)
// if (error) {
}
// console.log(error)
// modalDialogCustom.alert('Failed to create gist: ' + error.message)
const publishToGist = ( ) = > {
// } else {
// modalDialogCustom.confirm(
// // check for token
// 'Create a public gist',
// var tokenAccess = this._deps.config.get('settings/gist-access-token')
// '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.',
// if (!tokenAccess) {
// () => { this.toGist() }
// modalDialogCustom.alert(
toGist ( )
// '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=' +
const toGist = ( id? : string ) = > {
// queryParams.get().version + '&optimize=' + queryParams.get().optimize + '&runs=' + queryParams.get().runs + '&gist='
const proccedResult = function ( error , data ) {
// const gists = new Gists({ token: tokenAccess })
if ( error ) {
// modalDialogCustom.alert('Failed to manage gist: ' + error)
// if (id) {
console . log ( 'Failed to manage gist: ' + error )
// const originalFileList = getOriginalFiles(id)
} else {
// // Telling the GIST API to remove files
if ( data . html_url ) {
// const updatedFileList = Object.keys(packaged)
// modalDialogCustom.confirm('Gist is ready', `The gist is at ${data.html_url}. Would you like to open it in a new window?`, () => {
// const allItems = Object.keys(originalFileList)
// window.open(data.html_url, '_blank')
// .filter(fileName => updatedFileList.indexOf(fileName) === -1)
// })
// .reduce((acc, deleteFileName) => ({
} else {
// ...acc,
// modalDialogCustom.alert(data.message + ' ' + data.documentation_url + ' ' + JSON.stringify(data.errors, null, '\t'))
// [deleteFileName]: null
}
// }), originalFileList)
}
// // adding new files
}
// updatedFileList.forEach((file) => {
// const _items = file.split('/')
/ * *
// const _fileName = _items[_items.length - 1]
* This function is to get the original content of given gist
// allItems[_fileName] = packaged[file]
* @params id is the gist id to fetch
// })
* /
async function getOriginalFiles ( id ) {
// tooltip('Saving gist (' + id + ') ...')
if ( ! id ) {
// gists.edit({
return [ ]
// description: description,
}
// public: true,
// files: allItems,
const url = ` https://api.github.com/gists/ ${ id } `
// id: id
const res = await fetch ( url )
// }, (error, result) => {
const data = await res . json ( )
// proccedResult(error, result)
return data . files || [ ]
// if (!error) {
}
// for (const key in allItems) {
// if (allItems[key] === null) delete allItems[key]
// 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/'
// }
packageFiles ( files , folder , ( error , packaged ) = > {
// })
if ( error ) {
// } else {
console . log ( error )
// // id is not existing, need to create a new gist
// modalDialogCustom.alert('Failed to create gist: ' + error.message)
// tooltip('Creating a new gist ...')
} else {
// gists.create({
// check for token
// description: description,
if ( ! state . tokenAccess ) {
// public: true,
// modalDialogCustom.alert(
// files: packaged
// 'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.'
// }, (error, result) => {
// )
// proccedResult(error, result)
} 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 : state.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 = {}
@ -359,6 +397,26 @@ export const FileExplorer = (props: FileExplorerProps) => {
// }
// }
// }
// }
const label = ( data ) = > {
return (
< div className = 'remixui_items' >
< span
title = { data . path }
className = { 'remixui_label ' + ( data . isDirectory ? 'folder' : 'remixui_leaf' ) }
data - path = { data . path }
// onkeydown=${editModeOff}
// onblur=${editModeOff}
>
{ data . path . split ( '/' ) . pop ( ) }
< / span >
< / div >
)
}
const onDragEnd = result = > {
}
const handleClickFile = ( path ) = > {
const handleClickFile = ( path ) = > {
state . fileManager . open ( path )
state . fileManager . open ( path )
setState ( prevState = > {
setState ( prevState = > {
@ -414,7 +472,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
data - id = { 'fileExplorerNewFile' + action }
data - id = { 'fileExplorerNewFile' + action }
onClick = { ( e ) = > {
onClick = { ( e ) = > {
e . stopPropagation ( )
e . stopPropagation ( )
state . actions [ action ] ( )
action === 'createNewFile' ? createNewFile ( ) : state . actions [ action ] ( )
} }
} }
className = { 'newFile ' + icon + ' remixui_newFile' }
className = { 'newFile ' + icon + ' remixui_newFile' }
title = { title }
title = { title }
@ -436,45 +494,55 @@ export const FileExplorer = (props: FileExplorerProps) => {
const renderFiles = ( file , index ) = > {
const renderFiles = ( file , index ) = > {
if ( file . isDirectory ) {
if ( file . isDirectory ) {
return (
return (
< Draggable key = { index } axis = "y" bounds = "parent" >
< Droppable droppableId = { file . path } key = { index } >
< TreeViewItem
{ ( provided ) = > (
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 ) = > {
labelClass = { state . focusElement . findIndex ( item = > item === file . path ) !== - 1 ? 'bg-secondary' : '' }
e . stopPropagation ( )
controlBehaviour = { state . ctrlKey }
handleClickFolder ( file . path )
>
} }
{
labelClass = { state . focusElement . findIndex ( item = > item === file . path ) !== - 1 ? 'bg-secondary' : '' }
file . child ? < TreeView id = { ` treeView ${ file . path } ` } key = { index } > {
controlBehaviour = { state . ctrlKey }
file . child . map ( ( file , index ) = > {
>
return renderFiles ( file , index )
{
} )
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 } / >
}
}
< / TreeView > : < TreeView id = { ` treeView ${ file . path } ` } key = { index } / >
{ provided . placeholder }
}
< / TreeViewItem >
< / TreeViewItem >
) }
< / Draggable >
< / Dropp able >
)
)
} else {
} else {
return (
return (
< Draggable key = { index } axis = "y" bounds = "parent" >
< Draggable draggableId = { file . path } index = { index } key = { index } >
< TreeViewItem
{ ( provided ) = > (
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 } ` }
handleClickFile ( file . path )
key = { index }
} }
label = { label ( file ) }
icon = 'fa fa-file'
onClick = { ( e ) = > {
labelClass = { state . focusElement . findIndex ( item = > item === file . path ) !== - 1 ? 'bg-secondary' : '' }
e . stopPropagation ( )
/ >
handleClickFile ( file . path )
} }
icon = 'fa fa-file'
labelClass = { state . focusElement . findIndex ( item = > item === file . path ) !== - 1 ? 'bg-secondary' : '' }
/ >
) }
< / Draggable >
< / Draggable >
)
)
}
}
@ -486,14 +554,12 @@ export const FileExplorer = (props: FileExplorerProps) => {
tabIndex = { - 1 }
tabIndex = { - 1 }
onKeyDown = { ( e ) = > {
onKeyDown = { ( e ) = > {
if ( e . shiftKey ) {
if ( e . shiftKey ) {
console . log ( 'TRUE' )
setState ( prevState = > {
setState ( prevState = > {
return { . . . prevState , ctrlKey : true }
return { . . . prevState , ctrlKey : true }
} )
} )
}
}
} }
} }
onKeyUp = { ( ) = > {
onKeyUp = { ( ) = > {
console . log ( 'FALSE' )
setState ( prevState = > {
setState ( prevState = > {
return { . . . prevState , ctrlKey : false }
return { . . . prevState , ctrlKey : false }
} )
} )
@ -501,17 +567,24 @@ export const FileExplorer = (props: FileExplorerProps) => {
>
>
< TreeView id = 'treeView' >
< TreeView id = 'treeView' >
< TreeViewItem id = "treeViewItem" label = { renderMenuItems ( ) } expand = { true } >
< TreeViewItem id = "treeViewItem" label = { renderMenuItems ( ) } expand = { true } >
< Draggable onStart = { ( ) = > false } >
< 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 )
< / Draggable >
} )
}
< / TreeView >
{ provided . placeholder }
< / div >
) }
< / Droppable >
< / DragDropContext >
< / TreeViewItem >
< / TreeViewItem >
< / TreeView >
< / TreeView >
< / div >
< / div >