Merge pull request #1120 from ethereum/pluginAPISetup

[Plugins] store Config file
pull/3094/head
yann300 7 years ago committed by GitHub
commit 9efdb1ea91
  1. 17
      src/app.js
  2. 133
      src/app/files/browser-files-tree.js
  3. 2
      src/app/files/fileManager.js
  4. 7
      src/app/panels/file-panel.js
  5. 1
      src/config.js
  6. 46
      src/pluginManager.js
  7. 46
      test-browser/plugin/index.html
  8. 32
      test-browser/plugin/remix.js
  9. 1
      test-browser/tests/sharedFolderExplorer.js

@ -17,6 +17,7 @@ var GistHandler = require('./lib/gist-handler')
var helper = require('./lib/helper') var helper = require('./lib/helper')
var Storage = remixLib.Storage var Storage = remixLib.Storage
var Browserfiles = require('./app/files/browser-files') var Browserfiles = require('./app/files/browser-files')
var BrowserfilesTree = require('./app/files/browser-files-tree')
var chromeCloudStorageSync = require('./app/files/chromeCloudStorageSync') var chromeCloudStorageSync = require('./app/files/chromeCloudStorageSync')
var SharedFolder = require('./app/files/shared-folder') var SharedFolder = require('./app/files/shared-folder')
var Config = require('./config') var Config = require('./config')
@ -111,11 +112,14 @@ class App {
var self = this var self = this
self._api = {} self._api = {}
var fileStorage = new Storage('sol:') var fileStorage = new Storage('sol:')
var configStorage = new Storage('config:')
self._api.config = new Config(fileStorage) self._api.config = new Config(fileStorage)
executionContext.init(self._api.config) executionContext.init(self._api.config)
executionContext.listenOnLastBlock() executionContext.listenOnLastBlock()
self._api.filesProviders = {} self._api.filesProviders = {}
self._api.filesProviders['browser'] = new Browserfiles(fileStorage) self._api.filesProviders['browser'] = new Browserfiles(fileStorage)
self._api.filesProviders['config'] = new BrowserfilesTree('config', configStorage)
self._api.filesProviders['config'].init()
var remixd = new Remixd() var remixd = new Remixd()
remixd.event.register('system', (message) => { remixd.event.register('system', (message) => {
if (message.error) toolTip(message.error) if (message.error) toolTip(message.error)
@ -204,7 +208,7 @@ function run () {
var self = this var self = this
if (window.location.hostname === 'yann300.github.io') { 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' && } else if (window.location.hostname === 'ethereum.github.io' &&
window.location.pathname.indexOf('/remix-live-alpha') === 0) { window.location.pathname.indexOf('/remix-live-alpha') === 0) {
modalDialogCustom.alert(`This instance of the Remix IDE is an UNSTABLE ALPHA branch.\n 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) => { newAccount: (pass, cb) => {
udapp.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 = { var rhpEvents = {

@ -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

@ -24,8 +24,10 @@ class FileManager {
this.opt = opt this.opt = opt
this.opt.filesProviders['browser'].event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) }) 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['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['browser'].event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) })
this.opt.filesProviders['localhost'].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 // tabs
var $filesEl = $('#files') var $filesEl = $('#files')

@ -45,6 +45,7 @@ function filepanel (appAPI, filesProvider) {
var swarmExplorer = new FileExplorer(appAPI, filesProvider['swarm']) var swarmExplorer = new FileExplorer(appAPI, filesProvider['swarm'])
var githubExplorer = new FileExplorer(appAPI, filesProvider['github']) var githubExplorer = new FileExplorer(appAPI, filesProvider['github'])
var gistExplorer = new FileExplorer(appAPI, filesProvider['gist']) var gistExplorer = new FileExplorer(appAPI, filesProvider['gist'])
var configExplorer = new FileExplorer(appAPI, filesProvider['config'])
var dragbar = yo`<div onmousedown=${mousedown} class=${css.dragbar}></div>` var dragbar = yo`<div onmousedown=${mousedown} class=${css.dragbar}></div>`
@ -91,6 +92,7 @@ function filepanel (appAPI, filesProvider) {
</div> </div>
<div class=${css.treeviews}> <div class=${css.treeviews}>
<div class=${css.treeview}>${fileExplorer.init()}</div> <div class=${css.treeview}>${fileExplorer.init()}</div>
<div class="configexplorer ${css.treeview}">${configExplorer.init()}</div>
<div class="filesystemexplorer ${css.treeview}">${fileSystemExplorer.init()}</div> <div class="filesystemexplorer ${css.treeview}">${fileSystemExplorer.init()}</div>
<div class="swarmexplorer ${css.treeview}">${swarmExplorer.init()}</div> <div class="swarmexplorer ${css.treeview}">${swarmExplorer.init()}</div>
<div class="githubexplorer ${css.treeview}">${githubExplorer.init()}</div> <div class="githubexplorer ${css.treeview}">${githubExplorer.init()}</div>
@ -106,6 +108,7 @@ function filepanel (appAPI, filesProvider) {
self.event = event self.event = event
var element = template() var element = template()
fileExplorer.ensureRoot() fileExplorer.ensureRoot()
configExplorer.ensureRoot()
var websocketconn = element.querySelector('.websocketconn') var websocketconn = element.querySelector('.websocketconn')
filesProvider['localhost'].remixd.event.register('connecting', (event) => { filesProvider['localhost'].remixd.event.register('connecting', (event) => {
websocketconn.style.color = styles.colors.yellow websocketconn.style.color = styles.colors.yellow
@ -134,6 +137,10 @@ function filepanel (appAPI, filesProvider) {
appAPI.switchFile(path) appAPI.switchFile(path)
}) })
configExplorer.events.register('focus', function (path) {
appAPI.switchFile(path)
})
fileSystemExplorer.events.register('focus', function (path) { fileSystemExplorer.events.register('focus', function (path) {
appAPI.switchFile(path) appAPI.switchFile(path)
}) })

@ -35,6 +35,7 @@ function Config (storage) {
this.ensureStorageUpdated = function (key) { this.ensureStorageUpdated = function (key) {
if (key === 'currentFile') { if (key === 'currentFile') {
if (this.items[key] && this.items[key] !== '' && if (this.items[key] && this.items[key] !== '' &&
this.items[key].indexOf('config/') !== 0 &&
this.items[key].indexOf('browser/') !== 0 && this.items[key].indexOf('browser/') !== 0 &&
this.items[key].indexOf('localhost/') !== 0 && this.items[key].indexOf('localhost/') !== 0 &&
this.items[key].indexOf('swarm/') !== 0 && this.items[key].indexOf('swarm/') !== 0 &&

@ -7,12 +7,35 @@
* - compilationData (that is triggered just after a focus - and send the current compilation data or null) * - 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) * - 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: <requestid>
* }
* the plugin will reveice a response like:
* {
* type: 'getConfig',
* id: <requestid>
* 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 { class PluginManager {
constructor (api, events) { constructor (api, events) {
var self = this
this.plugins = {} this.plugins = {}
this.inFocus this.inFocus
var allowedapi = {'setConfig': 1, 'getConfig': 1, 'removeConfig': 1}
events.compiler.register('compilationFinished', (success, data, source) => { events.compiler.register('compilationFinished', (success, data, source) => {
if (this.inFocus) { if (this.inFocus) {
// trigger to the current focus // 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) { register (desc, content) {
this.plugins[desc.title] = {content, origin: desc.url} this.plugins[desc.title] = {content, origin: desc.url}

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!--
The MIT License (MIT)
Copyright (c) 2014, 2015, the individual contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<title></title>
<link rel="stylesheet" href="assets/css/pygment_trac.css">
<link rel="stylesheet" href="assets/css/font-awesome.min.css">
<script type="text/javascript" src="remix.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
</head>
<body>
<div>PLUGIN</div>
<input type="text" id="filename"></input>
<input type="text" id="valuetosend"></input> <br>
<input type="button" id="testmessageadd">add config</input> <br>
<input type="button" id="testmessageremove">remove config</input> <br>
<input type="button" id="testmessagerget">get config</input> <br>
<br>
<div id='compilationdata'></div>
</body>
</html>

@ -0,0 +1,32 @@
function receiveMessage (event) {
console.log('receiveMessage', event.data, event.source, event.origin)
document.getElementById('compilationdata').innerHTML += event.data + '<br>'
}
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')
})
}

@ -71,6 +71,7 @@ function runTests (browser, testData) {
.waitForElementVisible('[data-path="localhost/contract1.sol"]') .waitForElementVisible('[data-path="localhost/contract1.sol"]')
.assert.containsText('[data-path="localhost/contract1.sol"]', 'contract1.sol') .assert.containsText('[data-path="localhost/contract1.sol"]', 'contract1.sol')
.assert.containsText('[data-path="localhost/contract2.sol"]', 'contract2.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/contract1.sol"]', 'contract1.sol')
.assert.containsText('[data-path="localhost/folder1/contract2.sol"]', 'contract2.sol') // load and test sub folder .assert.containsText('[data-path="localhost/folder1/contract2.sol"]', 'contract2.sol') // load and test sub folder
.click('[data-path="localhost/folder1/contract2.sol"]') .click('[data-path="localhost/folder1/contract2.sol"]')

Loading…
Cancel
Save