diff --git a/apps/remix-ide/src/app/panels/file-panel.js b/apps/remix-ide/src/app/panels/file-panel.js index 08d4602aa5..7ec0d7042b 100644 --- a/apps/remix-ide/src/app/panels/file-panel.js +++ b/apps/remix-ide/src/app/panels/file-panel.js @@ -94,20 +94,20 @@ module.exports = class Filepanel extends ViewPlugin {
- + /> */}
diff --git a/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx b/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx index 9ce6041200..c851a10e84 100644 --- a/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx +++ b/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx @@ -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 (
- {key.split('/').pop()} + { data.path.split('/').pop() }
) } + 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) => { { 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 ( <> - { props.name } + { name } {items} ) } - 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 ( + { resolveDirectory(file.path) }} key={index} label={label(file)}> + { + file.child ? { + file.child.map((file, index) => { + return renderFiles(file, 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() }) - } - }) - }) + + ) + } else { + return ( + { state.fileManager.open(file.path) }} + /> + ) + } } return (
- - + + + { + state.files.map((file, index) => { + return renderFiles(file, index) + }) + } +
diff --git a/libs/remix-ui/file-explorer/src/lib/types/index.ts b/libs/remix-ui/file-explorer/src/lib/types/index.ts index a2ee05aea6..75d7fb3dc9 100644 --- a/libs/remix-ui/file-explorer/src/lib/types/index.ts +++ b/libs/remix-ui/file-explorer/src/lib/types/index.ts @@ -1,7 +1,7 @@ /* eslint-disable-next-line */ export interface FileExplorerProps { name: string, - localRegistry: any, + registry: any, files: any, menuItems?: string[], plugin: any diff --git a/libs/remix-ui/tree-view/src/types/index.ts b/libs/remix-ui/tree-view/src/types/index.ts index 9218469ec5..7ebbc05155 100644 --- a/libs/remix-ui/tree-view/src/types/index.ts +++ b/libs/remix-ui/tree-view/src/types/index.ts @@ -1,11 +1,11 @@ export interface TreeViewProps { children?: React.ReactNode, - id: string + id?: string } export interface TreeViewItemProps { children?: React.ReactNode, - id: string, + id?: string, label: string | number | React.ReactNode, expand?: boolean, onClick?: VoidFunction,