diff --git a/apps/remix-ide/src/app/files/file-explorer.js b/apps/remix-ide/src/app/files/file-explorer.js deleted file mode 100644 index cef877b68b..0000000000 --- a/apps/remix-ide/src/app/files/file-explorer.js +++ /dev/null @@ -1,702 +0,0 @@ -/* global FileReader */ -/* global fetch */ -const async = require('async') -const Gists = require('gists') -const modalDialogCustom = require('../ui/modal-dialog-custom') -const tooltip = require('../ui/tooltip') -const QueryParams = require('../../lib/query-params') -const helper = require('../../lib/helper') -const yo = require('yo-yo') -const Treeview = require('../ui/TreeView') -const modalDialog = require('../ui/modaldialog') -const EventManager = require('events') -const contextMenu = require('../ui/contextMenu') -const css = require('./styles/file-explorer-styles') -const globalRegistry = require('../../global/registry') -const queryParams = new QueryParams() -let MENU_HANDLE - -function fileExplorer (localRegistry, files, menuItems, plugin) { - var self = this - this.events = new EventManager() - // file provider backend - this.files = files - // element currently focused on - this.focusElement = null - // path currently focused on - this.focusPath = null - const allItems = - [ - { - action: 'createNewFile', - title: 'Create New File', - icon: 'fas fa-plus-circle' - }, - { - action: 'publishToGist', - title: 'Publish all [browser] explorer files to a github gist', - icon: 'fab fa-github' - }, - { - action: 'uploadFile', - title: 'Add Local file to the Browser Storage Explorer', - icon: 'far fa-folder-open' - }, - { - action: 'updateGist', - title: 'Update the current [gist] explorer', - icon: 'fab fa-github' - } - ] - // menu items - this.menuItems = allItems.filter( - (item) => { - if (menuItems) return menuItems.find((name) => { return name === item.action }) - } - ) - - self._components = {} - self._components.registry = localRegistry || globalRegistry - self._deps = { - config: self._components.registry.get('config').api, - editor: self._components.registry.get('editor').api, - 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 - function remixdDialog () { - return yo`
This file has been changed outside of Remix IDE.
` - } - - this.files.event.register('fileExternallyChanged', (path, file) => { - if (self._deps.config.get('currentFile') === path && self._deps.editor.currentContent() && self._deps.editor.currentContent() !== file.content) { - if (this.files.isReadOnly(path)) return self._deps.editor.setText(file.content) - - modalDialog(path + ' changed', remixdDialog(), - { - label: 'Replace by the new content', - fn: () => { - self._deps.editor.setText(file.content) - } - }, - { - label: 'Keep the content displayed in Remix', - fn: () => {} - } - ) - } - }) - - // register to event of the file provider - files.event.on('fileRemoved', fileRemoved) - files.event.on('fileRenamed', fileRenamed) - files.event.on('fileRenamedError', fileRenamedError) - files.event.on('fileAdded', fileAdded) - files.event.on('folderAdded', folderAdded) - - function fileRenamedError (error) { - modalDialogCustom.alert(error) - } - - function fileAdded (filepath) { - self.ensureRoot(() => { - 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) { - self.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') - } - }) - } - }) - } - - function extractNameFromKey (key) { - const keyPath = key.split('/') - - return keyPath[keyPath.length - 1] - } - - function folderAdded (folderpath) { - self.ensureRoot(() => { - folderpath = folderpath.split('/').slice(0, -1).join('/') - self.files.resolveDirectory(folderpath, (error, fileTree) => { - if (error) console.error(error) - if (!fileTree) return - fileTree = normalize(folderpath, fileTree) - self.treeView.updateNodeFromJSON(folderpath, fileTree, true) - if (!self.treeView.isExpanded(folderpath)) self.treeView.expand(folderpath) - }) - }) - } - - function fileRemoved (filepath) { - const label = self.treeView.labelAt(filepath) - filepath = filepath.split('/').slice(0, -1).join('/') - - if (label && label.parentElement) { - label.parentElement.removeChild(label) - } - - self.updatePath(filepath) - } - - function fileRenamed (oldName, newName, isFolder) { - fileRemoved(oldName) - fileAdded(newName) - } - - // make interface and register to nodeClick, leafClick - self.treeView = new Treeview({ - extractData: function extractData (value, tree, key) { - var newValue = {} - // var isReadOnly = false - var isFile = false - Object.keys(value).filter(function keep (x) { - if (x === '/content') isFile = true - if (x[0] !== '/') return true - }).forEach(function (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 - } - }, - formatSelf: function formatSelf (key, data, li) { - const isRoot = data.path === self.files.type - const isFolder = !!data.children - return yo` -
- - ${key.split('/').pop()} - - ${isRoot ? self.renderMenuItems() : ''} -
- ` - } - }) - - /** - * Extracts first two folders as a subpath from the path. - **/ - function extractExternalFolder (path) { - const firstSlIndex = path.indexOf('/', 1) - if (firstSlIndex === -1) return '' - const secondSlIndex = path.indexOf('/', firstSlIndex + 1) - if (secondSlIndex === -1) return '' - return path.substring(0, secondSlIndex) - } - - self.treeView.event.register('nodeRightClick', function (key, data, label, event) { - if (self.files.readonly) return - if (key === self.files.type) return - MENU_HANDLE && MENU_HANDLE.hide(null, true) - const actions = {} - const provider = self._deps.fileManager.fileProviderOf(key) - actions['Create File'] = () => self.createNewFile(key) - actions['Create Folder'] = () => self.createNewFolder(key) - // @todo(#2386) not fully implemented. Readd later when fixed - if (provider.isExternalFolder(key)) { - /* actions['Discard changes'] = () => { - modalDialogCustom.confirm( - 'Discard changes', - 'Are you sure you want to discard all your changes?', - () => { self.files.discardChanges(key) }, - () => {} - ) - } */ - } else { - const folderPath = extractExternalFolder(key) - actions.Rename = () => { - if (self.files.isReadOnly(key)) { return tooltip('cannot rename folder. ' + self.files.type + ' is a read only explorer') } - var name = label.querySelector('span[data-path="' + key + '"]') - if (name) editModeOn(name) - } - actions.Delete = () => { - if (self.files.isReadOnly(key)) { return tooltip('cannot delete folder. ' + self.files.type + ' is a read only explorer') } - const currentFoldername = extractNameFromKey(key) - - modalDialogCustom.confirm('Confirm to delete folder', `Are you sure you want to delete ${currentFoldername} folder?`, - async () => { - const fileManager = self._deps.fileManager - const removeFolder = await fileManager.remove(key) - - if (!removeFolder) { - tooltip(`failed to remove ${key}. Make sure the directory is empty before removing it.`) - } - }, () => {}) - } - if (folderPath === 'browser/gists') { - actions['Push changes to gist'] = () => { - const id = key.substr(key.lastIndexOf('/') + 1, key.length - 1) - modalDialogCustom.confirm( - 'Push back to Gist', - 'Are you sure you want to push all your changes back to Gist?', - () => { self.toGist(id) }, - () => {} - ) - } - } - } - MENU_HANDLE = contextMenu(event, actions) - }) - - self.treeView.event.register('leafRightClick', function (key, data, label, event) { - if (key === self.files.type) return - MENU_HANDLE && MENU_HANDLE.hide(null, true) - const actions = {} - const provider = self._deps.fileManager.fileProviderOf(key) - if (!provider.isExternalFolder(key)) { - actions['Create Folder'] = () => self.createNewFolder(self._deps.fileManager.extractPathOf(key)) - actions.Rename = () => { - if (self.files.isReadOnly(key)) { return tooltip('cannot rename file. ' + self.files.type + ' is a read only explorer') } - var name = label.querySelector('span[data-path="' + key + '"]') - if (name) editModeOn(name) - } - actions.Delete = () => { - if (self.files.isReadOnly(key)) { return tooltip('cannot delete file. ' + self.files.type + ' is a read only explorer') } - const currentFilename = extractNameFromKey(key) - - modalDialogCustom.confirm( - 'Delete file', `Are you sure you want to delete ${currentFilename} file?`, - async () => { - const fileManager = self._deps.fileManager - const removeFile = await fileManager.remove(key) - - if (!removeFile) { - tooltip(`Failed to remove file ${key}.`) - } - }, - () => {} - ) - } - if (key.endsWith('.js')) { - actions.Run = async () => { - provider.get(key, (error, content) => { - if (error) return console.log(error) - plugin.call('scriptRunner', 'execute', content) - }) - } - } - } - MENU_HANDLE = contextMenu(event, actions) - }) - - self.treeView.event.register('leafClick', function (key, data, label) { - self.events.trigger('focus', [key]) - }) - - self.treeView.event.register('nodeClick', function (path, childrenContainer) { - if (!childrenContainer) return - if (childrenContainer.style.display === 'none') return - self.updatePath(path) - }) - - // register to main app, trigger when the current file in the editor changed - self._deps.fileManager.events.on('currentFileChanged', (newFile) => { - const provider = self._deps.fileManager.fileProviderOf(newFile) - if (self.focusElement && self.focusPath !== newFile) { - self.focusElement.classList.remove('bg-secondary') - self.focusElement = null - self.focusPath = null - } - if (provider && (provider.type === files.type)) { - self.focusElement = self.treeView.labelAt(newFile) - if (self.focusElement) { - self.focusElement.classList.add('bg-secondary') - self.focusPath = newFile - } - } - }) - - self._deps.fileManager.events.on('noFileSelected', () => { - if (self.focusElement) { - self.focusElement.classList.remove('bg-secondary') - self.focusElement = null - self.focusPath = null - } - }) - - var textUnderEdit = null - - function selectElementContents (el) { - var range = document.createRange() - range.selectNodeContents(el) - var sel = window.getSelection() - sel.removeAllRanges() - sel.addRange(range) - } - - function editModeOn (label) { - textUnderEdit = label.innerText - label.setAttribute('contenteditable', true) - label.classList.add('bg-light') - label.focus() - selectElementContents(label) - } - - function editModeOff (event) { - const label = this - - const isFolder = label.className.indexOf('folder') !== -1 - function rename () { - var newPath = label.dataset.path - newPath = newPath.split('/') - newPath[newPath.length - 1] = label.innerText - newPath = newPath.join('/') - if (label.innerText === '') { - modalDialogCustom.alert('File name cannot be empty') - label.innerText = textUnderEdit - } else if (helper.checkSpecialChars(label.innerText)) { - modalDialogCustom.alert('Special characters are not allowed') - label.innerText = textUnderEdit - } else { - files.exists(newPath, (error, exist) => { - if (error) return modalDialogCustom.alert('Unexpected error while renaming: ' + error) - if (!exist) { - files.rename(label.dataset.path, newPath, isFolder) - } else { - modalDialogCustom.alert('File already exists.') - label.innerText = textUnderEdit - } - }) - } - } - - if (event.which === 13) event.preventDefault() - if ((event.type === 'blur' || event.which === 13) && label.getAttribute('contenteditable')) { - var save = textUnderEdit !== label.innerText - if (save) { - modalDialogCustom.confirm( - 'Confirm to rename a ' + (isFolder ? 'folder' : 'file'), - 'Are you sure you want to rename ' + textUnderEdit + '?', - () => { rename() }, - () => { label.innerText = textUnderEdit } - ) - } - label.removeAttribute('contenteditable') - label.classList.remove('bg-light') - } - } -} - -fileExplorer.prototype.updatePath = function (path) { - this.files.resolveDirectory(path, (error, fileTree) => { - if (error) console.error(error) - if (!fileTree) return - var newTree = normalize(path, fileTree) - this.treeView.updateNodeFromJSON(path, newTree, true) - }) -} - -fileExplorer.prototype.hide = function () { - if (this.container) this.container.style.display = 'none' -} - -fileExplorer.prototype.show = function () { - if (this.container) this.container.style.display = 'block' -} - -fileExplorer.prototype.init = function () { - this.container = yo`
` - return this.container -} - -fileExplorer.prototype.publishToGist = function () { - 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() } - ) -} - -fileExplorer.prototype.uploadFile = function (event) { - // 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. - - const self = this - - ;[...event.target.files].forEach((file) => { - const files = this.files - function loadFile () { - var fileReader = new FileReader() - fileReader.onload = async function (event) { - if (helper.checkSpecialChars(file.name)) { - modalDialogCustom.alert('Special characters are not allowed') - return - } - var 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) - } - var 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() }) - } - }) - }) -} - -fileExplorer.prototype.toGist = function (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 ? '/gists/' + id : '/' - 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) - }) - } - } - } - }) -} - -// return all the files, except the temporary/readonly ones.. -fileExplorer.prototype.packageFiles = function (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) - }) - } - }) -} - -fileExplorer.prototype.createNewFile = function (parentFolder = '/') { - 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) -} - -fileExplorer.prototype.createNewFolder = function (parentFolder) { - const self = this - modalDialogCustom.prompt('Create new folder', '', 'New folder', (input) => { - if (!input) { - return tooltip('Failed to create folder. The name can not be empty') - } - - const currentPath = !parentFolder ? self._deps.fileManager.currentPath() : parentFolder - let newName = currentPath ? currentPath + '/' + input : self.files.type + '/' + input - - newName = newName + '/' - self.files.exists(newName, (error, exist) => { - if (error) return tooltip('Unexpected error while creating folder: ' + error) - if (!exist) { - self.files.set(newName, '') - } else { - tooltip('Folder already exists.', () => {}) - } - }) - }, null, true) -} - -fileExplorer.prototype.renderMenuItems = function () { - let items = '' - if (this.menuItems) { - items = this.menuItems.map(({ action, title, icon }) => { - if (action === 'uploadFile') { - return yo` - - ` - } else { - return yo` - { event.stopPropagation(); this[action]() }} - class="newFile ${icon} ${css.newFile}" - title=${title} - > - - ` - } - }) - } - return yo`${items}` -} - -fileExplorer.prototype.ensureRoot = function (cb) { - cb = cb || (() => {}) - var self = this - if (self.element) return cb() - const root = {} - root[this.files.type] = {} - var element = self.treeView.render(root, false) - element.classList.add(css.fileexplorer) - element.events = self.events - element.api = self.api - self.container.appendChild(element) - self.element = element - if (cb) cb() - self.treeView.expand(self.files.type) -} - -function normalize (path, filesList) { - var prefix = path.split('/')[0] - var newList = {} - Object.keys(filesList).forEach(key => { - newList[prefix + '/' + key] = filesList[key].isDirectory ? {} : { '/content': true } - }) - return newList -} - -module.exports = fileExplorer diff --git a/apps/remix-ide/src/app/tabs/compile-tab.js b/apps/remix-ide/src/app/tabs/compile-tab.js index 285ecb5363..6d84a9721f 100644 --- a/apps/remix-ide/src/app/tabs/compile-tab.js +++ b/apps/remix-ide/src/app/tabs/compile-tab.js @@ -24,7 +24,6 @@ const profile = { documentation: 'https://remix-ide.readthedocs.io/en/latest/solidity_editor.html', version: packageJson.version, methods: ['getCompilationResult', 'compile', 'compileWithParameters', 'setCompilerConfig', 'compileFile'] - } // EditorApi: diff --git a/apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js b/apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js deleted file mode 100644 index a7214c3b3a..0000000000 --- a/apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js +++ /dev/null @@ -1,580 +0,0 @@ - -import toaster from '../../ui/tooltip' -import { canUseWorker, baseURLBin, baseURLWasm, urlFromVersion, pathToURL, promisedMiniXhr } from '@remix-project/remix-solidity' -const yo = require('yo-yo') -const helper = require('../../../lib/helper') -const addTooltip = require('../../ui/tooltip') -const semver = require('semver') -const modalDialogCustom = require('../../ui/modal-dialog-custom') -const css = require('../styles/compile-tab-styles') -const _paq = window._paq = window._paq || [] - -class CompilerContainer { - constructor (compileTabLogic, editor, config, queryParams) { - this._view = {} - this.compileTabLogic = compileTabLogic - this.editor = editor - this.config = config - this.queryParams = queryParams - this.hhCompilation = false - - this.data = { - hideWarnings: config.get('hideWarnings') || false, - autoCompile: config.get('autoCompile'), - compileTimeout: null, - timeout: 300, - allversions: null, - selectedVersion: null, - defaultVersion: 'soljson-v0.8.4+commit.c7e474f2.js' // this default version is defined: in makeMockCompiler (for browser test) - } - } - - /** - * Update the compilation button with the name of the current file - */ - set currentFile (name = '') { - if (name && name !== '') { - this._setCompilerVersionFromPragma(name) - } - if (!this._view.compilationButton) return - const button = this.compilationButton(name.split('/').pop()) - this._disableCompileBtn(!name || (name && !this.isSolFileSelected(name))) - yo.update(this._view.compilationButton, button) - } - - isSolFileSelected (currentFile = '') { - if (!currentFile) currentFile = this.config.get('currentFile') - if (!currentFile) return false - const extention = currentFile.substr(currentFile.length - 3, currentFile.length) - return extention.toLowerCase() === 'sol' || extention.toLowerCase() === 'yul' - } - - deactivate () { - // deactivate editor listeners - this.editor.event.unregister('contentChanged') - this.editor.event.unregister('sessionSwitched') - } - - activate () { - this.currentFile = this.config.get('currentFile') - this.listenToEvents() - } - - listenToEvents () { - this.editor.event.register('sessionSwitched', () => { - if (!this._view.compileIcon) return - this.scheduleCompilation() - }) - - this.compileTabLogic.event.on('startingCompilation', () => { - if (!this._view.compileIcon) return - this._view.compileIcon.setAttribute('title', 'compiling...') - this._view.compileIcon.classList.remove(`${css.bouncingIcon}`) - this._view.compileIcon.classList.add(`${css.spinningIcon}`) - }) - - this.compileTabLogic.compiler.event.register('compilationDuration', (speed) => { - if (!this._view.warnCompilationSlow) return - if (speed > 1000) { - const msg = `Last compilation took ${speed}ms. We suggest to turn off autocompilation.` - this._view.warnCompilationSlow.setAttribute('title', msg) - this._view.warnCompilationSlow.style.visibility = 'visible' - } else { - this._view.warnCompilationSlow.style.visibility = 'hidden' - } - }) - - this.editor.event.register('contentChanged', () => { - if (!this._view.compileIcon) return - this.scheduleCompilation() - this._view.compileIcon.classList.add(`${css.bouncingIcon}`) // @TODO: compileView tab - }) - - this.compileTabLogic.compiler.event.register('loadingCompiler', () => { - if (!this._view.compileIcon) return - this._disableCompileBtn(true) - this._view.compileIcon.setAttribute('title', 'compiler is loading, please wait a few moments.') - this._view.compileIcon.classList.add(`${css.spinningIcon}`) - this._view.warnCompilationSlow.style.visibility = 'hidden' - this._updateLanguageSelector() - }) - - this.compileTabLogic.compiler.event.register('compilerLoaded', () => { - if (!this._view.compileIcon) return - this._disableCompileBtn(false) - this._view.compileIcon.setAttribute('title', '') - this._view.compileIcon.classList.remove(`${css.spinningIcon}`) - if (this.data.autoCompile) this.compileIfAutoCompileOn() - }) - - this.compileTabLogic.compiler.event.register('compilationFinished', (success, data, source) => { - if (!this._view.compileIcon) return - this._view.compileIcon.setAttribute('title', 'idle') - this._view.compileIcon.classList.remove(`${css.spinningIcon}`) - this._view.compileIcon.classList.remove(`${css.bouncingIcon}`) - _paq.push(['trackEvent', 'compiler', 'compiled_with_version', this._retrieveVersion()]) - }) - } - - /************** - * SUBCOMPONENT - */ - compilationButton (name = '') { - const displayed = name || '' - const disabled = name && this.isSolFileSelected() ? '' : 'disabled' - return yo` - - ` - } - - _disableCompileBtn (shouldDisable) { - const btn = document.getElementById('compileBtn') - if (!btn) return - if (shouldDisable) { - btn.classList.add('disabled') - } else if (this.isSolFileSelected()) { - btn.classList.remove('disabled') - } - } - - // Load solc compiler version according to pragma in contract file - _setCompilerVersionFromPragma (filename) { - if (!this.data.allversions) return - this.compileTabLogic.fileManager.readFile(filename).then(data => { - const pragmaArr = data.match(/(pragma solidity (.+?);)/g) - if (pragmaArr && pragmaArr.length === 1) { - const pragmaStr = pragmaArr[0].replace('pragma solidity', '').trim() - const pragma = pragmaStr.substring(0, pragmaStr.length - 1) - const releasedVersions = this.data.allversions.filter(obj => !obj.prerelease).map(obj => obj.version) - const allVersions = this.data.allversions.map(obj => this._retrieveVersion(obj.version)) - const currentCompilerName = this._retrieveVersion(this._view.versionSelector.selectedOptions[0].label) - // contains only numbers part, for example '0.4.22' - const pureVersion = this._retrieveVersion() - // is nightly build newer than the last release - const isNewestNightly = currentCompilerName.includes('nightly') && semver.gt(pureVersion, releasedVersions[0]) - // checking if the selected version is in the pragma range - const isInRange = semver.satisfies(pureVersion, pragma) - // checking if the selected version is from official compilers list(excluding custom versions) and in range or greater - const isOfficial = allVersions.includes(currentCompilerName) - if (isOfficial && (!isInRange && !isNewestNightly)) { - const compilerToLoad = semver.maxSatisfying(releasedVersions, pragma) - const compilerPath = this.data.allversions.filter(obj => !obj.prerelease && obj.version === compilerToLoad)[0].path - if (this.data.selectedVersion !== compilerPath) { - this.data.selectedVersion = compilerPath - this._updateVersionSelector() - } - } - } - }) - } - - _retrieveVersion (version) { - if (!version) version = this._view.versionSelector.value - if (version === 'builtin') version = this.data.defaultVersion - return semver.coerce(version) ? semver.coerce(version).version : '' - } - - render () { - this.compileTabLogic.compiler.event.register('compilerLoaded', (version) => this.setVersionText(version)) - this.fetchAllVersion((allversions, selectedVersion, isURL) => { - this.data.allversions = allversions - if (isURL) this._updateVersionSelector(selectedVersion) - else { - this.data.selectedVersion = selectedVersion - if (this._view.versionSelector) this._updateVersionSelector() - } - }) - - this.hardhatCompilation = yo`` - this._view.warnCompilationSlow = yo`` - this._view.compileIcon = yo`` - this._view.autoCompile = yo` this.updateAutoCompile()} data-id="compilerContainerAutoCompile" id="autoCompile" type="checkbox" title="Auto compile">` - this._view.hideWarningsBox = yo` this.hideWarnings()} id="hideWarningsBox" type="checkbox" title="Hide warnings">` - if (this.data.autoCompile) this._view.autoCompile.setAttribute('checked', '') - if (this.data.hideWarnings) this._view.hideWarningsBox.setAttribute('checked', '') - - this._view.optimize = yo` this.onchangeOptimize()} class="custom-control-input" id="optimize" type="checkbox">` - if (this.compileTabLogic.optimize) this._view.optimize.setAttribute('checked', '') - - this._view.runs = yo` this.onchangeRuns()} - >` - if (this.compileTabLogic.optimize) { - this._view.runs.removeAttribute('disabled') - this._view.runs.value = this.compileTabLogic.runs - } else { - this._view.runs.setAttribute('disabled', '') - } - - this._view.versionSelector = yo` - ` - this._view.languageSelector = yo` - ` - this._view.version = yo`` - - this._view.evmVersionSelector = yo` - ` - if (this.compileTabLogic.evmVersion) { - const s = this._view.evmVersionSelector - let i - for (i = 0; i < s.options.length; i++) { - if (s.options[i].value === this.compileTabLogic.evmVersion) { - break - } - } - if (i === s.options.length) { // invalid evmVersion from queryParams - s.selectedIndex = 0 // compiler default - this.onchangeEvmVersion() - } else { - s.selectedIndex = i - this.onchangeEvmVersion() - } - } - - this._view.compilationButton = this.compilationButton() - - this._view.includeNightlies = yo` - this._updateVersionSelector()}> - ` - this._view.compileContainer = yo` -
- -
-
-
- - ${this._view.versionSelector} -
-
- ${this._view.includeNightlies} - -
-
- - ${this._view.languageSelector} -
-
- - ${this._view.evmVersionSelector} -
-
-

Compiler Configuration

-
- ${this._view.autoCompile} - -
-
-
- ${this._view.optimize} - - ${this._view.runs} -
-
-
- ${this._view.hideWarningsBox} - -
-
- ${this.hardhatCompilation} - ${this._view.compilationButton} -
-
- -
` - - return this._view.compileContainer - } - - promtCompiler () { - modalDialogCustom.prompt( - 'Add a custom compiler', - 'URL', - '', - (url) => this.addCustomCompiler(url) - ) - } - - addCustomCompiler (url) { - this.data.selectedVersion = this._view.versionSelector.value - this._updateVersionSelector(url) - } - - updateAutoCompile (event) { - this.config.set('autoCompile', this._view.autoCompile.checked) - } - - updatehhCompilation (event) { - this.hhCompilation = event.target.checked - } - - compile (event) { - const currentFile = this.config.get('currentFile') - if (!this.isSolFileSelected()) return - - this._setCompilerVersionFromPragma(currentFile) - this.compileTabLogic.runCompiler(this.hhCompilation) - } - - compileIfAutoCompileOn () { - if (this.config.get('autoCompile')) { - this.compile() - } - } - - hideWarnings (event) { - this.config.set('hideWarnings', this._view.hideWarningsBox.checked) - this.compileIfAutoCompileOn() - } - - /* - The following functions are handlers for internal events. - */ - - onchangeOptimize () { - this.compileTabLogic.setOptimize(!!this._view.optimize.checked) - if (this.compileTabLogic.optimize) { - this._view.runs.removeAttribute('disabled') - this.compileTabLogic.setRuns(parseInt(this._view.runs.value)) - } else { - this.compileTabLogic.setRuns(200) - this._view.runs.setAttribute('disabled', '') - } - this.compileIfAutoCompileOn() - } - - onchangeRuns () { - this.compileTabLogic.setRuns(parseInt(this._view.runs.value)) - this.compileIfAutoCompileOn() - } - - onchangeLanguage () { - this.compileTabLogic.setLanguage(this._view.languageSelector.value) - this.compileIfAutoCompileOn() - } - - onchangeEvmVersion () { - const s = this._view.evmVersionSelector - let v = s.value - if (v === 'default') { - v = null - } - this.compileTabLogic.setEvmVersion(v) - for (let i = 0; i < s.options.length; i++) { - if (i === s.selectedIndex) { - s.options[s.selectedIndex].setAttribute('selected', 'selected') - } else { - s.options[i].removeAttribute('selected') - } - } - - this.compileIfAutoCompileOn() - } - - onchangeLoadVersion () { - this.data.selectedVersion = this._view.versionSelector.value - this._updateVersionSelector() - this._updateLanguageSelector() - } - - /* - The following functions map with the above event handlers. - They are an external API for modifying the compiler configuration. - */ - setConfiguration (settings) { - this.setLanguage(settings.language) - this.setEvmVersion(settings.evmVersion) - this.setOptimize(settings.optimize) - this.setRuns(settings.runs) - this.setVersion(settings.version) - } - - setOptimize (enabled) { - this._view.optimize.checked = enabled - this.onchangeOptimize() - } - - setRuns (value) { - if (value) { - this._view.runs.value = value - this.onchangeRuns() - } - } - - setLanguage (lang) { - this._view.languageSelector.value = lang - this.onchangeLanguage() - } - - setEvmVersion (version) { - this._view.evmVersionSelector.value = version || 'default' - this.onchangeEvmVersion() - } - - setVersion (version) { - this._view.versionSelector.value = `soljson-v${version}.js` - this.onchangeLoadVersion() - } - - _shouldBeAdded (version) { - return !version.includes('nightly') || - (version.includes('nightly') && this._view.includeNightlies.checked) - } - - _updateVersionSelector (customUrl = '') { - // update selectedversion if previous one got filtered out - if (!this.data.selectedVersion || !this._shouldBeAdded(this.data.selectedVersion)) { - this.data.selectedVersion = this.data.defaultVersion - } - this._view.versionSelector.innerHTML = '' - this._view.versionSelector.removeAttribute('disabled') - this.queryParams.update({ version: this.data.selectedVersion }) - let url - if (customUrl !== '') { - this.data.selectedVersion = customUrl - this._view.versionSelector.appendChild(yo``) - url = customUrl - this.queryParams.update({ version: this.data.selectedVersion }) - } else if (this.data.selectedVersion === 'builtin') { - let location = window.document.location - let path = location.pathname - if (!path.startsWith('/')) path = '/' + path - location = `${location.protocol}//${location.host}${path}assets/js` - if (location.endsWith('index.html')) location = location.substring(0, location.length - 10) - if (!location.endsWith('/')) location += '/' - url = location + 'soljson.js' - } else { - if (this.data.selectedVersion.indexOf('soljson') !== 0 || helper.checkSpecialChars(this.data.selectedVersion)) { - return console.log('loading ' + this.data.selectedVersion + ' not allowed') - } - url = `${urlFromVersion(this.data.selectedVersion)}` - } - - this.data.allversions.forEach(build => { - const option = build.path === this.data.selectedVersion - ? yo`` - : yo`` - - if (this._shouldBeAdded(option.innerText)) { - this._view.versionSelector.appendChild(option) - } - }) - if (this.data.selectedVersion !== 'builtin' && semver.lt(this._retrieveVersion(), 'v0.4.12+commit.194ff033.js')) { - toaster(yo` -
- Old compiler usage detected. -

You are using a compiler older than v0.4.12.

-

Some functionality may not work.

-
` - ) - } - - // Workers cannot load js on "file:"-URLs and we get a - // "Uncaught RangeError: Maximum call stack size exceeded" error on Chromium, - // resort to non-worker version in that case. - if (canUseWorker(this._retrieveVersion())) { - this.compileTabLogic.compiler.loadVersion(true, url) - this.setVersionText('(loading using worker)') - } else { - this.compileTabLogic.compiler.loadVersion(false, url) - this.setVersionText('(loading)') - } - } - - _updateLanguageSelector () { - // This is the first version when Yul is available - if (!semver.valid(this._retrieveVersion()) || semver.lt(this._retrieveVersion(), 'v0.5.7+commit.6da8b019.js')) { - this._view.languageSelector.setAttribute('disabled', '') - this._view.languageSelector.value = 'Solidity' - this.compileTabLogic.setLanguage('Solidity') - } else { - this._view.languageSelector.removeAttribute('disabled') - } - } - - setVersionText (text) { - if (this._view.version) this._view.version.innerText = text - } - - // fetching both normal and wasm builds and creating a [version, baseUrl] map - async fetchAllVersion (callback) { - let selectedVersion, allVersionsWasm, isURL - let allVersions = [{ path: 'builtin', longVersion: 'Stable local version - 0.8.4' }] - // fetch normal builds - const binRes = await promisedMiniXhr(`${baseURLBin}/list.json`) - // fetch wasm builds - const wasmRes = await promisedMiniXhr(`${baseURLWasm}/list.json`) - if (binRes.event.type === 'error' && wasmRes.event.type === 'error') { - selectedVersion = 'builtin' - return callback(allVersions, selectedVersion) - } - try { - const versions = JSON.parse(binRes.json).builds.slice().reverse() - allVersions = [...allVersions, ...versions] - selectedVersion = this.data.defaultVersion - if (this.queryParams.get().version) selectedVersion = this.queryParams.get().version - // Check if version is a URL and corresponding filename starts with 'soljson' - if (selectedVersion.startsWith('https://')) { - const urlArr = selectedVersion.split('/') - if (urlArr[urlArr.length - 1].startsWith('soljson')) isURL = true - } - if (wasmRes.event.type !== 'error') { - allVersionsWasm = JSON.parse(wasmRes.json).builds.slice().reverse() - } - } catch (e) { - addTooltip('Cannot load compiler version list. It might have been blocked by an advertisement blocker. Please try deactivating any of them from this page and reload. Error: ' + e) - } - // replace in allVersions those compiler builds which exist in allVersionsWasm with new once - if (allVersionsWasm && allVersions) { - allVersions.forEach((compiler, index) => { - const wasmIndex = allVersionsWasm.findIndex(wasmCompiler => { return wasmCompiler.longVersion === compiler.longVersion }) - if (wasmIndex !== -1) { - allVersions[index] = allVersionsWasm[wasmIndex] - pathToURL[compiler.path] = baseURLWasm - } else { - pathToURL[compiler.path] = baseURLBin - } - }) - } - callback(allVersions, selectedVersion, isURL) - } - - scheduleCompilation () { - if (!this.config.get('autoCompile')) return - if (this.data.compileTimeout) window.clearTimeout(this.data.compileTimeout) - this.data.compileTimeout = window.setTimeout(() => this.compileIfAutoCompileOn(), this.data.timeout) - } -} - -module.exports = CompilerContainer diff --git a/libs/remix-ui/solidity-compiler/src/lib/logic/compiler-abstract.ts b/libs/remix-ui/solidity-compiler/src/lib/logic/compiler-abstract.ts deleted file mode 100644 index f0c4fc3ad1..0000000000 --- a/libs/remix-ui/solidity-compiler/src/lib/logic/compiler-abstract.ts +++ /dev/null @@ -1,51 +0,0 @@ -'use strict' -import * as remixLib from '@remix-project/remix-lib' - -const txHelper = remixLib.execution.txHelper - -export class CompilerAbstract { - public languageversion: string - public data: Record - public source: Record - - constructor (languageversion, data, source) { - this.languageversion = languageversion - this.data = data - this.source = source // source code - } - - getContracts () { - return this.data.contracts - } - - getContract (name) { - return txHelper.getContract(name, this.data.contracts) - } - - visitContracts (calllback) { - return txHelper.visitContracts(this.data.contracts, calllback) - } - - getData () { - return this.data - } - - getAsts () { - return this.data.sources // ast - } - - getSourceName (fileIndex) { - if (this.data && this.data.sources) { - return Object.keys(this.data.sources)[fileIndex] - } else if (Object.keys(this.source.sources).length === 1) { - // if we don't have ast, we return the only one filename present. - const sourcesArray = Object.keys(this.source.sources) - return sourcesArray[0] - } - return null - } - - getSourceCode () { - return this.source - } -} diff --git a/libs/remix-ui/solidity-compiler/src/lib/logic/compiler-helpers.ts b/libs/remix-ui/solidity-compiler/src/lib/logic/compiler-helpers.ts deleted file mode 100644 index 64c56bd7a5..0000000000 --- a/libs/remix-ui/solidity-compiler/src/lib/logic/compiler-helpers.ts +++ /dev/null @@ -1,19 +0,0 @@ -'use strict' -import { canUseWorker, urlFromVersion } from './compiler-utils' -import { Compiler } from '@remix-project/remix-solidity' -import { CompilerAbstract } from './compiler-abstract' - -export const compile = async (compilationTargets, settings, contentResolverCallback) => { - return new Promise((resolve) => { - const compiler = new Compiler(contentResolverCallback) - compiler.set('evmVersion', settings.evmVersion) - compiler.set('optimize', settings.optimize) - compiler.set('language', settings.language) - compiler.set('runs', settings.runs) - compiler.loadVersion(canUseWorker(settings.version), urlFromVersion(settings.version)) - compiler.event.register('compilationFinished', (success, compilationData, source) => { - resolve(new CompilerAbstract(settings.version, compilationData, source)) - }) - compiler.event.register('compilerLoaded', () => compiler.compile(compilationTargets, '')) - }) -} diff --git a/libs/remix-ui/solidity-compiler/src/lib/logic/compiler-utils.ts b/libs/remix-ui/solidity-compiler/src/lib/logic/compiler-utils.ts deleted file mode 100644 index fe26765ac1..0000000000 --- a/libs/remix-ui/solidity-compiler/src/lib/logic/compiler-utils.ts +++ /dev/null @@ -1,46 +0,0 @@ -const semver = require('semver') -const minixhr = require('minixhr') -/* global Worker */ - -export const baseURLBin = 'https://binaries.soliditylang.org/bin' -export const baseURLWasm = 'https://binaries.soliditylang.org/wasm' - -export const pathToURL = {} - -/** - * Retrieves the URL of the given compiler version - * @param version is the version of compiler with or without 'soljson-v' prefix and .js postfix - */ -export function urlFromVersion (version) { - if (!version.startsWith('soljson-v')) version = 'soljson-v' + version - if (!version.endsWith('.js')) version = version + '.js' - return `${pathToURL[version]}/${version}` -} - -/** - * Checks if the worker can be used to load a compiler. - * checks a compiler whitelist, browser support and OS. - */ -export function canUseWorker (selectedVersion) { - const version = semver.coerce(selectedVersion) - const isNightly = selectedVersion.includes('nightly') - return browserSupportWorker() && ( - // All compiler versions (including nightlies) after 0.6.3 are wasm compiled - semver.gt(version, '0.6.3') || - // Only releases are wasm compiled starting with 0.3.6 - (semver.gte(version, '0.3.6') && !isNightly) - ) -} - -function browserSupportWorker () { - return document.location.protocol !== 'file:' && Worker !== undefined -} - -// returns a promise for minixhr -export function promisedMiniXhr (url) { - return new Promise((resolve) => { - minixhr(url, (json, event) => { - resolve({ json, event }) - }) - }) -} diff --git a/libs/remix-ui/solidity-compiler/src/lib/logic/index.ts b/libs/remix-ui/solidity-compiler/src/lib/logic/index.ts index b67d648896..e2c8f6d006 100644 --- a/libs/remix-ui/solidity-compiler/src/lib/logic/index.ts +++ b/libs/remix-ui/solidity-compiler/src/lib/logic/index.ts @@ -1,5 +1,2 @@ export * from './compileTabLogic' -export * from './compiler-abstract' -export * from './compiler-helpers' -export * from './compiler-utils' export * from './contract-parser'