From 33da4e0d8ad0c0983efa22a7e846f8c8528f963f Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 7 Jan 2019 15:04:39 +0100 Subject: [PATCH] add minimal API for plugin --- package.json | 1 + src/app.js | 81 ++++++++++++++++--- .../components/plugin-manager-component.js | 35 +++++--- src/app/components/swap-panel-api.js | 2 +- src/app/components/vertical-icons-api.js | 2 +- src/app/editor/SourceHighlighters.js | 35 ++++++++ src/app/editor/editor.js | 3 + src/app/files/browser-files-tree.js | 7 ++ src/app/files/fileManager.js | 49 +++++++++++ src/app/panels/editor-panel.js | 48 +++++------ src/app/tabs/compile-tab.js | 5 +- src/universal-dapp.js | 36 +++++++++ 12 files changed, 257 insertions(+), 47 deletions(-) create mode 100644 src/app/editor/SourceHighlighters.js diff --git a/package.json b/package.json index c7fbb313fc..3b186abf8d 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "csslint": "^1.0.2", "deep-equal": "^1.0.1", "ethereumjs-util": "^5.1.2", + "events": "^3.0.0", "execr": "^1.0.1", "exorcist": "^0.4.0", "fast-async": "6.3.1", diff --git a/src/app.js b/src/app.js index 88ce6c1211..efc62d8c9b 100644 --- a/src/app.js +++ b/src/app.js @@ -6,6 +6,7 @@ var async = require('async') var request = require('request') var remixLib = require('remix-lib') var EventManager = require('./lib/events') +var EventEmitter = require('events') var registry = require('./global/registry') var UniversalDApp = require('./universal-dapp.js') @@ -203,6 +204,13 @@ class App { var self = this run.apply(self) } + + profile () { + return { + type: 'app', + methods: ['getExecutionContextProvider', 'getProviderEndpoint', 'detectNetWork', 'addProvider', 'removeProvider'] + } + } render () { var self = this @@ -283,6 +291,34 @@ class App { if (callback) callback(error) }) } + + getExecutionContextProvider (cb) { + cb(null, executionContext.getProvider()) + } + + getProviderEndpoint (cb) { + if (executionContext.getProvider() === 'web3') { + cb(null, executionContext.web3().currentProvider.host) + } else { + cb('no endpoint: current provider is either injected or vm') + } + } + + detectNetWork (cb) { + executionContext.detectNetwork((error, network) => { + cb(error, network) + }) + } + + addProvider (name, url, cb) { + executionContext.addProvider({ name, url }) + cb() + } + + removeProvider (name, cb) { + executionContext.removeProvider(name) + cb() + } } module.exports = App @@ -359,33 +395,60 @@ Please make a backup of your contracts and start using http://remix.ethereum.org }) registry.put({api: eventsDecoder, name: 'eventsdecoder'}) + /* + that proxy is used by appManager to broadcast new transaction event + */ + const txListenerModuleProxy = { + event: new EventEmitter(), + profile() { + return { + type: 'txListener', + events: ['newTransaction'] + } + } + } + txlistener.event.register('newTransaction', (tx) => { + txListenerModule.event.emit('newTransaction', tx) + }) + txlistener.startListening() // TODO: There are still a lot of dep between editorpanel and filemanager - // ----------------- editor panel ---------------------- - self._components.editorpanel = new EditorPanel() - registry.put({ api: self._components.editorpanel, name: 'editorpanel' }) - // ----------------- file manager ---------------------------- self._components.fileManager = new FileManager() var fileManager = self._components.fileManager registry.put({api: fileManager, name: 'filemanager'}) + // ----------------- editor panel ---------------------- + self._components.editorpanel = new EditorPanel() + registry.put({ api: self._components.editorpanel, name: 'editorpanel' }) + // ----------------- Renderer ----------------- var renderer = new Renderer() registry.put({api: renderer, name: 'renderer'}) // ----------------- app manager ---------------------------- - const PluginManagerProfile = { - type: 'pluginManager', - methods: [] - } + + /* + TODOs: + - for each activated plugin, + an internal module (associated only with the plugin) should be created for accessing specific part of the UI. detail to be discussed + - the current API is not optimal. For instance methods of `app` only refers to `executionContext`, wich does not make really sense. + */ const appManager = new AppManager({modules: [],plugins : []}) const swapPanelComponent = new SwapPanelComponent() - const pluginManagerComponent = new PluginManagerComponent() + const pluginManagerComponent = new PluginManagerComponent( + { + app: this, + udapp: udapp, + fileManager: fileManager, + sourceHighlighters: registry.get('editor').api.sourceHighlighters, + config: self._components.filesProviders['config'], + txListener: txListenerModuleProxy + }) registry.put({api: pluginManagerComponent.proxy(), name: 'pluginmanager'}) self._components.editorpanel.init() diff --git a/src/app/components/plugin-manager-component.js b/src/app/components/plugin-manager-component.js index 08ede7bc40..1ca95bf82f 100644 --- a/src/app/components/plugin-manager-component.js +++ b/src/app/components/plugin-manager-component.js @@ -21,11 +21,20 @@ const EventEmitter = require ('events') class PluginManagerComponent { - constructor () { + constructor ({ app, udapp, fileManager, sourceHighlighters, config, txListener }) { this.event = new EventEmitter() this.modulesDefinition = { - 'FilePanel': { name: 'FilePanel', Type: FilePanel, icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRSBzdmcgIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4nICAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz48c3ZnIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDUwIDUwIiBoZWlnaHQ9IjUwcHgiIGlkPSJMYXllcl8xIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA1MCA1MCIgd2lkdGg9IjUwcHgiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iNTAiIHdpZHRoPSI1MCIvPjxwb2x5bGluZSBmaWxsPSJub25lIiBwb2ludHM9IjQ0LDIxIDQ0LDQ5IDYsNDkgICA2LDIxICIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PHBvbHlsaW5lIGZpbGw9Im5vbmUiIHBvaW50cz0iMTksNDkgMTksMjggMzEsMjggICAzMSw0OSAiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS13aWR0aD0iMiIvPjxwb2x5Z29uIHBvaW50cz0iMzUsNSAzNSw4LjAxNiAzNywxMC4wOTQgMzcsNyAzOSw3IDM5LDEyLjIwMyA0MSwxNC4yNjYgNDEsNSAiLz48cG9seWxpbmUgZmlsbD0ibm9uZSIgcG9pbnRzPSIgIDEuMTEsMjUuOTQyIDI1LDEuMDUzIDQ4Ljg5LDI1Ljk0MyAiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPg==' }, + // service module. They can be seen as daemon + // they usually don't have UI and only represent the minimal API a plugins can access. + 'App': { name: 'App', target: app }, + 'Udapp': { name: 'Udapp', target: udapp }, + 'FileManager': { name: 'FileManager', target: fileManager }, + 'SourceHighlighters': { name: 'SourceHighlighters', target: sourceHighlighters }, + 'Config': { name: 'Config', target: config }, + 'TxListener': { name: 'TxListener', target: txListener }, + // internal components. They are mostly views, they don't provide external API for plugins 'Solidity Compile': { name: 'Solidity Compile', class: 'evm-compiler', Type: CompileTab, icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRSBzdmcgIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4nICAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz48c3ZnIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDUwIDUwIiBoZWlnaHQ9IjUwcHgiIGlkPSJMYXllcl8xIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA1MCA1MCIgd2lkdGg9IjUwcHgiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iNTAiIHdpZHRoPSI1MCIvPjxwYXRoIGQ9Ik00NiwxNXYtNCAgYzAtMS4xMDQtMC44OTYtMi0yLTJjMCwwLTI0LjY0OCwwLTI2LDBjLTEuNDY5LDAtMi40ODQtNC00LTRIM0MxLjg5Niw1LDEsNS44OTYsMSw3djR2Mjl2NGMwLDEuMTA0LDAuODk2LDIsMiwyaDM5ICBjMS4xMDQsMCwyLTAuODk2LDItMiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PHBhdGggZD0iTTEsNDRsNS0yNyAgYzAtMS4xMDQsMC44OTYtMiwyLTJoMzljMS4xMDQsMCwyLDAuODk2LDIsMmwtNSwyNyIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+' }, + 'FilePanel': { name: 'FilePanel', Type: FilePanel, icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRSBzdmcgIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4nICAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz48c3ZnIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDUwIDUwIiBoZWlnaHQ9IjUwcHgiIGlkPSJMYXllcl8xIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA1MCA1MCIgd2lkdGg9IjUwcHgiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iNTAiIHdpZHRoPSI1MCIvPjxwb2x5bGluZSBmaWxsPSJub25lIiBwb2ludHM9IjQ0LDIxIDQ0LDQ5IDYsNDkgICA2LDIxICIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PHBvbHlsaW5lIGZpbGw9Im5vbmUiIHBvaW50cz0iMTksNDkgMTksMjggMzEsMjggICAzMSw0OSAiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS13aWR0aD0iMiIvPjxwb2x5Z29uIHBvaW50cz0iMzUsNSAzNSw4LjAxNiAzNywxMC4wOTQgMzcsNyAzOSw3IDM5LDEyLjIwMyA0MSwxNC4yNjYgNDEsNSAiLz48cG9seWxpbmUgZmlsbD0ibm9uZSIgcG9pbnRzPSIgIDEuMTEsMjUuOTQyIDI1LDEuMDUzIDQ4Ljg5LDI1Ljk0MyAiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPg==' }, 'Test': { name: 'Test', dep: 'Solidity Compile', Type: TestTab, icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRSBzdmcgIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4nICAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz48c3ZnIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDUwIDUwIiBoZWlnaHQ9IjUwcHgiIGlkPSJMYXllcl8xIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA1MCA1MCIgd2lkdGg9IjUwcHgiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iNTAiIHdpZHRoPSI1MCIvPjxwYXRoIGQ9Ik00OSw0djI1YzAsMC01LjI3MywzLTEyLDMgIGMtMTEuOTI5LDAtMTUuODY5LTQtMjQtNFMxLDMwLDEsMzBWM2MwLDAsMi4wODUtMiwxMi0yczE0LjA0Nyw2LDI0LDZDNDMuMjgxLDcsNDguMTMsNC40NzEsNDksNHoiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS13aWR0aD0iMiIvPjxsaW5lIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS13aWR0aD0iMiIgeDE9IjEiIHgyPSIxIiB5MT0iMyIgeTI9IjQ5Ii8+PC9zdmc+' }, 'Run': { name: 'Run', Type: RunTab, icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRSBzdmcgIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4nICAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz48c3ZnIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDUwIDUwIiBoZWlnaHQ9IjUwcHgiIGlkPSJMYXllcl8xIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA1MCA1MCIgd2lkdGg9IjUwcHgiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iNTAiIHdpZHRoPSI1MCIvPjxwYXRoIGQ9Ik00My43MzQsMjFjLTMuNjMxLDAtMTUuMDkyLDAtMTUuMDkyLDAgIFMxNi4yNSw1LjE4OCwxNi4wNDcsNC45MzhzLTAuNDIyLTAuNTk0LTEuMTI1LTAuNjcyYy0wLjg1OS0wLjA5NS0xLjk2OS0wLjIwMy0yLjMyOC0wLjIzNGMtMC40MDYtMC4wMzUtMC43MTksMC4xNDEtMC40OTYsMC43MzQgIEMxMi4zODgsNS41MzksMTguNzQ4LDIxLDE4Ljc0OCwyMUg2LjAzNGMwLDAtMi40NTgtNC43MjItMi44NzgtNS41MzFDMi45NjUsMTUuMTAxLDIuNTU3LDE1LjAxNCwyLDE1SDEuMjk3ICBjLTAuMTI1LDAtMC4zMTIsMC0wLjI4MSwwLjM0NEMxLjA1OCwxNS44MTEsMywyNSwzLDI1cy0xLjg4OCw5LjE5Ny0xLjk4NCw5LjY1NkMwLjk1MywzNC45NTMsMS4xNzIsMzUsMS4yOTcsMzVIMiAgYzAuOTY2LTAuMDA5LDAuOTU0LTAuMDc5LDEuMTU2LTAuNDY5QzMuNTc2LDMzLjcyMiw2LjAzNCwyOSw2LjAzNCwyOWgxMi43MTRjMCwwLTYuMzYsMTUuNDYxLTYuNjUsMTYuMjM0ICBjLTAuMjIzLDAuNTk0LDAuMDksMC43NywwLjQ5NiwwLjczNGMwLjM1OS0wLjAzMSwxLjQ2OS0wLjEzOSwyLjMyOC0wLjIzNGMwLjcwMy0wLjA3OCwwLjkyMi0wLjQyMiwxLjEyNS0wLjY3MlMyOC42NDMsMjksMjguNjQzLDI5ICBzMTEuNDYxLDAsMTUuMDkyLDBjMy43NjYsMCw1LjI2NC0zLjAzMSw1LjI2NC00UzQ3LjQ4NCwyMSw0My43MzQsMjF6IiBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+' }, 'Solidity Static Analysis': { name: 'Solidity Static Analysis', Type: AnalysisTab, icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRSBzdmcgIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4nICAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz48c3ZnIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDUwIDUwIiBoZWlnaHQ9IjUwcHgiIGlkPSJMYXllcl8xIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA1MCA1MCIgd2lkdGg9IjUwcHgiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iNTAiIHdpZHRoPSI1MCIvPjxjaXJjbGUgY3g9IjIiIGN5PSIyNSIgcj0iMiIvPjxjaXJjbGUgY3g9IjE1IiBjeT0iMTkiIHI9IjIiLz48Y2lyY2xlIGN4PSIyNSIgY3k9IjExIiByPSIyIi8+PGNpcmNsZSBjeD0iMzUiIGN5PSIxNyIgcj0iMiIvPjxjaXJjbGUgY3g9IjQ4IiBjeT0iNSIgcj0iMiIvPjxjaXJjbGUgY3g9IjIiIGN5PSIzOSIgcj0iMiIvPjxjaXJjbGUgY3g9IjE1IiBjeT0iNDEiIHI9IjIiLz48Y2lyY2xlIGN4PSIyNSIgY3k9IjMzIiByPSIyIi8+PGNpcmNsZSBjeD0iMzUiIGN5PSI0MyIgcj0iMiIvPjxjaXJjbGUgY3g9IjQ4IiBjeT0iMzEiIHI9IjIiLz48cG9seWxpbmUgZmlsbD0ibm9uZSIgcG9pbnRzPSIyLDI1IDE1LDE5IDI1LDExICAgMzUsMTcgNDgsNSAiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS13aWR0aD0iMiIvPjxwb2x5bGluZSBmaWxsPSJub25lIiBwb2ludHM9IjIsMzkgMTUsNDEgMjUsMzMgICAzNSw0MyA0OCwzMSAiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPg==' }, @@ -45,6 +54,13 @@ class PluginManagerComponent { } initDefault () { + this.activateInternal('App') + this.activateInternal('Udapp') + this.activateInternal('FileManager') + this.activateInternal('SourceHighlighters') + this.activateInternal('Config') + this.activateInternal('TxListener') + this.activateInternal('FilePanel') this.activateInternal('Solidity Compile') this.activateInternal('Run') @@ -63,13 +79,12 @@ class PluginManagerComponent { ` } - activatePlugin (name, api) { - let profile = { json: Plugin1Profile, api: pluginManagerApi } + activatePlugin (jsonProfile, api) { + let profile = { json: jsonProfile, api } let plugin = new Plugin(profile, api) this.appManager.addPlugin(plugin) - // Plugin1Profile.location - // mainpanel or swappanel or bottom-bar - // plugin.render() // plugin.create() + this.event.emit('displayableModuleActivated', jsonProfile, plugin.render()) + this.activated[jsonProfile.name] = plugin } activateInternal (name) { @@ -78,12 +93,14 @@ class PluginManagerComponent { if (mod.dep) dep = this.activateInternal(mod.dep) let instance = mod.target if (!instance && mod.Type) instance = new mod.Type(registry, dep) - if (!instance) return console.log('PluginManagerComponent: no Type or instance to add') + if (!instance) return console.log(`PluginManagerComponent: no Type or instance to add: ${JSON.stringify(mod)}`) registry.put({api: instance, name: mod.name.toLocaleLowerCase()}) if (instance.profile && typeof instance.profile === 'function') { this.event.emit('requestActivation', instance.profile(), instance) } - this.event.emit('internalActivated', mod, instance.render()) + if (mod.icon && instance.render && typeof instance.render === 'function') { + this.event.emit('requestContainer', mod, instance.render()) + } // if of type evm-compiler, we forward to the internal components if (mod.class === 'evm-compiler') { this.data.proxy.register(mod, instance) diff --git a/src/app/components/swap-panel-api.js b/src/app/components/swap-panel-api.js index 376f016f10..24cbd40ce5 100644 --- a/src/app/components/swap-panel-api.js +++ b/src/app/components/swap-panel-api.js @@ -13,7 +13,7 @@ class SwapPanelApi { verticalIconsComponent.event.on('showContent', (moduleName) => { this.component.showContent(moduleName) }) - pluginManagerComponent.event.on('internalActivated', (mod, content) => { + pluginManagerComponent.event.on('requestContainer', (mod, content) => { this.add(mod.name, content) }) } diff --git a/src/app/components/vertical-icons-api.js b/src/app/components/vertical-icons-api.js index 3cb69883d5..0ddd3c21bc 100644 --- a/src/app/components/vertical-icons-api.js +++ b/src/app/components/vertical-icons-api.js @@ -10,7 +10,7 @@ class VerticalIconsApi { constructor(verticalIconsComponent, pluginManagerComponent) { this.component = verticalIconsComponent - pluginManagerComponent.event.on('internalActivated', (mod, content) => verticalIconsComponent.addIcon(mod) ) + pluginManagerComponent.event.on('requestContainer', (mod, content) => verticalIconsComponent.addIcon(mod) ) } } module.exports = VerticalIconsApi diff --git a/src/app/editor/SourceHighlighters.js b/src/app/editor/SourceHighlighters.js new file mode 100644 index 0000000000..e5eaa3d92b --- /dev/null +++ b/src/app/editor/SourceHighlighters.js @@ -0,0 +1,35 @@ +'use strict' +var SourceHighlighter = require('./sourceHighlighter') + +module.exports = class SourceHighlighters { + + constructor () { + this.highlighters = {} + } + + profile () { + return { + type: 'sourcehighlighter', + methods: ['highlight', 'discardHighlight'] + } + } + + // TODO what to do with mod? + highlight (mod, lineColumnPos, filePath, hexColor, cb) { + var position + try { + position = JSON.parse(lineColumnPos) + } catch (e) { + return cb(e.message) + } + if (!highlighters[mod]) highlighters[mod] = new SourceHighlighter() + highlighters[mod].currentSourceLocation(null) + highlighters[mod].currentSourceLocationFromfileName(position, filePath, hexColor) + cb() + } + + discardHighlight (mod, cb) { + if (highlighters[mod]) highlighters[mod].currentSourceLocation(null) + cb() + } +} \ No newline at end of file diff --git a/src/app/editor/editor.js b/src/app/editor/editor.js index e2d27838c4..bdb9734a2a 100644 --- a/src/app/editor/editor.js +++ b/src/app/editor/editor.js @@ -7,6 +7,7 @@ var ace = require('brace') require('brace/theme/tomorrow_night_blue') var globalRegistry = require('../../global/registry') +const SourceHighlighters = require('./SourceHighlighters') var Range = ace.acequire('ace/range').Range require('brace/ext/language_tools') @@ -317,6 +318,8 @@ function Editor (opts = {}, localRegistry) { editor.commands.bindKeys({ 'ctrl-t': null }) editor.setShowPrintMargin(false) editor.resize(true) + + this.sourceHighlighters = new SourceHighlighters() } function editorOnChange (self) { diff --git a/src/app/files/browser-files-tree.js b/src/app/files/browser-files-tree.js index bc434e0561..b527d4cd71 100644 --- a/src/app/files/browser-files-tree.js +++ b/src/app/files/browser-files-tree.js @@ -130,6 +130,13 @@ function FilesTree (name, storage) { if (path[0] === '/') return path.substring(1) return path } + + this.profile = function () { + return { + type: this.type, + methods: ['get', 'set', 'remove'] + } + } } module.exports = FilesTree diff --git a/src/app/files/fileManager.js b/src/app/files/fileManager.js index 5361093123..65b8d19cb2 100644 --- a/src/app/files/fileManager.js +++ b/src/app/files/fileManager.js @@ -2,6 +2,7 @@ var $ = require('jquery') var yo = require('yo-yo') +var EventEmitter = require ('events') var EventManager = require('../../lib/events') var globalRegistry = require('../../global/registry') var CompilerImport = require('../compiler/compiler-imports') @@ -15,6 +16,7 @@ class FileManager { constructor (localRegistry) { this.tabbedFiles = {} this.event = new EventManager() + this.nodeEvent = new EventEmitter() this._components = {} this._components.compilerImport = new CompilerImport() this._components.registry = localRegistry || globalRegistry @@ -42,6 +44,18 @@ class FileManager { self._deps.gistExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) }) self._deps.localhostExplorer.event.register('errored', (event) => { this.removeTabsOf(self._deps.localhostExplorer) }) self._deps.localhostExplorer.event.register('closed', (event) => { this.removeTabsOf(self._deps.localhostExplorer) }) + + self.event.register('currentFileChanged', (file, provider) => { + this.nodeEvent.emit('currentFileChanged', file) + }) + } + + profile () { + return { + type: 'fileManager', + methods: ['getFilesFromPath', 'getCurrentFile', 'getFile', 'setFile'], + events: ['currentFileChanged'] + } } fileRenamedEvent (oldName, newName, isFolder) { @@ -94,6 +108,41 @@ class FileManager { return path ? path[1] : null } + getCurrentFile (cb) { + var path = this.currentFile() + if (!path) { + cb('no file selected') + } else { + cb(null, path) + } + } + + getFile (path, cb) { + var provider = this.fileProviderOf(path) + if (provider) { + // TODO add approval to user for external plugin to get the content of the given `path` + provider.get(path, (error, content) => { + cb(error, content) + }) + } else { + cb(path + ' not available') + } + } + + setFile (path, content, cb) { + var provider = this.fileProviderOf(path) + if (provider) { + // TODO add approval to user for external plugin to set the content of the given `path` + provider.set(path, content, (error) => { + if (error) return cb(error) + this.syncEditor(path) + cb() + }) + } else { + cb(path + ' not available') + } + } + removeTabsOf (provider) { for (var tab in this.tabbedFiles) { if (this.fileProviderOf(tab).type === provider.type) { diff --git a/src/app/panels/editor-panel.js b/src/app/panels/editor-panel.js index 6906222f01..6dc8f84467 100644 --- a/src/app/panels/editor-panel.js +++ b/src/app/panels/editor-panel.js @@ -15,9 +15,11 @@ var css = styles.css class EditorPanel { constructor (localRegistry) { var self = this + self.event = new EventManager() self._components = {} self._components.registry = localRegistry || globalRegistry - self.event = new EventManager() + self._components.editor = new Editor({}) + self._components.registry.put({api: self._components.editor, name: 'editor'}) } init () { var self = this @@ -38,32 +40,26 @@ class EditorPanel { } } self._view = {} - var editor = new Editor({}) - self._components.registry.put({api: editor, name: 'editor'}) - - var contextualListener = new ContextualListener({editor, pluginManager: self._deps.pluginManager}) - var contextView = new ContextView({contextualListener, editor}) + + var contextualListener = new ContextualListener({editor: self._components.editor, pluginManager: self._deps.pluginManager}) + var contextView = new ContextView({contextualListener, editor: self._components.editor}) - self._components = { - editor: editor, - contextualListener: contextualListener, - contextView: contextView, - // TODO list of compilers is always empty; should find a path to add plugin compiler here - terminal: new Terminal({ - udapp: self._deps.udapp, - compilers: {} - }, - { - getPosition: (event) => { - var limitUp = 36 - var limitDown = 20 - var height = window.innerHeight - var newpos = (event.pageY < limitUp) ? limitUp : event.pageY - newpos = (newpos < height - limitDown) ? newpos : height - limitDown - return newpos - } - }) - } + self._components.contextualListener = contextualListener + self._components.contextView = contextView + self._components.terminal = new Terminal({ + udapp: self._deps.udapp, + compilers: {} + }, + { + getPosition: (event) => { + var limitUp = 36 + var limitDown = 20 + var height = window.innerHeight + var newpos = (event.pageY < limitUp) ? limitUp : event.pageY + newpos = (newpos < height - limitDown) ? newpos : height - limitDown + return newpos + } + }) self._components.terminal.event.register('filterChanged', (type, value) => { this.event.trigger('terminalFilterChanged', [type, value]) diff --git a/src/app/tabs/compile-tab.js b/src/app/tabs/compile-tab.js index e2fba7c881..262a78a4f1 100644 --- a/src/app/tabs/compile-tab.js +++ b/src/app/tabs/compile-tab.js @@ -182,10 +182,13 @@ module.exports = class CompileTab { } }) } + getCompilationResult (cb) { + cb(null, self._components.compiler.lastCompilationResult) + } profile () { return { type: 'solidityCompile', - methods: {}, + methods: ['getCompilationResult'], events: ['compilationFinished'] } } diff --git a/src/universal-dapp.js b/src/universal-dapp.js index 7c77508a65..f15cb7d2b7 100644 --- a/src/universal-dapp.js +++ b/src/universal-dapp.js @@ -51,6 +51,13 @@ function UniversalDApp (opts, localRegistry) { self.resetEnvironment() } +UniversalDApp.prototype.profile = function () { + return { + type: 'udapp', + methods: ['runTestTx', 'getAccounts', 'createVMAccount'] + } +} + UniversalDApp.prototype.resetEnvironment = function () { this.accounts = {} if (executionContext.isVM()) { @@ -77,6 +84,7 @@ UniversalDApp.prototype.resetAPI = function (transactionContextAPI) { } UniversalDApp.prototype.createVMAccount = function (privateKey, balance, cb) { + if (executionContext.getProvider() !== 'vm') return cb('plugin API does not allow creating a new account through web3 connection. Only vm mode is allowed') this._addAccount(privateKey, balance) executionContext.vm().stateManager.cache.flush(function () {}) privateKey = Buffer.from(privateKey, 'hex') @@ -286,6 +294,34 @@ UniversalDApp.prototype.getInputs = function (funABI) { return txHelper.inputParametersDeclarationToString(funABI.inputs) } +/** + * This function send a tx only to javascript VM or testnet, will return an error for the mainnet + * SHOULD BE TAKEN CAREFULLY! + * + * @param {Object} tx - transaction. + * @param {Function} callback - callback. + */ +UniversalDApp.prototype.runTestTx = function (tx, cb) { + executionContext.detectNetwork((error, network) => { + if (error) return cb(error) + if (network.name === 'Main' && network.id === '1') { + return cb('It is not allowed to make this action against mainnet') + } + udapp.silentRunTx(tx, (error, result) => { + if (error) return cb(error) + cb(null, { + transactionHash: result.transactionHash, + status: result.result.status, + gasUsed: '0x' + result.result.gasUsed.toString('hex'), + error: result.result.vm.exceptionError, + return: result.result.vm.return ? '0x' + result.result.vm.return.toString('hex') : '0x', + createdAddress: result.result.createdAddress ? '0x' + result.result.createdAddress.toString('hex') : undefined + }) + }) + }) +} + + /** * This function send a tx without alerting the user (if mainnet or if gas estimation too high). * SHOULD BE TAKEN CAREFULLY!