@ -17,17 +17,28 @@ import './css/file-explorer.css'
const queryParams = new QueryParams ( )
const initialActions = [ {
export const FileExplorer = ( props : FileExplorerProps ) = > {
const { name , registry , plugin , focusRoot , contextMenuItems , displayInput , externalUploads } = props
const [ state , setState ] = useState ( {
focusElement : [ {
key : '' ,
type : 'folder'
} ] ,
files : [ ] ,
fileManager : null ,
ctrlKey : false ,
newFileName : '' ,
actions : [ {
id : 'newFile' ,
name : 'New File' ,
type : [ 'folder' ] ,
type : [ 'folder' , 'gist ' ] ,
path : [ ] ,
extension : [ ] ,
pattern : [ ]
} , {
id : 'newFolder' ,
name : 'New Folder' ,
type : [ 'folder' ] ,
type : [ 'folder' , 'gist ' ] ,
path : [ ] ,
extension : [ ] ,
pattern : [ ]
@ -41,39 +52,46 @@ const initialActions = [{
} , {
id : 'delete' ,
name : 'Delete' ,
type : [ 'file' , 'folder' ] ,
type : [ 'file' , 'folder' , 'gist ' ] ,
path : [ ] ,
extension : [ ] ,
pattern : [ ]
} , {
id : 'run' ,
name : 'Run' ,
type : [ ] ,
path : [ ] ,
extension : [ '.js' ] ,
pattern : [ ]
} , {
id : 'pushChangesToGist' ,
name : 'Push changes to gist' ,
type : [ ] ,
type : [ 'gist' ] ,
path : [ ] ,
extension : [ ] ,
pattern : [ '^browser/gists/([0-9]|[a-z])*$' ]
pattern : [ ]
} , {
id : 'run ' ,
name : 'Run ' ,
type : [ ] ,
id : 'publishFolderToGist ' ,
name : 'Publish folder to gist ' ,
type : [ 'folder' ] ,
path : [ ] ,
extension : [ '.js' ] ,
extension : [ ] ,
pattern : [ ]
} , {
id : 'publishFileToGist' ,
name : 'Publish file to gist' ,
type : [ 'file' ] ,
path : [ ] ,
extension : [ ] ,
pattern : [ ]
} , {
id : 'copy' ,
name : 'Copy' ,
type : [ 'folder' , 'file' ] ,
path : [ ] ,
extension : [ ] ,
pattern : [ ]
} ]
export const FileExplorer = ( props : FileExplorerProps ) = > {
const { name , registry , plugin , focusRoot , contextMenuItems , displayInput , externalUploads } = props
const [ state , setState ] = useState ( {
focusElement : [ {
key : '' ,
type : 'folder'
} ] ,
focusPath : null ,
files : [ ] ,
fileManager : null ,
ctrlKey : false ,
newFileName : '' ,
actions : initialActions ,
focusContext : {
element : null ,
x : null ,
@ -91,21 +109,20 @@ export const FileExplorer = (props: FileExplorerProps) => {
hide : true ,
title : '' ,
message : '' ,
ok : {
label : '' ,
fn : ( ) = > { }
} ,
cancel : {
label : '' ,
fn : ( ) = > { }
} ,
okLabel : '' ,
okFn : ( ) = > { } ,
cancelLabel : '' ,
cancelFn : ( ) = > { } ,
handleHide : null
} ,
modals : [ ] ,
toasterMsg : '' ,
mouseOverElement : null ,
showContextMenu : false
showContextMenu : false ,
reservedKeywords : [ name , 'gist-' ] ,
copyElement : [ ]
} )
const [ canPaste , setCanPaste ] = useState ( false )
const [ fileSystem , dispatch ] = useReducer ( fileSystemReducer , fileSystemInitialState )
const editRef = useRef ( null )
@ -125,13 +142,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
useEffect ( ( ) = > {
if ( fileSystem . notification . message ) {
modal ( fileSystem . notification . title , fileSystem . notification . message , {
label : fileSystem.notification.labelOk ,
fn : fileSystem.notification.actionOk
} , {
label : fileSystem.notification.labelCancel ,
fn : fileSystem.notification.actionCancel
} )
modal ( fileSystem . notification . title , fileSystem . notification . message , fileSystem . notification . labelOk , fileSystem . notification . actionOk , fileSystem . notification . labelCancel , fileSystem . notification . actionCancel )
}
} , [ fileSystem . notification . message ] )
@ -174,9 +185,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
useEffect ( ( ) = > {
if ( contextMenuItems ) {
setState ( prevState = > {
return { . . . prevState , actions : [ . . . initialActions , . . . contextMenuItems ] }
} )
addMenuItems ( contextMenuItems )
}
} , [ contextMenuItems ] )
@ -201,8 +210,10 @@ export const FileExplorer = (props: FileExplorerProps) => {
hide : false ,
title : prevState.modals [ 0 ] . title ,
message : prevState.modals [ 0 ] . message ,
ok : prevState.modals [ 0 ] . ok ,
cancel : prevState.modals [ 0 ] . cancel ,
okLabel : prevState.modals [ 0 ] . okLabel ,
okFn : prevState.modals [ 0 ] . okFn ,
cancelLabel : prevState.modals [ 0 ] . cancelLabel ,
cancelFn : prevState.modals [ 0 ] . cancelFn ,
handleHide : prevState.modals [ 0 ] . handleHide
}
@ -216,6 +227,38 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
} , [ state . modals ] )
useEffect ( ( ) = > {
if ( canPaste ) {
addMenuItems ( [ {
id : 'paste' ,
name : 'Paste' ,
type : [ 'folder' , 'file' ] ,
path : [ ] ,
extension : [ ] ,
pattern : [ ]
} ] )
} else {
removeMenuItems ( [ 'paste' ] )
}
} , [ canPaste ] )
const addMenuItems = ( items : { id : string , name : string , type : string [ ] , path : string [ ] , extension : string [ ] , pattern : string [ ] } [ ] ) = > {
setState ( prevState = > {
// filter duplicate items
const actions = items . filter ( ( { name } ) = > prevState . actions . findIndex ( action = > action . name === name ) === - 1 )
return { . . . prevState , actions : [ . . . prevState . actions , . . . actions ] }
} )
}
const removeMenuItems = ( ids : string [ ] ) = > {
setState ( prevState = > {
const actions = prevState . actions . filter ( ( { id } ) = > ids . findIndex ( value = > value === id ) === - 1 )
return { . . . prevState , actions }
} )
}
const extractNameFromKey = ( key : string ) : string = > {
const keyPath = key . split ( '/' )
@ -230,6 +273,20 @@ export const FileExplorer = (props: FileExplorerProps) => {
return keyPath . join ( '/' )
}
const hasReservedKeyword = ( content : string ) : boolean = > {
if ( state . reservedKeywords . findIndex ( value = > content . startsWith ( value ) ) !== - 1 ) return true
else return false
}
const getFocusedFolder = ( ) = > {
if ( state . focusElement [ 0 ] ) {
if ( state . focusElement [ 0 ] . type === 'folder' && state . focusElement [ 0 ] . key ) return state . focusElement [ 0 ] . key
else if ( state . focusElement [ 0 ] . type === 'gist' && state . focusElement [ 0 ] . key ) return state . focusElement [ 0 ] . key
else if ( state . focusElement [ 0 ] . type === 'file' && state . focusElement [ 0 ] . key ) return extractParentFromKey ( state . focusElement [ 0 ] . key ) ? extractParentFromKey ( state . focusElement [ 0 ] . key ) : name
else return name
}
}
const createNewFile = async ( newFilePath : string ) = > {
const fileManager = state . fileManager
@ -248,10 +305,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
} )
}
} catch ( error ) {
return modal ( 'File Creation Failed' , typeof error === 'string' ? error : error.message , {
label : 'Close' ,
fn : async ( ) = > { }
} , null )
return modal ( 'File Creation Failed' , typeof error === 'string' ? error : error.message , 'Close' , async ( ) = > { } )
}
}
@ -263,20 +317,14 @@ export const FileExplorer = (props: FileExplorerProps) => {
const exists = await fileManager . exists ( dirName )
if ( exists ) {
return modal ( 'Rename File Failed' , ` A file or folder ${ extractNameFromKey ( newFolderPath ) } already exists at this location. Please choose a different name. ` , {
label : 'Close' ,
fn : ( ) = > { }
} , null )
return modal ( 'Rename File Failed' , ` A file or folder ${ extractNameFromKey ( newFolderPath ) } already exists at this location. Please choose a different name. ` , 'Close' , ( ) = > { } )
}
await fileManager . mkdir ( dirName )
setState ( prevState = > {
return { . . . prevState , focusElement : [ { key : newFolderPath , type : 'folder' } ] }
} )
} catch ( e ) {
return modal ( 'Folder Creation Failed' , typeof e === 'string' ? e : e.message , {
label : 'Close' ,
fn : async ( ) = > { }
} , null )
return modal ( 'Folder Creation Failed' , typeof e === 'string' ? e : e.message , 'Close' , async ( ) = > { } )
}
}
@ -288,9 +336,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
const isDir = state . fileManager . isDirectory ( path )
modal ( ` Delete ${ isDir ? 'folder' : 'file' } ` , ` Are you sure you want to delete ${ path } ${ isDir ? 'folder' : 'file' } ? ` , {
label : 'OK' ,
fn : async ( ) = > {
modal ( ` Delete ${ isDir ? 'folder' : 'file' } ` , ` Are you sure you want to delete ${ path } ${ isDir ? 'folder' : 'file' } ? ` , 'OK' , async ( ) = > {
try {
const fileManager = state . fileManager
@ -298,11 +344,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
} catch ( e ) {
toast ( ` Failed to remove ${ isDir ? 'folder' : 'file' } ${ path } . ` )
}
}
} , {
label : 'Cancel' ,
fn : ( ) = > { }
} )
} , 'Cancel' , ( ) = > { } )
}
const renamePath = async ( oldPath : string , newPath : string ) = > {
@ -311,18 +353,12 @@ export const FileExplorer = (props: FileExplorerProps) => {
const exists = await fileManager . exists ( newPath )
if ( exists ) {
modal ( 'Rename File Failed' , ` A file or folder ${ extractNameFromKey ( newPath ) } already exists at this location. Please choose a different name. ` , {
label : 'Close' ,
fn : ( ) = > { }
} , null )
modal ( 'Rename File Failed' , ` A file or folder ${ extractNameFromKey ( newPath ) } already exists at this location. Please choose a different name. ` , 'Close' , ( ) = > { } )
} else {
await fileManager . rename ( oldPath , newPath )
}
} catch ( error ) {
modal ( 'Rename File Failed' , 'Unexpected error while renaming: ' + typeof error === 'string' ? error : error.message , {
label : 'Close' ,
fn : async ( ) = > { }
} , null )
modal ( 'Rename File Failed' , 'Unexpected error while renaming: ' + typeof error === 'string' ? error : error.message , 'Close' , async ( ) = > { } )
}
}
@ -332,7 +368,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
// 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.
const parentFolder = state . focusElement [ 0 ] ? state . focusElement [ 0 ] . type === 'folder' ? state . focusElement [ 0 ] . key : extractParentFromKey ( state . focusElement [ 0 ] . key ) : name
const parentFolder = getFocusedFolder ( )
const expandPath = [ . . . new Set ( [ . . . state . expandPath , parentFolder ] ) ]
setState ( prevState = > {
@ -345,19 +381,13 @@ export const FileExplorer = (props: FileExplorerProps) => {
fileReader . onload = async function ( event ) {
if ( helper . checkSpecialChars ( file . name ) ) {
modal ( 'File Upload Failed' , 'Special characters are not allowed' , {
label : 'Close' ,
fn : async ( ) = > { }
} , null )
modal ( 'File Upload Failed' , 'Special characters are not allowed' , 'Close' , async ( ) = > { } )
return
}
const success = await filesProvider . set ( name , event . target . result )
if ( ! success ) {
return modal ( 'File Upload Failed' , 'Failed to create file ' + name , {
label : 'Close' ,
fn : async ( ) = > { }
} , null )
return modal ( 'File Upload Failed' , 'Failed to create file ' + name , 'Close' , async ( ) = > { } )
}
const config = registry . get ( 'config' ) . api
const editor = registry . get ( 'editor' ) . api
@ -374,15 +404,9 @@ export const FileExplorer = (props: FileExplorerProps) => {
if ( ! exist ) {
loadFile ( name )
} else {
modal ( 'Confirm overwrite' , ` The file ${ name } already exists! Would you like to overwrite it? ` , {
label : 'OK' ,
fn : ( ) = > {
modal ( 'Confirm overwrite' , ` The file ${ name } already exists! Would you like to overwrite it? ` , 'OK' , ( ) = > {
loadFile ( name )
}
} , {
label : 'Cancel' ,
fn : ( ) = > { }
} )
} , 'Cancel' , ( ) = > { } )
}
} ) . catch ( error = > {
if ( error ) console . log ( error )
@ -390,42 +414,56 @@ export const FileExplorer = (props: FileExplorerProps) => {
} )
}
const publishToGist = ( ) = > {
modal ( 'Create a public gist' , ` Are you sure you want to anonymously publish all your files in the ${ name } workspace as a public gist on github.com? ` , {
label : 'OK' ,
fn : toGist
} , {
label : 'Cancel' ,
fn : ( ) = > { }
} )
const copyFile = ( src : string , dest : string ) = > {
const fileManager = state . fileManager
try {
fileManager . copyFile ( src , dest )
} catch ( error ) {
console . log ( 'Oops! An error ocurred while performing copyFile operation.' + error )
}
}
const copyFolder = ( src : string , dest : string ) = > {
const fileManager = state . fileManager
try {
fileManager . copyDir ( src , dest )
} catch ( error ) {
console . log ( 'Oops! An error ocurred while performing copyDir operation.' + error )
}
}
const publishToGist = ( path? : string , type ? : string ) = > {
modal ( 'Create a public gist' , ` Are you sure you want to anonymously publish all your files in the ${ name } workspace as a public gist on github.com? ` , 'OK' , ( ) = > toGist ( path , type ) , 'Cancel' , ( ) = > { } )
}
const pushChangesToGist = ( path? : string , type ? : string ) = > {
modal ( 'Create a public gist' , 'Are you sure you want to push changes to remote gist file on github.com?' , 'OK' , ( ) = > toGist ( path , type ) , 'Cancel' , ( ) = > { } )
}
const publishFolderToGist = ( path? : string , type ? : string ) = > {
modal ( 'Create a public gist' , ` Are you sure you want to anonymously publish all your files in the ${ path } folder as a public gist on github.com? ` , 'OK' , ( ) = > toGist ( path , type ) , 'Cancel' , ( ) = > { } )
}
const publishFileToGist = ( path? : string , type ? : string ) = > {
modal ( 'Create a public gist' , ` Are you sure you want to anonymously publish ${ path } file as a public gist on github.com? ` , 'OK' , ( ) = > toGist ( path , type ) , 'Cancel' , ( ) = > { } )
}
const toGist = ( id? : string ) = > {
const toGist = ( path? : string , type ? : string ) = > {
const filesProvider = fileSystem . provider . provider
const proccedResult = function ( error , data ) {
if ( error ) {
modal ( 'Publish to gist Failed' , 'Failed to manage gist: ' + error , {
label : 'Close' ,
fn : async ( ) = > { }
} , null )
modal ( 'Publish to gist Failed' , 'Failed to manage gist: ' + error , 'Close' , ( ) = > { } )
} else {
if ( data . html_url ) {
modal ( 'Gist is ready' , ` The gist is at ${ data . html_url } . Would you like to open it in a new window? ` , {
label : 'OK' ,
fn : ( ) = > {
modal ( 'Gist is ready' , ` The gist is at ${ data . html_url } . Would you like to open it in a new window? ` , 'OK' , ( ) = > {
window . open ( data . html_url , '_blank' )
}
} , {
label : 'Cancel' ,
fn : ( ) = > { }
} )
} , 'Cancel' , ( ) = > { } )
} else {
const error = JSON . stringify ( data . errors , null , '\t' ) || ''
const message = data . message === 'Not Found' ? data . message + '. Please make sure the API token has right to create a gist.' : data . message
modal ( 'Publish to gist Failed' , message + ' ' + data . documentation_url + ' ' + error , {
label : 'Close' ,
fn : async ( ) = > { }
} , null )
modal ( 'Publish to gist Failed' , message + ' ' + data . documentation_url + ' ' + error , 'Close' , ( ) = > { } )
}
}
}
@ -446,25 +484,20 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
// 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 ? '/gists/' + id : '/'
const folder = path || '/'
const id = type === 'gist' ? extractNameFromKey ( path ) . split ( '-' ) [ 1 ] : null
packageFiles ( filesProvider , folder , async ( error , packaged ) = > {
if ( error ) {
console . log ( error )
modal ( 'Publish to gist Failed' , 'Failed to create gist: ' + error . message , {
label : 'Close' ,
fn : async ( ) = > { }
} , null )
modal ( 'Publish to gist Failed' , 'Failed to create gist: ' + error . message , 'Close' , async ( ) = > { } )
} else {
// check for token
const config = registry . get ( 'config' ) . api
const accessToken = config . get ( 'settings/gist-access-token' )
if ( ! accessToken ) {
modal ( 'Authorize Token' , 'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.' , {
label : 'Close' ,
fn : async ( ) = > { }
} , null )
modal ( 'Authorize Token' , 'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.' , 'Close' , ( ) = > { } )
} 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='
@ -536,7 +569,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
} )
}
const modal = ( title : string , message : string , ok : { label : string , f n: ( ) = > void } , cancel : { label : string , fn : ( ) = > void } ) = > {
const modal = ( title : string , message : string , okLabel : string , okF n : ( ) = > void , cancelLabel? : string , cancelFn ? : ( ) = > void ) = > {
setState ( prevState = > {
return {
. . . prevState ,
@ -544,8 +577,10 @@ export const FileExplorer = (props: FileExplorerProps) => {
{
message ,
title ,
ok ,
cancel ,
okLabel ,
okFn ,
cancelLabel ,
cancelFn ,
handleHide : handleHideModal
} ]
}
@ -558,15 +593,15 @@ export const FileExplorer = (props: FileExplorerProps) => {
} )
}
const handleClickFile = ( path : string ) = > {
const handleClickFile = ( path : string , type : string ) = > {
path = path . indexOf ( props . name + '/' ) === 0 ? path . replace ( props . name + '/' , '' ) : path
state . fileManager . open ( path )
setState ( prevState = > {
return { . . . prevState , focusElement : [ { key : path , type : 'file' } ] }
return { . . . prevState , focusElement : [ { key : path , type } ] }
} )
}
const handleClickFolder = async ( path : string ) = > {
const handleClickFolder = async ( path : string , type : string ) = > {
if ( state . ctrlKey ) {
if ( state . focusElement . findIndex ( item = > item . key === path ) !== - 1 ) {
setState ( prevState = > {
@ -574,7 +609,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
} )
} else {
setState ( prevState = > {
return { . . . prevState , focusElement : [ . . . prevState . focusElement , { key : path , type : 'folder' } ] }
return { . . . prevState , focusElement : [ . . . prevState . focusElement , { key : path , type } ] }
} )
}
} else {
@ -588,22 +623,22 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
setState ( prevState = > {
return { . . . prevState , focusElement : [ { key : path , type : 'folder' } ] , expandPath }
return { . . . prevState , focusElement : [ { key : path , type } ] , expandPath }
} )
}
}
const handleContextMenuFile = ( pageX : number , pageY : number , path : string , content : string ) = > {
const handleContextMenuFile = ( pageX : number , pageY : number , path : string , content : string , type : string ) = > {
if ( ! content ) return
setState ( prevState = > {
return { . . . prevState , focusContext : { element : path , x : pageX , y : pageY , type : 'file' } , focusEdit : { . . . prevState . focusEdit , lastEdit : content } , showContextMenu : prevState.focusEdit.element !== path }
return { . . . prevState , focusContext : { element : path , x : pageX , y : pageY , type } , focusEdit : { . . . prevState . focusEdit , lastEdit : content } , showContextMenu : prevState.focusEdit.element !== path }
} )
}
const handleContextMenuFolder = ( pageX : number , pageY : number , path : string , content : string ) = > {
const handleContextMenuFolder = ( pageX : number , pageY : number , path : string , content : string , type : string ) = > {
if ( ! content ) return
setState ( prevState = > {
return { . . . prevState , focusContext : { element : path , x : pageX , y : pageY , type : 'folder' } , focusEdit : { . . . prevState . focusEdit , lastEdit : content } , showContextMenu : prevState.focusEdit.element !== path }
return { . . . prevState , focusContext : { element : path , x : pageX , y : pageY , type } , focusEdit : { . . . prevState . focusEdit , lastEdit : content } , showContextMenu : prevState.focusEdit.element !== path }
} )
}
@ -644,14 +679,20 @@ export const FileExplorer = (props: FileExplorerProps) => {
} )
}
if ( helper . checkSpecialChars ( content ) ) {
modal ( 'Validation Error' , 'Special characters are not allowed' , {
label : 'OK' ,
fn : ( ) = > { }
} , null )
modal ( 'Validation Error' , 'Special characters are not allowed' , 'OK' , ( ) = > { } )
} else {
if ( state . focusEdit . isNew ) {
if ( hasReservedKeyword ( content ) ) {
removeInputField ( parentFolder ) ( dispatch )
modal ( 'Reserved Keyword' , ` File name contains remix reserved keywords. ' ${ content } ' ` , 'Close' , ( ) = > { } )
} else {
state . focusEdit . type === 'file' ? createNewFile ( joinPath ( parentFolder , content ) ) : createNewFolder ( joinPath ( parentFolder , content ) )
removeInputField ( parentFolder ) ( dispatch )
}
} else {
if ( hasReservedKeyword ( content ) ) {
editRef . current . textContent = state . focusEdit . lastEdit
modal ( 'Reserved Keyword' , ` File name contains remix reserved keywords. ' ${ content } ' ` , 'Close' , ( ) = > { } )
} else {
const oldPath : string = state . focusEdit . element
const oldName = extractNameFromKey ( oldPath )
@ -660,6 +701,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
editRef . current . textContent = extractNameFromKey ( oldPath )
renamePath ( oldPath , newPath )
}
}
setState ( prevState = > {
return { . . . prevState , focusEdit : { element : null , isNew : false , type : '' , lastEdit : '' } }
} )
@ -668,7 +710,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
const handleNewFileInput = async ( parentFolder? : string ) = > {
if ( ! parentFolder ) parentFolder = state . focusElement [ 0 ] ? state . focusElement [ 0 ] . type === 'folder' ? state . focusElement [ 0 ] . key ? state . focusElement [ 0 ] . key : name : extractParentFromKey ( state . focusElement [ 0 ] . key ) ? extractParentFromKey ( state . focusElement [ 0 ] . key ) : name : name
if ( ! parentFolder ) parentFolder = getFocusedFolder ( )
const expandPath = [ . . . new Set ( [ . . . state . expandPath , parentFolder ] ) ]
await addInputField ( fileSystem . provider . provider , 'file' , parentFolder ) ( dispatch )
@ -679,7 +721,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
const handleNewFolderInput = async ( parentFolder? : string ) = > {
if ( ! parentFolder ) parentFolder = state . focusElement [ 0 ] ? state . focusElement [ 0 ] . type === 'folder' ? state . focusElement [ 0 ] . key ? state . focusElement [ 0 ] . key : name : extractParentFromKey ( state . focusElement [ 0 ] . key ) ? extractParentFromKey ( state . focusElement [ 0 ] . key ) : name : name
if ( ! parentFolder ) parentFolder = getFocusedFolder ( )
else if ( ( parentFolder . indexOf ( '.sol' ) !== - 1 ) || ( parentFolder . indexOf ( '.js' ) !== - 1 ) ) parentFolder = extractParentFromKey ( parentFolder )
const expandPath = [ . . . new Set ( [ . . . state . expandPath , parentFolder ] ) ]
@ -709,6 +751,25 @@ export const FileExplorer = (props: FileExplorerProps) => {
} )
}
const handleCopyClick = ( path : string , type : string ) = > {
setState ( prevState = > {
return { . . . prevState , copyElement : [ { key : path , type } ] }
} )
setCanPaste ( true )
toast ( ` Copied to clipboard ${ path } ` )
}
const handlePasteClick = ( dest : string , destType : string ) = > {
dest = destType === 'file' ? extractParentFromKey ( dest ) || props.name : dest
state . copyElement . map ( ( { key , type } ) = > {
type === 'file' ? copyFile ( key , dest ) : copyFolder ( key , dest )
} )
setState ( prevState = > {
return { . . . prevState , copyElement : [ ] }
} )
setCanPaste ( false )
}
const label = ( file : File ) = > {
return (
< div
@ -755,12 +816,12 @@ export const FileExplorer = (props: FileExplorerProps) => {
label = { label ( file ) }
onClick = { ( e ) = > {
e . stopPropagation ( )
if ( state . focusEdit . element !== file . path ) handleClickFolder ( file . path )
if ( state . focusEdit . element !== file . path ) handleClickFolder ( file . path , file . type )
} }
onContextMenu = { ( e ) = > {
e . preventDefault ( )
e . stopPropagation ( )
handleContextMenuFolder ( e . pageX , e . pageY , file . path , e . target . textContent )
handleContextMenuFolder ( e . pageX , e . pageY , file . path , e . target . textContent , file . type )
} }
labelClass = { labelClass }
controlBehaviour = { state . ctrlKey }
@ -792,12 +853,12 @@ export const FileExplorer = (props: FileExplorerProps) => {
label = { label ( file ) }
onClick = { ( e ) = > {
e . stopPropagation ( )
if ( state . focusEdit . element !== file . path ) handleClickFile ( file . path )
if ( state . focusEdit . element !== file . path ) handleClickFile ( file . path , file . type )
} }
onContextMenu = { ( e ) = > {
e . preventDefault ( )
e . stopPropagation ( )
handleContextMenuFile ( e . pageX , e . pageY , file . path , e . target . textContent )
handleContextMenuFile ( e . pageX , e . pageY , file . path , e . target . textContent , file . type )
} }
icon = { icon }
labelClass = { labelClass }
@ -865,8 +926,10 @@ export const FileExplorer = (props: FileExplorerProps) => {
title = { state . focusModal . title }
message = { state . focusModal . message }
hide = { state . focusModal . hide }
ok = { state . focusModal . ok }
cancel = { state . focusModal . cancel }
okLabel = { state . focusModal . okLabel }
okFn = { state . focusModal . okFn }
cancelLabel = { state . focusModal . cancelLabel }
cancelFn = { state . focusModal . cancelFn }
handleHide = { handleHideModal }
/ >
}
@ -880,6 +943,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
deletePath = { deletePath }
renamePath = { editModeOn }
runScript = { runScript }
copy = { handleCopyClick }
paste = { handlePasteClick }
emit = { emitContextMenuEvent }
pageX = { state . focusContext . x }
pageY = { state . focusContext . y }
@ -889,6 +954,9 @@ export const FileExplorer = (props: FileExplorerProps) => {
e . stopPropagation ( )
handleMouseOver ( state . focusContext . element )
} }
pushChangesToGist = { pushChangesToGist }
publishFolderToGist = { publishFolderToGist }
publishFileToGist = { publishFileToGist }
/ >
}
< / div >
@ -898,12 +966,34 @@ export const FileExplorer = (props: FileExplorerProps) => {
export default FileExplorer
async function packageFiles ( filesProvider , directory , callback ) {
const isFile = filesProvider . isFile ( directory )
const ret = { }
if ( isFile ) {
try {
filesProvider . get ( directory , ( error , content ) = > {
if ( error ) throw new Error ( 'An error ocurred while getting file content. ' + directory )
if ( /^\s+$/ . test ( content ) || ! content . length ) {
content = '// this line is added to create a gist. Empty file is not allowed.'
}
directory = directory . replace ( /\//g , '...' )
ret [ directory ] = { content }
callback ( null , ret )
} )
} catch ( e ) {
return callback ( e )
}
} else {
try {
await filesProvider . copyFolderToJson ( directory , ( { path , content } ) = > {
if ( /^\s+$/ . test ( content ) || ! content . length ) {
content = '// this line is added to create a gist. Empty file is not allowed.'
}
if ( path . indexOf ( 'gist-' ) === 0 ) {
path = path . split ( '/' )
path . shift ( )
path = path . join ( '/' )
}
path = path . replace ( /\//g , '...' )
ret [ path ] = { content }
} )
@ -912,6 +1002,7 @@ async function packageFiles (filesProvider, directory, callback) {
return callback ( e )
}
}
}
function joinPath ( . . . paths ) {
paths = paths . filter ( ( value ) = > value !== '' ) . map ( ( path ) = > path . replace ( /^\/|\/$/g , '' ) ) // remove first and last slash)