/* global alert, confirm, prompt, Option, Worker, chrome */ 'use strict' var async = require('async') var $ = require('jquery') var base64 = require('js-base64').Base64 var swarmgw = require('swarmgw') var csjs = require('csjs-inject') var yo = require('yo-yo') var EventManager = require('ethereum-remix').lib.EventManager var UniversalDApp = require('./universal-dapp.js') var Remixd = require('./lib/remixd') var OffsetToLineColumnConverter = require('./lib/offsetToLineColumnConverter') var QueryParams = require('./app/query-params') var GistHandler = require('./app/gist-handler') var Storage = require('./app/files/storage') var Browserfiles = require('./app/files/browser-files') var SharedFolder = require('./app/files/shared-folder') var Config = require('./app/config') var Editor = require('./app/editor') var Renderer = require('./app/renderer') var Compiler = require('./app/compiler') var ExecutionContext = require('./app/execution-context') var Debugger = require('./app/debugger') var StaticAnalysis = require('./app/staticanalysis/staticAnalysisView') var FilePanel = require('./app/file-panel') var EditorPanel = require('./app/editor-panel') var RighthandPanel = require('./app/righthand-panel') var examples = require('./app/example-contracts') var Txlistener = require('./app/txListener') var css = csjs` html { box-sizing: border-box; } *, *:before, *:after { box-sizing: inherit; } body { margin : 0; padding : 0; font-size : 12px; color : #111111; font-weight : normal; } .browsersolidity { position : relative; width : 100vw; height : 100vh; overflow : hidden; } .centerpanel { display : flex; flex-direction : column; position : absolute; top : 0; bottom : 0; overflow : hidden; } .leftpanel { display : flex; flex-direction : column; position : absolute; top : 0; bottom : 0; left : 0; overflow : hidden; } .rightpanel { display : flex; flex-direction : column; position : absolute; top : 0; right : 0; bottom : 0; overflow : hidden; } ` class App { constructor (opts = {}) { var self = this self._api = {} var fileStorage = new Storage('sol:') self._api.config = new Config(fileStorage) self._api.filesProviders = {} self._api.filesProviders['browser'] = new Browserfiles(fileStorage) self._api.filesProviders['localhost'] = new SharedFolder(new Remixd()) self._view = {} self._components = {} self.data = { _layout: { right: { offset: self._api.config.get('right-offset') || 400, show: true }, // @TODO: adapt sizes proportionally to browser window size left: { offset: self._api.config.get('left-offset') || 200, show: true } } } // ----------------- editor ---------------------------- self._components.editor = new Editor({}) // @TODO: put into editorpanel // ----------------- editor panel ---------------------- self._components.editorpanel = new EditorPanel({ api: { editor: self._components.editor } }) self._components.editorpanel.event.register('resize', direction => self._adjustLayout(direction)) } _adjustLayout (direction, delta) { var self = this var layout = self.data._layout[direction] if (layout) { if (delta === undefined) { layout.show = !layout.show if (layout.show) delta = layout.offset else delta = 0 } else { self._api.config.set(`${direction}-offset`, delta) layout.offset = delta } } if (direction === 'left') { self._view.leftpanel.style.width = delta + 'px' self._view.centerpanel.style.left = delta + 'px' } if (direction === 'right') { self._view.rightpanel.style.width = delta + 'px' self._view.centerpanel.style.right = delta + 'px' } } init () { var self = this run.apply(self) } render () { var self = this if (self._view.el) return self._view.el self._view.leftpanel = yo`
${''}
` self._view.centerpanel = yo`
${self._components.editorpanel.render()}
` self._view.rightpanel = yo`
${''}
` self._view.el = yo`
${self._view.leftpanel} ${self._view.centerpanel} ${self._view.rightpanel}
` // INIT self._adjustLayout('left', self.data._layout.left.offset) self._adjustLayout('right', self.data._layout.right.offset) return self._view.el } } module.exports = App function run () { var self = this var queryParams = new QueryParams() var gistHandler = new GistHandler() var editor = self._components.editor // The event listener needs to be registered as early as possible, because the // parent will send the message upon the "load" event. var filesToLoad = null var loadFilesCallback = function (files) { filesToLoad = files } // will be replaced later window.addEventListener('message', function (ev) { if (typeof ev.data === typeof [] && ev.data[0] === 'loadFiles') { loadFilesCallback(ev.data[1]) } }, false) this.event = new EventManager() var config = self._api.config var filesProviders = self._api.filesProviders var tabbedFiles = {} // list of files displayed in the tabs bar // return all the files, except the temporary/readonly ones.. package only files from the browser storage. function packageFiles (cb) { var ret = {} var files = filesProviders['browser'] var filtered = Object.keys(files.list()).filter(function (path) { if (!files.isReadOnly(path)) { return path } }) async.eachSeries(filtered, function (path, cb) { ret[path] = { content: files.get(path) } cb() }, () => { cb(ret) }) } function createNonClashingName (path) { var counter = '' if (path.endsWith('.sol')) path = path.substring(0, path.lastIndexOf('.sol')) while (filesProviders['browser'].exists(path + counter + '.sol')) { counter = (counter | 0) + 1 } return path + counter + '.sol' } // Add files received from remote instance (i.e. another browser-solidity) function loadFiles (filesSet) { for (var f in filesSet) { filesProviders['browser'].set(createNonClashingName(f), filesSet[f].content) } switchToNextFile() } // Replace early callback with instant response loadFilesCallback = function (files) { loadFiles(files) } // Run if we did receive an event from remote instance while starting up if (filesToLoad !== null) { loadFiles(filesToLoad) } // ------------------ gist load ---------------- var loadingFromGist = gistHandler.handleLoad(queryParams.get(), function (gistId) { $.ajax({ url: 'https://api.github.com/gists/' + gistId, jsonp: 'callback', dataType: 'jsonp', success: function (response) { if (response.data) { if (!response.data.files) { alert('Gist load error: ' + response.data.message) return } loadFiles(response.data.files) } } }) }) // insert ballot contract if there are no files available if (!loadingFromGist && Object.keys(filesProviders['browser'].list()).length === 0) { if (!filesProviders['browser'].set(examples.ballot.name, examples.ballot.content)) { alert('Failed to store example contract in browser. Remix will not work properly. Please ensure Remix has access to LocalStorage. Safari in Private mode is known not to work.') } } // ----------------- Chrome cloud storage sync -------------------- function chromeCloudSync () { if (typeof chrome === 'undefined' || !chrome || !chrome.storage || !chrome.storage.sync) { return } var obj = {} var done = false var count = 0 function check (key) { chrome.storage.sync.get(key, function (resp) { console.log('comparing to cloud', key, resp) if (typeof resp[key] !== 'undefined' && obj[key] !== resp[key] && confirm('Overwrite "' + key + '"? Click Ok to overwrite local file with file from cloud. Cancel will push your local file to the cloud.')) { console.log('Overwriting', key) filesProviders['browser'].set(key, resp[key]) } else { console.log('add to obj', obj, key) filesProviders['browser'].get(key, (error, content) => { if (error) { console.log(error) } else { obj[key] = content } }) } done++ if (done >= count) { chrome.storage.sync.set(obj, function () { console.log('updated cloud files with: ', obj, this, arguments) }) } }) } for (var y in filesProviders['browser'].list()) { console.log('checking', y) filesProviders['browser'].get(y, (error, content) => { if (error) { console.log(error) } else { obj[y] = content count++ check(y) } }) } } window.syncStorage = chromeCloudSync chromeCloudSync() // ---------------- FilePanel -------------------- var FilePanelAPI = { createName: createNonClashingName, switchToFile: switchToFile, event: this.event, currentFile: function () { return config.get('currentFile') }, currentContent: function () { return editor.get(config.get('currentFile')) }, setText: function (text) { editor.setText(text) } } var filePanel = new FilePanel(FilePanelAPI, filesProviders) // TODO this should happen inside file-panel.js var filepanelContainer = document.querySelector('#filepanel') filepanelContainer.appendChild(filePanel.render()) filePanel.event.register('resize', delta => self._adjustLayout('left', delta)) function fileRenamedEvent (oldName, newName, isFolder) { // TODO please never use 'window' when it is possible to use a variable // that references the DOM node [...window.files.querySelectorAll('.file .name')].forEach(function (span) { if (span.innerText === oldName) span.innerText = newName }) if (!isFolder) { config.set('currentFile', '') editor.discard(oldName) if (tabbedFiles[oldName]) { delete tabbedFiles[oldName] tabbedFiles[newName] = newName } switchToFile(newName) } else { var newFocus for (var k in tabbedFiles) { if (k.indexOf(oldName + '/') === 0) { var newAbsolutePath = k.replace(oldName, newName) tabbedFiles[newAbsolutePath] = newAbsolutePath delete tabbedFiles[k] if (config.get('currentFile') === k) { newFocus = newAbsolutePath } } } if (newFocus) { switchToFile(newFocus) } } refreshTabs() } filesProviders['browser'].event.register('fileRenamed', fileRenamedEvent) filesProviders['localhost'].event.register('fileRenamed', fileRenamedEvent) function fileRemovedEvent (path) { if (path === config.get('currentFile')) { config.set('currentFile', '') switchToNextFile() } editor.discard(path) delete tabbedFiles[path] refreshTabs() } filesProviders['browser'].event.register('fileRemoved', fileRemovedEvent) filesProviders['localhost'].event.register('fileRemoved', fileRemovedEvent) // ------------------ gist publish -------------- $('#gist').click(function () { if (confirm('Are you sure you want to publish all your files anonymously as a public gist on github.com?')) { packageFiles((error, packaged) => { if (error) { console.log(error) } else { var description = 'Created using browser-solidity: Realtime Ethereum Contract Compiler and Runtime. \n Load this file by pasting this gists URL or ID at https://ethereum.github.io/browser-solidity/#version=' + queryParams.get().version + '&optimize=' + queryParams.get().optimize + '&gist=' $.ajax({ url: 'https://api.github.com/gists', type: 'POST', data: JSON.stringify({ description: description, public: true, files: packaged }) }).done(function (response) { if (response.html_url && confirm('Created a gist at ' + response.html_url + ' Would you like to open it in a new window?')) { window.open(response.html_url, '_blank') } }).fail(function (xhr, text, err) { alert('Failed to create gist: ' + (err || 'Unknown transport error')) }) } }) } }) $('#copyOver').click(function () { var target = prompt( 'To which other browser-solidity instance do you want to copy over all files?', 'https://ethereum.github.io/browser-solidity/' ) if (target === null) { return } packageFiles((error, packaged) => { if (error) { console.log(error) } else { $('