diff --git a/package.json b/package.json index dee59f2d32..f45b38f644 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ }, "dependencies": { "http-server": "0.9.0", - "remix-plugin": "0.0.1-alpha.33", + "remix-plugin": "0.0.1-alpha.39", "remixd": "0.1.8-alpha.6" }, "repository": { diff --git a/src/app.js b/src/app.js index 5fc3e69559..046fc5a640 100644 --- a/src/app.js +++ b/src/app.js @@ -7,7 +7,6 @@ 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') var UniversalDAppUI = require('./universal-dapp-ui.js') @@ -55,8 +54,10 @@ const FilePanel = require('./app/panels/file-panel') import PanelsResize from './lib/panels-resize' import { EntityStore } from './lib/store' import { RemixAppManager } from './remixAppManager' -import { generateHomePage, homepageProfile } from './app/ui/landing-page/generate' +import { LandingPage } from './app/ui/landing-page/landing-page' import framingService from './framingService' +import { ApiFactory } from 'remix-plugin' +import { TxListenerModule } from './app/tabs/txlistener-module' var css = csjs` html { box-sizing: border-box; } @@ -116,8 +117,9 @@ var css = csjs` } ` -class App { +class App extends ApiFactory { constructor (api = {}, events = {}, opts = {}) { + super() var self = this this.event = new EventManager() self._components = {} @@ -173,7 +175,7 @@ class App { run.apply(self) } - profile () { + get profile () { return { name: 'app', description: 'service - provides information about current context (network).', @@ -373,26 +375,13 @@ Please make a backup of your contracts and start using http://remix.ethereum.org /* that proxy is used by appManager to broadcast new transaction event */ - const txListenerModuleProxy = { - event: new EventEmitter(), - profile () { - return { - name: 'txListener', - displayName: 'transaction listener', - events: ['newTransaction'], - description: 'service - notify new transactions' - } - } - } - txlistener.event.register('newTransaction', (tx) => { - txListenerModuleProxy.event.emit('newTransaction', tx) - }) + const txListenerModule = new TxListenerModule(txlistener) txlistener.startListening() // TODO: There are still a lot of dep between editorpanel and filemanager - let appStore = new EntityStore('module', { actives: [], ids: [], entities: {} }) + let appStore = new EntityStore('module', 'name') const appManager = new RemixAppManager(appStore) registry.put({api: appManager, name: 'appmanager'}) @@ -458,33 +447,35 @@ Please make a backup of your contracts and start using http://remix.ethereum.org let settings = new SettingsTab(self._components.registry) let analysis = new AnalysisTab(registry) let debug = new DebuggerTab() + const landingPage = new LandingPage(appManager, appStore) // 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'] appManager.init([ - { profile: homepageProfile(), api: generateHomePage(appManager, appStore) }, - { profile: this.profile(), api: this }, - { profile: udapp.profile(), api: udapp }, - { profile: fileManager.profile(), api: fileManager }, - { profile: sourceHighlighters.profile(), api: sourceHighlighters }, - { profile: configProvider.profile(), api: configProvider }, - { profile: txListenerModuleProxy.profile(), api: txListenerModuleProxy }, - { profile: filePanel.profile(), api: filePanel }, + this.api(), + landingPage.api(), + udapp.api(), + fileManager.api(), + sourceHighlighters.api(), + configProvider.api(), + txListenerModule.api(), + filePanel.api(), // { profile: support.profile(), api: support }, - { profile: settings.profile(), api: settings }, - { profile: pluginManagerComponent.profile(), api: pluginManagerComponent }]) + settings.api(), + pluginManagerComponent.api() + ]) appManager.registerMany([ - { profile: compileTab.profile(), api: compileTab }, - { profile: run.profile(), api: run }, - { profile: debug.profile(), api: debug }, - { profile: analysis.profile(), api: analysis }, - { profile: test.profile(), api: test }, - { profile: filePanel.remixdHandle.profile(), api: filePanel.remixdHandle } + compileTab.api(), + run.api(), + debug.api(), + analysis.api(), + test.api(), + filePanel.remixdHandle.api(), + ...appManager.plugins() ]) - appManager.registerMany(appManager.plugins()) framingService.start(appStore, swapPanelApi, verticalIconsApi, mainPanelApi, this._components.resizeFeature) @@ -512,7 +503,7 @@ Please make a backup of your contracts and start using http://remix.ethereum.org var txLogger = new TxLogger() // eslint-disable-line txLogger.event.register('debuggingRequested', (hash) => { if (!appStore.isActive('debugger')) appManager.activateOne('debugger') - appStore.getOne('debugger').api.debugger().debug(hash) + debug.debugger().debug(hash) verticalIconsApi.select('debugger') }) diff --git a/src/app/components/local-plugin.js b/src/app/components/local-plugin.js index 5fc4ab2936..59c1a5bcf5 100644 --- a/src/app/components/local-plugin.js +++ b/src/app/components/local-plugin.js @@ -6,7 +6,7 @@ module.exports = class LocalPlugin { /** * Open a modal to create a local plugin - * @param {{profile: any, api: any}[]} plugins The list of the plugins in the store + * @param {PluginApi[]} plugins The list of the plugins in the store * @returns {Promise<{api: any, profile: any}>} A promise with the new plugin profile */ open (plugins) { @@ -90,7 +90,7 @@ module.exports = class LocalPlugin { /** * The form to create a local plugin - * @param {Profile[]} plugins Liste of profile of the plugins + * @param {ProfileApi[]} plugins Liste of profile of the plugins */ form (plugins = []) { const name = this.profile.name || '' diff --git a/src/app/components/plugin-manager-component.js b/src/app/components/plugin-manager-component.js index 51e5f0ec7f..add33bcc27 100644 --- a/src/app/components/plugin-manager-component.js +++ b/src/app/components/plugin-manager-component.js @@ -2,7 +2,7 @@ const yo = require('yo-yo') const csjs = require('csjs-inject') const EventEmitter = require('events') const LocalPlugin = require('./local-plugin') -import { Plugin } from 'remix-plugin' +import { Plugin, ApiFactory } from 'remix-plugin' const css = csjs` .pluginSearch { @@ -34,9 +34,10 @@ const css = csjs` } ` -class PluginManagerComponent { +class PluginManagerComponent extends ApiFactory { constructor () { + super() this.event = new EventEmitter() this.views = { root: null, @@ -46,7 +47,7 @@ class PluginManagerComponent { this.filter = '' } - profile () { + get profile () { return { displayName: 'plugin manager', name: 'pluginManager', @@ -66,15 +67,15 @@ class PluginManagerComponent { this.store = store this.store.event.on('activate', (name) => { this.reRender() }) this.store.event.on('deactivate', (name) => { this.reRender() }) - this.store.event.on('add', (entity) => { this.reRender() }) - this.store.event.on('remove', (entity) => { this.reRender() }) + this.store.event.on('add', (api) => { this.reRender() }) + this.store.event.on('remove', (api) => { this.reRender() }) } renderItem (name) { - const mod = this.store.getOne(name) - if (!mod) return + const api = this.store.getOne(name) + if (!api) return const isActive = this.store.actives.includes(name) - const displayName = (mod.profile.displayName) ? mod.profile.displayName : name + const displayName = (api.profile.displayName) ? api.profile.displayName : name const activationButton = isActive ? yo` @@ -92,7 +93,7 @@ class PluginManagerComponent {
${displayName}
${activationButton} -

${mod.profile.description}

+

${api.profile.description}

` } @@ -107,9 +108,7 @@ class PluginManagerComponent { try { const profile = await this.localPlugin.open(this.store.getAll()) if (!profile) return - const resolveLocaton = (iframe) => this.appManager.resolveLocation(profile, iframe) - const api = new Plugin(profile, { resolveLocaton }) - this.appManager.registerOne({profile, api}) + this.appManager.registerOne(new Plugin(profile)) this.appManager.activateOne(profile.name) } catch (err) { // TODO : Use an alert to handle this error instead of a console.log @@ -119,11 +118,11 @@ class PluginManagerComponent { render () { // Filtering helpers - const isFiltered = ({profile}) => profile.name.toLowerCase().includes(this.filter) + const isFiltered = (api) => api.name.toLowerCase().includes(this.filter) const isNotRequired = ({profile}) => !profile.required const sortByName = (a, b) => { - const nameA = a.profile.name.toUpperCase() - const nameB = b.profile.name.toUpperCase() + const nameA = a.name.toUpperCase() + const nameB = b.name.toUpperCase() return (nameA < nameB) ? -1 : (nameA > nameB) ? 1 : 0 } @@ -132,10 +131,10 @@ class PluginManagerComponent { .filter(isFiltered) .filter(isNotRequired) .sort(sortByName) - .reduce(({actives, inactives}, {profile}) => { - return this.store.actives.includes(profile.name) - ? { actives: [...actives, profile.name], inactives } - : { inactives: [...inactives, profile.name], actives } + .reduce(({actives, inactives}, api) => { + return this.store.actives.includes(api.name) + ? { actives: [...actives, api.name], inactives } + : { inactives: [...inactives, api.name], actives } }, { actives: [], inactives: [] }) const activeTile = actives.length !== 0 diff --git a/src/app/components/swap-panel-api.js b/src/app/components/swap-panel-api.js index 9b2f8cc3bf..3fa4e742b1 100644 --- a/src/app/components/swap-panel-api.js +++ b/src/app/components/swap-panel-api.js @@ -5,7 +5,7 @@ class SwapPanelApi { this.event = new EventEmmitter() this.component = swapPanelComponent this.currentContent - verticalIconsComponent.event.on('showContent', (moduleName) => { + verticalIconsComponent.events.on('showContent', (moduleName) => { if (!swapPanelComponent.contents[moduleName]) return if (this.currentContent === moduleName) { this.event.emit('toggle', moduleName) diff --git a/src/app/components/swap-panel-component.js b/src/app/components/swap-panel-component.js index e2c9e6d1ba..485a9027a1 100644 --- a/src/app/components/swap-panel-component.js +++ b/src/app/components/swap-panel-component.js @@ -11,15 +11,10 @@ class SwapPanelComponent { // name of the current displayed content this.currentNode - appManager.event.on('pluginNeedsLocation', (profile, domEl) => { - if ((profile.prefferedLocation === this.name) || (!profile.prefferedLocation && opt.default)) { - this.add(profile.name, domEl) - } - }) - this.store.event.on('activate', (name) => { - const { profile, api } = this.store.getOne(name) - if (((profile.prefferedLocation === this.name) || (!profile.prefferedLocation && opt.default)) && + const api = this.store.getOne(name) + const profile = api.profile + if (((profile.location === this.name) || (!profile.location && opt.default)) && profile.icon && api.render && typeof api.render === 'function') { this.add(name, api.render()) } @@ -28,8 +23,8 @@ class SwapPanelComponent { this.store.event.on('deactivate', (name) => { if (this.contents[name]) this.remove(name) }) - this.store.event.on('add', (entity) => { }) - this.store.event.on('remove', (entity) => { }) + this.store.event.on('add', (api) => { }) + this.store.event.on('remove', (api) => { }) } showContent (moduleName) { @@ -40,8 +35,8 @@ class SwapPanelComponent { } this.contents[moduleName].style.display = 'block' this.currentNode = moduleName - var item = this.store.getOne(moduleName) - this.header.innerHTML = item.profile ? item.profile.displayName : ' - ' + var api = this.store.getOne(moduleName) + this.header.innerHTML = api.profile ? api.profile.displayName : ' - ' return } } diff --git a/src/app/components/vertical-icons-component.js b/src/app/components/vertical-icons-component.js index ff2c3d5326..e81c34c068 100644 --- a/src/app/components/vertical-icons-component.js +++ b/src/app/components/vertical-icons-component.js @@ -1,14 +1,14 @@ var yo = require('yo-yo') var csjs = require('csjs-inject') -const EventEmmitter = require('events') +const EventEmitter = require('events') // Component class VerticalIconComponent { constructor (name, appStore) { this.store = appStore - this.event = new EventEmmitter() + this.events = new EventEmitter() this.icons = {} this.iconKind = {} this.name = name @@ -16,28 +16,34 @@ class VerticalIconComponent { this.store.event.on('activate', (name) => { const { profile } = this.store.getOne(name) if (!profile.icon) return - if (profile.prefferedLocation === this.name || !profile.prefferedLocation) { + if (profile.location === this.name || !profile.location) { this.addIcon(profile) } }) this.store.event.on('deactivate', (name) => { - const item = this.store.getOne(name) - if (item && this.icons[name]) this.removeIcon(item.profile) + const api = this.store.getOne(name) + if (api && this.icons[name]) this.removeIcon(api.profile) }) - this.store.event.on('add', (entity) => { }) - this.store.event.on('remove', (entity) => { }) + this.store.event.on('add', (api) => { }) + this.store.event.on('remove', (api) => { }) } - addIcon (mod) { - let kind = mod.kind || 'other' - this.icons[mod.name] = yo`
{ this._iconClick(mod.name) }} title=${mod.name} >${mod.name}
` + /** + * Add an icon to the map + * @param {ModuleProfile} profile The profile of the module + */ + addIcon ({kind, name, icon}) { + this.icons[name] = yo`
${name}
` - this.iconKind[kind].appendChild(this.icons[mod.name]) + this.iconKind[kind || 'other'].appendChild(this.icons[name]) } - removeIcon (mod) { - let kind = mod.kind || 'other' - if (this.icons[mod.name]) this.iconKind[kind].removeChild(this.icons[mod.name]) + /** + * Remove an icon from the map + * @param {ModuleProfile} profile The profile of the module + */ + removeIcon ({kind, name}) { + if (this.icons[name]) this.iconKind[kind || 'other'].removeChild(this.icons[name]) } select (name) { @@ -53,7 +59,7 @@ class VerticalIconComponent { let activate = this.view.querySelector(`[title="${name}"]`) if (activate) activate.classList.toggle(`${css.active}`) } - this.event.emit('showContent', name) + this.events.emit('showContent', name) } _iconClick (name) { diff --git a/src/app/editor/SourceHighlighters.js b/src/app/editor/SourceHighlighters.js index 59243c646a..85c69e2537 100644 --- a/src/app/editor/SourceHighlighters.js +++ b/src/app/editor/SourceHighlighters.js @@ -1,13 +1,16 @@ 'use strict' const SourceHighlighter = require('./sourceHighlighter') -class SourceHighlighters { +import { ApiFactory } from 'remix-plugin' + +class SourceHighlighters extends ApiFactory { constructor () { + super() this.highlighters = {} } - profile () { + get profile () { return { displayName: 'source highlighters', name: 'sourceHighlighters', @@ -16,27 +19,21 @@ class SourceHighlighters { } } - // TODO what to do with mod? - async highlight (mod, lineColumnPos, filePath, hexColor) { - return new Promise((resolve, reject) => { - let position - try { - position = JSON.parse(lineColumnPos) - } catch (e) { - throw e - } - if (!this.highlighters[mod]) this.highlighters[mod] = new SourceHighlighter() - this.highlighters[mod].currentSourceLocation(null) - this.highlighters[mod].currentSourceLocationFromfileName(position, filePath, hexColor) - resolve() - }) + highlight (lineColumnPos, filePath, hexColor) { + const { from } = this.currentRequest + try { + const position = JSON.parse(lineColumnPos) + if (!this.highlighters[from]) this.highlighters[from] = new SourceHighlighter() + this.highlighters[from].currentSourceLocation(null) + this.highlighters[from].currentSourceLocationFromfileName(position, filePath, hexColor) + } catch (e) { + throw e + } } - async discardHighlight (mod) { - return new Promise((resolve, reject) => { - if (this.highlighters[mod]) this.highlighters[mod].currentSourceLocation(null) - resolve() - }) + discardHighlight () { + const { from } = this.currentRequest + if (this.highlighters[from]) this.highlighters[from].currentSourceLocation(null) } } diff --git a/src/app/files/browser-files-tree.js b/src/app/files/browser-files-tree.js index 8051b742c6..3c8eecf991 100644 --- a/src/app/files/browser-files-tree.js +++ b/src/app/files/browser-files-tree.js @@ -2,21 +2,35 @@ var EventManager = require('../../lib/events') -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) { +import { ApiFactory } from 'remix-plugin' + +class FilesTree extends ApiFactory { + + constructor (name, storage) { + super() + this.event = new EventManager() + this.storage = storage + this.type = name + this.structFile = '.' + name + '.tree' + this.tree = {} + } + + get profile () { + // TODO should make them promisable + return { + name: this.type, + methods: ['get', 'set', 'remove'], + description: 'service - read/write file to the `config` explorer without need of additionnal permission.' + } + } + + exists (path, cb) { cb(null, this._exists(path)) } - function updateRefs (path, type) { + updateRefs (path, type) { var split = path.split('/') // this should be unprefixed path - var crawlpath = self.tree + var crawlpath = this.tree var intermediatePath = '' split.forEach((pathPart, index) => { intermediatePath += pathPart @@ -30,91 +44,90 @@ function FilesTree (name, storage) { delete crawlpath[intermediatePath] } }) - storage.set(self.structFile, JSON.stringify(self.tree)) + this.storage.set(this.structFile, JSON.stringify(this.tree)) } - this._exists = function (path) { + _exists (path) { var unprefixedpath = this.removePrefix(path) - return storage.exists(unprefixedpath) + return this.storage.exists(unprefixedpath) } - this.init = function (cb) { - var tree = storage.get(this.structFile) + init (cb) { + var tree = this.storage.get(this.structFile) this.tree = tree ? JSON.parse(tree) : {} if (cb) cb() } - this.get = function (path, cb) { + get (path, cb) { var unprefixedpath = this.removePrefix(path) - var content = storage.get(unprefixedpath) + var content = this.storage.get(unprefixedpath) if (cb) { cb(null, content) } return content } - this.set = function (path, content, cb) { + set (path, content, cb) { var unprefixedpath = this.removePrefix(path) - updateRefs(unprefixedpath, 'add') - var exists = storage.exists(unprefixedpath) - if (!storage.set(unprefixedpath, content)) { + this.updateRefs(unprefixedpath, 'add') + var exists = this.storage.exists(unprefixedpath) + if (!this.storage.set(unprefixedpath, content)) { if (cb) cb('error updating ' + path) return false } if (!exists) { - event.trigger('fileAdded', [this.type + '/' + unprefixedpath, false]) + this.event.trigger('fileAdded', [this.type + '/' + unprefixedpath, false]) } else { - event.trigger('fileChanged', [this.type + '/' + unprefixedpath]) + this.event.trigger('fileChanged', [this.type + '/' + unprefixedpath]) } if (cb) cb() return true } - this.addReadOnly = function (path, content) { + addReadOnly (path, content) { return this.set(path, content) } - this.isReadOnly = function (path) { + isReadOnly (path) { return false } - this.remove = function (path) { + remove (path) { var unprefixedpath = this.removePrefix(path) - updateRefs(unprefixedpath, 'remove') + this.updateRefs(unprefixedpath, 'remove') if (!this._exists(unprefixedpath)) { return false } - if (!storage.remove(unprefixedpath)) { + if (!this.storage.remove(unprefixedpath)) { return false } - event.trigger('fileRemoved', [this.type + '/' + unprefixedpath]) + this.event.trigger('fileRemoved', [this.type + '/' + unprefixedpath]) return true } - this.rename = function (oldPath, newPath, isFolder) { + rename (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)) { + this.updateRefs(unprefixedoldPath, 'remove') + this.updateRefs(unprefixednewPath, 'add') + if (this.storage.exists(unprefixedoldPath)) { + if (!this.storage.rename(unprefixedoldPath, unprefixednewPath)) { return false } - event.trigger('fileRenamed', [this.type + '/' + unprefixedoldPath, this.type + '/' + unprefixednewPath, isFolder]) + this.event.trigger('fileRenamed', [this.type + '/' + unprefixedoldPath, this.type + '/' + unprefixednewPath, isFolder]) return true } return false } - this.resolveDirectory = function (path, callback) { - var self = this + resolveDirectory (path, callback) { if (path[0] === '/') path = path.substring(1) - if (!path) return callback(null, { [self.type]: { } }) + if (!path) return callback(null, { [this.type]: { } }) var tree = {} - path = self.removePrefix(path) + path = this.removePrefix(path) var split = path.split('/') // this should be unprefixed path - var crawlpath = self.tree + var crawlpath = this.tree split.forEach((pathPart, index) => { if (crawlpath[pathPart]) crawlpath = crawlpath[pathPart] }) @@ -125,20 +138,12 @@ function FilesTree (name, storage) { callback(null, tree) } - this.removePrefix = function (path) { + removePrefix (path) { path = path.indexOf(this.type) === 0 ? path.replace(this.type, '') : path if (path[0] === '/') return path.substring(1) return path } - this.profile = function () { - // TODO should make them promisable - return { - name: this.type, - methods: ['get', 'set', 'remove'], - description: 'service - read/write file to the `config` explorer without need of additionnal permission.' - } - } } module.exports = FilesTree diff --git a/src/app/files/fileManager.js b/src/app/files/fileManager.js index e54a1a286d..63966ba15f 100644 --- a/src/app/files/fileManager.js +++ b/src/app/files/fileManager.js @@ -3,14 +3,16 @@ const EventEmitter = require('events') var globalRegistry = require('../../global/registry') var CompilerImport = require('../compiler/compiler-imports') +import { ApiFactory } from 'remix-plugin' /* attach to files event (removed renamed) trigger: currentFileChanged */ -class FileManager { +class FileManager extends ApiFactory { constructor (localRegistry) { + super() this.openedFiles = {} // list all opened files this.events = new EventEmitter() this._components = {} @@ -19,30 +21,29 @@ class FileManager { } init () { - var self = this - self._deps = { - editor: self._components.registry.get('editor').api, - config: self._components.registry.get('config').api, - browserExplorer: self._components.registry.get('fileproviders/browser').api, - localhostExplorer: self._components.registry.get('fileproviders/localhost').api, - configExplorer: self._components.registry.get('fileproviders/config').api, - gistExplorer: self._components.registry.get('fileproviders/gist').api, - filesProviders: self._components.registry.get('fileproviders').api + this._deps = { + editor: this._components.registry.get('editor').api, + config: this._components.registry.get('config').api, + browserExplorer: this._components.registry.get('fileproviders/browser').api, + localhostExplorer: this._components.registry.get('fileproviders/localhost').api, + configExplorer: this._components.registry.get('fileproviders/config').api, + gistExplorer: this._components.registry.get('fileproviders/gist').api, + filesProviders: this._components.registry.get('fileproviders').api } - self._deps.browserExplorer.event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) }) - self._deps.localhostExplorer.event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) }) - self._deps.configExplorer.event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) }) - self._deps.gistExplorer.event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) }) - self._deps.browserExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) }) - self._deps.localhostExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) }) - self._deps.configExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) }) - 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) }) + this._deps.browserExplorer.event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) }) + this._deps.localhostExplorer.event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) }) + this._deps.configExplorer.event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) }) + this._deps.gistExplorer.event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) }) + this._deps.browserExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) }) + this._deps.localhostExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) }) + this._deps.configExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) }) + this._deps.gistExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) }) + this._deps.localhostExplorer.event.register('errored', (event) => { this.removeTabsOf(this._deps.localhostExplorer) }) + this._deps.localhostExplorer.event.register('closed', (event) => { this.removeTabsOf(this._deps.localhostExplorer) }) } - profile () { + get profile () { return { displayName: 'file manager', name: 'fileManager', @@ -53,10 +54,9 @@ class FileManager { } fileRenamedEvent (oldName, newName, isFolder) { - var self = this if (!isFolder) { - self._deps.config.set('currentFile', '') - self._deps.editor.discard(oldName) + this._deps.config.set('currentFile', '') + this._deps.editor.discard(oldName) if (this.openedFiles[oldName]) { delete this.openedFiles[oldName] this.openedFiles[newName] = newName @@ -69,7 +69,7 @@ class FileManager { var newAbsolutePath = k.replace(oldName, newName) this.openedFiles[newAbsolutePath] = newAbsolutePath delete this.openedFiles[k] - if (self._deps.config.get('currentFile') === k) { + if (this._deps.config.get('currentFile') === k) { newFocus = newAbsolutePath } } @@ -105,8 +105,7 @@ class FileManager { } currentPath () { - var self = this - var currentFile = self._deps.config.get('currentFile') + var currentFile = this._deps.config.get('currentFile') var reg = /(.*)(\/).*/ var path = reg.exec(currentFile) return path ? path[1] : null @@ -154,47 +153,45 @@ class FileManager { } fileRemovedEvent (path) { - var self = this if (!this.openedFiles[path]) return - if (path === self._deps.config.get('currentFile')) { - self._deps.config.set('currentFile', '') + if (path === this._deps.config.get('currentFile')) { + this._deps.config.set('currentFile', '') } - self._deps.editor.discard(path) + this._deps.editor.discard(path) delete this.openedFiles[path] this.events.emit('fileRemoved', path) this.switchFile() } switchFile (file) { - var self = this + const _switchFile = (file) => { + this.saveCurrentFile() + this._deps.config.set('currentFile', file) + this.openedFiles[file] = file + this.fileProviderOf(file).get(file, (error, content) => { + if (error) { + console.log(error) + } else { + if (this.fileProviderOf(file).isReadOnly(file)) { + this._deps.editor.openReadOnly(file, content) + } else { + this._deps.editor.open(file, content) + } + this.events.emit('currentFileChanged', file) + } + }) + } if (file) return _switchFile(file) else { - var browserProvider = self._deps.filesProviders['browser'] + var browserProvider = this._deps.filesProviders['browser'] browserProvider.resolveDirectory('browser', (error, filesTree) => { if (error) console.error(error) var fileList = Object.keys(filesTree) if (fileList.length) { _switchFile(browserProvider.type + '/' + fileList[0]) } else { - self.events.emit('currentFileChanged') - self._deps.editor.displayEmptyReadOnlySession() - } - }) - } - function _switchFile (file) { - self.saveCurrentFile() - self._deps.config.set('currentFile', file) - self.openedFiles[file] = file - self.fileProviderOf(file).get(file, (error, content) => { - if (error) { - console.log(error) - } else { - if (self.fileProviderOf(file).isReadOnly(file)) { - self._deps.editor.openReadOnly(file, content) - } else { - self._deps.editor.open(file, content) - } - self.events.emit('currentFileChanged', file) + this.events.emit('currentFileChanged') + this._deps.editor.displayEmptyReadOnlySession() } }) } @@ -243,7 +240,6 @@ class FileManager { } syncEditor (path) { - var self = this var currentFile = this._deps.config.get('currentFile') if (path !== currentFile) return @@ -251,7 +247,7 @@ class FileManager { if (provider) { provider.get(currentFile, (error, content) => { if (error) console.log(error) - self._deps.editor.setText(content) + this._deps.editor.setText(content) }) } else { console.log('cannot save ' + currentFile + '. Does not belong to any explorer') diff --git a/src/app/files/remixd-handle.js b/src/app/files/remixd-handle.js index 19b62947fc..1cc02c28f4 100644 --- a/src/app/files/remixd-handle.js +++ b/src/app/files/remixd-handle.js @@ -1,3 +1,5 @@ +import { ApiFactory } from 'remix-plugin' + var yo = require('yo-yo') var modalDialog = require('../ui/modaldialog') @@ -14,13 +16,14 @@ var css = csjs` } ` -export class RemixdHandle { +export class RemixdHandle extends ApiFactory { constructor (fileSystemExplorer, locahostProvider) { + super() this.fileSystemExplorer = fileSystemExplorer this.locahostProvider = locahostProvider } - profile () { + get profile () { return { name: 'remixd', methods: [], diff --git a/src/app/panels/file-panel.js b/src/app/panels/file-panel.js index 47abfc628c..154171f562 100644 --- a/src/app/panels/file-panel.js +++ b/src/app/panels/file-panel.js @@ -17,6 +17,8 @@ var globalRegistry = require('../../global/registry') var css = require('./styles/file-panel-styles') +import { ApiFactory } from 'remix-plugin' + var canUpload = window.File || window.FileReader || window.FileList || window.Blob /* @@ -36,285 +38,289 @@ var canUpload = window.File || window.FileReader || window.FileList || window.Bl - call fileProvider API */ -function filepanel (localRegistry) { - var self = this - self._components = {} - self._components.registry = localRegistry || globalRegistry - self._deps = { - fileProviders: self._components.registry.get('fileproviders').api, - fileManager: self._components.registry.get('filemanager').api, - config: self._components.registry.get('config').api, - pluginManager: self._components.registry.get('pluginmanager').api - } - var fileExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['browser']) - var fileSystemExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['localhost']) - var swarmExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['swarm']) - var githubExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['github']) - var gistExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['gist']) - var configExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['config']) - var httpExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['http']) - var httpsExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['https']) - - self.remixdHandle = new RemixdHandle(fileSystemExplorer, self._deps.fileProviders['localhost']) - - // ----------------- editor panel ---------------------- - self._compilerMetadata = new CompilerMetadata( - { - fileManager: self._deps.fileManager, - pluginManager: self._deps.pluginManager, - config: self._deps.config +module.exports = class Filepanel extends ApiFactory { + + constructor (localRegistry) { + super() + var self = this + self._components = {} + self._components.registry = localRegistry || globalRegistry + self._deps = { + fileProviders: self._components.registry.get('fileproviders').api, + fileManager: self._components.registry.get('filemanager').api, + config: self._components.registry.get('config').api, + pluginManager: self._components.registry.get('pluginmanager').api } - ) - self._compilerMetadata.syncContractMetadata() - - self.compilerMetadata = () => { return self._compilerMetadata } - - function template () { - return yo` -
-
-
- - - - ${canUpload ? yo` - - + var fileExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['browser']) + var fileSystemExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['localhost']) + var swarmExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['swarm']) + var githubExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['github']) + var gistExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['gist']) + var configExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['config']) + var httpExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['http']) + var httpsExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['https']) + + self.remixdHandle = new RemixdHandle(fileSystemExplorer, self._deps.fileProviders['localhost']) + + // ----------------- editor panel ---------------------- + self._compilerMetadata = new CompilerMetadata( + { + fileManager: self._deps.fileManager, + pluginManager: self._deps.pluginManager, + config: self._deps.config + } + ) + self._compilerMetadata.syncContractMetadata() + + self.compilerMetadata = () => { return self._compilerMetadata } + + function template () { + return yo` +
+
+
+ + - ` : ''} - publishToGist('browser')}> - - - updateGist()}> - - - - - -
-
-
${fileExplorer.init()}
-
${configExplorer.init()}
-
${fileSystemExplorer.init()}
-
${swarmExplorer.init()}
-
${githubExplorer.init()}
-
${gistExplorer.init()}
-
${httpExplorer.init()}
-
${httpsExplorer.init()}
+ ${canUpload ? yo` + + + + ` : ''} + publishToGist('browser')}> + + + updateGist()}> + + + + + +
+
+
${fileExplorer.init()}
+
${configExplorer.init()}
+
${fileSystemExplorer.init()}
+
${swarmExplorer.init()}
+
${githubExplorer.init()}
+
${gistExplorer.init()}
+
${httpExplorer.init()}
+
${httpsExplorer.init()}
+
-
- ` - } + ` + } - var event = new EventManager() - self.event = event - var element = template() - fileExplorer.ensureRoot() - configExplorer.ensureRoot() - self._deps.fileProviders['localhost'].event.register('connecting', (event) => { - }) + var event = new EventManager() + self.event = event + var element = template() + fileExplorer.ensureRoot() + configExplorer.ensureRoot() + self._deps.fileProviders['localhost'].event.register('connecting', (event) => { + }) - self._deps.fileProviders['localhost'].event.register('connected', (event) => { - fileSystemExplorer.show() - }) + self._deps.fileProviders['localhost'].event.register('connected', (event) => { + fileSystemExplorer.show() + }) - self._deps.fileProviders['localhost'].event.register('errored', (event) => { - fileSystemExplorer.hide() - }) + self._deps.fileProviders['localhost'].event.register('errored', (event) => { + fileSystemExplorer.hide() + }) - self._deps.fileProviders['localhost'].event.register('closed', (event) => { - fileSystemExplorer.hide() - }) + self._deps.fileProviders['localhost'].event.register('closed', (event) => { + fileSystemExplorer.hide() + }) - fileExplorer.events.register('focus', function (path) { - self._deps.fileManager.switchFile(path) - }) + fileExplorer.events.register('focus', function (path) { + self._deps.fileManager.switchFile(path) + }) - configExplorer.events.register('focus', function (path) { - self._deps.fileManager.switchFile(path) - }) + configExplorer.events.register('focus', function (path) { + self._deps.fileManager.switchFile(path) + }) - fileSystemExplorer.events.register('focus', function (path) { - self._deps.fileManager.switchFile(path) - }) + fileSystemExplorer.events.register('focus', function (path) { + self._deps.fileManager.switchFile(path) + }) - swarmExplorer.events.register('focus', function (path) { - self._deps.fileManager.switchFile(path) - }) + swarmExplorer.events.register('focus', function (path) { + self._deps.fileManager.switchFile(path) + }) - githubExplorer.events.register('focus', function (path) { - self._deps.fileManager.switchFile(path) - }) + githubExplorer.events.register('focus', function (path) { + self._deps.fileManager.switchFile(path) + }) - gistExplorer.events.register('focus', function (path) { - self._deps.fileManager.switchFile(path) - }) + gistExplorer.events.register('focus', function (path) { + self._deps.fileManager.switchFile(path) + }) - httpExplorer.events.register('focus', function (path) { - self._deps.fileManager.switchFile(path) - }) + httpExplorer.events.register('focus', function (path) { + self._deps.fileManager.switchFile(path) + }) - httpsExplorer.events.register('focus', function (path) { - self._deps.fileManager.switchFile(path) - }) + httpsExplorer.events.register('focus', function (path) { + self._deps.fileManager.switchFile(path) + }) - self.render = function render () { return element } + self.render = function render () { return element } + + function uploadFile (event) { + // TODO The file explorer is merely a view on the current state of + // the files module. Please ask the user here if they want to overwrite + // a file and then just use `files.add`. The file explorer will + // pick that up via the 'fileAdded' event from the files module. + + ;[...this.files].forEach((file) => { + var files = fileExplorer.files + function loadFile () { + var fileReader = new FileReader() + fileReader.onload = function (event) { + if (helper.checkSpecialChars(file.name)) { + modalDialogCustom.alert('Special characters are not allowed') + return + } + var success = files.set(name, event.target.result) + if (!success) modalDialogCustom.alert('Failed to create file ' + name) + else self.event.trigger('focus', [name]) + } + fileReader.readAsText(file) + } - self.profile = function () { - return { - name: 'fileExplorers', - displayName: 'file explorers', - methods: [], - events: [], - icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNjk2IDM4NHE0MCAwIDY4IDI4dDI4IDY4djEyMTZxMCA0MC0yOCA2OHQtNjggMjhoLTk2MHEtNDAgMC02OC0yOHQtMjgtNjh2LTI4OGgtNTQ0cS00MCAwLTY4LTI4dC0yOC02OHYtNjcycTAtNDAgMjAtODh0NDgtNzZsNDA4LTQwOHEyOC0yOCA3Ni00OHQ4OC0yMGg0MTZxNDAgMCA2OCAyOHQyOCA2OHYzMjhxNjgtNDAgMTI4LTQwaDQxNnptLTU0NCAyMTNsLTI5OSAyOTloMjk5di0yOTl6bS02NDAtMzg0bC0yOTkgMjk5aDI5OXYtMjk5em0xOTYgNjQ3bDMxNi0zMTZ2LTQxNmgtMzg0djQxNnEwIDQwLTI4IDY4dC02OCAyOGgtNDE2djY0MGg1MTJ2LTI1NnEwLTQwIDIwLTg4dDQ4LTc2em05NTYgODA0di0xMTUyaC0zODR2NDE2cTAgNDAtMjggNjh0LTY4IDI4aC00MTZ2NjQwaDg5NnoiLz48L3N2Zz4=', - description: ' - ', - kind: 'fileexplorer' + var name = files.type + '/' + file.name + files.exists(name, (error, exist) => { + if (error) console.log(error) + if (!exist) { + loadFile() + } else { + modalDialogCustom.confirm(null, `The file ${name} already exists! Would you like to overwrite it?`, () => { loadFile() }) + } + }) + }) } - } - function uploadFile (event) { - // TODO The file explorer is merely a view on the current state of - // the files module. Please ask the user here if they want to overwrite - // a file and then just use `files.add`. The file explorer will - // pick that up via the 'fileAdded' event from the files module. - - ;[...this.files].forEach((file) => { - var files = fileExplorer.files - function loadFile () { - var fileReader = new FileReader() - fileReader.onload = function (event) { - if (helper.checkSpecialChars(file.name)) { - modalDialogCustom.alert('Special characters are not allowed') - return + function createNewFile () { + modalDialogCustom.prompt(null, 'File Name', 'Untitled.sol', (input) => { + helper.createNonClashingName(input, self._deps.fileProviders['browser'], (error, newName) => { + if (error) return modalDialogCustom.alert('Failed to create file ' + newName + ' ' + error) + if (!self._deps.fileProviders['browser'].set(newName, '')) { + modalDialogCustom.alert('Failed to create file ' + newName) + } else { + var file = self._deps.fileProviders['browser'].type + '/' + newName + self._deps.fileManager.switchFile(file) + if (file.includes('_test.sol')) { + self.event.trigger('newTestFileCreated', [file]) + } } - var success = files.set(name, event.target.result) - if (!success) modalDialogCustom.alert('Failed to create file ' + name) - else self.event.trigger('focus', [name]) - } - fileReader.readAsText(file) + }) + }, null, true) + } + + // ------------------ gist publish -------------- + + function updateGist () { + var gistId = self._deps.fileProviders['gist'].id + if (!gistId) { + tooltip('no gist content is currently loaded.') + } else { + toGist('gist', gistId) } + } - var name = files.type + '/' + file.name - files.exists(name, (error, exist) => { - if (error) console.log(error) - if (!exist) { - loadFile() - } else { - modalDialogCustom.confirm(null, `The file ${name} already exists! Would you like to overwrite it?`, () => { loadFile() }) - } + function publishToGist (fileProviderName) { + modalDialogCustom.confirm(null, 'Are you very sure you want to publish all your files anonymously as a public gist on github.com?', () => { + toGist(fileProviderName) }) - }) - } + } - function createNewFile () { - modalDialogCustom.prompt(null, 'File Name', 'Untitled.sol', (input) => { - helper.createNonClashingName(input, self._deps.fileProviders['browser'], (error, newName) => { - if (error) return modalDialogCustom.alert('Failed to create file ' + newName + ' ' + error) - if (!self._deps.fileProviders['browser'].set(newName, '')) { - modalDialogCustom.alert('Failed to create file ' + newName) + function toGist (fileProviderName, id) { + packageFiles(self._deps.fileProviders[fileProviderName], (error, packaged) => { + if (error) { + console.log(error) + modalDialogCustom.alert('Failed to create gist: ' + error) } else { - var file = self._deps.fileProviders['browser'].type + '/' + newName - self._deps.fileManager.switchFile(file) - if (file.includes('_test.sol')) { - self.event.trigger('newTestFileCreated', [file]) + var tokenAccess = self._deps.config.get('settings/gist-access-token') + if (!tokenAccess) { + modalDialogCustom.alert('Remix requires an access token (which includes gists creation permission). Please go to the settings tab for more information.') + } else { + var description = 'Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. \n Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=' + queryParams.get().version + '&optimize=' + queryParams.get().optimize + '&gist=' + var gists = new Gists({ + token: tokenAccess + }) + if (id) { + tooltip('Saving gist (' + id + ') ...') + gists.edit({ + description: description, + public: true, + files: packaged, + id: id + }, (error, result) => { + cb(error, result) + }) + } else { + tooltip('Creating a new gist ...') + gists.create({ + description: description, + public: true, + files: packaged + }, (error, result) => { + cb(error, result) + }) + } } } }) - }, null, true) - } - - // ------------------ gist publish -------------- - - function updateGist () { - var gistId = self._deps.fileProviders['gist'].id - if (!gistId) { - tooltip('no gist content is currently loaded.') - } else { - toGist('gist', gistId) } - } - - function publishToGist (fileProviderName) { - modalDialogCustom.confirm(null, 'Are you very sure you want to publish all your files anonymously as a public gist on github.com?', () => { - toGist(fileProviderName) - }) - } - function toGist (fileProviderName, id) { - packageFiles(self._deps.fileProviders[fileProviderName], (error, packaged) => { + function cb (error, data) { if (error) { - console.log(error) - modalDialogCustom.alert('Failed to create gist: ' + error) + modalDialogCustom.alert('Failed to manage gist: ' + error) } else { - var tokenAccess = self._deps.config.get('settings/gist-access-token') - if (!tokenAccess) { - modalDialogCustom.alert('Remix requires an access token (which includes gists creation permission). Please go to the settings tab for more information.') - } else { - var description = 'Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. \n Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=' + queryParams.get().version + '&optimize=' + queryParams.get().optimize + '&gist=' - var gists = new Gists({ - token: tokenAccess + if (data.html_url) { + modalDialogCustom.confirm(null, `The gist is at ${data.html_url}. Would you like to open it in a new window?`, () => { + window.open(data.html_url, '_blank') }) - if (id) { - tooltip('Saving gist (' + id + ') ...') - gists.edit({ - description: description, - public: true, - files: packaged, - id: id - }, (error, result) => { - cb(error, result) - }) - } else { - tooltip('Creating a new gist ...') - gists.create({ - description: description, - public: true, - files: packaged - }, (error, result) => { - cb(error, result) - }) - } + } else { + modalDialogCustom.alert(data.message + ' ' + data.documentation_url + ' ' + JSON.stringify(data.errors, null, '\t')) } } - }) - } + } + + // ------------------ copy files -------------- - function cb (error, data) { - if (error) { - modalDialogCustom.alert('Failed to manage gist: ' + error) - } else { - if (data.html_url) { - modalDialogCustom.confirm(null, `The gist is at ${data.html_url}. Would you like to open it in a new window?`, () => { - window.open(data.html_url, '_blank') + function copyFiles () { + modalDialogCustom.prompt(null, 'To which other remix-ide instance do you want to copy over all files?', 'https://remix.ethereum.org', (target) => { + doCopy(target) + }) + function doCopy (target) { + // package only files from the browser storage. + packageFiles(self._deps.fileProviders['browser'], (error, packaged) => { + if (error) { + console.log(error) + } else { + $('