diff --git a/src/app.js b/src/app.js index 02096d324c..ed24d0ef2b 100644 --- a/src/app.js +++ b/src/app.js @@ -17,6 +17,7 @@ var GistHandler = require('./lib/gist-handler') var helper = require('./lib/helper') var Storage = remixLib.Storage var Browserfiles = require('./app/files/browser-files') +var BrowserfilesTree = require('./app/files/browser-files-tree') var chromeCloudStorageSync = require('./app/files/chromeCloudStorageSync') var SharedFolder = require('./app/files/shared-folder') var Config = require('./config') @@ -111,11 +112,14 @@ class App { var self = this self._api = {} var fileStorage = new Storage('sol:') + var configStorage = new Storage('config:') self._api.config = new Config(fileStorage) executionContext.init(self._api.config) executionContext.listenOnLastBlock() self._api.filesProviders = {} self._api.filesProviders['browser'] = new Browserfiles(fileStorage) + self._api.filesProviders['config'] = new BrowserfilesTree('config', configStorage) + self._api.filesProviders['config'].init() var remixd = new Remixd() remixd.event.register('system', (message) => { if (message.error) toolTip(message.error) @@ -204,7 +208,7 @@ function run () { var self = this if (window.location.hostname === 'yann300.github.io') { - modalDialogCustom.alert(`This UNSTABLE ALPHA branch of Remix has been moved to http://ethereum.github.io/remix-live-alpha.`) + modalDialogCustom.alert('This UNSTABLE ALPHA branch of Remix has been moved to http://ethereum.github.io/remix-live-alpha.') } else if (window.location.hostname === 'ethereum.github.io' && window.location.pathname.indexOf('/remix-live-alpha') === 0) { modalDialogCustom.alert(`This instance of the Remix IDE is an UNSTABLE ALPHA branch.\n @@ -697,6 +701,17 @@ Please make a backup of your contracts and start using http://remix.ethereum.org }, newAccount: (pass, cb) => { udapp.newAccount(pass, cb) + }, + setConfig: (mod, path, content, cb) => { + self._api.filesProviders['config'].set(mod + '/' + path, content) + cb() + }, + getConfig: (mod, path, cb) => { + cb(null, self._api.filesProviders['config'].get(mod + '/' + path)) + }, + removeConfig: (mod, path, cb) => { + cb(null, self._api.filesProviders['config'].remove(mod + '/' + path)) + if (cb) cb() } } var rhpEvents = { diff --git a/src/app/files/browser-files-tree.js b/src/app/files/browser-files-tree.js new file mode 100644 index 0000000000..b9bde21c2d --- /dev/null +++ b/src/app/files/browser-files-tree.js @@ -0,0 +1,133 @@ +'use strict' + +var EventManager = require('remix-lib').EventManager + +function FilesTree (name, storage) { + var self = this + var event = new EventManager() + this.event = event + this.type = name + this.structFile = '.' + name + '.tree' + this.tree = {} + + this.exists = function (path, cb) { + cb(null, this._exists(path)) + } + + function updateRefs (path, type) { + var split = path.split('/') // this should be unprefixed path + var crawlpath = self.tree + var intermediatePath = '' + split.forEach((pathPart, index) => { + intermediatePath += pathPart + if (!crawlpath[pathPart]) crawlpath[intermediatePath] = {} + if (index < split.length - 1) { + crawlpath = crawlpath[intermediatePath] + intermediatePath += '/' + } else if (type === 'add') { + crawlpath[intermediatePath] = path + } else if (type === 'remove' && crawlpath[intermediatePath]) { + delete crawlpath[intermediatePath] + } + }) + storage.set(self.structFile, JSON.stringify(self.tree)) + } + + this._exists = function (path) { + var unprefixedpath = this.removePrefix(path) + return storage.exists(unprefixedpath) + } + + this.init = function (cb) { + var tree = storage.get(this.structFile) + this.tree = tree ? JSON.parse(tree) : {} + if (cb) cb() + } + + this.get = function (path, cb) { + var unprefixedpath = this.removePrefix(path) + var content = storage.get(unprefixedpath) + if (cb) { + cb(null, content) + } + return content + } + + this.set = function (path, content) { + var unprefixedpath = this.removePrefix(path) + updateRefs(unprefixedpath, 'add') + var exists = storage.exists(unprefixedpath) + if (!storage.set(unprefixedpath, content)) { + return false + } + if (!exists) { + event.trigger('fileAdded', [this.type + '/' + unprefixedpath, false]) + } else { + event.trigger('fileChanged', [this.type + '/' + unprefixedpath]) + } + return true + } + + this.addReadOnly = function (path, content) { + return this.set(path, content) + } + + this.isReadOnly = function (path) { + return false + } + + this.remove = function (path) { + var unprefixedpath = this.removePrefix(path) + updateRefs(unprefixedpath, 'remove') + if (!this._exists(unprefixedpath)) { + return false + } + if (!storage.remove(unprefixedpath)) { + return false + } + event.trigger('fileRemoved', [this.type + '/' + unprefixedpath]) + return true + } + + this.rename = function (oldPath, newPath, isFolder) { + var unprefixedoldPath = this.removePrefix(oldPath) + var unprefixednewPath = this.removePrefix(newPath) + updateRefs(unprefixedoldPath, 'remove') + updateRefs(unprefixednewPath, 'add') + if (storage.exists(unprefixedoldPath)) { + if (!storage.rename(unprefixedoldPath, unprefixednewPath)) { + return false + } + event.trigger('fileRenamed', [this.type + '/' + unprefixedoldPath, this.type + '/' + unprefixednewPath, isFolder]) + return true + } + return false + } + + this.resolveDirectory = function (path, callback) { + var self = this + if (path[0] === '/') path = path.substring(1) + if (!path) return callback(null, { [self.type]: { } }) + var tree = {} + path = self.removePrefix(path) + + var split = path.split('/') // this should be unprefixed path + var crawlpath = self.tree + split.forEach((pathPart, index) => { + if (crawlpath[pathPart]) crawlpath = crawlpath[pathPart] + }) + + for (var item in crawlpath) { + tree[item] = { isDirectory: typeof crawlpath[item] !== 'string' } + } + callback(null, tree) + } + + this.removePrefix = function (path) { + path = path.indexOf(this.type) === 0 ? path.replace(this.type, '') : path + if (path[0] === '/') return path.substring(1) + return path + } +} + +module.exports = FilesTree diff --git a/src/app/files/fileManager.js b/src/app/files/fileManager.js index 7614ed6779..ec044adb49 100644 --- a/src/app/files/fileManager.js +++ b/src/app/files/fileManager.js @@ -24,8 +24,10 @@ class FileManager { this.opt = opt this.opt.filesProviders['browser'].event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) }) this.opt.filesProviders['localhost'].event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) }) + this.opt.filesProviders['config'].event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) }) this.opt.filesProviders['browser'].event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) }) this.opt.filesProviders['localhost'].event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) }) + this.opt.filesProviders['config'].event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) }) // tabs var $filesEl = $('#files') diff --git a/src/app/panels/file-panel.js b/src/app/panels/file-panel.js index 98cae06722..b112a2af35 100644 --- a/src/app/panels/file-panel.js +++ b/src/app/panels/file-panel.js @@ -45,6 +45,7 @@ function filepanel (appAPI, filesProvider) { var swarmExplorer = new FileExplorer(appAPI, filesProvider['swarm']) var githubExplorer = new FileExplorer(appAPI, filesProvider['github']) var gistExplorer = new FileExplorer(appAPI, filesProvider['gist']) + var configExplorer = new FileExplorer(appAPI, filesProvider['config']) var dragbar = yo`
` @@ -91,6 +92,7 @@ function filepanel (appAPI, filesProvider) {
${fileExplorer.init()}
+
${configExplorer.init()}
${fileSystemExplorer.init()}
${swarmExplorer.init()}
${githubExplorer.init()}
@@ -106,6 +108,7 @@ function filepanel (appAPI, filesProvider) { self.event = event var element = template() fileExplorer.ensureRoot() + configExplorer.ensureRoot() var websocketconn = element.querySelector('.websocketconn') filesProvider['localhost'].remixd.event.register('connecting', (event) => { websocketconn.style.color = styles.colors.yellow @@ -134,6 +137,10 @@ function filepanel (appAPI, filesProvider) { appAPI.switchFile(path) }) + configExplorer.events.register('focus', function (path) { + appAPI.switchFile(path) + }) + fileSystemExplorer.events.register('focus', function (path) { appAPI.switchFile(path) }) diff --git a/src/config.js b/src/config.js index 49c349ec3f..5339159cbf 100644 --- a/src/config.js +++ b/src/config.js @@ -35,6 +35,7 @@ function Config (storage) { this.ensureStorageUpdated = function (key) { if (key === 'currentFile') { if (this.items[key] && this.items[key] !== '' && + this.items[key].indexOf('config/') !== 0 && this.items[key].indexOf('browser/') !== 0 && this.items[key].indexOf('localhost/') !== 0 && this.items[key].indexOf('swarm/') !== 0 && diff --git a/src/pluginManager.js b/src/pluginManager.js index 2c8d4393dd..fd263b31d6 100644 --- a/src/pluginManager.js +++ b/src/pluginManager.js @@ -7,12 +7,35 @@ * - compilationData (that is triggered just after a focus - and send the current compilation data or null) * - compilationFinished (that is only sent to the plugin that has focus) * - * @param {String} txHash - hash of the transaction + * Plugin can emit messages and receive response. + * + * CONFIG: + * - getConfig(filename). The data to send should be formatted like: + * { + * type: 'getConfig', + * arguments: ['filename.ext'], + * id: + * } + * the plugin will reveice a response like: + * { + * type: 'getConfig', + * id: + * error, + * result + * } + * same apply for the other call + * - setConfig(filename, content) + * - removeConfig + * + * See index.html and remix.js in test-browser folder for sample + * */ class PluginManager { constructor (api, events) { + var self = this this.plugins = {} this.inFocus + var allowedapi = {'setConfig': 1, 'getConfig': 1, 'removeConfig': 1} events.compiler.register('compilationFinished', (success, data, source) => { if (this.inFocus) { // trigger to the current focus @@ -46,6 +69,27 @@ class PluginManager { })) } }) + + window.addEventListener('message', (event) => { + function response (type, callid, error, result) { + self.post(self.inFocus, JSON.stringify({ + id: callid, + type: type, + error: error, + result: result + })) + } + if (event.type === 'message' && this.inFocus && this.plugins[this.inFocus] && this.plugins[this.inFocus].origin === event.origin) { + var data = JSON.parse(event.data) + data.arguments.unshift(this.inFocus) + if (allowedapi[data.type]) { + data.arguments.push((error, result) => { + response(data.type, data.id, error, result) + }) + api[data.type].apply({}, data.arguments) + } + } + }, false) } register (desc, content) { this.plugins[desc.title] = {content, origin: desc.url} diff --git a/test-browser/plugin/index.html b/test-browser/plugin/index.html new file mode 100644 index 0000000000..2cb1662902 --- /dev/null +++ b/test-browser/plugin/index.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + +
PLUGIN
+ +
+ add config
+ remove config
+ get config
+
+
+ + diff --git a/test-browser/plugin/remix.js b/test-browser/plugin/remix.js new file mode 100644 index 0000000000..5449df96f1 --- /dev/null +++ b/test-browser/plugin/remix.js @@ -0,0 +1,32 @@ + +function receiveMessage (event) { + console.log('receiveMessage', event.data, event.source, event.origin) + document.getElementById('compilationdata').innerHTML += event.data + '
' +} +window.addEventListener('message', receiveMessage, false) + +window.onload = function () { + document.querySelector('input#testmessageadd').addEventListener('click', function () { + window.parent.postMessage(JSON.stringify({ + type: 'setConfig', + arguments: [document.getElementById('filename').value, document.getElementById('valuetosend').value], + id: 34 + }), 'http://127.0.0.1:8080') + }) + + document.querySelector('input#testmessageremove').addEventListener('click', function () { + window.parent.postMessage(JSON.stringify({ + type: 'removeConfig', + arguments: [document.getElementById('filename').value], + id: 35 + }), 'http://127.0.0.1:8080') + }) + + document.querySelector('input#testmessagerget').addEventListener('click', function () { + window.parent.postMessage(JSON.stringify({ + type: 'getConfig', + arguments: [document.getElementById('filename').value], + id: 36 + }), 'http://127.0.0.1:8080') + }) +} diff --git a/test-browser/tests/sharedFolderExplorer.js b/test-browser/tests/sharedFolderExplorer.js index 9eb553dd1e..711359d59c 100644 --- a/test-browser/tests/sharedFolderExplorer.js +++ b/test-browser/tests/sharedFolderExplorer.js @@ -71,6 +71,7 @@ function runTests (browser, testData) { .waitForElementVisible('[data-path="localhost/contract1.sol"]') .assert.containsText('[data-path="localhost/contract1.sol"]', 'contract1.sol') .assert.containsText('[data-path="localhost/contract2.sol"]', 'contract2.sol') + .waitForElementVisible('[data-path="localhost/folder1/contract1.sol"]') .assert.containsText('[data-path="localhost/folder1/contract1.sol"]', 'contract1.sol') .assert.containsText('[data-path="localhost/folder1/contract2.sol"]', 'contract2.sol') // load and test sub folder .click('[data-path="localhost/folder1/contract2.sol"]')