From fc29644f50cd0fc5b08d9bc124e3805ff5233f4d Mon Sep 17 00:00:00 2001 From: Iuri Matias Date: Wed, 16 Jan 2019 10:52:12 -0500 Subject: [PATCH] Refactor other tabs, start refactoring compile tab move compile tab styles to its own file refactor: simplify some methods; transform functions into blocks move events to its own method move compiler and compiler import out of _dependencies simplify passing registry to compile tab move queryParams & remove _dependencies var refactor optimize param move to compile tab logic extract code for compiler container into a separate module remove unused method move compiler imports logic out of the UI --- src/app.js | 14 +- src/app/tabs/compile-tab.js | 563 ++----------------- src/app/tabs/compileTab/compileTab.js | 98 ++++ src/app/tabs/compileTab/compilerContainer.js | 227 ++++++++ src/app/tabs/styles/compile-tab-styles.js | 213 +++++++ 5 files changed, 592 insertions(+), 523 deletions(-) create mode 100644 src/app/tabs/compileTab/compileTab.js create mode 100644 src/app/tabs/compileTab/compilerContainer.js create mode 100644 src/app/tabs/styles/compile-tab-styles.js diff --git a/src/app.js b/src/app.js index be343c9de5..038f1bda03 100644 --- a/src/app.js +++ b/src/app.js @@ -413,7 +413,7 @@ Please make a backup of your contracts and start using http://remix.ethereum.org const swapPanelComponent = new SwapPanelComponent() const verticalIconComponent = new VerticalIconsComponent() const swapPanelApi = new SwapPanelApi(swapPanelComponent, verticalIconComponent) // eslint-disable-line - const verticalIconsApi = new VerticalIconsApi(verticalIconComponent) // eslint-disable-line + const verticalIconsApi = new VerticalIconsApi(verticalIconComponent) // eslint-disable-line let appStore = new EntityStore('module', { actives: [], ids: [], entities: {} }) const appManager = new RemixAppManager(appStore, swapPanelApi, verticalIconsApi) @@ -444,13 +444,17 @@ Please make a backup of your contracts and start using http://remix.ethereum.org registry.get('compilersartefacts').api ) let settings = new SettingsTab(self._components.registry) - let analysis = new AnalysisTab(self._components.registry) - let debug = new DebuggerTab(self._components.registry) - let support = new SupportTab(self._components.registry) + let analysis = new AnalysisTab(registry) + let debug = new DebuggerTab() + let support = new SupportTab() let test = new TestTab(self._components.registry, compileTab) let sourceHighlighters = registry.get('editor').api.sourceHighlighters let configProvider = self._components.filesProviders['config'] + registry.get('app').api.event.register('tabChanged', (tabName) => { + if (tabName === 'Support') support.loadTab() + }) + appManager.init([ { profile: this.profile(), api: this }, { profile: udapp.profile(), api: udapp }, @@ -505,7 +509,7 @@ Please make a backup of your contracts and start using http://remix.ethereum.org self.loadFiles(filesToLoad) } - var txLogger = new TxLogger() // eslint-disable-line + var txLogger = new TxLogger() // eslint-disable-line txLogger.event.register('debuggingRequested', (hash) => { debug.debugger().debug(hash) }) let transactionContextAPI = { diff --git a/src/app/tabs/compile-tab.js b/src/app/tabs/compile-tab.js index c96e4d037b..027c8c3143 100644 --- a/src/app/tabs/compile-tab.js +++ b/src/app/tabs/compile-tab.js @@ -3,128 +3,75 @@ const EventEmitter = require('events') const async = require('async') const $ = require('jquery') const yo = require('yo-yo') -const csjs = require('csjs-inject') const copy = require('clipboard-copy') -var minixhr = require('minixhr') -var remixTests = require('remix-tests') -var Compiler = require('remix-solidity').Compiler -var CompilerImport = require('../compiler/compiler-imports') var QueryParams = require('../../lib/query-params') -var globalRegistry = require('../../global/registry') const TreeView = require('../ui/TreeView') const modalDialog = require('../ui/modaldialog') const copyToClipboard = require('../ui/copy-to-clipboard') const modalDialogCustom = require('../ui/modal-dialog-custom') -const styleGuide = require('../ui/styles-guide/theme-chooser') const parseContracts = require('../contract/contractParser') const publishOnSwarm = require('../contract/publishOnSwarm') const addTooltip = require('../ui/tooltip') -var helper = require('../../lib/helper') +const styleGuide = require('../ui/styles-guide/theme-chooser') const styles = styleGuide.chooser() +var css = require('./styles/compile-tab-styles') + +const CompileTabLogic = require('./compileTab/compileTab.js') +const CompilerContainer = require('./compileTab/compilerContainer.js') + +class CompileTab { -module.exports = class CompileTab { - constructor (localRegistry) { + constructor (registry) { const self = this self.event = new EventEmitter() self._view = { el: null, - autoCompile: null, - compileButton: null, warnCompilationSlow: null, - compileIcon: null, - compileContainer: null, errorContainer: null, errorContainerHead: null, contractNames: null, - contractEl: null, - config: { - solidity: null - }, - optimize: null + contractEl: null } - self._components = {} - self._components.registry = localRegistry || globalRegistry - self._components.queryParams = new QueryParams() - self._components.compilerImport = new CompilerImport(() => { return self._deps.config.get('settings/gist-access-token') }) - self._components.compiler = new Compiler((url, cb) => self.importFileCb(url, cb)) + self.queryParams = new QueryParams() + // dependencies self._deps = { - editor: self._components.registry.get('editor').api, - config: self._components.registry.get('config').api, - renderer: self._components.registry.get('renderer').api, - swarmfileProvider: self._components.registry.get('fileproviders/swarm').api, - fileManager: self._components.registry.get('filemanager').api, - fileProviders: self._components.registry.get('fileproviders').api + editor: registry.get('editor').api, + config: registry.get('config').api, + renderer: registry.get('renderer').api, + swarmfileProvider: registry.get('fileproviders/swarm').api, + fileManager: registry.get('filemanager').api, + fileProviders: registry.get('fileproviders').api, + pluginManager: registry.get('pluginmanager').api } self.data = { - hideWarnings: self._deps.config.get('hideWarnings') || false, - autoCompile: self._deps.config.get('autoCompile'), - compileTimeout: null, - contractsDetails: {}, - maxTime: 1000, - timeout: 300, - allversions: null, - selectedVersion: null, - defaultVersion: 'soljson-v0.5.1+commit.c8a2cb62.js', // this default version is defined: in makeMockCompiler (for browser test) and in package.json (downloadsolc_root) for the builtin compiler - baseurl: 'https://solc-bin.ethereum.org/bin' + contractsDetails: {} } - self.data.optimize = self._components.queryParams.get().optimize - self.data.optimize = self.data.optimize === 'true' - self._components.queryParams.update({ optimize: self.data.optimize }) - self._components.compiler.setOptimize(self.data.optimize) - self._deps.editor.event.register('contentChanged', scheduleCompilation) - self._deps.editor.event.register('sessionSwitched', scheduleCompilation) - function scheduleCompilation () { - if (!self._deps.config.get('autoCompile')) return - if (self.data.compileTimeout) window.clearTimeout(self.data.compileTimeout) - self.data.compileTimeout = window.setTimeout(() => self.runCompiler(), self.data.timeout) - } - self._components.compiler.event.register('compilationDuration', function tabHighlighting (speed) { - if (!self._view.warnCompilationSlow) return - if (speed > self.data.maxTime) { - const msg = `Last compilation took ${speed}ms. We suggest to turn off autocompilation.` - self._view.warnCompilationSlow.setAttribute('title', msg) - self._view.warnCompilationSlow.style.visibility = 'visible' - } else { - self._view.warnCompilationSlow.style.visibility = 'hidden' - } - }) - self._deps.editor.event.register('contentChanged', function changedFile () { - if (!self._view.compileIcon) return - self._view.compileIcon.classList.add(`${css.bouncingIcon}`) // @TODO: compileView tab - }) - self._components.compiler.event.register('loadingCompiler', function start () { - if (!self._view.compileIcon) return - self._view.compileIcon.classList.add(`${css.spinningIcon}`) - self._view.warnCompilationSlow.style.visibility = 'hidden' - self._view.compileIcon.setAttribute('title', 'compiler is loading, please wait a few moments.') - }) - self._components.compiler.event.register('compilationStarted', function start () { - if (!self._view.compileIcon) return + this.compileTabLogic = new CompileTabLogic(self.queryParams, self._deps.fileManager, self._deps.editor, self._deps.config, self._deps.fileProviders) + this.compiler = this.compileTabLogic.compiler + this.compileTabLogic.init() + + this.compilerContainer = new CompilerContainer(self.compileTabLogic, self._deps.editor, self._deps.config, self.queryParams) + + this.listenToEvents() + } + + listenToEvents () { + const self = this + + self.compiler.event.register('compilationStarted', () => { self._view.errorContainer.innerHTML = '' self._view.errorContainerHead.innerHTML = '' - self._view.compileIcon.classList.remove(`${css.bouncingIcon}`) - self._view.compileIcon.classList.add(`${css.spinningIcon}`) - self._view.compileIcon.setAttribute('title', 'compiling...') }) - self._components.compiler.event.register('compilerLoaded', function loaded () { - if (!self._view.compileIcon) return - self._view.compileIcon.classList.remove(`${css.spinningIcon}`) - self._view.compileIcon.setAttribute('title', '') - }) - self._components.compiler.event.register('compilationFinished', function finish (success, data, source) { + self.compiler.event.register('compilationFinished', (success, data, source) => { if (success) { // forwarding the event to the appManager infra self.event.emit('compilationFinished', source.target, source, self.data.selectedVersion, data) } - if (self._view.compileIcon) { - self._view.compileIcon.style.color = styles.colors.black - self._view.compileIcon.classList.remove(`${css.spinningIcon}`) - self._view.compileIcon.classList.remove(`${css.bouncingIcon}`) - self._view.compileIcon.setAttribute('title', 'idle') - } + const compileTab = document.querySelector('.compileView') + compileTab.style.color = styles.colors.black // reset the contractMetadata list (used by the publish action) self.data.contractsDetails = {} // refill the dropdown list @@ -132,8 +79,8 @@ module.exports = class CompileTab { if (success) { // TODO consider using compile tab as a proper module instead of just forwarding event self._view.contractNames.removeAttribute('disabled') - self._components.compiler.visitContracts(contract => { - self.data.contractsDetails[contract.name] = parseContracts(contract.name, contract.object, self._components.compiler.getSource(contract.file)) + self.compiler.visitContracts(contract => { + self.data.contractsDetails[contract.name] = parseContracts(contract.name, contract.object, self.compiler.getSource(contract.file)) var contractName = yo`` self._view.contractNames.appendChild(contractName) }) @@ -154,7 +101,7 @@ module.exports = class CompileTab { } if (data.errors && data.errors.length) { error = true - data.errors.forEach(function (err) { + data.errors.forEach((err) => { if (self._deps.config.get('hideWarnings')) { if (err.severity !== 'warning') { self._deps.renderer.error(err.formattedMessage, self._view.errorContainer, {type: err.severity}) @@ -165,102 +112,26 @@ module.exports = class CompileTab { }) } if (!error && data.contracts) { - self._components.compiler.visitContracts((contract) => { + self.compiler.visitContracts((contract) => { self._deps.renderer.error(contract.name, self._view.errorContainer, {type: 'success'}) }) } }) // Run the compiler instead of trying to save the website - $(window).keydown(function (e) { + $(window).keydown((e) => { // ctrl+s or command+s if ((e.metaKey || e.ctrlKey) && e.keyCode === 83) { e.preventDefault() - self.runCompiler() + self.compileTabLogic.runCompiler() } }) } - getCompilationResult (cb) { - cb(null, this._components.compiler.lastCompilationResult) - } - profile () { - return { - name: 'solidity', - methods: ['getCompilationResult'], - events: ['compilationFinished'], - icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNDcyIDBxMjYgMCA0NSAxOXQxOSA0NXYxNjY0cTAgMjYtMTkgNDV0LTQ1IDE5aC0xMjgwcS0yNiAwLTQ1LTE5dC0xOS00NXYtMTY2NHEwLTI2IDE5LTQ1dDQ1LTE5aDEyODB6bS04MzIgMjg4djY0cTAgMTQgOSAyM3QyMyA5aDY0cTE0IDAgMjMtOXQ5LTIzdi02NHEwLTE0LTktMjN0LTIzLTloLTY0cS0xNCAwLTIzIDl0LTkgMjN6bTAgMjU2djY0cTAgMTQgOSAyM3QyMyA5aDY0cTE0IDAgMjMtOXQ5LTIzdi02NHEwLTE0LTktMjN0LTIzLTloLTY0cS0xNCAwLTIzIDl0LTkgMjN6bTAgMjU2djY0cTAgMTQgOSAyM3QyMyA5aDY0cTE0IDAgMjMtOXQ5LTIzdi02NHEwLTE0LTktMjN0LTIzLTloLTY0cS0xNCAwLTIzIDl0LTkgMjN6bTAgMjU2djY0cTAgMTQgOSAyM3QyMyA5aDY0cTE0IDAgMjMtOXQ5LTIzdi02NHEwLTE0LTktMjN0LTIzLTloLTY0cS0xNCAwLTIzIDl0LTkgMjN6bS0xMjggMzIwdi02NHEwLTE0LTktMjN0LTIzLTloLTY0cS0xNCAwLTIzIDl0LTkgMjN2NjRxMCAxNCA5IDIzdDIzIDloNjRxMTQgMCAyMy05dDktMjN6bTAtMjU2di02NHEwLTE0LTktMjN0LTIzLTloLTY0cS0xNCAwLTIzIDl0LTkgMjN2NjRxMCAxNCA5IDIzdDIzIDloNjRxMTQgMCAyMy05dDktMjN6bTAtMjU2di02NHEwLTE0LTktMjN0LTIzLTloLTY0cS0xNCAwLTIzIDl0LTkgMjN2NjRxMCAxNCA5IDIzdDIzIDloNjRxMTQgMCAyMy05dDktMjN6bTAtMjU2di02NHEwLTE0LTktMjN0LTIzLTloLTY0cS0xNCAwLTIzIDl0LTkgMjN2NjRxMCAxNCA5IDIzdDIzIDloNjRxMTQgMCAyMy05dDktMjN6bTAtMjU2di02NHEwLTE0LTktMjN0LTIzLTloLTY0cS0xNCAwLTIzIDl0LTkgMjN2NjRxMCAxNCA5IDIzdDIzIDloNjRxMTQgMCAyMy05dDktMjN6bTUxMiAxMjgwdi0xOTJxMC0xNC05LTIzdC0yMy05aC0zMjBxLTE0IDAtMjMgOXQtOSAyM3YxOTJxMCAxNCA5IDIzdDIzIDloMzIwcTE0IDAgMjMtOXQ5LTIzem0wLTUxMnYtNjRxMC0xNC05LTIzdC0yMy05aC02NHEtMTQgMC0yMyA5dC05IDIzdjY0cTAgMTQgOSAyM3QyMyA5aDY0cTE0IDAgMjMtOXQ5LTIzem0wLTI1NnYtNjRxMC0xNC05LTIzdC0yMy05aC02NHEtMTQgMC0yMyA5dC05IDIzdjY0cTAgMTQgOSAyM3QyMyA5aDY0cTE0IDAgMjMtOXQ5LTIzem0wLTI1NnYtNjRxMC0xNC05LTIzdC0yMy05aC02NHEtMTQgMC0yMyA5dC05IDIzdjY0cTAgMTQgOSAyM3QyMyA5aDY0cTE0IDAgMjMtOXQ5LTIzem0wLTI1NnYtNjRxMC0xNC05LTIzdC0yMy05aC02NHEtMTQgMC0yMyA5dC05IDIzdjY0cTAgMTQgOSAyM3QyMyA5aDY0cTE0IDAgMjMtOXQ5LTIzem0yNTYgMTAyNHYtNjRxMC0xNC05LTIzdC0yMy05aC02NHEtMTQgMC0yMyA5dC05IDIzdjY0cTAgMTQgOSAyM3QyMyA5aDY0cTE0IDAgMjMtOXQ5LTIzem0wLTI1NnYtNjRxMC0xNC05LTIzdC0yMy05aC02NHEtMTQgMC0yMyA5dC05IDIzdjY0cTAgMTQgOSAyM3QyMyA5aDY0cTE0IDAgMjMtOXQ5LTIzem0wLTI1NnYtNjRxMC0xNC05LTIzdC0yMy05aC02NHEtMTQgMC0yMyA5dC05IDIzdjY0cTAgMTQgOSAyM3QyMyA5aDY0cTE0IDAgMjMtOXQ5LTIzem0wLTI1NnYtNjRxMC0xNC05LTIzdC0yMy05aC02NHEtMTQgMC0yMyA5dC05IDIzdjY0cTAgMTQgOSAyM3QyMyA5aDY0cTE0IDAgMjMtOXQ5LTIzem0wLTI1NnYtNjRxMC0xNC05LTIzdC0yMy05aC02NHEtMTQgMC0yMyA5dC05IDIzdjY0cTAgMTQgOSAyM3QyMyA5aDY0cTE0IDAgMjMtOXQ5LTIzeiIvPjwvc3ZnPg==', - description: 'compile solidity contracts' - } - } - addWarning (msg, settings) { - const self = this - self._deps.renderer.error(msg, self._view.errorContainerHead, settings) - } + render () { const self = this if (self._view.el) return self._view.el - function onchangeLoadVersion (event) { - self.data.selectedVersion = self._view.versionSelector.value - self._updateVersionSelector() - } - - function onchangeOptimize (event) { - self.data.optimize = !!self._view.optimize.checked - self._components.queryParams.update({ optimize: self.data.optimize }) - self._components.compiler.setOptimize(self.data.optimize) - self.runCompiler() - } - - self._components.compiler.event.register('compilerLoaded', (version) => self.setVersionText(version)) - self.fetchAllVersion((allversions, selectedVersion) => { - self.data.allversions = allversions - self.data.selectedVersion = selectedVersion - if (self._view.versionSelector) self._updateVersionSelector() - }) - - self._view.optimize = yo`` - if (self.data.optimize) self._view.optimize.setAttribute('checked', '') - - self._view.versionSelector = yo` - ` - self._view.version = yo`` - - self._view.warnCompilationSlow = yo`` - self._view.compileIcon = yo`` - self._view.compileButton = yo`
${self._view.compileIcon} Start to compile (Ctrl-S)
` - self._view.autoCompile = yo`` - self._view.hideWarningsBox = yo`` - if (self.data.autoCompile) self._view.autoCompile.setAttribute('checked', '') - if (self.data.hideWarnings) self._view.hideWarningsBox.setAttribute('checked', '') - self._view.compileContainer = yo` -
-
- Current version: ${self._view.version} -
- ${self._view.versionSelector} -
-
-
-
- ${self._view.autoCompile} - -
-
-
${self._view.optimize}
- -
-
- ${self._view.hideWarningsBox} - -
-
- ${self._view.compileButton} -
-
-
` self._view.errorContainer = yo`
` self._view.errorContainerHead = yo`
` self._view.contractNames = yo`` @@ -284,7 +155,7 @@ module.exports = class CompileTab { ` self._view.el = yo`
- ${self._view.compileContainer} + ${this.compilerContainer.render()} ${self._view.contractEl} ${self._view.errorContainerHead} ${self._view.errorContainer} @@ -303,12 +174,6 @@ module.exports = class CompileTab { 'swarmLocation': 'Swarm url where all metadata information can be found (contract needs to be published first)', 'web3Deploy': 'Copy/paste this code to any JavaScript/Web3 console to deploy this contract' } - function updateAutoCompile (event) { self._deps.config.set('autoCompile', self._view.autoCompile.checked) } - function compile (event) { self.runCompiler() } - function hideWarnings (event) { - self._deps.config.set('hideWarnings', self._view.hideWarningsBox.checked) - compile() - } function getContractProperty (property) { const select = self._view.contractNames if (select.children.length > 0 && select.selectedIndex >= 0) { @@ -419,345 +284,7 @@ module.exports = class CompileTab { } return self._view.el } - setVersionText (text) { - const self = this - self.data.version = text - if (self._view.version) self._view.version.innerText = text - } - _updateVersionSelector () { - const self = this - self._view.versionSelector.innerHTML = '' - self._view.versionSelector.appendChild(yo``) - self.data.allversions.forEach(build => self._view.versionSelector.appendChild(yo``)) - self._view.versionSelector.removeAttribute('disabled') - self._components.queryParams.update({ version: self.data.selectedVersion }) - var url - if (self.data.selectedVersion === 'builtin') { - var location = window.document.location - location = location.protocol + '//' + location.host + '/' + location.pathname - if (location.endsWith('index.html')) location = location.substring(0, location.length - 10) - if (!location.endsWith('/')) location += '/' - url = location + 'soljson.js' - } else { - if (self.data.selectedVersion.indexOf('soljson') !== 0 || helper.checkSpecialChars(self.data.selectedVersion)) { - return console.log('loading ' + self.data.selectedVersion + ' not allowed') - } - url = `${self.data.baseurl}/${self.data.selectedVersion}` - } - var isFirefox = typeof InstallTrigger !== 'undefined' - if (document.location.protocol !== 'file:' && Worker !== undefined && isFirefox) { - // 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. - self._components.compiler.loadVersion(true, url) - self.setVersionText('(loading using worker)') - } else { - self._components.compiler.loadVersion(false, url) - self.setVersionText('(loading)') - } - } - fetchAllVersion (callback) { - var self = this - minixhr(`${self.data.baseurl}/list.json`, function (json, event) { - // @TODO: optimise and cache results to improve app loading times - var allversions, selectedVersion - if (event.type !== 'error') { - try { - const data = JSON.parse(json) - allversions = data.builds.slice().reverse() - selectedVersion = self.data.defaultVersion - if (self._components.queryParams.get().version) selectedVersion = self._components.queryParams.get().version - } 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.') - } - } else { - allversions = [{ path: 'builtin', longVersion: 'latest local version' }] - selectedVersion = 'builtin' - } - callback(allversions, selectedVersion) - }) - } - runCompiler () { - const self = this - self._deps.fileManager.saveCurrentFile() - self._deps.editor.clearAnnotations() - var currentFile = self._deps.config.get('currentFile') - if (currentFile) { - if (/.(.sol)$/.exec(currentFile)) { - // only compile *.sol file. - var target = currentFile - var sources = {} - var provider = self._deps.fileManager.fileProviderOf(currentFile) - if (provider) { - provider.get(target, (error, content) => { - if (error) { - console.log(error) - } else { - sources[target] = { content } - self._components.compiler.compile(sources, target) - } - }) - } else { - console.log('cannot compile ' + currentFile + '. Does not belong to any explorer') - } - } - } - } - importExternal (url, cb) { - const self = this - self._components.compilerImport.import(url, - (loadingMsg) => { - addTooltip(loadingMsg) - }, - (error, content, cleanUrl, type, url) => { - if (!error) { - if (self._deps.fileProviders[type]) { - self._deps.fileProviders[type].addReadOnly(cleanUrl, content, url) - } - cb(null, content) - } else { - cb(error) - } - }) - } - importFileCb (url, filecb) { - const self = this - if (url.indexOf('/remix_tests.sol') !== -1) { - return filecb(null, remixTests.assertLibCode) - } - var provider = self._deps.fileManager.fileProviderOf(url) - if (provider) { - if (provider.type === 'localhost' && !provider.isConnected()) { - return filecb(`file provider ${provider.type} not available while trying to resolve ${url}`) - } - provider.exists(url, (error, exist) => { - if (error) return filecb(error) - if (exist) { - return provider.get(url, filecb) - } else { - self.importExternal(url, filecb) - } - }) - } else if (self._components.compilerImport.isRelativeImport(url)) { - // try to resolve localhost modules (aka truffle imports) - var splitted = /([^/]+)\/(.*)$/g.exec(url) - async.tryEach([ - (cb) => { self.importFileCb('localhost/installed_contracts/' + url, cb) }, - (cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { self.importFileCb('localhost/installed_contracts/' + splitted[1] + '/contracts/' + splitted[2], cb) } }, - (cb) => { self.importFileCb('localhost/node_modules/' + url, cb) }, - (cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { self.importFileCb('localhost/node_modules/' + splitted[1] + '/contracts/' + splitted[2], cb) } }], - (error, result) => { filecb(error, result) } - ) - } else { - self.importExternal(url, filecb) - } - } + } -const css = csjs` - .title { - font-size: 1.1em; - font-weight: bold; - margin-bottom: 1em; - } - .panicError { - color: red; - font-size: 20px; - } - .crow { - display: flex; - overflow: auto; - clear: both; - padding: .2em; - } - .checkboxText { - font-weight: normal; - } - .crow label { - cursor:pointer; - } - .crowNoFlex { - overflow: auto; - clear: both; - } - .select { - font-weight: bold; - margin: 10px 0px; - ${styles.rightPanel.settingsTab.dropdown_SelectCompiler}; - } - .info { - ${styles.rightPanel.settingsTab.box_SolidityVersionInfo} - margin-bottom: 1em; - word-break: break-word; - } - .compileTabView { - padding: 2%; - } - .contract { - display: block; - margin: 3% 0; - } - .compileContainer { - ${styles.rightPanel.compileTab.box_CompileContainer}; - margin-bottom: 2%; - } - .autocompileContainer { - display: flex; - align-items: center; - } - .hideWarningsContainer { - display: flex; - align-items: center; - } - .autocompile {} - .autocompileTitle { - font-weight: bold; - margin: 1% 0; - } - .autocompileText { - margin: 1% 0; - font-size: 12px; - overflow: hidden; - word-break: normal; - line-height: initial; - } - .warnCompilationSlow { - color: ${styles.rightPanel.compileTab.icon_WarnCompilation_Color}; - margin-left: 1%; - } - .compileButtons { - display: flex; - align-items: center; - flex-wrap: wrap; - justify-content: flex-end; - } - .name { - display: flex; - } - .size { - display: flex; - } - .checkboxes { - display: flex; - width: 100%; - justify-content: space-between; - flex-wrap: wrap; - } - .compileButton { - ${styles.rightPanel.compileTab.button_Compile}; - width: 100%; - margin: 15px 0 10px 0; - font-size: 12px; - } - .container { - ${styles.rightPanel.compileTab.box_CompileContainer}; - margin: 0; - margin-bottom: 2%; - } - .contractContainer { - display: flex; - align-items: center; - margin-bottom: 2%; - } - .optimizeContainer { - display: flex; - } - .contractNames { - ${styles.rightPanel.compileTab.dropdown_CompileContract}; - width:78%; - } - .contractHelperButtons { - display: flex; - cursor: pointer; - text-align: center; - justify-content: flex-end; - margin: 15px 15px 10px 0; - } - .copyButton { - ${styles.rightPanel.compileTab.button_Publish}; - padding: 0 7px; - min-width: 50px; - width: auto; - margin-left: 5px; - background-color: inherit; - border: inherit; - } - .bytecodeButton { - min-width: 80px; - } - .copyIcon { - margin-right: 5px; - } - .details { - ${styles.rightPanel.compileTab.button_Details}; - min-width: 70px; - width: 80px; - } - .publish { - display: flex; - align-items: center; - margin-left: 10px; - cursor: pointer; - } - .log { - ${styles.rightPanel.compileTab.box_CompileContainer}; - display: flex; - flex-direction: column; - margin-bottom: 5%; - overflow: visible; - } - .key { - margin-right: 5px; - color: ${styles.rightPanel.text_Primary}; - text-transform: uppercase; - width: 100%; - } - .value { - display: flex; - width: 100%; - margin-top: 1.5%; - } - .questionMark { - margin-left: 2%; - cursor: pointer; - color: ${styles.rightPanel.icon_Color_TogglePanel}; - } - .questionMark:hover { - color: ${styles.rightPanel.icon_HoverColor_TogglePanel}; - } - .detailsJSON { - padding: 8px 0; - background-color: ${styles.rightPanel.modalDialog_BackgroundColor_Primary}; - border: none; - color: ${styles.rightPanel.modalDialog_text_Secondary}; - } - .icon { - margin-right: 0.3em; - } - .spinningIcon { - margin-right: .3em; - animation: spin 2s linear infinite; - } - .bouncingIcon { - margin-right: .3em; - animation: bounce 2s infinite; - } - @keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } - } - @-webkit-keyframes bounce { - 0% { - margin-bottom: 0; - color: ${styles.colors.transparent}; - } - 70% { - margin-bottom: 0; - color: ${styles.rightPanel.text_Secondary}; - } - 100% { - margin-bottom: 0; - color: ${styles.colors.transparent}; - } - } -` +module.exports = CompileTab diff --git a/src/app/tabs/compileTab/compileTab.js b/src/app/tabs/compileTab/compileTab.js new file mode 100644 index 0000000000..14a10cf31d --- /dev/null +++ b/src/app/tabs/compileTab/compileTab.js @@ -0,0 +1,98 @@ +const async = require('async') +var remixTests = require('remix-tests') +var Compiler = require('remix-solidity').Compiler +var CompilerImport = require('../../compiler/compiler-imports') + +// TODO: move this to the UI +const addTooltip = require('../../ui/tooltip') + +class CompileTab { + + constructor (queryParams, fileManager, editor, config, fileProviders) { + this.queryParams = queryParams + this.compilerImport = new CompilerImport() + this.compiler = new Compiler((url, cb) => this.importFileCb(url, cb)) + this.fileManager = fileManager + this.editor = editor + this.config = config + this.fileProviders = fileProviders + } + + init () { + this.optimize = this.queryParams.get().optimize + this.optimize = this.optimize === 'true' + this.queryParams.update({ optimize: this.optimize }) + this.compiler.setOptimize(this.optimize) + } + + setOptimize (newOptimizeValue) { + this.optimize = newOptimizeValue + this.queryParams.update({ optimize: this.optimize }) + this.compiler.setOptimize(this.optimize) + } + + runCompiler () { + this.fileManager.saveCurrentFile() + this.editor.clearAnnotations() + var currentFile = this.config.get('currentFile') + if (!currentFile && !/.(.sol)$/.exec(currentFile)) return + // only compile *.sol file. + var target = currentFile + var sources = {} + var provider = this.fileManager.fileProviderOf(currentFile) + if (!provider) return console.log('cannot compile ' + currentFile + '. Does not belong to any explorer') + provider.get(target, (error, content) => { + if (error) return console.log(error) + sources[target] = { content } + this.compiler.compile(sources, target) + }) + } + + importExternal (url, cb) { + this.compilerImport.import(url, + + // TODO: move to an event that is generated, the UI shouldn't be here + (loadingMsg) => { addTooltip(loadingMsg) }, + (error, content, cleanUrl, type, url) => { + if (error) return cb(error) + + if (this._deps.fileProviders[type]) { + this._deps.fileProviders[type].addReadOnly(cleanUrl, content, url) + } + cb(null, content) + }) + } + + importFileCb (url, filecb) { + if (url.indexOf('/remix_tests.sol') !== -1) return filecb(null, remixTests.assertLibCode) + + var provider = this._deps.fileManager.fileProviderOf(url) + if (provider) { + if (provider.type === 'localhost' && !provider.isConnected()) { + return filecb(`file provider ${provider.type} not available while trying to resolve ${url}`) + } + return provider.exists(url, (error, exist) => { + if (error) return filecb(error) + if (exist) { + return provider.get(url, filecb) + } + this.importExternal(url, filecb) + }) + } + if (this.compilerImport.isRelativeImport(url)) { + // try to resolve localhost modules (aka truffle imports) + var splitted = /([^/]+)\/(.*)$/g.exec(url) + return async.tryEach([ + (cb) => { this.importFileCb('localhost/installed_contracts/' + url, cb) }, + (cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.importFileCb('localhost/installed_contracts/' + splitted[1] + '/contracts/' + splitted[2], cb) } }, + (cb) => { this.importFileCb('localhost/node_modules/' + url, cb) }, + (cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.importFileCb('localhost/node_modules/' + splitted[1] + '/contracts/' + splitted[2], cb) } }], + (error, result) => { filecb(error, result) } + ) + } + this.importExternal(url, filecb) + } + +} + +module.exports = CompileTab diff --git a/src/app/tabs/compileTab/compilerContainer.js b/src/app/tabs/compileTab/compilerContainer.js new file mode 100644 index 0000000000..d9803c9e56 --- /dev/null +++ b/src/app/tabs/compileTab/compilerContainer.js @@ -0,0 +1,227 @@ +/* global Worker */ +const yo = require('yo-yo') +var minixhr = require('minixhr') +var helper = require('../../../lib/helper') +const addTooltip = require('../../ui/tooltip') + +const styleGuide = require('../../ui/styles-guide/theme-chooser') +const styles = styleGuide.chooser() +var css = require('../styles/compile-tab-styles') + +class CompilerContainer { + + constructor (compileTabLogic, editor, config, queryParams) { + this._view = {} + this.compileTabLogic = compileTabLogic + this.editor = editor + this.config = config + this.queryParams = queryParams + + this.data = { + hideWarnings: config.get('hideWarnings') || false, + autoCompile: config.get('autoCompile'), + compileTimeout: null, + timeout: 300, + allversions: null, + selectedVersion: null, + defaultVersion: 'soljson-v0.5.1+commit.c8a2cb62.js', // this default version is defined: in makeMockCompiler (for browser test) and in package.json (downloadsolc_root) for the builtin compiler + baseurl: 'https://solc-bin.ethereum.org/bin' + } + + this.listenToEvents() + } + + listenToEvents () { + this.editor.event.register('contentChanged', this.scheduleCompilation.bind(this)) + this.editor.event.register('sessionSwitched', this.scheduleCompilation.bind(this)) + + 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._view.compileIcon.classList.add(`${css.bouncingIcon}`) // @TODO: compileView tab + }) + + this.compileTabLogic.compiler.event.register('loadingCompiler', () => { + if (!this._view.compileIcon) return + this._view.compileIcon.classList.add(`${css.spinningIcon}`) + this._view.warnCompilationSlow.style.visibility = 'hidden' + this._view.compileIcon.setAttribute('title', 'compiler is loading, please wait a few moments.') + }) + + this.compileTabLogic.compiler.event.register('compilationStarted', () => { + if (!this._view.compileIcon) return + this._view.compileIcon.classList.remove(`${css.bouncingIcon}`) + this._view.compileIcon.classList.add(`${css.spinningIcon}`) + this._view.compileIcon.setAttribute('title', 'compiling...') + }) + + this.compileTabLogic.compiler.event.register('compilerLoaded', () => { + if (!this._view.compileIcon) return + this._view.compileIcon.classList.remove(`${css.spinningIcon}`) + this._view.compileIcon.setAttribute('title', '') + }) + + this.compileTabLogic.compiler.event.register('compilationFinished', (success, data, source) => { + if (!this._view.compileIcon) return + this._view.compileIcon.style.color = styles.colors.black + this._view.compileIcon.classList.remove(`${css.spinningIcon}`) + this._view.compileIcon.classList.remove(`${css.bouncingIcon}`) + this._view.compileIcon.setAttribute('title', 'idle') + }) + } + + render () { + this.compileTabLogic.compiler.event.register('compilerLoaded', (version) => this.setVersionText(version)) + this.fetchAllVersion((allversions, selectedVersion) => { + this.data.allversions = allversions + this.data.selectedVersion = selectedVersion + if (this._view.versionSelector) this._updateVersionSelector() + }) + + this._view.warnCompilationSlow = yo`` + this._view.compileIcon = yo`` + this._view.compileButton = yo`
${this._view.compileIcon} Start to compile (Ctrl-S)
` + this._view.autoCompile = yo`` + this._view.hideWarningsBox = yo`` + if (this.data.autoCompile) this._view.autoCompile.setAttribute('checked', '') + if (this.data.hideWarnings) this._view.hideWarningsBox.setAttribute('checked', '') + + this._view.optimize = yo`` + if (this.compileTabLogic.optimize) this._view.optimize.setAttribute('checked', '') + + this._view.versionSelector = yo` + ` + this._view.version = yo`` + + this._view.compileContainer = yo` +
+
+ Current version: ${this._view.version} +
+ ${this._view.versionSelector} +
+
+
+
+ ${this._view.autoCompile} + +
+
+
${this._view.optimize}
+ +
+
+ ${this._view.hideWarningsBox} + +
+
+ ${this._view.compileButton} +
+
+
` + + return this._view.compileContainer + } + + updateAutoCompile (event) { + this.config.set('autoCompile', this._view.autoCompile.checked) + } + + compile (event) { + this.compileTabLogic.runCompiler() + } + + hideWarnings (event) { + this.config.set('hideWarnings', this._view.hideWarningsBox.checked) + this.compile() + } + + onchangeOptimize () { + this.compileTabLogic.setOptimize(!!this._view.optimize.checked) + this.compileTabLogic.runCompiler() + } + + onchangeLoadVersion (event) { + this.data.selectedVersion = this._view.versionSelector.value + this._updateVersionSelector() + } + + _updateVersionSelector () { + this._view.versionSelector.innerHTML = '' + this._view.versionSelector.appendChild(yo``) + this.data.allversions.forEach(build => this._view.versionSelector.appendChild(yo``)) + this._view.versionSelector.removeAttribute('disabled') + this.queryParams.update({ version: this.data.selectedVersion }) + var url + if (this.data.selectedVersion === 'builtin') { + var location = window.document.location + location = location.protocol + '//' + location.host + '/' + location.pathname + 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 = `${this.data.baseurl}/${this.data.selectedVersion}` + } + var isFirefox = typeof InstallTrigger !== 'undefined' + if (document.location.protocol !== 'file:' && Worker !== undefined && isFirefox) { + // 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. + this.compileTabLogic.compiler.loadVersion(true, url) + this.setVersionText('(loading using worker)') + } else { + this.compileTabLogic.compiler.loadVersion(false, url) + this.setVersionText('(loading)') + } + } + + setVersionText (text) { + this.data.version = text + if (this._view.version) this._view.version.innerText = text + } + + fetchAllVersion (callback) { + minixhr(`${this.data.baseurl}/list.json`, (json, event) => { + // @TODO: optimise and cache results to improve app loading times + var allversions, selectedVersion + if (event.type !== 'error') { + try { + const data = JSON.parse(json) + allversions = data.builds.slice().reverse() + selectedVersion = this.data.defaultVersion + if (this.queryParams.get().version) selectedVersion = this.queryParams.get().version + } 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.') + } + } else { + allversions = [{ path: 'builtin', longVersion: 'latest local version' }] + selectedVersion = 'builtin' + } + callback(allversions, selectedVersion) + }) + } + + scheduleCompilation () { + if (!this.config.get('autoCompile')) return + if (this.data.compileTimeout) window.clearTimeout(this.data.compileTimeout) + this.data.compileTimeout = window.setTimeout(() => this.compileTabLogic.runCompiler(), this.data.timeout) + } + +} + +module.exports = CompilerContainer diff --git a/src/app/tabs/styles/compile-tab-styles.js b/src/app/tabs/styles/compile-tab-styles.js new file mode 100644 index 0000000000..84a171b998 --- /dev/null +++ b/src/app/tabs/styles/compile-tab-styles.js @@ -0,0 +1,213 @@ +const csjs = require('csjs-inject') +const styleGuide = require('../../ui/styles-guide/theme-chooser') +const styles = styleGuide.chooser() + +const css = csjs` + .title { + font-size: 1.1em; + font-weight: bold; + margin-bottom: 1em; + } + .panicError { + color: red; + font-size: 20px; + } + .crow { + display: flex; + overflow: auto; + clear: both; + padding: .2em; + } + .checkboxText { + font-weight: normal; + } + .crow label { + cursor:pointer; + } + .crowNoFlex { + overflow: auto; + clear: both; + } + .select { + font-weight: bold; + margin: 10px 0px; + ${styles.rightPanel.settingsTab.dropdown_SelectCompiler}; + } + .info { + ${styles.rightPanel.settingsTab.box_SolidityVersionInfo} + margin-bottom: 1em; + word-break: break-word; + } + .compileTabView { + padding: 2%; + } + .contract { + display: block; + margin: 3% 0; + } + .compileContainer { + ${styles.rightPanel.compileTab.box_CompileContainer}; + margin-bottom: 2%; + } + .autocompileContainer { + display: flex; + align-items: center; + } + .hideWarningsContainer { + display: flex; + align-items: center; + } + .autocompile {} + .autocompileTitle { + font-weight: bold; + margin: 1% 0; + } + .autocompileText { + margin: 1% 0; + font-size: 12px; + overflow: hidden; + word-break: normal; + line-height: initial; + } + .warnCompilationSlow { + color: ${styles.rightPanel.compileTab.icon_WarnCompilation_Color}; + margin-left: 1%; + } + .compileButtons { + display: flex; + align-items: center; + flex-wrap: wrap; + justify-content: flex-end; + } + .name { + display: flex; + } + .size { + display: flex; + } + .checkboxes { + display: flex; + width: 100%; + justify-content: space-between; + flex-wrap: wrap; + } + .compileButton { + ${styles.rightPanel.compileTab.button_Compile}; + width: 100%; + margin: 15px 0 10px 0; + font-size: 12px; + } + .container { + ${styles.rightPanel.compileTab.box_CompileContainer}; + margin: 0; + margin-bottom: 2%; + } + .contractContainer { + display: flex; + align-items: center; + margin-bottom: 2%; + } + .optimizeContainer { + display: flex; + } + .contractNames { + ${styles.rightPanel.compileTab.dropdown_CompileContract}; + width:78%; + } + .contractHelperButtons { + display: flex; + cursor: pointer; + text-align: center; + justify-content: flex-end; + margin: 15px 15px 10px 0; + } + .copyButton { + ${styles.rightPanel.compileTab.button_Publish}; + padding: 0 7px; + min-width: 50px; + width: auto; + margin-left: 5px; + background-color: inherit; + border: inherit; + } + .bytecodeButton { + min-width: 80px; + } + .copyIcon { + margin-right: 5px; + } + .details { + ${styles.rightPanel.compileTab.button_Details}; + min-width: 70px; + width: 80px; + } + .publish { + display: flex; + align-items: center; + margin-left: 10px; + cursor: pointer; + } + .log { + ${styles.rightPanel.compileTab.box_CompileContainer}; + display: flex; + flex-direction: column; + margin-bottom: 5%; + overflow: visible; + } + .key { + margin-right: 5px; + color: ${styles.rightPanel.text_Primary}; + text-transform: uppercase; + width: 100%; + } + .value { + display: flex; + width: 100%; + margin-top: 1.5%; + } + .questionMark { + margin-left: 2%; + cursor: pointer; + color: ${styles.rightPanel.icon_Color_TogglePanel}; + } + .questionMark:hover { + color: ${styles.rightPanel.icon_HoverColor_TogglePanel}; + } + .detailsJSON { + padding: 8px 0; + background-color: ${styles.rightPanel.modalDialog_BackgroundColor_Primary}; + border: none; + color: ${styles.rightPanel.modalDialog_text_Secondary}; + } + .icon { + margin-right: 0.3em; + } + .spinningIcon { + margin-right: .3em; + animation: spin 2s linear infinite; + } + .bouncingIcon { + margin-right: .3em; + animation: bounce 2s infinite; + } + @keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } + @-webkit-keyframes bounce { + 0% { + margin-bottom: 0; + color: ${styles.colors.transparent}; + } + 70% { + margin-bottom: 0; + color: ${styles.rightPanel.text_Secondary}; + } + 100% { + margin-bottom: 0; + color: ${styles.colors.transparent}; + } + } +` + +module.exports = css