Merge branch 'swap_it' into urlresolver

pull/1/head
yann300 6 years ago committed by GitHub
commit 16f1424fa3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      package.json
  2. 89
      src/app.js
  3. 4
      src/app/components/local-plugin.js
  4. 37
      src/app/components/plugin-manager-component.js
  5. 2
      src/app/components/swap-panel-api.js
  6. 19
      src/app/components/swap-panel-component.js
  7. 36
      src/app/components/vertical-icons-component.js
  8. 96
      src/app/debugger/debuggerUI.js
  9. 39
      src/app/editor/SourceHighlighters.js
  10. 105
      src/app/files/browser-files-tree.js
  11. 276
      src/app/files/file-explorer.js
  12. 108
      src/app/files/fileManager.js
  13. 7
      src/app/files/remixd-handle.js
  14. 16
      src/app/files/styles/file-explorer-styles.js
  15. 386
      src/app/panels/file-panel.js
  16. 16
      src/app/panels/styles/file-panel-styles.js
  17. 16
      src/app/panels/tab-proxy.js
  18. 138
      src/app/panels/terminal.js
  19. 10
      src/app/tabs/analysis-tab.js
  20. 84
      src/app/tabs/compile-tab.js
  21. 8
      src/app/tabs/debugger-tab.js
  22. 28
      src/app/tabs/run-tab.js
  23. 217
      src/app/tabs/settings-tab.js
  24. 65
      src/app/tabs/styles/settings-tab-styles.js
  25. 36
      src/app/tabs/styles/tabbed-menu-styles.js
  26. 85
      src/app/tabs/support-tab.js
  27. 90
      src/app/tabs/tabbed-menu.js
  28. 343
      src/app/tabs/test-tab.js
  29. 85
      src/app/tabs/testTab/testTab.js
  30. 22
      src/app/tabs/txlistener-module.js
  31. 188
      src/app/ui/auto-complete-popup.js
  32. 55
      src/app/ui/landing-page/generate.js
  33. 139
      src/app/ui/landing-page/landing-page.js
  34. 27
      src/app/ui/modaldialog.js
  35. 5
      src/app/ui/styles/auto-complete-popup-styles.js
  36. 7
      src/framingService.js
  37. 3
      src/lib/store.js
  38. 43
      src/remixAppManager.js
  39. 580
      src/universal-dapp.js

@ -65,7 +65,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": {
@ -178,7 +178,7 @@
"remixd": "remixd -s ./contracts --remix-ide http://127.0.0.1:8080",
"selenium": "execr --silent selenium-standalone start",
"selenium-install": "selenium-standalone install",
"serve": "execr --silent http-server .",
"serve": "http-server .",
"serve_debugger": "execr --silent http-server src/app/debugger/remix-debugger",
"sourcemap": "exorcist --root ../ build/app.js.map > build/app.js",
"start": "npm-run-all -lpr serve watch onchange remixd",

@ -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')
@ -47,7 +46,6 @@ const CompileTab = require('./app/tabs/compile-tab')
const SettingsTab = require('./app/tabs/settings-tab')
const AnalysisTab = require('./app/tabs/analysis-tab')
const DebuggerTab = require('./app/tabs/debugger-tab')
// const SupportTab = require('./app/tabs/support-tab')
const TestTab = require('./app/tabs/test-tab')
const RunTab = require('./app/tabs/run-tab')
const FilePanel = require('./app/panels/file-panel')
@ -55,8 +53,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 +116,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 +174,7 @@ class App {
run.apply(self)
}
profile () {
get profile () {
return {
name: 'app',
description: 'service - provides information about current context (network).',
@ -373,26 +374,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'})
@ -443,7 +431,15 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
let filePanel = new FilePanel()
registry.put({api: filePanel, name: 'filepanel'})
let compileTab = new CompileTab(registry)
let compileTab = new CompileTab(
registry.get('editor').api,
registry.get('config').api,
registry.get('renderer').api,
registry.get('fileproviders/swarm').api,
registry.get('filemanager').api,
registry.get('fileproviders').api,
registry.get('pluginmanager').api
)
let run = new RunTab(
registry.get('udapp').api,
registry.get('udappUI').api,
@ -455,36 +451,45 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
registry.get('pluginmanager').api,
registry.get('compilersartefacts').api
)
let settings = new SettingsTab(self._components.registry)
let settings = new SettingsTab(
registry.get('config').api,
registry.get('editor').api,
appManager
)
let analysis = new AnalysisTab(registry)
let debug = new DebuggerTab()
// let support = new SupportTab()
let test = new TestTab(self._components.registry, compileTab)
const landingPage = new LandingPage(appManager, appStore)
let test = new TestTab(
registry.get('filemanager').api,
registry.get('filepanel').api,
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 +517,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')
})

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

@ -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 {
<h6 class="${css.displayName}">${displayName}</h6>
${activationButton}
</div>
<p class="${css.description}">${mod.profile.description}</p>
<p class="${css.description}">${api.profile.description}</p>
</article>
`
}
@ -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

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

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

@ -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`<div class="${css.icon}" onclick=${(e) => { this._iconClick(mod.name) }} title=${mod.name} ><img src="${mod.icon}" alt="${mod.name}" /></div>`
/**
* Add an icon to the map
* @param {ModuleProfile} profile The profile of the module
*/
addIcon ({kind, name, icon}) {
this.icons[name] = yo`<div class="${css.icon}" onclick="${(e) => { this._iconClick(name) }}" title="${name}" ><img src="${icon}" alt="${name}" /></div>`
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) {

@ -12,8 +12,6 @@ var executionContext = require('../../execution-context')
var globalRegistry = require('../../global/registry')
var remixLib = require('remix-lib')
var Web3Providers = remixLib.vm.Web3Providers
var DummyProvider = remixLib.vm.DummyProvider
var init = remixLib.init
@ -30,72 +28,12 @@ var css = csjs`
}
`
class ContextManager {
constructor () {
this.executionContext = executionContext
this.web3 = this.executionContext.web3()
this.event = new EventManager()
}
initProviders () {
this.web3Providers = new Web3Providers()
this.addProvider('DUMMYWEB3', new DummyProvider())
this.switchProvider('DUMMYWEB3')
this.addProvider('vm', this.executionContext.vm())
this.addProvider('injected', this.executionContext.internalWeb3())
this.addProvider('web3', this.executionContext.internalWeb3())
this.switchProvider(this.executionContext.getProvider())
}
getWeb3 () {
return this.web3
}
addProvider (type, obj) {
this.web3Providers.addProvider(type, obj)
this.event.trigger('providerAdded', [type])
}
switchProvider (type) {
var self = this
this.web3Providers.get(type, function (error, obj) {
if (error) {
console.log('provider ' + type + ' not defined')
} else {
self.web3 = obj
self.executionContext.detectNetwork((error, network) => {
if (error || !network) {
self.web3 = obj
} else {
var webDebugNode = init.web3DebugNode(network.name)
self.web3 = (!webDebugNode ? obj : webDebugNode)
}
self.event.trigger('providerChanged', [type, self.web3])
})
self.event.trigger('providerChanged', [type, self.web3])
}
})
}
}
class DebuggerUI {
constructor (container) {
this.registry = globalRegistry
this.event = new EventManager()
this.executionContext = executionContext
this.contextManager = new ContextManager()
this.contextManager.initProviders()
this.contextManager.event.register('providerChanged', () => {
if (this.debugger) this.debugger.updateWeb3(this.contextManager.getWeb3())
})
this.isActive = false
this.sourceHighlighter = new SourceHighlighter()
@ -173,19 +111,29 @@ class DebuggerUI {
if (compilers['__last']) lastCompilationResult = compilers['__last']
// TODO debugging with source highlight is disabled. see line 98
this.debugger = new Debugger({
web3: this.contextManager.getWeb3(),
offsetToLineColumnConverter: this.registry.get('offsettolinecolumnconverter').api,
compiler: { lastCompilationResult }
})
this.listenToEvents()
this.debugger.debugger.updateWeb3(this.executionContext.web3())
this.debugger.debug(blockNumber, txNumber, tx, () => {
self.stepManager = new StepManagerUI(this.debugger.step_manager)
self.vmDebugger = new VmDebugger(this.debugger.vmDebuggerLogic)
self.renderDebugger()
executionContext.detectNetwork((error, network) => {
let web3
if (error || !network) {
web3 = init.web3DebugNode(executionContext.web3())
} else {
var webDebugNode = init.web3DebugNode(network.name)
web3 = (!webDebugNode ? executionContext.web3() : webDebugNode)
}
init.extendWeb3(web3)
this.debugger = new Debugger({
web3,
offsetToLineColumnConverter: this.registry.get('offsettolinecolumnconverter').api,
compiler: { lastCompilationResult }
})
this.listenToEvents()
this.debugger.debug(blockNumber, txNumber, tx, () => {
self.stepManager = new StepManagerUI(this.debugger.step_manager)
self.vmDebugger = new VmDebugger(this.debugger.vmDebuggerLogic)
self.renderDebugger()
})
})
}

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

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

@ -1,19 +1,22 @@
/* global FileReader */
var async = require('async')
var Gists = require('gists')
var modalDialogCustom = require('../ui/modal-dialog-custom')
var tooltip = require('../ui/tooltip')
var QueryParams = require('../../lib/query-params')
var helper = require('../../lib/helper')
var yo = require('yo-yo')
var Treeview = require('../ui/TreeView')
var modalDialog = require('../ui/modaldialog')
var modalDialogCustom = require('../ui/modal-dialog-custom')
var EventManager = require('../../lib/events')
var contextMenu = require('../ui/contextMenu')
var addTooltip = require('../ui/tooltip')
var helper = require('../../lib/helper')
var css = require('./styles/file-explorer-styles')
var globalRegistry = require('../../global/registry')
var queryParams = new QueryParams()
let MENU_HANDLE
function fileExplorer (localRegistry, files) {
function fileExplorer (localRegistry, files, menuItems) {
var self = this
this.events = new EventManager()
// file provider backend
@ -22,6 +25,35 @@ function fileExplorer (localRegistry, files) {
this.focusElement = null
// path currently focused on
this.focusPath = null
let allItems =
[
{ action: 'createNewFile',
title: 'Create New File in the Browser Storage Explorer',
icon: 'fa fa-plus-circle'
},
{ action: 'publishToGist',
title: 'Publish all [browser] explorer files to a github gist',
icon: 'fa fa-github'
},
{ action: 'copyFiles',
title: 'Copy all files to another instance of Remix IDE',
icon: 'fa fa-files-o'
},
{ action: 'uploadFile',
title: 'Add Local file to the Browser Storage Explorer',
icon: 'fa fa-folder-open'
},
{ action: 'updateGist',
title: 'Update the current [gist] explorer',
icon: 'fa fa-github'
}
]
// menu items
this.menuItems = allItems.filter(
(item) => {
if (menuItems) return menuItems.find((name) => { return name === item.action })
}
)
self._components = {}
self._components.registry = localRegistry || globalRegistry
@ -122,13 +154,19 @@ function fileExplorer (localRegistry, files) {
},
formatSelf: function formatSelf (key, data, li) {
var isRoot = data.path === self.files.type
return yo`<label
class=${css.label}
data-path="${data.path}"
style="${isRoot ? 'font-weight:bold;' : ''}"
onkeydown=${editModeOff}
onblur=${editModeOff}
>${key.split('/').pop()}</label>`
return yo`
<div class="${css.items}">
<label
class=${css.label}
data-path="${data.path}"
style="${isRoot ? 'font-weight:bold;' : ''}"
onkeydown=${editModeOff}
onblur=${editModeOff}
>${key.split('/').pop()}
</label>
${isRoot ? self.renderMenuItems() : ''}
</div>
`
}
})
@ -276,6 +314,218 @@ fileExplorer.prototype.init = function () {
return this.container
}
fileExplorer.prototype.publishToGist = function () {
modalDialogCustom.confirm(
null,
'Are you sure you want to publish all your files anonymously as a public gist on github.com?',
() => { this.toGist() }
)
}
fileExplorer.prototype.uploadFile = function (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.
let self = this
;[...event.target.files].forEach((file) => {
let files = this.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.events.trigger('focus', [name])
}
}
fileReader.readAsText(file)
}
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() })
}
})
})
}
fileExplorer.prototype.toGist = function (id) {
let proccedResult = function (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')
})
} else {
modalDialogCustom.alert(data.message + ' ' + data.documentation_url + ' ' + JSON.stringify(data.errors, null, '\t'))
}
}
}
this.packageFiles(this.files, (error, packaged) => {
if (error) {
console.log(error)
modalDialogCustom.alert('Failed to create gist: ' + error)
} else {
var tokenAccess = this._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 to create one.'
)
} 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) => {
proccedResult(error, result)
})
} else {
tooltip('Creating a new gist ...')
gists.create({
description: description,
public: true,
files: packaged
}, (error, result) => {
proccedResult(error, result)
})
}
}
}
})
}
// return all the files, except the temporary/readonly ones..
fileExplorer.prototype.packageFiles = function (filesProvider, callback) {
var ret = {}
filesProvider.resolveDirectory(filesProvider.type, (error, files) => {
if (error) callback(error)
else {
async.eachSeries(Object.keys(files), (path, cb) => {
filesProvider.get(path, (error, content) => {
if (/^\s+$/.test(content)) {
content = '// this line is added to create a gist. Empty file is not allowed.'
}
if (error) cb(error)
else {
ret[path] = { content }
cb()
}
})
}, (error) => {
callback(error, ret)
})
}
})
}
// ------------------ copy files --------------
fileExplorer.prototype.copyFiles = function () {
let self = this
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.
self.packageFiles(self.files, (error, packaged) => {
if (error) {
console.log(error)
} else {
let iframe = yo`
<iframe src=${target} style='display:none;'></iframe>
`
iframe.onload = function () {
iframe.contentWindow.postMessage(['loadFiles', packaged], '*')
tooltip('Files copied successfully.')
}
document.querySelector('body').appendChild(iframe)
}
})
}
}
// ------------------ gist publish --------------
fileExplorer.prototype.updateGist = function () {
var gistId = this.files.id
if (!gistId) {
tooltip('no gist content is currently loaded.')
} else {
this.toGist(gistId)
}
}
fileExplorer.prototype.createNewFile = function () {
let self = this
modalDialogCustom.prompt(null, 'File Name', 'Untitled.sol', (input) => {
helper.createNonClashingName(input, self.files, (error, newName) => {
if (error) return modalDialogCustom.alert('Failed to create file ' + newName + ' ' + error)
if (!self.files.set(newName, '')) {
modalDialogCustom.alert('Failed to create file ' + newName)
} else {
var file = self.files.type + '/' + newName
self._deps.fileManager.switchFile(file)
if (file.includes('_test.sol')) {
self.event.trigger('newTestFileCreated', [file])
}
}
})
}, null, true)
}
fileExplorer.prototype.renderMenuItems = function () {
let items = ''
if (this.menuItems) {
items = this.menuItems.map(({action, title, icon}) => {
if (action === 'uploadFile') {
return yo`
<label class="${icon} ${css.newFile}">
<input type="file" onchange=${(event) => {
event.stopPropagation()
this.uploadFile(event)
}} multiple />
</label>
`
} else {
return yo`
<span onclick=${(event) => { event.stopPropagation(); this[ action ]() }} class="newFile ${css.newFile}" title=${title}>
<i class=${icon}></i>
</span>
`
}
})
}
return yo`<span class=${css.menu}>${items}</span>`
}
fileExplorer.prototype.ensureRoot = function (cb) {
cb = cb || (() => {})
var self = this

@ -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,57 +153,55 @@ 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()
}
})
}
}
getFilesFromPath (path) {
const provider = this.fileProviderOf(path)
if (!provider) throw new Error(`provider for path ${path} not found`)
// TODO : Change provider with promise
return new Promise((resolve, reject) => {
const provider = this.fileProviderOf(path)
if (!provider) return reject(`provider for path ${path} not found`)
provider.resolveDirectory(path, (error, filesTree) => {
if (error) reject(error)
resolve(filesTree)
@ -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')

@ -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: [],

@ -16,6 +16,22 @@ var css = csjs`
cursor : pointer;
}
.file {
padding : 4px;
}
.newFile {
padding : 4px;
}
.newFile i {
cursor : pointer;
}
.newFile i:hover {
color : var(--secondary)
}
.menu {
margin-left : 20px;
}
.items {
display : inline
}
.hasFocus {
}

@ -1,22 +1,13 @@
/* global FileReader */
var async = require('async')
var $ = require('jquery')
var yo = require('yo-yo')
var CompilerMetadata = require('../files/compiler-metadata')
var EventManager = require('../../lib/events')
var Gists = require('gists')
var FileExplorer = require('../files/file-explorer')
var modalDialogCustom = require('../ui/modal-dialog-custom')
var tooltip = require('../ui/tooltip')
var QueryParams = require('../../lib/query-params')
var queryParams = new QueryParams()
var helper = require('../../lib/helper')
var { RemixdHandle } = require('../files/remixd-handle.js')
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,134 +27,120 @@ 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`
<div class=${css.container}>
<div class="${css.fileexplorer}">
<div class=${css.menu}>
<span onclick=${createNewFile} class="newFile ${css.newFile}" title="Create New File in the Browser Storage Explorer">
<i class="fa fa-plus-circle"></i>
</span>
${canUpload ? yo`
<span class=${css.uploadFile} title="Add Local file to the Browser Storage Explorer">
<label class="fa fa-folder-open">
<input type="file" onchange=${uploadFile} multiple />
</label>
</span>
` : ''}
<span class="${css.gist}" title="Publish all [browser] explorer files to a github gist" onclick=${() => publishToGist('browser')}>
<i class="fa fa-github"></i>
</span>
<span class="${css.gist}" title="Update the current [gist] explorer" onclick=${() => updateGist()}>
<i class="fa fa-github"></i>
</span>
<span class="${css.copyFiles}" title="Copy all files to another instance of Remix IDE" onclick=${copyFiles}>
<i class="fa fa-files-o" aria-hidden="true"></i>
</span>
</div>
<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="swarmexplorer ${css.treeview}">${swarmExplorer.init()}</div>
<div class="githubexplorer ${css.treeview}">${githubExplorer.init()}</div>
<div class="gistexplorer ${css.treeview}">${gistExplorer.init()}</div>
<div class="httpexplorer ${css.treeview}">${httpExplorer.init()}</div>
<div class="httpsexplorer ${css.treeview}">${httpsExplorer.init()}</div>
var fileExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['browser'],
['createNewFile', 'publishToGist', 'copyFiles', canUpload ? 'uploadFile' : '']
)
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'], ['updateGist'])
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'],
self._deps.fileProviders['localhost'].isReadOnly ? ['createNewFile'] : [])
// ----------------- 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`
<div class=${css.container}>
<div class="${css.fileexplorer}">
<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="swarmexplorer ${css.treeview}">${swarmExplorer.init()}</div>
<div class="githubexplorer ${css.treeview}">${githubExplorer.init()}</div>
<div class="gistexplorer ${css.treeview}">${gistExplorer.init()}</div>
<div class="httpexplorer ${css.treeview}">${httpExplorer.init()}</div>
<div class="httpsexplorer ${css.treeview}">${httpsExplorer.init()}</div>
</div>
</div>
</div>
</div>
`
}
`
}
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 }
}
self.profile = function () {
get profile () {
return {
name: 'fileExplorers',
displayName: 'file explorers',
@ -174,170 +151,5 @@ function filepanel (localRegistry) {
kind: 'fileexplorer'
}
}
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)
}
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 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])
}
}
})
}, 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) => {
if (error) {
console.log(error)
modalDialogCustom.alert('Failed to create 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 (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)
})
}
}
}
})
}
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')
})
} else {
modalDialogCustom.alert(data.message + ' ' + data.documentation_url + ' ' + JSON.stringify(data.errors, null, '\t'))
}
}
}
// ------------------ copy files --------------
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 {
$('<iframe/>', {
src: target,
style: 'display:none;',
load: function () { this.contentWindow.postMessage(['loadFiles', packaged], '*') }
}).appendTo('body')
}
})
}
}
}
// return all the files, except the temporary/readonly ones..
function packageFiles (filesProvider, callback) {
var ret = {}
filesProvider.resolveDirectory(filesProvider.type, (error, files) => {
if (error) callback(error)
else {
async.eachSeries(Object.keys(files), (path, cb) => {
filesProvider.get(path, (error, content) => {
if (error) cb(error)
else {
ret[path] = { content }
cb()
}
})
}, (error) => {
callback(error, ret)
})
}
})
}
module.exports = filepanel

@ -14,22 +14,6 @@ var css = csjs`
position : relative;
width : 100%;
}
.menu {
margin-top : -0.2em;
flex-shrink : 0;
display : flex;
flex-direction : row;
min-width : 160px;
}
.newFile {
padding : 10px;
}
.newFile i {
cursor : pointer;
}
.newFile i:hover {
color : var(--secondary)
}
.gist {
padding : 10px;
}

@ -52,13 +52,15 @@ export class TabProxy {
appStore.event.on('activate', (name) => {
const { profile } = appStore.getOne(name)
if (profile.prefferedLocation === 'mainPanel') {
this.addTab(name, () => {
this.event.emit('switchApp', name)
}, () => {
this.event.emit('closeApp', name)
this.appManager.deactivateOne(name)
})
if (profile.location === 'mainPanel') {
this.addTab(
name,
() => this.event.emit('switchApp', name),
() => {
this.event.emit('closeApp', name)
this.appManager.deactivateOne(name)
}
)
}
})

@ -13,7 +13,6 @@ var CommandInterpreterAPI = require('../../lib/cmdInterpreterAPI')
var executionContext = require('../../execution-context')
var Dropdown = require('../ui/dropdown')
var AutoCompletePopup = require('../ui/auto-complete-popup')
var Commands = require('../constants/commands')
var csjs = require('csjs-inject')
@ -63,17 +62,12 @@ class Terminal {
})
self._components.autoCompletePopup = new AutoCompletePopup()
self._components.autoCompletePopup.event.register('handleSelect', function (input) {
self._components.autoCompletePopup.data._options = []
self._components.autoCompletePopup._startingElement = 0
let textList = self._view.input.innerText.split(' ')
textList.pop()
textList.push(input)
self._view.input.innerText = `${textList}`.replace(/,/g, ' ')
self._view.input.focus()
yo.update(self._view.autoCompletePopup, self._components.autoCompletePopup.render())
})
self._components.autoCompletePopup.event.register('updateList', function () {
yo.update(self._view.autoCompletePopup, self._components.autoCompletePopup.render())
self.putCursor2End(self._view.input)
})
self._commands = {}
self.commands = {}
@ -115,7 +109,7 @@ class Terminal {
if (self._view.el) return self._view.el
self._view.journal = yo`<div class=${css.journal}></div>`
self._view.input = yo`
<span class=${css.input} contenteditable="true" onpaste=${paste} onkeydown=${change}></span>
<span class=${css.input} spellcheck="false" contenteditable="true" onpaste=${paste} onkeydown=${change}></span>
`
self._view.input.innerText = '\n'
self._view.cli = yo`
@ -150,7 +144,7 @@ class Terminal {
${self._view.dropdown}
<div class=${css.search}>
<i class="fa fa-search ${css.searchIcon} bg-light btn" aria-hidden="true"></i>
<input type="text" class=${css.filter} onkeydown=${filter} placeholder="Search transactions">
<input spellcheck="false" type="text" class=${css.filter} onkeydown=${filter} placeholder="Search transactions">
</div>
</div>
</div>
@ -163,7 +157,6 @@ class Terminal {
</div>
</div>
`
self._view.autoCompletePopup = self._components.autoCompletePopup.render()
self._view.el = yo`
<div class="${css.panel}">
${self._view.bar}
@ -406,14 +399,16 @@ class Terminal {
return self._view.el
function change (event) {
handleAutoComplete(event)
if (self._components.autoCompletePopup.handleAutoComplete(
event,
self._view.input.innerText)) return
if (self._view.input.innerText.length === 0) self._view.input.innerText += '\n'
if (event.which === 13) {
if (event.ctrlKey) { // <ctrl+enter>
self._view.input.innerText += '\n'
putCursor2End(self._view.input)
self.putCursor2End(self._view.input)
self.scroll2bottom()
removeAutoComplete()
self._components.autoCompletePopup.removeAutoComplete()
} else { // <enter>
self._cmdIndex = -1
self._cmdTemp = ''
@ -424,102 +419,55 @@ class Terminal {
self._cmdHistory.unshift(script)
self.commands.script(script)
}
removeAutoComplete()
self._components.autoCompletePopup.removeAutoComplete()
}
} else if (event.which === 38) { // <arrowUp>
if (self._components.autoCompletePopup.data._options.length > self._components.autoCompletePopup._elementsToShow) {
self._components.autoCompletePopup._view.autoComplete.children[1].children[0].onclick(event)
} else {
var len = self._cmdHistory.length
if (len === 0) return event.preventDefault()
if (self._cmdHistory.length - 1 > self._cmdIndex) {
self._cmdIndex++
}
self._view.input.innerText = self._cmdHistory[self._cmdIndex]
putCursor2End(self._view.input)
self.scroll2bottom()
var len = self._cmdHistory.length
if (len === 0) return event.preventDefault()
if (self._cmdHistory.length - 1 > self._cmdIndex) {
self._cmdIndex++
}
self._view.input.innerText = self._cmdHistory[self._cmdIndex]
self.putCursor2End(self._view.input)
self.scroll2bottom()
} else if (event.which === 40) { // <arrowDown>
if (self._components.autoCompletePopup.data._options.length > self._components.autoCompletePopup._elementsToShow) {
self._components.autoCompletePopup._view.autoComplete.children[1].children[1].onclick(event)
} else {
if (self._cmdIndex > -1) {
self._cmdIndex--
}
self._view.input.innerText = self._cmdIndex >= 0 ? self._cmdHistory[self._cmdIndex] : self._cmdTemp
putCursor2End(self._view.input)
self.scroll2bottom()
if (self._cmdIndex > -1) {
self._cmdIndex--
}
self._view.input.innerText = self._cmdIndex >= 0 ? self._cmdHistory[self._cmdIndex] : self._cmdTemp
self.putCursor2End(self._view.input)
self.scroll2bottom()
} else {
self._cmdTemp = self._view.input.innerText
}
}
function putCursor2End (editable) {
var range = document.createRange()
range.selectNode(editable)
var child = editable
var chars
}
putCursor2End (editable) {
var range = document.createRange()
range.selectNode(editable)
var child = editable
var chars
while (child) {
if (child.lastChild) child = child.lastChild
else break
if (child.nodeType === Node.TEXT_NODE) {
chars = child.textContent.length
} else {
chars = child.innerHTML.length
}
while (child) {
if (child.lastChild) child = child.lastChild
else break
if (child.nodeType === Node.TEXT_NODE) {
chars = child.textContent.length
} else {
chars = child.innerHTML.length
}
}
range.setEnd(child, chars)
var toStart = true
var toEnd = !toStart
range.collapse(toEnd)
range.setEnd(child, chars)
var toStart = true
var toEnd = !toStart
range.collapse(toEnd)
var sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
var sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
editable.focus()
}
function handleAutoComplete (event) {
if (event.which === 9) {
event.preventDefault()
let textList = self._view.input.innerText.split(' ')
let autoCompleteInput = textList.length > 1 ? textList[textList.length - 1] : textList[0]
if (self._view.input.innerText.length >= 2) {
self._components.autoCompletePopup.data._options = []
Commands.allPrograms.forEach(item => {
if (Object.keys(item)[0].substring(0, Object.keys(item)[0].length - 1).includes(autoCompleteInput.trim())) {
self._components.autoCompletePopup.data._options.push(item)
} else if (autoCompleteInput.trim().includes(Object.keys(item)[0]) || (Object.keys(item)[0] === autoCompleteInput.trim())) {
Commands.allCommands.forEach(item => {
if (Object.keys(item)[0].includes(autoCompleteInput.trim())) {
self._components.autoCompletePopup.data._options.push(item)
}
})
}
})
}
if (self._components.autoCompletePopup.data._options.length === 1) {
textList.pop()
textList.push(Object.keys(self._components.autoCompletePopup.data._options[0])[0])
self._view.input.innerText = `${textList}`.replace(/,/g, ' ')
self._components.autoCompletePopup.data._options = []
putCursor2End(self._view.input)
}
}
if (event.which === 27 || event.which === 8 || event.which === 46) {
self._components.autoCompletePopup.data._options = []
self._components.autoCompletePopup._startingElement = 0
}
yo.update(self._view.autoCompletePopup, self._components.autoCompletePopup.render())
}
function removeAutoComplete () {
self._components.autoCompletePopup.data._options = []
self._components.autoCompletePopup._startingElement = 0
self._components.autoCompletePopup._removePopUp()
yo.update(self._view.autoCompletePopup, self._components.autoCompletePopup.render())
}
editable.focus()
}
updateJournal (filterEvent) {
var self = this

@ -3,13 +3,17 @@ var StaticAnalysis = require('../staticanalysis/staticAnalysisView')
var EventManager = require('../../lib/events')
var css = require('./styles/analysis-tab-styles')
class AnalysisTab {
import { ApiFactory } from 'remix-plugin'
class AnalysisTab extends ApiFactory {
constructor (registry) {
super()
this.event = new EventManager()
this.registry = registry
}
profile () {
get profile () {
return {
name: 'solidityStaticAnalysis',
displayName: 'solidity static analysis',
@ -20,6 +24,7 @@ class AnalysisTab {
kind: 'analysis'
}
}
render () {
var staticanalysis = new StaticAnalysis()
this.registry.put({api: staticanalysis, name: 'staticanalysis'})
@ -28,6 +33,7 @@ class AnalysisTab {
this.el = yo`<div class="${css.analysisTabView}" id="staticanalysisView">${staticanalysis.render()}</div>`
return this.el
}
}
module.exports = AnalysisTab

@ -17,9 +17,12 @@ var css = require('./styles/compile-tab-styles')
const CompileTabLogic = require('./compileTab/compileTab.js')
const CompilerContainer = require('./compileTab/compilerContainer.js')
class CompileTab {
import { ApiFactory } from 'remix-plugin'
constructor (registry) {
class CompileTab extends ApiFactory {
constructor (editor, config, renderer, swarmfileProvider, fileManager, fileProviders, pluginManager) {
super()
this.events = new EventEmitter()
this._view = {
el: null,
@ -30,37 +33,40 @@ class CompileTab {
this.queryParams = new QueryParams()
// dependencies
this._deps = {
editor: registry.get('editor').api,
config: registry.get('config').api,
renderer: registry.get('renderer').api,
swarmfileProvider: registry.get('fileproviders/swarm').api,
fileManager: registry.get('filemanager').api,
fileProviders: registry.get('fileproviders').api,
pluginManager: registry.get('pluginmanager').api
}
this.editor = editor
this.config = config
this.renderer = renderer
this.swarmfileProvider = swarmfileProvider
this.fileManager = fileManager
this.fileProviders = fileProviders
this.pluginManager = pluginManager
this.data = {
contractsDetails: {}
}
this.compileTabLogic = new CompileTabLogic(this.queryParams, this._deps.fileManager, this._deps.editor, this._deps.config, this._deps.fileProviders)
this.compileTabLogic = new CompileTabLogic(this.queryParams, this.fileManager, this.editor, this.config, this.fileProviders)
this.compiler = this.compileTabLogic.compiler
this.compileTabLogic.init()
this.compilerContainer = new CompilerContainer(
this.compileTabLogic,
this._deps.editor,
this._deps.config,
this.editor,
this.config,
this.queryParams
)
}
activate () {
this.listenToEvents()
this.compilerContainer.activate()
}
deactivate () {
get profile () {
return {
displayName: 'solidity compiler',
name: 'solidity',
methods: ['getCompilationResult'],
events: ['compilationFinished'],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE2LjAuMywgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zOnNrZXRjaD0iaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoL25zIgoJIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iMTMwMHB4IiBoZWlnaHQ9IjEzMDBweCIKCSB2aWV3Qm94PSIwIDAgMTMwMCAxMzAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAxMzAwIDEzMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8dGl0bGU+VmVjdG9yIDE8L3RpdGxlPgo8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KPGcgaWQ9IlBhZ2UtMSIgc2tldGNoOnR5cGU9Ik1TUGFnZSI+Cgk8ZyBpZD0ic29saWRpdHkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQwMi4wMDAwMDAsIDExOC4wMDAwMDApIiBza2V0Y2g6dHlwZT0iTVNMYXllckdyb3VwIj4KCQk8ZyBpZD0iR3JvdXAiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPgoJCQk8cGF0aCBpZD0iU2hhcGUiIG9wYWNpdHk9IjAuNDUiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgICAgIiBkPSJNMzcxLjc3MiwxMzUuMzA4TDI0MS4wNjgsMzY3LjYxSC0yMC4xNThsMTMwLjYxNC0yMzIuMzAyCgkJCQlIMzcxLjc3MiIvPgoJCQk8cGF0aCBpZD0iU2hhcGVfMV8iIG9wYWNpdHk9IjAuNiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAgICAiIGQ9Ik0yNDEuMDY4LDM2Ny42MWgyNjEuMzE4TDM3MS43NzIsMTM1LjMwOEgxMTAuNDU2CgkJCQlMMjQxLjA2OCwzNjcuNjF6Ii8+CgkJCTxwYXRoIGlkPSJTaGFwZV8yXyIgb3BhY2l0eT0iMC44IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3ICAgICIgZD0iTTExMC40NTYsNTk5LjgyMkwyNDEuMDY4LDM2Ny42MUwxMTAuNDU2LDEzNS4zMDgKCQkJCUwtMjAuMTU4LDM2Ny42MUwxMTAuNDU2LDU5OS44MjJ6Ii8+CgkJCTxwYXRoIGlkPSJTaGFwZV8zXyIgb3BhY2l0eT0iMC40NSIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAgICAiIGQ9Ik0xMTEuNzIxLDk0OC4yNzVsMTMwLjcwNC0yMzIuMzAzaDI2MS4zMThMMzczLjAzOCw5NDguMjc1CgkJCQlIMTExLjcyMSIvPgoJCQk8cGF0aCBpZD0iU2hhcGVfNF8iIG9wYWNpdHk9IjAuNiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAgICAiIGQ9Ik0yNDIuNDI0LDcxNS45NzNILTE4Ljg5M2wxMzAuNjEzLDIzMi4zMDNoMjYxLjMxNwoJCQkJTDI0Mi40MjQsNzE1Ljk3M3oiLz4KCQkJPHBhdGggaWQ9IlNoYXBlXzVfIiBvcGFjaXR5PSIwLjgiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgICAgIiBkPSJNMzczLjAzOCw0ODMuNzYxTDI0Mi40MjQsNzE1Ljk3M2wxMzAuNjE0LDIzMi4zMDMKCQkJCWwxMzAuNzA0LTIzMi4zMDNMMzczLjAzOCw0ODMuNzYxeiIvPgoJCTwvZz4KCTwvZz4KPC9nPgo8L3N2Zz4K',
description: 'compile solidity contracts',
kind: 'compile'
}
}
/************
@ -74,7 +80,7 @@ class CompileTab {
}
})
this._deps.fileManager.events.on('currentFileChanged', (name) => {
this.fileManager.events.on('currentFileChanged', (name) => {
this.compilerContainer.currentFile = name
})
this.compiler.event.register('compilationFinished', (success, data, source) => {
@ -98,7 +104,7 @@ class CompileTab {
yo.update(this._view.contractSelection, contractSelection)
if (data['error']) {
this._deps.renderer.error(data['error'].formattedMessage, this._view.errorContainer, {type: data['error'].severity || 'error'})
this.renderer.error(data['error'].formattedMessage, this._view.errorContainer, {type: data['error'].severity || 'error'})
if (data['error'].mode === 'panic') {
return modalDialogCustom.alert(yo`<div><i class="fa fa-exclamation-circle ${css.panicError}" aria-hidden="true"></i>
The compiler returned with the following internal error: <br> <b>${data['error'].formattedMessage}.<br>
@ -109,12 +115,12 @@ class CompileTab {
}
if (data.errors && data.errors.length) {
data.errors.forEach((err) => {
if (this._deps.config.get('hideWarnings')) {
if (this.config.get('hideWarnings')) {
if (err.severity !== 'warning') {
this._deps.renderer.error(err.formattedMessage, this._view.errorContainer, {type: err.severity})
this.renderer.error(err.formattedMessage, this._view.errorContainer, {type: err.severity})
}
} else {
this._deps.renderer.error(err.formattedMessage, this._view.errorContainer, {type: err.severity})
this.renderer.error(err.formattedMessage, this._view.errorContainer, {type: err.severity})
}
})
}
@ -130,22 +136,8 @@ class CompileTab {
})
}
profile () {
return {
displayName: 'solidity compiler',
name: 'solidity',
methods: ['getCompilationResult'],
events: ['compilationFinished'],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE2LjAuMywgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zOnNrZXRjaD0iaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoL25zIgoJIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iMTMwMHB4IiBoZWlnaHQ9IjEzMDBweCIKCSB2aWV3Qm94PSIwIDAgMTMwMCAxMzAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAxMzAwIDEzMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8dGl0bGU+VmVjdG9yIDE8L3RpdGxlPgo8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KPGcgaWQ9IlBhZ2UtMSIgc2tldGNoOnR5cGU9Ik1TUGFnZSI+Cgk8ZyBpZD0ic29saWRpdHkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQwMi4wMDAwMDAsIDExOC4wMDAwMDApIiBza2V0Y2g6dHlwZT0iTVNMYXllckdyb3VwIj4KCQk8ZyBpZD0iR3JvdXAiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPgoJCQk8cGF0aCBpZD0iU2hhcGUiIG9wYWNpdHk9IjAuNDUiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgICAgIiBkPSJNMzcxLjc3MiwxMzUuMzA4TDI0MS4wNjgsMzY3LjYxSC0yMC4xNThsMTMwLjYxNC0yMzIuMzAyCgkJCQlIMzcxLjc3MiIvPgoJCQk8cGF0aCBpZD0iU2hhcGVfMV8iIG9wYWNpdHk9IjAuNiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAgICAiIGQ9Ik0yNDEuMDY4LDM2Ny42MWgyNjEuMzE4TDM3MS43NzIsMTM1LjMwOEgxMTAuNDU2CgkJCQlMMjQxLjA2OCwzNjcuNjF6Ii8+CgkJCTxwYXRoIGlkPSJTaGFwZV8yXyIgb3BhY2l0eT0iMC44IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3ICAgICIgZD0iTTExMC40NTYsNTk5LjgyMkwyNDEuMDY4LDM2Ny42MUwxMTAuNDU2LDEzNS4zMDgKCQkJCUwtMjAuMTU4LDM2Ny42MUwxMTAuNDU2LDU5OS44MjJ6Ii8+CgkJCTxwYXRoIGlkPSJTaGFwZV8zXyIgb3BhY2l0eT0iMC40NSIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAgICAiIGQ9Ik0xMTEuNzIxLDk0OC4yNzVsMTMwLjcwNC0yMzIuMzAzaDI2MS4zMThMMzczLjAzOCw5NDguMjc1CgkJCQlIMTExLjcyMSIvPgoJCQk8cGF0aCBpZD0iU2hhcGVfNF8iIG9wYWNpdHk9IjAuNiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAgICAiIGQ9Ik0yNDIuNDI0LDcxNS45NzNILTE4Ljg5M2wxMzAuNjEzLDIzMi4zMDNoMjYxLjMxNwoJCQkJTDI0Mi40MjQsNzE1Ljk3M3oiLz4KCQkJPHBhdGggaWQ9IlNoYXBlXzVfIiBvcGFjaXR5PSIwLjgiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgICAgIiBkPSJNMzczLjAzOCw0ODMuNzYxTDI0Mi40MjQsNzE1Ljk3M2wxMzAuNjE0LDIzMi4zMDMKCQkJCWwxMzAuNzA0LTIzMi4zMDNMMzczLjAzOCw0ODMuNzYxeiIvPgoJCTwvZz4KCTwvZz4KPC9nPgo8L3N2Zz4K',
description: 'compile solidity contracts',
kind: 'compile'
}
}
getCompilationResult () {
return new Promise((resolve, reject) => {
resolve(this.compileTabLogic.compiler.lastCompilationResult)
})
return this.compileTabLogic.compiler.lastCompilationResult
}
/*********
@ -215,12 +207,12 @@ class CompileTab {
}
publish () {
let contract = this.data.contractsDetails[this.selectedContract]
if (contract) {
if (this.selectedContract) {
var contract = this.data.contractsDetails[this.selectedContract]
if (contract.metadata === undefined || contract.metadata.length === 0) {
modalDialogCustom.alert('This contract may be abstract, may not implement an abstract parent\'s methods completely or not invoke an inherited contract\'s constructor correctly.')
} else {
publishOnSwarm(contract, this._deps.fileManager, function (err, uploaded) {
publishOnSwarm(contract, this.fileManager, function (err, uploaded) {
if (err) {
try {
err = JSON.stringify(err)
@ -234,7 +226,7 @@ class CompileTab {
modalDialogCustom.alert(yo`<span>Metadata published successfully.<br> <pre>${result}</pre> </span>`)
}
}, (item) => { // triggered each time there's a new verified publish (means hash correspond)
this._deps.swarmfileProvider.addReadOnly(item.hash, item.content)
this.swarmfileProvider.addReadOnly(item.hash, item.content)
})
}
}
@ -338,11 +330,13 @@ class CompileTab {
render () {
if (this._view.el) return this._view.el
this.listenToEvents()
this.compilerContainer.activate()
this._view.errorContainer = yo`<div></div>`
this._view.contractSelection = this.contractSelection()
this._view.compilerContainer = this.compilerContainer.render()
const currentFile = this._deps.fileManager.currentFile()
const currentFile = this.fileManager.currentFile()
if (currentFile) this.compilerContainer.currentFile = currentFile
this._view.el = yo`

@ -3,12 +3,16 @@ var css = require('./styles/debugger-tab-styles')
var DebuggerUI = require('../debugger/debuggerUI')
class DebuggerTab {
import { ApiFactory } from 'remix-plugin'
class DebuggerTab extends ApiFactory {
constructor () {
super()
this.el = null
}
profile () {
get profile () {
return {
displayName: 'debugger',
name: 'debugger',

@ -12,9 +12,12 @@ var ContractDropdownUI = require('./runTab/contractDropdown.js')
var Recorder = require('./runTab/model/recorder.js')
var RecorderUI = require('./runTab/recorder.js')
class RunTab {
import { ApiFactory } from 'remix-plugin'
class RunTab extends ApiFactory {
constructor (udapp, udappUI, config, fileManager, editor, logCallback, filePanel, pluginManager, compilersArtefacts) {
super()
this.event = new EventManager()
this.renderInstanceContainer()
@ -25,6 +28,18 @@ class RunTab {
this.renderContainer()
}
get profile () {
return {
name: 'run',
displayName: 'run transactions',
methods: [],
events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNTc2IDkyN2wtMTMyOCA3MzhxLTIzIDEzLTM5LjUgM3QtMTYuNS0zNnYtMTQ3MnEwLTI2IDE2LjUtMzZ0MzkuNSAzbDEzMjggNzM4cTIzIDEzIDIzIDMxdC0yMyAzMXoiLz48L3N2Zz4=',
description: 'execute and save transactions',
kind: 'run'
}
}
renderContainer () {
this.container = yo`<div class="${css.runTabView}" id="runTabView" ></div>`
@ -147,17 +162,6 @@ class RunTab {
return this.container
}
profile () {
return {
name: 'run',
displayName: 'run transactions',
methods: [],
events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNTc2IDkyN2wtMTMyOCA3MzhxLTIzIDEzLTM5LjUgM3QtMTYuNS0zNnYtMTQ3MnEwLTI2IDE2LjUtMzZ0MzkuNSAzbDEzMjggNzM4cTIzIDEzIDIzIDMxdC0yMyAzMXoiLz48L3N2Zz4=',
description: 'execute and save transactions',
kind: 'run'
}
}
}
module.exports = RunTab

@ -1,43 +1,39 @@
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var remixLib = require('remix-lib')
var globalRegistry = require('../../global/registry')
var tooltip = require('../ui/tooltip')
var copyToClipboard = require('../ui/copy-to-clipboard')
var styleGuide = require('../ui/styles-guide/theme-chooser')
var Storage = remixLib.Storage
var EventManager = require('../../lib/events')
var css = require('./styles/settings-tab-styles')
import { ApiFactory } from 'remix-plugin'
module.exports = class SettingsTab {
constructor (localRegistry) {
const self = this
self._components = {}
self._components.registry = localRegistry || globalRegistry
// dependencies
self._deps = {
config: self._components.registry.get('config').api,
editorPanel: self._components.registry.get('editorpanel').api,
editor: self._components.registry.get('editor').api,
appManager: self._components.registry.get('appmanager').api
}
self._view = { /* eslint-disable */
module.exports = class SettingsTab extends ApiFactory {
constructor (config, editor, appManager) {
super()
this.config = config
this.editor = editor
this.appManager = appManager
this._components = {}
this._view = { /* eslint-disable */
el: null,
optionVM: null, personal: null, warnPersonalMode: null, generateContractMetadata: null,
pluginInput: null, versionSelector: null, version: null,
theme: { dark: null, light: null, clean: null },
plugins: {},
config: {
general: null, themes: null,
plugin: null
general: null, themes: null
}
} /* eslint-enable */
self.data = {}
self.event = new EventManager()
self._components.themeStorage = new Storage('style:')
self.data.currentTheme = self._components.themeStorage.get('theme') || 'light'
this.event = new EventManager()
this.initTheme()
}
initTheme () {
const themeStorage = new Storage('style:')
this.currentTheme = themeStorage.get('theme') || 'light'
}
profile () {
get profile () {
return {
displayName: 'settings',
name: 'settings',
@ -45,118 +41,120 @@ module.exports = class SettingsTab {
events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xMTUyIDg5NnEwLTEwNi03NS0xODF0LTE4MS03NS0xODEgNzUtNzUgMTgxIDc1IDE4MSAxODEgNzUgMTgxLTc1IDc1LTE4MXptNTEyLTEwOXYyMjJxMCAxMi04IDIzdC0yMCAxM2wtMTg1IDI4cS0xOSA1NC0zOSA5MSAzNSA1MCAxMDcgMTM4IDEwIDEyIDEwIDI1dC05IDIzcS0yNyAzNy05OSAxMDh0LTk0IDcxcS0xMiAwLTI2LTlsLTEzOC0xMDhxLTQ0IDIzLTkxIDM4LTE2IDEzNi0yOSAxODYtNyAyOC0zNiAyOGgtMjIycS0xNCAwLTI0LjUtOC41dC0xMS41LTIxLjVsLTI4LTE4NHEtNDktMTYtOTAtMzdsLTE0MSAxMDdxLTEwIDktMjUgOS0xNCAwLTI1LTExLTEyNi0xMTQtMTY1LTE2OC03LTEwLTctMjMgMC0xMiA4LTIzIDE1LTIxIDUxLTY2LjV0NTQtNzAuNXEtMjctNTAtNDEtOTlsLTE4My0yN3EtMTMtMi0yMS0xMi41dC04LTIzLjV2LTIyMnEwLTEyIDgtMjN0MTktMTNsMTg2LTI4cTE0LTQ2IDM5LTkyLTQwLTU3LTEwNy0xMzgtMTAtMTItMTAtMjQgMC0xMCA5LTIzIDI2LTM2IDk4LjUtMTA3LjV0OTQuNS03MS41cTEzIDAgMjYgMTBsMTM4IDEwN3E0NC0yMyA5MS0zOCAxNi0xMzYgMjktMTg2IDctMjggMzYtMjhoMjIycTE0IDAgMjQuNSA4LjV0MTEuNSAyMS41bDI4IDE4NHE0OSAxNiA5MCAzN2wxNDItMTA3cTktOSAyNC05IDEzIDAgMjUgMTAgMTI5IDExOSAxNjUgMTcwIDcgOCA3IDIyIDAgMTItOCAyMy0xNSAyMS01MSA2Ni41dC01NCA3MC41cTI2IDUwIDQxIDk4bDE4MyAyOHExMyAyIDIxIDEyLjV0OCAyMy41eiIvPjwvc3ZnPg==',
description: ' - ',
kind: 'settings'
kind: 'settings',
location: 'swapPanel'
}
}
render () {
const self = this
if (self._view.el) return self._view.el
// Gist settings
var gistAccessToken = yo`<input id="gistaccesstoken" type="password" class="form-control mb-2 ${css.inline}" placeholder="Token">`
var token = self._deps.config.get('settings/gist-access-token')
var token = this.config.get('settings/gist-access-token')
if (token) gistAccessToken.value = token
var gistAddToken = yo`<input class="${css.savegisttoken} btn btn-sm btn-primary" id="savegisttoken" onclick=${() => { self._deps.config.set('settings/gist-access-token', gistAccessToken.value); tooltip('Access token saved') }} value="Save" type="button">`
var gistRemoveToken = yo`<input class="btn btn-sm btn-primary" id="removegisttoken" onclick=${() => { gistAccessToken.value = ''; self._deps.config.set('settings/gist-access-token', ''); tooltip('Access token removed') }} value="Remove" type="button">`
self._view.gistToken = yo`<div class="${css.checkboxText}">${gistAccessToken}${copyToClipboard(() => self._deps.config.get('settings/gist-access-token'))}${gistAddToken}${gistRemoveToken}</div>`
var gistAddToken = yo`<input class="${css.savegisttoken} btn btn-sm btn-primary" id="savegisttoken" onclick=${() => { this.config.set('settings/gist-access-token', gistAccessToken.value); tooltip('Access token saved') }} value="Save" type="button">`
var gistRemoveToken = yo`<input class="btn btn-sm btn-primary" id="removegisttoken" onclick=${() => { gistAccessToken.value = ''; this.config.set('settings/gist-access-token', ''); tooltip('Access token removed') }} value="Remove" type="button">`
this._view.gistToken = yo`<div class="${css.checkboxText}">${gistAccessToken}${copyToClipboard(() => this.config.get('settings/gist-access-token'))}${gistAddToken}${gistRemoveToken}</div>`
//
self._view.optionVM = yo`<input onchange=${onchangeOption} class="align-middle form-check-input" id="alwaysUseVM" type="checkbox">`
if (self._deps.config.get('settings/always-use-vm')) self._view.optionVM.setAttribute('checked', '')
self._view.personal = yo`<input onchange=${onchangePersonal} id="personal" type="checkbox" class="align-middle form-check-input">`
if (self._deps.config.get('settings/personal-mode')) self._view.personal.setAttribute('checked', '')
this._view.optionVM = yo`<input onchange=${onchangeOption} class="align-middle form-check-input" id="alwaysUseVM" type="checkbox">`
if (this.config.get('settings/always-use-vm')) this._view.optionVM.setAttribute('checked', '')
this._view.personal = yo`<input onchange=${onchangePersonal} id="personal" type="checkbox" class="align-middle form-check-input">`
if (this.config.get('settings/personal-mode')) this._view.personal.setAttribute('checked', '')
var warnText = `Transaction sent over Web3 will use the web3.personal API - be sure the endpoint is opened before enabling it.
This mode allows to provide the passphrase in the Remix interface without having to unlock the account.
Although this is very convenient, you should completely trust the backend you are connected to (Geth, Parity, ...).
It is not recommended (and also most likely not relevant) to use this mode with an injected provider (Mist, Metamask, ...) or with JavaScript VM.
Remix never persist any passphrase.`.split('\n').map(s => s.trim()).join(' ')
self._view.warnPersonalMode = yo`<i title=${warnText} class="${css.icon} fa fa-exclamation-triangle text-warning" aria-hidden="true"></i>`
self._view.generateContractMetadata = yo`<input onchange=${onchangeGenerateContractMetadata} id="generatecontractmetadata" type="checkbox" class="form-check-input">`
this._view.warnPersonalMode = yo`<i title=${warnText} class="${css.icon} fa fa-exclamation-triangle text-warning" aria-hidden="true"></i>`
this._view.generateContractMetadata = yo`<input onchange=${onchangeGenerateContractMetadata} id="generatecontractmetadata" type="checkbox" class="form-check-input">`
if (self._deps.config.get('settings/generate-contract-metadata')) self._view.generateContractMetadata.setAttribute('checked', '')
if (this.config.get('settings/generate-contract-metadata')) this._view.generateContractMetadata.setAttribute('checked', '')
self._view.pluginInput = yo`<textarea rows="4" cols="70" id="plugininput" type="text" class="${css.pluginTextArea}" ></textarea>`
this._view.pluginInput = yo`<textarea rows="4" cols="70" id="plugininput" type="text" class="${css.pluginTextArea}" ></textarea>`
self._view.theme.light = yo`<input onchange=${onswitch2lightTheme} class="align-middle form-check-input" name="theme" id="themeLight" type="radio">`
self._view.theme.dark = yo`<input onchange=${onswitch2darkTheme} class="align-middle form-check-input" name="theme" id="themeDark" type="radio">`
self._view.theme.clean = yo`<input onchange=${onswitch2cleanTheme} class="align-middle form-check-input" name="theme" id="themeClean" type="radio">`
self._view.theme[self.data.currentTheme].setAttribute('checked', 'checked')
this._view.theme.light = yo`<input onchange=${onswitch2lightTheme} class="align-middle form-check-input" name="theme" id="themeLight" type="radio">`
this._view.theme.dark = yo`<input onchange=${onswitch2darkTheme} class="align-middle form-check-input" name="theme" id="themeDark" type="radio">`
this._view.theme.clean = yo`<input onchange=${onswitch2cleanTheme} class="align-middle form-check-input" name="theme" id="themeClean" type="radio">`
this._view.theme[this.currentTheme].setAttribute('checked', 'checked')
self._view.config.homePage = yo`
this._view.config.homePage = yo`
<div class="${css.info} card">
<div class="card-body">
<h6 class="${css.title} card-title">Home</h6>
<div class="btn-group">
<button class="btn btn-primary sm-1" onclick="${() => { this._deps.appManager.ensureActivated('home') }}" >Home</button>
<button class="btn btn-primary sm-1" onclick="${() => { this.appManager.ensureActivated('home') }}" >Home</button>
<button class="btn btn-primary sm-1" onclick="${() => { window.open('https://gitter.im/ethereum/remix') }}">Gitter Channel</button>
</div>
</div>
</div>`
self._view.config.general = yo`
this._view.config.general = yo`
<div class="${css.info} card">
<div class="card-body">
<h6 class="${css.title} card-title">General settings</h6>
<div class="form-check ${css.frow}">
<div>${self._view.generateContractMetadata}</div>
<div>${this._view.generateContractMetadata}</div>
<label class="form-check-label align-middle" for="generatecontractmetadata">Generate contract metadata. Generate a JSON file in the contract folder. Allows to specify library addresses the contract depends on. If nothing is specified, Remix deploys libraries automatically.</label>
</div>
<div class="form-check ${css.frow}">
<div>${self._view.optionVM}</div>
<div>${this._view.optionVM}</div>
<label class="form-check-label align-middle" for="alwaysUseVM">Always use Ethereum VM at Load</label>
</div>
<div class="form-check ${css.frow}">
<div><input id="editorWrap" class="form-check-input align-middle" type="checkbox" onchange=${function () { self._deps.editor.resize(this.checked) }}></div>
<div><input id="editorWrap" class="form-check-input align-middle" type="checkbox" onchange=${function () { this.editor.resize(this.checked) }}></div>
<label class="form-check-label align-middle" for="editorWrap">Text Wrap</label>
</div>
<div class="form-check ${css.frow}">
<div>${self._view.personal}></div>
<label class="form-check-label align-middle" for="personal">Enable Personal Mode ${self._view.warnPersonalMode}></label>
<div>${this._view.personal}></div>
<label class="form-check-label align-middle" for="personal">Enable Personal Mode ${this._view.warnPersonalMode}></label>
</div>
</div>
</div>
`
self._view.gistToken = yo`
this._view.gistToken = yo`
<div class="${css.info} card">
<div class="card-body">
<h6 class="${css.title} card-title">Gist Access Token</h6>
<p class="">Manage the access token used to publish to Gist and retrieve Github contents.</p>
<p class="">Go to github token page (link below) to create a new token and save it in Remix. Make sure this token has only 'create gist' permission.</p>
<p class="${css.crowNoFlex}"><a target="_blank" href="https://github.com/settings/tokens">https://github.com/settings/tokens</a></p>
<div class="${css.crowNoFlex}">${self._view.gistToken}</div>
<div class="${css.crowNoFlex}">${this._view.gistToken}</div>
</div>
</div>`
self._view.config.themes = yo`
this._view.config.themes = yo`
<div class="${css.info} card">
<div class="card-body">
<h6 class="${css.title} card-title">Themes</h6>
<div class="card-text">
<div class="${css.frow} form-check ${css.crow}">
${self._view.theme.light}
${this._view.theme.light}
<label class="form-check-label" for="themeLight">Light Theme</label>
</div>
<div class="${css.frow} form-check ${css.crow}">
${self._view.theme.dark}
${this._view.theme.dark}
<label class="form-check-label" for="themeDark">Dark Theme</label>
</div>
<div class="${css.frow} form-check ${css.crow}">
${self._view.theme.clean}
${this._view.theme.clean}
<label class="form-check-label" for="themeClean">Clean Theme</label>
</div>
</div>
</div>
</div>`
self._view.el = yo`
this._view.el = yo`
<div class="${css.settingsTabView}" id="settingsView">
${self._view.config.homePage}
${self._view.config.general}
${self._view.gistToken}
${self._view.config.themes}
${this._view.config.homePage}
${this._view.config.general}
${this._view.gistToken}
${this._view.config.themes}
</div>`
function onchangeGenerateContractMetadata (event) {
self._deps.config.set('settings/generate-contract-metadata', !self._deps.config.get('settings/generate-contract-metadata'))
self.config.set('settings/generate-contract-metadata', !self.config.get('settings/generate-contract-metadata'))
}
function onchangeOption (event) {
self._deps.config.set('settings/always-use-vm', !self._deps.config.get('settings/always-use-vm'))
self.config.set('settings/always-use-vm', !self.config.get('settings/always-use-vm'))
}
function onswitch2darkTheme (event) {
styleGuide.switchTheme('dark')
@ -168,100 +166,9 @@ module.exports = class SettingsTab {
styleGuide.switchTheme('clean')
}
function onchangePersonal (event) {
self._deps.config.set('settings/personal-mode', !self._deps.config.get('settings/personal-mode'))
self.config.set('settings/personal-mode', !self.config.get('settings/personal-mode'))
}
styleGuide.switchTheme()
return self._view.el
return this._view.el
}
}
const css = csjs`
.settingsTabView {
padding: 2%;
}
.info {
margin-bottom: .6rem;
word-break: break-word;
font-size: .8rem;
}
.info h7 {
margin-bottom: .5rem;
}
.title {
// font-size: 1.1em;
// font-weight: bold;
// margin-bottom: 1em;
}
.frow {
margin-bottom: .5rem;
}
.crow {
// display: flex;
// overflow: auto;
// clear: both;
// padding: .2em;
}
.checkboxText {
font-weight: normal;
}
.crow label {
cursor:pointer;
}
.crowNoFlex {
overflow: auto;
clear: both;
}
.attention {
margin-bottom: 1em;
padding: .5em;
font-weight: bold;
}
.heading {
margin-bottom: 0;
}
.explaination {
margin-top: 3px;
margin-bottom: 3px;
}
input {
margin-right: 5px;
cursor: pointer;
width: inherit;
}
input[type=radio] {
margin-top: 2px;
}
.pluginTextArea {
font-family: unset;
}
.removePlugin {
cursor: pointer;
}
.icon {
margin-right: .5em;
}
.savegisttoken {
margin-left: 5px;
}
.aPlugin {
display: inline-block;
padding-left: 10px;
padding-top: 4px;
padding-bottom: 6px;
max-width: 100px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
vertical-align: middle;
}
.removePlugin{
padding-left: 7px;
padding-right: 7px;
margin-left: 10px;
}
.inline {
display: inline;
width: 50%;
}
`

@ -1,24 +1,30 @@
var csjs = require('csjs-inject')
var css = csjs`
const css = csjs`
.settingsTabView {
padding: 2%;
display: flex;
}
.info {
margin-bottom: 1em;
margin-bottom: .6rem;
word-break: break-word;
font-size: .8rem;
}
.info h7 {
margin-bottom: .5rem;
}
.title {
font-size: 1.1em;
font-weight: bold;
margin-bottom: 1em;
// font-size: 1.1em;
// font-weight: bold;
// margin-bottom: 1em;
}
.frow {
margin-bottom: .5rem;
}
.crow {
display: flex;
overflow: auto;
clear: both;
padding: .2em;
// display: flex;
// overflow: auto;
// clear: both;
// padding: .2em;
}
.checkboxText {
font-weight: normal;
@ -35,10 +41,6 @@ var css = csjs`
padding: .5em;
font-weight: bold;
}
.select {
font-weight: bold;
margin-top: 1em;
}
.heading {
margin-bottom: 0;
}
@ -49,6 +51,7 @@ var css = csjs`
input {
margin-right: 5px;
cursor: pointer;
width: inherit;
}
input[type=radio] {
margin-top: 2px;
@ -56,24 +59,36 @@ var css = csjs`
.pluginTextArea {
font-family: unset;
}
.pluginLoad {
vertical-align: top;
}
i.warnIt {
color: var(--warning);
.removePlugin {
cursor: pointer;
}
.icon {
margin-right: .5em;
}
.remixdinstallation {
padding: 3px;
border-radius: 2px;
margin-left: 5px;
}
.savegisttoken {
margin-left: 5px;
}
}
.aPlugin {
display: inline-block;
padding-left: 10px;
padding-top: 4px;
padding-bottom: 6px;
max-width: 100px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
vertical-align: middle;
}
.removePlugin{
padding-left: 7px;
padding-right: 7px;
margin-left: 10px;
}
.inline {
display: inline;
width: 50%;
}
`
module.exports = css

@ -1,36 +0,0 @@
const csjs = require('csjs-inject')
const css = csjs`
.menu {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
.active {
}
.options {
float: left;
padding-top: 0.7em;
min-width: 60px;
font-size: 0.9em;
cursor: pointer;
font-size: 1em;
text-align: center;
}
.optionViews {
overflow: scroll;
height: 100%;
}
.optionViews > div {
display: none;
}
.optionViews .pre {
word-wrap: break-word;
border-radius: 3px;
display: inline-block;
padding: 0 0.6em;
}
`
module.exports = css

@ -1,85 +0,0 @@
const yo = require('yo-yo')
var css = require('./styles/support-tab-styles')
class SupportTab {
constructor (localRegistry) {
this.el = null
this.gitterIframe = ''
this.gitterIsLoaded = false
}
__showing () {
if (this.gitterIsLoaded) return
const iframe = yo`<iframe class="${css.chatIframe}" src='https://gitter.im/ethereum/remix/~embed'></iframe>`
this.gitterIframe.parentNode.replaceChild(iframe, this.gitterIframe)
this.gitterIframe = iframe
this.el.style.display = 'block'
this.gitterIsLoaded = true
}
profile () {
return {
name: 'support',
methods: [],
events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik03MDQgMTkycTAtMjYtMTktNDV0LTQ1LTE5LTQ1IDE5LTE5IDQ1IDE5IDQ1IDQ1IDE5IDQ1LTE5IDE5LTQ1em04OTYtMzJ2MzIwcTAgMTYtMTIgMjUtOCA3LTIwIDctNCAwLTctMWwtNDQ4LTk2cS0xMS0yLTE4LTExdC03LTIwaC0yNTZ2MTAycTExMSAyMyAxODMuNSAxMTF0NzIuNSAyMDN2ODAwcTAgMjYtMTkgNDV0LTQ1IDE5aC01MTJxLTI2IDAtNDUtMTl0LTE5LTQ1di04MDBxMC0xMDYgNjIuNS0xOTAuNXQxNjEuNS0xMTQuNXYtMTExaC0zMnEtNTkgMC0xMTUgMjMuNXQtOTEuNSA1My02NiA2Ni41LTQwLjUgNTMuNS0xNCAyNC41cS0xNyAzNS01NyAzNS0xNiAwLTI5LTctMjMtMTItMzEuNS0zN3QzLjUtNDlxNS0xMCAxNC41LTI2dDM3LjUtNTMuNSA2MC41LTcwIDg1LTY3IDEwOC41LTUyLjVxLTI1LTQyLTI1LTg2IDAtNjYgNDctMTEzdDExMy00NyAxMTMgNDcgNDcgMTEzcTAgMzMtMTQgNjRoMzAycTAtMTEgNy0yMHQxOC0xMWw0NDgtOTZxMy0xIDctMSAxMiAwIDIwIDcgMTIgOSAxMiAyNXoiLz48L3N2Zz4=',
description: 'help center'
}
}
render () {
if (this.el) return this.el
this.gitterIframe = yo`<div></div>`
const remixd = yo`
<div class="${css.info}">
<div class=${css.title}>Accessing local files</div>
<div class="${css.crow}">
Remixd is a tool which allow Remix IDE to access files located in your local computer.
it can also be used to setup a development environment.
</div>
<div class="${css.crow}">More infos:</div>
<div class="${css.crow}"><a target="_blank" href="https://github.com/ethereum/remixd"> https://github.com/ethereum/remixd</a></div>
<div class="${css.crow}"><a target="_blank" href="https://remix.readthedocs.io/en/latest/tutorial_remixd_filesystem">http://remix.readthedocs.io/en/latest/tutorial_remixd_filesystem.html</a></div>
<div class="${css.crow}">Installation: <pre class=${css.remixdinstallation}>npm install remixd -g</pre></div>
</div>`
const localremixd = yo`
<div class="${css.info}">
<div class=${css.title}>Running Remix locally</div>
<div class="${css.crow}">
as a NPM module:
</div>
<a target="_blank" href="https://www.npmjs.com/package/remix-ide">https://www.npmjs.com/package/remix-ide</a>
<pre class=${css.remixdinstallation}>npm install remix-ide -g</pre>
<div class="${css.crow}">
as an electron app:
</div>
<a target="_blank" href="https://github.com/horizon-games/remix-app">https://github.com/horizon-games/remix-app</a>
</div>`
this.el = yo`
<div class="${css.supportTabView}" id="supportView">
<div class="${css.infoBox}">
Have a question, found a bug or want to propose a feature? Have a look at the
<a target="_blank" href='https://github.com/ethereum/remix-ide/issues'> issues</a> or check out
<a target="_blank" href='https://remix.readthedocs.io/en/latest/'> the documentation page on Remix</a> or
<a target="_blank" href='https://solidity.readthedocs.io/en/latest/'> Solidity</a>.
</div>
<div class="${css.chat}">
<div class="${css.chatTitle}" onclick=${() => { window.open('https://gitter.im/ethereum/remix') }} title='Click to open chat in Gitter'>
<div class="${css.chatTitleText}">ethereum/remix community chat</div>
</div>
${this.gitterIframe}
</div>
${remixd}
${localremixd}
</div>`
return this.el
}
}
module.exports = SupportTab

@ -1,90 +0,0 @@
var yo = require('yo-yo')
var css = require('./styles/tabbed-menu-styles')
var globalRegistry = require('../../global/registry')
var helper = require('../../lib/helper')
var EventManager = require('../../lib/events')
class TabbedMenu {
constructor (localRegistry) {
const self = this
self.event = new EventManager()
self._components = {}
self._components.registry = localRegistry || globalRegistry
self._deps = {
app: self._components.registry.get('app').api
}
self._view = { el: null, viewport: null, tabs: {}, contents: {} }
}
render () {
const self = this
if (self._view.el) return self._view.el
self._view.el = yo`<ul class=${css.menu}>${Object.values(self._view.tabs)}</ul>`
return self._view.el
}
renderViewport () {
const self = this
if (self._view.viewport) return self._view.viewport
self._view.viewport = yo`
<div id="optionViews" class=${css.optionViews}>
${Object.values(self._view.contents)}
</div>`
return self._view.viewport
}
addTab (title, cssClass, content) {
const self = this
if (helper.checkSpecialChars(title)) return
if (self._view.contents[title] || self._view.tabs[title]) throw new Error('tab already exists')
self._view.contents[title] = content
self._view.tabs[title] = yo`<li class="${css.options} ${cssClass}" onclick=${function (ev) { self.selectTab(this) }} title=${title}>${title}</li>`
if (self._view.el) self._view.el.appendChild(self._view.tabs[title])
if (self._view.viewport) self._view.viewport.appendChild(self._view.contents[title])
}
removeTabByTitle (title) {
const self = this
if (self._view.tabs[title]) {
self._view.tabs[title].parentNode.removeChild(self._view.tabs[title])
}
if (self._view.contents[title]) {
self._view.contents[title].parentNode.removeChild(self._view.contents[title])
}
delete self._view.contents[title]
delete self._view.tabs[title]
}
getTabByClass (tabClass) {
const self = this
return self._view.el.querySelector(`li.${tabClass}`)
}
updateTabTitle (tabClass, title) {
const self = this
var tab = self.getTabByClass(tabClass)
if (tab) tab.innerHTML = title
}
selectTabByTitle (title) {
const self = this
self.selectTab(self._view.tabs[title])
}
selectTabByClassName (tabClass) {
const self = this
var tab = self.getTabByClass(tabClass)
if (tab) self.selectTab(tab)
return tab
}
selectTab (el) {
const self = this
if (!el.classList.contains(css.active)) {
var nodes = Object.values(self._view.tabs)
for (var i = 0; i < nodes.length; ++i) {
nodes[i].classList.remove(css.active)
self._view.contents[nodes[i].getAttribute('title')].style.display = 'none'
}
}
var title = el.getAttribute('title')
self._view.contents[el.getAttribute('title')].style.display = 'block'
el.classList.add(css.active)
self._deps.app.event.trigger('tabChanged', [title])
}
}
module.exports = TabbedMenu

@ -1,29 +1,26 @@
var yo = require('yo-yo')
var async = require('async')
var helper = require('../../lib/helper.js')
var tooltip = require('../ui/tooltip')
var modalDialogCustom = require('../ui/modal-dialog-custom')
var globalRegistry = require('../../global/registry')
var css = require('./styles/test-tab-styles')
var remixTests = require('remix-tests')
module.exports = class TestTab {
constructor (localRegistry, compileTab) {
// TODO here is a direct reference to compile tab, should be removed
const self = this
self.compileTab = compileTab
self._view = { el: null }
self._components = {}
self._components.registry = localRegistry || globalRegistry
// dependencies
self._deps = {
fileManager: self._components.registry.get('filemanager').api,
filePanel: self._components.registry.get('filepanel').api
}
self.data = {}
self.testList = yo`<div class=${css.testList}></div>`
import { ApiFactory } from 'remix-plugin'
const TestTabLogic = require('./testTab/testTab')
module.exports = class TestTab extends ApiFactory {
constructor (fileManager, filePanel, compileTab) {
super()
this.compileTab = compileTab
this._view = { el: null }
this.compileTab = compileTab
this.fileManager = fileManager
this.filePanel = filePanel
this.testTabLogic = new TestTabLogic(fileManager)
this.data = {}
this.testList = yo`<div class=${css.testList}></div>`
}
profile () {
get profile () {
return {
name: 'solidityUnitTesting',
displayName: 'solidity unit testing',
@ -33,164 +30,136 @@ module.exports = class TestTab {
description: ' - '
}
}
render () {
const self = this
var testsOutput = yo`<div class="${css.container} border border-primary border-right-0 border-left-0 border-bottom-0" hidden='true' id="tests"></div>`
var testsSummary = yo`<div class="${css.container} border border-primary border-right-0 border-left-0 border-bottom-0" hidden='true' id="tests"></div>`
var testCallback = function (result) {
testsOutput.hidden = false
if (result.type === 'contract') {
testsOutput.appendChild(yo`<div class="${css.outputTitle}">${result.filename} (${result.value})</div>`)
} else if (result.type === 'testPass') {
testsOutput.appendChild(yo`<div class="${css.testPass} ${css.testLog} bg-success">✓ (${result.value})</div>`)
} else if (result.type === 'testFailure') {
testsOutput.appendChild(yo`<div class="${css.testFailure} ${css.testLog} bg-danger">✘ (${result.value})</div>`)
}
}
var resultsCallback = function (_err, result, cb) {
// total stats for the test
// result.passingNum
// result.failureNum
// result.timePassed
cb()
}
var updateFinalResult = function (_err, result, filename) {
testsSummary.hidden = false
if (_err) {
testsSummary.appendChild(yo`<div class="${css.testFailureSummary} text-danger" >${_err.message}</div>`)
return
}
testsSummary.appendChild(yo`<div class=${css.summaryTitle}> ${filename} </div>`)
if (result.totalPassing > 0) {
testsSummary.appendChild(yo`<div class="text-success">${result.totalPassing} passing (${result.totalTime}s)</div>`)
testsSummary.appendChild(yo`<br>`)
}
if (result.totalFailing > 0) {
testsSummary.appendChild(yo`<div class="text-danger" >${result.totalFailing} failing</div>`)
testsSummary.appendChild(yo`<br>`)
}
result.errors.forEach((error, index) => {
testsSummary.appendChild(yo`<div class="text-danger" >${error.context} - ${error.value} </div>`)
testsSummary.appendChild(yo`<div class="${css.testFailureSummary} text-danger" >${error.message}</div>`)
testsSummary.appendChild(yo`<br>`)
})
}
function runTest (testFilePath, callback) {
self._deps.fileManager.fileProviderOf(testFilePath).get(testFilePath, (error, content) => {
if (!error) {
var runningTest = {}
runningTest[testFilePath] = { content }
remixTests.runTestSources(runningTest, testCallback, resultsCallback, (error, result) => {
updateFinalResult(error, result, testFilePath)
callback(error)
}, (url, cb) => { self.compileTab.compileTabLogic.importFileCb(url, cb) })
}
})
}
activate () {
this.listenToEvents()
}
function getTests (self, cb) {
var path = self._deps.fileManager.currentPath()
if (!path) return cb(null, [])
var provider = self._deps.fileManager.fileProviderOf(path)
if (!provider) return cb(null, [])
var tests = []
self._deps.fileManager.getFilesFromPath(path)
.then((files) => {
for (var file in files) {
if (/.(_test.sol)$/.exec(file)) tests.push(provider.type + '/' + file)
}
cb(null, tests)
})
.catch(err => cb(err))
}
deactivate () {
}
self._deps.filePanel.event.register('newTestFileCreated', file => {
var testList = self.view.querySelector("[class^='testList']")
var test = yo`<label class="singleTestLabel"><input class="singleTest" onchange=${(e) => toggleCheckbox(e.target.checked, file)} type="checkbox" checked="true">${file}</label>`
listenToEvents () {
this.filePanel.event.register('newTestFileCreated', file => {
var testList = this.view.querySelector("[class^='testList']")
var test = yo`<label class="singleTestLabel"><input class="singleTest" onchange=${(e) => this.toggleCheckbox(e.target.checked, file)} type="checkbox" checked="true">${file}</label>`
testList.appendChild(test)
self.data.allTests.push(file)
self.data.selectedTests.push(file)
this.data.allTests.push(file)
this.data.selectedTests.push(file)
})
self._deps.fileManager.events.on('currentFileChanged', (file) => {
getTests(self, (error, tests) => {
this.fileManager.events.on('currentFileChanged', (file, provider) => {
this.testTabLogic.getTests((error, tests) => {
if (error) return tooltip(error)
self.data.allTests = tests
self.data.selectedTests = [...self.data.allTests]
if (!tests.length) {
yo.update(self.testList, yo`<div class=${css.testList}>No test file available</div>`)
} else {
yo.update(self.testList, yo`<div class=${css.testList}>${listTests()}</div>`)
}
testsOutput.hidden = true
testsSummary.hidden = true
testsOutput.innerHTML = ''
testsSummary.innerHTML = ''
this.data.allTests = tests
this.data.selectedTests = [...this.data.allTests]
const testsMessage = (tests.length ? this.listTests() : 'No test file available')
yo.update(this.testList, yo`<div class=${css.testList}>${testsMessage}</div>`)
if (!this.testsOutput || !this.testsSummary) return
this.testsOutput.hidden = true
this.testsSummary.hidden = true
this.testsOutput.innerHTML = ''
this.testsSummary.innerHTML = ''
})
})
}
// self._events.filePanel.register('fileRenamed', (oldName, newName, isFolder) => {
// debugger
// self.data.allTests = self.data.allTests.filter(e => e != oldName)
// self.data.selectedTests = self.data.selectedTests.filter(e => e !== oldName)
// if (/.(_test.sol)$/.exec(newName)) self.data.allTests.push(newName)
// })
listTests () {
return this.data.allTests.map(test => yo`<label class="singleTestLabel"><input class="singleTest" onchange =${(e) => this.toggleCheckbox(e.target.checked, test)} type="checkbox" checked="true">${test} </label>`)
}
function listTests () {
var tests = self.data.allTests
return tests.map(test => yo`<label class="singleTestLabel"><input class="singleTest" onchange =${(e) => toggleCheckbox(e.target.checked, test)} type="checkbox" checked="true">${test} </label>`)
toggleCheckbox (eChecked, test) {
if (!this.data.selectedTests) {
this.data.selectedTests = this._view.el.querySelectorAll('.singleTest:checked')
}
function toggleCheckbox (eChecked, test) {
if (!self.data.selectedTests) {
self.data.selectedTests = self._view.el.querySelectorAll('.singleTest:checked')
}
let selectedTests = self.data.selectedTests
selectedTests = eChecked ? [...selectedTests, test] : selectedTests.filter(el => el !== test)
self.data.selectedTests = selectedTests
let checkAll = self._view.el.querySelector('[id="checkAllTests"]')
if (eChecked) {
checkAll.checked = true
} else if (!selectedTests.length) {
checkAll.checked = false
}
let selectedTests = this.data.selectedTests
selectedTests = eChecked ? [...selectedTests, test] : selectedTests.filter(el => el !== test)
this.data.selectedTests = selectedTests
let checkAll = this._view.el.querySelector('[id="checkAllTests"]')
if (eChecked) {
checkAll.checked = true
} else if (!selectedTests.length) {
checkAll.checked = false
}
}
function checkAll (event) {
let checkBoxes = self._view.el.querySelectorAll('.singleTest')
const checkboxesLabels = self._view.el.querySelectorAll('.singleTestLabel')
// checks/unchecks all
for (let i = 0; i < checkBoxes.length; i++) {
checkBoxes[i].checked = event.target.checked
toggleCheckbox(event.target.checked, checkboxesLabels[i].innerText)
}
checkAll (event) {
let checkBoxes = this._view.el.querySelectorAll('.singleTest')
const checkboxesLabels = this._view.el.querySelectorAll('.singleTestLabel')
// checks/unchecks all
for (let i = 0; i < checkBoxes.length; i++) {
checkBoxes[i].checked = event.target.checked
this.toggleCheckbox(event.target.checked, checkboxesLabels[i].innerText)
}
}
var runTests = function () {
testsOutput.innerHTML = 'Running tests ...'
var tests = self.data.selectedTests
async.eachOfSeries(tests, (value, key, callback) => { runTest(value, callback) })
testCallback (result) {
this.testsOutput.hidden = false
if (result.type === 'contract') {
this.testsOutput.appendChild(yo`<div class=${css.outputTitle}>${result.filename} (${result.value})</div>`)
} else if (result.type === 'testPass') {
this.testsOutput.appendChild(yo`<div class="${css.testPass} ${css.testLog} bg-success">✓ (${result.value})</div>`)
} else if (result.type === 'testFailure') {
this.testsOutput.appendChild(yo`<div class="${css.testFailure} ${css.testLog} bg-danger">✘ (${result.value})</div>`)
}
}
resultsCallback (_err, result, cb) {
// total stats for the test
// result.passingNum
// result.failureNum
// result.timePassed
cb()
}
var generateTestFile = function () {
var fileManager = self._deps.fileManager
var path = fileManager.currentPath()
var fileProvider = fileManager.fileProviderOf(path)
if (fileProvider) {
helper.createNonClashingNameWithPrefix(path + '/test.sol', fileProvider, '_test', (error, newFile) => {
if (error) return modalDialogCustom.alert('Failed to create file. ' + newFile + ' ' + error)
if (!fileProvider.set(newFile, testContractSample)) {
modalDialogCustom.alert('Failed to create test file ' + newFile)
} else {
fileManager.switchFile(newFile)
}
})
}
updateFinalResult (_err, result, filename) {
this.testsSummary.hidden = false
if (_err) {
this.testsSummary.appendChild(yo`<div class="${css.testFailureSummary} text-danger" >${_err.message}</div>`)
return
}
this.testsSummary.appendChild(yo`<div class=${css.summaryTitle}> ${filename} </div>`)
if (result.totalPassing > 0) {
this.testsSummary.appendChild(yo`<div class="text-success" >${result.totalPassing} passing (${result.totalTime}s)</div>`)
this.testsSummary.appendChild(yo`<br>`)
}
if (result.totalFailing > 0) {
this.testsSummary.appendChild(yo`<div class="text-danger" >${result.totalFailing} failing</div>`)
this.testsSummary.appendChild(yo`<br>`)
}
result.errors.forEach((error, index) => {
this.testsSummary.appendChild(yo`<div class="text-danger" >${error.context} - ${error.value} </div>`)
this.testsSummary.appendChild(yo`<div class="${css.testFailureSummary} text-danger" >${error.message}</div>`)
this.testsSummary.appendChild(yo`<br>`)
})
}
runTest (testFilePath, callback) {
this.fileManager.fileProviderOf(testFilePath).get(testFilePath, (error, content) => {
if (error) return
var runningTest = {}
runningTest[testFilePath] = { content }
remixTests.runTestSources(runningTest, (result) => { this.testCallback(result) }, (_err, result, cb) => { this.resultsCallback(_err, result, cb) }, (error, result) => {
this.updateFinalResult(error, result, testFilePath)
callback(error)
}, (url, cb) => {
return this.compileTab.compileTabLogic.importFileCb(url, cb)
})
})
}
runTests () {
this.testsOutput.innerHTML = ''
this.testsSummary.innerHTML = ''
var tests = this.data.selectedTests
async.eachOfSeries(tests, (value, key, callback) => { this.runTest(value, callback) })
}
render () {
this.testsOutput = yo`<div class="${css.container} border border-primary border-right-0 border-left-0 border-bottom-0" hidden='true' id="tests"></div>`
this.testsSummary = yo`<div class="${css.container} border border-primary border-right-0 border-left-0 border-bottom-0" hidden='true' id="tests"></div>`
var el = yo`
<div class="${css.testTabView} card" id="testView">
@ -203,70 +172,28 @@ module.exports = class TestTab {
<br/>
For more details, see
How to test smart contracts guide in our documentation.
<br/>
<button class="${css.generateTestFile} btn btn-primary m-1" onclick="${generateTestFile}">Generate test file</button>
<div class="${css.generateTestFile} btn btn-primary m-1" onclick="${this.testTabLogic.generateTestFile(this)}">Generate test file</div>
</div>
<div class="${css.tests}">
${self.testList}
${this.testList}
<div class="${css.buttons} btn-group">
<button class="${css.runButton} btn btn-primary m-1" onclick="${runTests}">Run Tests</button>
<div class="${css.runButton} btn btn-primary m-1" onclick="${this.runTests.bind(this)}">Run Tests</div>
<label class="${css.label}" for="checkAllTests">
<input id="checkAllTests"
type="checkbox"
onclick="${function (event) { checkAll(event) }}"
onclick="${(event) => { this.checkAll(event) }}"
checked="true"
>
Check/Uncheck all
</label>
</div>
${testsOutput}
${testsSummary}
${this.testsOutput}
${this.testsSummary}
</div>
</div>
`
if (!self._view.el) self._view.el = el
if (!this._view.el) this._view.el = el
return el
}
}
var testContractSample = `pragma solidity >=0.4.0 <0.6.0;
import "remix_tests.sol"; // this import is automatically injected by Remix.
// file name has to end with '_test.sol'
contract test_1 {
function beforeAll() public {
// here should instantiate tested contract
Assert.equal(uint(4), uint(3), "error in before all function");
}
function check1() public {
// use 'Assert' to test the contract
Assert.equal(uint(2), uint(1), "error message");
Assert.equal(uint(2), uint(2), "error message");
}
function check2() public view returns (bool) {
// use the return value (true or false) to test the contract
return true;
}
}
contract test_2 {
function beforeAll() public {
// here should instantiate tested contract
Assert.equal(uint(4), uint(3), "error in before all function");
}
function check1() public {
// use 'Assert' to test the contract
Assert.equal(uint(2), uint(1), "error message");
Assert.equal(uint(2), uint(2), "error message");
}
function check2() public view returns (bool) {
// use the return value (true or false) to test the contract
return true;
}
}`

@ -0,0 +1,85 @@
var helper = require('../../../lib/helper.js')
var modalDialogCustom = require('../../ui/modal-dialog-custom')
class TestTabLogic {
constructor (fileManager) {
this.fileManager = fileManager
}
generateTestFile () {
var path = this.fileManager.currentPath()
var fileProvider = this.fileManager.fileProviderOf(path)
if (!fileProvider) return
helper.createNonClashingNameWithPrefix(path + '/test.sol', fileProvider, '_test', (error, newFile) => {
if (error) return modalDialogCustom.alert('Failed to create file. ' + newFile + ' ' + error)
if (!fileProvider.set(newFile, this.generateTestContractSample())) return modalDialogCustom.alert('Failed to create test file ' + newFile)
this.fileManager.switchFile(newFile)
})
}
async getTests (cb) {
var path = this.fileManager.currentPath()
if (!path) return cb(null, [])
var provider = this.fileManager.fileProviderOf(path)
if (!provider) return cb(null, [])
var tests = []
let files
try {
files = await this.fileManager.getFilesFromPath(path)
} catch (e) {
cb(e.message)
}
for (var file in files) {
if (/.(_test.sol)$/.exec(file)) tests.push(provider.type + '/' + file)
}
cb(null, tests)
}
generateTestContractSample () {
return `pragma solidity >=0.4.0 <0.6.0;
import "remix_tests.sol"; // this import is automatically injected by Remix.
// file name has to end with '_test.sol'
contract test_1 {
function beforeAll() public {
// here should instantiate tested contract
Assert.equal(uint(4), uint(3), "error in before all function");
}
function check1() public {
// use 'Assert' to test the contract
Assert.equal(uint(2), uint(1), "error message");
Assert.equal(uint(2), uint(2), "error message");
}
function check2() public view returns (bool) {
// use the return value (true or false) to test the contract
return true;
}
}
contract test_2 {
function beforeAll() public {
// here should instantiate tested contract
Assert.equal(uint(4), uint(3), "error in before all function");
}
function check1() public {
// use 'Assert' to test the contract
Assert.equal(uint(2), uint(1), "error message");
Assert.equal(uint(2), uint(2), "error message");
}
function check2() public view returns (bool) {
// use the return value (true or false) to test the contract
return true;
}
}`
}
}
module.exports = TestTabLogic

@ -0,0 +1,22 @@
import { ApiFactory } from 'remix-plugin'
import { EventEmitter } from 'events'
export class TxListenerModule extends ApiFactory {
constructor (txlistener) {
super()
this.events = new EventEmitter()
txlistener.event.register('newTransaction', (tx) => {
this.events.emit('newTransaction', tx)
})
}
get profile () {
return {
name: 'txListener',
displayName: 'transaction listener',
events: ['newTransaction'],
description: 'service - notify new transactions'
}
}
}

@ -1,12 +1,12 @@
var yo = require('yo-yo')
var remixLib = require('remix-lib')
var EventManager = remixLib.EventManager
var Commands = require('../constants/commands')
var modal = require('./modaldialog.js')
// -------------- styling ----------------------
var css = require('./styles/auto-complete-popup-styles')
// var cssModal = require('./styles/modaldialog-styles')
/* USAGE:
@ -22,118 +22,180 @@ class AutoCompletePopup {
constructor (opts = {}) {
var self = this
self.event = new EventManager()
self.isOpen = false
self.data = {
_options: opts.options || []
}
self._components = {
modal: null
}
self._view = {}
self._startingElement = 0
self._elementsToShow = 3
self._removePopUp = this.resetCSSValuesModalContainer
}
resetCSSValuesModalContainer () {
/*
var modalContainer = document.querySelector(`.${cssModal.modal}`)
modalContainer.style.display = 'none'
var modalContent = document.querySelector(`.${css.modalContent}`)
let newModalContent = modalContent ? document.querySelector(`.${css.modalContent}`) : document.querySelector(`.${cssModal.modalContent}`)
newModalContent.className = cssModal.modalContent
*/
self._elementsToShow = 4
self._selectedElement = 0
this.render()
}
render () {
var self = this
var header = yo`<div class="${css.text}">Remix Commands</div>`
self._view.autoComplete = yo`
<div class="${css.popup}">
<div>
${self.data._options.map((item, index) => {
return yo`
<div class="${css.listHandlerHide}">
<a value=${index}>
<div onclick=${handleSelect}>
${Object.keys(item)[0]}
</div>
</a>
<div class="${css.autoCompleteItem} ${css.listHandlerHide} item ${self._selectedElement === index ? 'bg-secondary' : ''}">
<div value=${index} onclick=${(event) => { self.handleSelect(event.srcElement.innerText) }}>
${getKeyOf(item)}
</div>
<div>
${Object.values(item)[0]}
${getValueOf(item)}
</div>
<hr/>
</div>
`
})}
</div>
<div class="${css.listHandlerHide}">
<button value=false onclick=${handleListIteration}></button>
<button value=true onclick=${handleListIteration}></button>
<div class="${css.pageNumberAlignment}">Page ${(self._startingElement / self._elementsToShow) + 1} of ${Math.ceil(self.data._options.length / self._elementsToShow)}</div>
</div>
</div>
`
function setUpPopUp () {
handleOpenPopup()
handleNagivationButtons()
handleListSize()
}
function handleOpenPopup () {
if (self.data._options.length > 1) {
if (self.data._options.length > 0) {
self._view.autoComplete.style.display = 'block'
modal(header.innerText, self._view.autoComplete, {label: null},
{
fn: () => { self._removePopUp() }
})
editCSSValuesModalContainer()
}
}
function handleSelect (event) {
self._removePopUp()
self._view.autoComplete.style.display = 'none'
self.event.trigger('handleSelect', [event.srcElement.innerText])
}
function handleNagivationButtons () {
if (self.data._options.length > self._elementsToShow) {
self._view.autoComplete.children[1].className = css.listHandlerButtonShow
self._components.modal = modal('', self._view.autoComplete, {label: null}, {label: null}, null, { class: css.modalContent, hideClose: true })
}
}
function handleListSize () {
if (self.data._options.length >= self._startingElement) {
for (let i = self._startingElement; i < (self._elementsToShow + self._startingElement); i++) {
if (self._view.autoComplete.children[0].children[i]) {
self._view.autoComplete.children[0].children[i].className = css.listHandlerShow
let el = self._view.autoComplete.querySelectorAll('.item')[i]
if (el) {
el.classList.remove(css.listHandlerHide)
el.classList.add(css.listHandlerShow)
}
}
}
}
function handleListIteration (event) {
if (event.srcElement.value === 'true' || event.which === 40) {
if ((self._startingElement + self._elementsToShow) < self.data._options.length) {
self._startingElement += self._elementsToShow
}
} else {
if (self._startingElement > 0) {
self._startingElement -= self._elementsToShow
}
setUpPopUp()
return self._view
}
handleSelect (text) {
this.removeAutoComplete()
this.event.trigger('handleSelect', [text])
}
moveUp () {
if (this._selectedElement === 0) return
this._selectedElement--
this._startingElement = this._selectedElement > 0 ? this._selectedElement - 1 : 0
this.event.trigger('updateList')
yo.update(this._view, this.render())
}
moveDown () {
if (this.data._options.length <= this._selectedElement + 1) return
this._selectedElement++
this._startingElement = this._selectedElement - 1
this.event.trigger('updateList')
yo.update(this._view, this.render())
}
handleAutoComplete (event, inputString) {
if (this.isOpen && (event.which === 27 || event.which === 8 || event.which === 46)) {
// backspace or any key that should remove the autocompletion
this.removeAutoComplete()
return true
}
if (this.isOpen && (event.which === 13 || event.which === 9)) {
// enter and tab (validate completion)
event.preventDefault()
if (this.data._options[this._selectedElement]) {
this.handleSelect(getKeyOf(this.data._options[this._selectedElement]))
}
self.event.trigger('updateList')
this.removeAutoComplete()
return true
}
if (this.isOpen && event.which === 38) {
// move up
event.preventDefault()
this.isOpen = true
this.moveUp()
return true
}
if (this.isOpen && event.which === 40) {
// move down
event.preventDefault()
this.isOpen = true
this.moveDown()
return true
}
if (event.which === 13 || event.which === 9) {
// enter || tab and autocompletion is off, just returning false
return false
}
let textList = inputString.split(' ')
let autoCompleteInput = textList.length > 1 ? textList[textList.length - 1] : textList[0]
if (inputString.length >= 2) {
// more than 2 letters, start completion
this.isOpen = true
this.data._options = []
Commands.allPrograms.forEach(item => {
let program = getKeyOf(item)
if (program.substring(0, program.length - 1).includes(autoCompleteInput.trim())) {
this.data._options.push(item)
} else if (autoCompleteInput.trim().includes(program) || (program === autoCompleteInput.trim())) {
Commands.allCommands.forEach(item => {
let command = getKeyOf(item)
if (command.includes(autoCompleteInput.trim())) {
this.data._options.push(item)
}
})
}
})
if (this.data._options.length === 1 && event.which === 9) {
// if only one option and tab is pressed, we resolve it
event.preventDefault()
textList.pop()
textList.push(getKeyOf(this.data._options[0]))
this.handleSelect(`${textList}`.replace(/,/g, ' '))
this.removeAutoComplete()
return
}
function editCSSValuesModalContainer () {
/*
var modalContent = document.querySelector(`.${cssModal.modalContent}`)
let newModalContent = modalContent ? document.querySelector(`.${cssModal.modalContent}`) : document.querySelector(`.${css.modalContent}`)
newModalContent.className = css.modalContent
*/
yo.update(this._view, this.render())
return true
}
return false
}
setUpPopUp()
return self._view
removeAutoComplete () {
if (!this.isOpen) return
this._view.autoComplete.style.display = 'none'
if (this._components.modal) this._components.modal.cancelListener()
this.isOpen = false
this.data._options = []
this._startingElement = 0
this._selectedElement = 0
yo.update(this._view, this.render())
}
}
function getKeyOf (item) {
return Object.keys(item)[0]
}
function getValueOf (item) {
return Object.values(item)[0]
}
module.exports = AutoCompletePopup

@ -2,10 +2,11 @@
import LandingPage from './landing-page'
import Section from './section'
import { defaultWorkspaces } from './workspace'
// var globalRegistry = require('../../../global/registry')
export function homepageProfile () {
return {
displayName: 'home',
displayName: 'Home',
name: 'home',
methods: [],
events: [],
@ -16,31 +17,32 @@ export function homepageProfile () {
}
export function generateHomePage (appManager, appStore) {
/*
var actions1 = [
{label: 'new file', type: `callback`, payload: () => { alert(`-new file created-`) }},
{label: 'import from GitHub', type: `callback`, payload: () => { alert(`-imported from GitHub-`) }},
{label: 'import from gist', type: `callback`, payload: () => { alert(`-imported from gist-`) }}
]
var actions2 = [
{label: '...', type: `callback`, payload: () => { alert(`-...-`) }}
]
/* var actions1 = [
{ label: 'New file',
type: 'callback',
payload: () => {
let fileManager = globalRegistry.get('fileexplorerbrowser').api
fileManager.creatNewFile()
}
},
{label: 'Import from GitHub', type: `callback`, payload: () => { this.alert(`-imported from GitHub-`) }},
{label: 'Import from gist', type: `callback`, payload: () => { this.alert(`-imported from gist-`) }}
] */
var actions3 = [
{label: 'Remix documentation', type: `link`, payload: `https://remix.readthedocs.io/en/latest/#`},
{label: 'GitHub repository', type: `link`, payload: `https://github.com/ethereum/remix-ide`},
{label: 'acces local file system (remixd)', type: `link`, payload: `https://remix.readthedocs.io/en/latest/tutorial_remixd_filesystem.html`},
{label: 'Access local file system with remixd', type: `link`, payload: `https://remix.readthedocs.io/en/latest/tutorial_remixd_filesystem.html`},
{label: 'npm module for remixd', type: `link`, payload: `https://www.npmjs.com/package/remixd`},
{label: 'medium posts', type: `link`, payload: `https://medium.com/remix-ide`},
{label: 'tutorials', type: `link`, payload: `https://github.com/ethereum/remix-workshops`}
{label: 'Medium posts', type: `link`, payload: `https://medium.com/remix-ide`},
{label: 'Tutorials', type: `link`, payload: `https://github.com/ethereum/remix-workshops`}
]
var actions4 = [
{label: 'Remix plugins & modules', type: `link`, payload: `https://github.com/ethereum/remix-plugin/blob/master/readme.md`},
{label: 'repository on GitHub', type: `link`, payload: `https://github.com/ethereum/remix-plugin`},
{label: 'examples', type: `link`, payload: `https://github.com/ethereum/remix-plugin/tree/master/examples`},
{label: 'build plugin for Remix', type: `link`, payload: `https://medium.com/remix-ide/build-a-plugin-for-remix-90d43b209c5a`}
{label: 'Repository on GitHub', type: `link`, payload: `https://github.com/ethereum/remix-plugin`},
{label: 'Examples', type: `link`, payload: `https://github.com/ethereum/remix-plugin/tree/master/examples`},
{label: 'Build a plugin for Remix', type: `link`, payload: `https://medium.com/remix-ide/build-a-plugin-for-remix-90d43b209c5a`}
]
var actions5 = [
@ -49,12 +51,10 @@ export function generateHomePage (appManager, appStore) {
{label: 'Reddit', type: `link`, payload: `https://www.reddit.com/r/ethdev/search?q=remix&restrict_sr=1`}
]
var section1 = new Section('Start', actions1)
var section2 = new Section('Recent', actions2)
var section3 = new Section('Learn', actions3)
var section4 = new Section('Plugins', actions4)
var section5 = new Section('Help', actions5)
*/
// var sectionStart = new Section('Start', actions1)
var sectionLearn = new Section('Learn', actions3)
var sectionPlugins = new Section('Plugins', actions4)
var sectionHelp = new Section('Help', actions5)
var sectionsWorkspaces = []
sectionsWorkspaces.push({
@ -66,8 +66,13 @@ export function generateHomePage (appManager, appStore) {
.forEach(({profile}) => { appManager.deactivateOne(profile.name) })
}})
defaultWorkspaces(appManager).forEach((workspace) => {
sectionsWorkspaces.push({label: workspace.title, type: 'callback', payload: () => { workspace.activate() }})
sectionsWorkspaces.push({
label: workspace.title,
type: 'callback',
payload: () => { workspace.activate() }
})
})
var sectionWorkspace = new Section('Workspaces', sectionsWorkspaces)
return new LandingPage([sectionWorkspace])
return new LandingPage([sectionWorkspace, /* sectionStart, */sectionLearn, sectionPlugins, sectionHelp])
}

File diff suppressed because one or more lines are too long

@ -1,17 +1,20 @@
var yo = require('yo-yo')
var css = require('./styles/modaldialog-styles')
module.exports = (title, content, ok, cancel, focusSelector) => {
module.exports = (title, content, ok, cancel, focusSelector, opts) => {
opts = opts || {}
var container = document.querySelector(`.${css.modal}`)
if (!container) {
document.querySelector('body').appendChild(html())
document.querySelector('body').appendChild(html(opts))
container = document.querySelector(`.${css.modal}`)
}
var closeDiv = document.getElementById('modal-close')
if (opts.hideClose) closeDiv.style.display = 'none'
var okDiv = document.getElementById('modal-footer-ok')
okDiv.innerHTML = (ok && ok.label !== undefined) ? ok.label : 'OK'
okDiv.style.display = okDiv.innerHTML === '' ? 'none' : 'inline-block'
var cancelDiv = document.getElementById('modal-footer-cancel')
cancelDiv.innerHTML = (cancel && cancel.label !== undefined) ? cancel.label : 'Cancel'
@ -38,6 +41,10 @@ module.exports = (title, content, ok, cancel, focusSelector) => {
removeEventListener()
hide()
if (cancel && cancel.fn) cancel.fn()
if (container) {
container.class = css.modal
container = null
}
}
function modalKeyEvent (e) {
@ -50,10 +57,11 @@ module.exports = (title, content, ok, cancel, focusSelector) => {
}
function hide () {
container.style.display = 'none'
if (container) container.style.display = 'none'
}
function show () {
if (!container) return
container.style.display = 'block'
if (focusSelector) {
const focusTarget = document.querySelector(`.${css.modal} ${focusSelector}`)
@ -71,19 +79,24 @@ module.exports = (title, content, ok, cancel, focusSelector) => {
cancelDiv.removeEventListener('click', cancelListener)
closeDiv.removeEventListener('click', cancelListener)
document.removeEventListener('keydown', modalKeyEvent)
document.getElementById('modal-background').removeEventListener('click', cancelListener)
if (document.getElementById('modal-background')) {
document.getElementById('modal-background').removeEventListener('click', cancelListener)
}
}
okDiv.addEventListener('click', okListener)
cancelDiv.addEventListener('click', cancelListener)
closeDiv.addEventListener('click', cancelListener)
document.addEventListener('keydown', modalKeyEvent)
document.getElementById('modal-background').addEventListener('click', cancelListener)
if (document.getElementById('modal-background')) {
document.getElementById('modal-background').addEventListener('click', cancelListener)
}
return { container, okListener, cancelListener }
}
function html () {
function html (opts) {
return yo`<div id="modal-dialog" class="${css.modal}">
<div id="modal-background" class="${css['modalBackground']}"> </div>
<div class="${css['modalContent']} bg-light text-secondary">
<div class="${css['modalContent']} bg-light text-secondary ${opts.class}">
<div class="${css['modalHeader']}">
<h3></h3>
<i id="modal-close" title="Close" class="fa fa-times ${css['modalClose']}" aria-hidden="true"></i>

@ -11,6 +11,11 @@ var css = csjs`
padding-bottom : 13px;
}
.autoCompleteItem {
padding : 4px;
border-radius : 2px;
}
.popup a {
cursor : pointer;
}

@ -1,13 +1,10 @@
export default {
start: (appStore, swapPanelApi, verticalIconApi, mainPanelApi, resizeFeature) => {
swapPanelApi.event.on('toggle', (moduleName) => {
swapPanelApi.event.on('toggle', () => {
resizeFeature.panel1.clientWidth !== 0 ? resizeFeature.minimize() : resizeFeature.maximise()
})
swapPanelApi.event.on('showing', (moduleName) => {
swapPanelApi.event.on('showing', () => {
resizeFeature.panel1.clientWidth === 0 ? resizeFeature.maximise() : ''
var current = appStore.getOne(moduleName)
// warn the content that it is being displayed. TODO should probably be done in each view
if (current && current.api.__showing) current.api.__showing()
})
mainPanelApi.event.on('toggle', () => {
resizeFeature.maximise()

@ -135,7 +135,8 @@ export class EntityStore extends Store {
* Add a new entity to the state
* @param {Object} entity
*/
add (id, entity) {
add (entity) {
const id = entity[this.keyId]
this.state.entities[id] = entity
this.state.ids.push(id)
this.event.emit('add', entity)

@ -14,8 +14,8 @@ export class RemixAppManager extends AppManagerApi {
}
}
ensureActivated (module) {
if (!this.store.isActive(module)) this.activateOne(module)
ensureActivated (apiName) {
if (!this.store.isActive(apiName)) this.activateOne(apiName)
}
proxy () {
@ -24,33 +24,23 @@ export class RemixAppManager extends AppManagerApi {
}
setActive (name, isActive) {
const entity = this.getEntity(name)
const api = this.getEntity(name)
// temp
if (entity && (name === 'solidity' || name === 'vyper')) {
isActive ? this.data.proxy.register(name, entity.api) : this.data.proxy.unregister(name, entity.api)
if (api && (name === 'solidity' || name === 'vyper')) {
isActive ? this.data.proxy.register(name, api) : this.data.proxy.unregister(name, api)
}
isActive ? this.store.activate(name) : this.store.deactivate(name)
if (!isActive) {
this.removeHiddenServices(entity)
this.removeHiddenServices(api)
}
}
getEntity (entityName) {
return this.store.getOne(entityName)
getEntity (apiName) {
return this.store.getOne(apiName)
}
addEntity (entity) {
this.store.add(entity.profile.name, entity)
}
// this function is only used for iframe plugins
resolveLocation (profile, domEl) {
if (profile.icon) {
this.event.emit('pluginNeedsLocation', profile, domEl)
} else {
this.hiddenServices[profile.name] = domEl
document.body.appendChild(domEl)
}
addEntity (api) {
this.store.add(api)
}
removeHiddenServices (profile) {
@ -78,13 +68,14 @@ export class RemixAppManager extends AppManagerApi {
notifications: {
'solidity': ['compilationFinished']
},
url: 'https://remix-pipeline.surge.sh',
description: ' - ',
url: 'https://pipeline-alpha.pipeos.one',
description: 'Visual IDE for contracts and dapps',
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAAAAACIM/FCAAAJU0lEQVR42u3ce1QUVRgA8EEemgoWvjkltIZlIJhCmmTmqVBJJSofaaWpYUUn08wHmkfR6GFWqKh51CDzLZkPKs2MNAkFVAgxw007WZklaWqsLutt0b13LusM7LDfN+x07vcXe3dmvvkxszN35rszEvmfhCQgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgHgspXbcsdVoybKQsXrPfqivk0qoBJqSImV+uG6Tyq34mxOi+7oI+EMt4E3L0P60H5PRjJvSIOYwPsejgsO9ev6FDXjXpEgMvIUM2mXSKibgQWz+9IJ3OoEJWcbtx+t4yM2gcK8jk/k9TUSHyefClUjNClM0NpQnCLiJCfmCOJDNSLGQpNiJCVgQ1b+YfcFPrdt1KsSDmwSFtAgP8AwLbjMeCVGYOu1FyRPvEfBzG4WlRXo4cfvGv/4sByYmQ+Gg8/hiC4/1W1ZIEZbgNWb62+ueKV/0kp+i2FZqR19/LOcnQ392EJEpdc7iP1u7S9XHDZlhH4a0KSW7+xV2IJD28hW2PxyWlaLEF0pEbrpgk6qTbEEnq8uW1TyMl5QiE/MmHqyTpesl9iH2rVB3NCyS1GAHnmKua5C0IiH2rZFdGqOaQ1kM58puo5vA/DQKxb1v5zzZPb8zb+XpnuSEGCjJWXqZpXPa+7EkmuWEyEESO4Y4z1AYf1vQpEOQGtsQZtmsd7VT5nHW+zpATCUqO/uxnt9qXts2EcexiSeRe71TWllU3iC2zZwMlxy1cjyGZNibAQF6jy+vDrUgf2jivTpC9d6n86DrlyROdpo0dYSCD6PKWc2vyFW0cXQdI5cvqhygp7JMrdLrmjqYmMJBuNMXxaqvniPvrABki1Rg9yhzTRdIW4LOhVQkSpR0ytWZH30I6IT06+sJA2O5crgS5RzNkd4OaGJE75SnphK1hIA/SHFwK8hdtHKgVUtG+Jke0TZ5yK228DwbyAl3eFG51ZtLGaVohu2vesbayCS+E0rZxMJBldHlNyliSMtZr+UArZFTNEP9sx3R/RrG2dBhIqXwBUuRIcvRm1nZEI8TWku2UwxUlvslV/bfKTWx7SAElQF2ULmyRzdOrTryW9ObyoVJrX+t7OmfTP7i+Vv+PmsofmsSOGsxfyE2E6jRm8pc5A0cP5C/et2uFfEznfEruNEblEpKiurO1OwIFkY9b10W85uuR2XI/wQF5ZFvVkcpiUsuxEsxhzm2kkqNxkWbIeDrvrmuQe3IdXxxQueoZYwaM91Qgq4lmyHN03n1VkDi5Y0W+aKiUYuiPkBBzip9SkneIdgj7YRSQxN77qp9hWihsD1iH/bLd97ocfiuIe5ATzlOefNT5d77EDB6bna8guhUSNyEK8W0MlyHgtSNmhDi2gD+uBK+/QhAghOQ/3NDXS5K8/Rr3KjEjxY+jmvrZk3j5+EVuryQEB0J2seLFKizInjtpivmEoEEudqJZInbjOArj2f+qBBFCZrI0HaYj1HrKFnRmCR4nmJDjoXK1MmxQ0ouw8WQXrqz7GSqEvK1XeTqe4EJ+7ayPo30uMoQcDtMFsplgQ0iWHo6ZBB9CcqKwGbevrNQDQn7ui+u4O4cQXSDk8sYBeIyeS88RvSD2KJ7RB0UxYWclIXpCqvorP31fDBtlZ0ldwx2IR8X/FMIKkvsNDmE1nhyDQ9jd7wyDQz6kkLEGhxSxO7x/GRtibUYlE/4xNISrj7Sds99QFifIF5IHh1ejkIcmbbjkEsQS7MkSx83HPBcgZI3nQySv0Rdqh5DpBpBIwUdqh9juMoKk9alaIeTcy0aQ3Hu5Vgghac0MIHnDBQgp/6hHA0+HND7uAsQeJz4eFx8T1dWjIiKEL80tdA3imWH9ZoI3hfQxMsQeKynE94KxIYSNX8oxOGQyhSwxOGQ9hUw3OGQPhbxkcEg+hSQaHFIgIMpR/u2m1bCxZsv+8zpDbCULHkKpKIQmrPxJR0jpMMwqT9IpvSAZobj1Kn6UNCLk7BD8SugUKz7E9rweNd0UfEiSLkV202JsyAZ9HKbQIlxIRS+dIKancCEZXKrBs9Lmg0Zachy3+AJMiLUnyzPmM4xRZxvk8v2zmJA9LM0InOFz5h9iaYbbziBCUoPbtmzRsm2wqfchrBGNOeGmdm1atmgVFJKFBvklNYRe9yfsxYJ8Hk2fOe245CwK5NAr/Bj8hmOyMRirHvHmkgTOOQkPyfBxutPn/TY442iSt1OSZruBIValR5YGHYV1HIi5PofPHFCI7QnF268DQN+4UxSmmGSu88rk0W9e0A6ZpHIjeSwkpLdyDi+nt4HIZc6JmiE7VW+JAz7Pk6qWw6es+trMo1/M0wqxdFWFhIM9sZCnXpMZWX11Yml7llbIfG6hvVIWpfTiPs+AgnAPp3jHv7VoaiSXZC+/Nj+z5t+0QjqwWSOvFYbz5DLjrUCv3cmXKx+xV28/XMkKYi1DuZUp6UhbOxCNkINsgXG0Vm+JY21Ab3hho3nk91T8fTvbRAs/uBoLU0fKD3JKs7RC1tI5g+QHtMvZvysNBvIsXd598tDMQz411t52aIUk0TnXKumehIF0psv7zoWj/tWIJ1oh/eisxVxjMfv1w0DoY7l+/OvaanqUu+kxzZBoOm8F11hBGyNgILSIHKbYGVGIbaTuEAt/bqGNnWAg9LVa4S5C3iTaIX3pzPwjTyXAu1agY3GNXNq1AneQOkA878c+Ruk9p7VDWD24A3f4ZSfJd2Egz7BzlXz4LQ5QQPjHpim/eFbTCdFSvyfEpZnZJTa11awdcll+5rS3o4vCjshSO6CLqzy5izLE0UVhBxkpwZVLDa2dxlmLZunQaUxX7zS6Afn3TtWj4G2HoSC71Lvxrj1YabwLKzcgKm8/s/evzYARrXKpu5jAQWzD9Lj5EO7azQd3IMQap5DifuAXJRy4Q2G/SiKgEE+6QecmhBwcUO2W6QPbzAjxYTRPCXjmJIGH1MNNbE2PI2oqK6TQssKDR7Eg+ayssI4QNIhHFHogINZoJkk8iOHYI5feniaIEI8ohoJAKmL0Kk8PJ6gQ/QYMFCJDPGAIBxDk4mg9HBMJOqT+hzlBQcjl97AHnq0hRA9IfQ8FBIQQ29fJWIMzZxcRoh+kKuptuCw0xNNCQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQATEY+M/DV/rjLxphEwAAAAASUVORK5CYII=',
prefferedLocation: 'mainPanel'
location: 'mainPanel'
}
const plugins = [{ profile: pipeline, api: new Plugin(pipeline, { resolveLocaton: (iframe) => { return this.resolveLocation(pipeline, iframe) } }) },
{ profile: vyper, api: new Plugin(vyper, { resolveLocaton: (iframe) => { return this.resolveLocation(vyper, iframe) } }) }]
return plugins
return [
new Plugin(pipeline),
new Plugin(vyper)
]
}
}

@ -7,349 +7,345 @@ var TxRunner = remixLib.execution.txRunner
var txHelper = remixLib.execution.txHelper
var EventManager = remixLib.EventManager
var executionContext = remixLib.execution.executionContext
import { ApiFactory } from 'remix-plugin'
function UniversalDApp (registry) {
this.event = new EventManager()
var self = this
self._deps = {
config: registry.get('config').api
}
self._txRunnerAPI = {
config: self._deps.config,
detectNetwork: (cb) => {
executionContext.detectNetwork(cb)
},
personalMode: () => {
return self._deps.config.get('settings/personal-mode')
module.exports = class UniversalDApp extends ApiFactory {
constructor (registry) {
super()
this.event = new EventManager()
this._deps = {
config: registry.get('config').api
}
this._txRunnerAPI = {
config: this._deps.config,
detectNetwork: (cb) => {
executionContext.detectNetwork(cb)
},
personalMode: () => {
return this._deps.config.get('settings/personal-mode')
}
}
this.txRunner = new TxRunner({}, this._txRunnerAPI)
this.accounts = {}
this.resetEnvironment()
executionContext.event.register('contextChanged', this.resetEnvironment.bind(this))
}
self.txRunner = new TxRunner({}, self._txRunnerAPI)
self.accounts = {}
self.resetEnvironment()
executionContext.event.register('contextChanged', this.resetEnvironment.bind(this))
}
UniversalDApp.prototype.profile = function () {
return {
name: 'udapp',
displayName: 'universal dapp',
methods: ['runTestTx', 'getAccounts', 'createVMAccount'],
description: 'service - run transaction and access account'
get profile () {
return {
name: 'udapp',
displayName: 'universal dapp',
methods: ['runTestTx', 'getAccounts', 'createVMAccount'],
description: 'service - run transaction and access account'
}
}
}
UniversalDApp.prototype.resetEnvironment = function () {
this.accounts = {}
if (executionContext.isVM()) {
this._addAccount('3cd7232cd6f3fc66a57a6bedc1a8ed6c228fff0a327e169c2bcc5e869ed49511', '0x56BC75E2D63100000')
this._addAccount('2ac6c190b09897cd8987869cc7b918cfea07ee82038d492abce033c75c1b1d0c', '0x56BC75E2D63100000')
this._addAccount('dae9801649ba2d95a21e688b56f77905e5667c44ce868ec83f82e838712a2c7a', '0x56BC75E2D63100000')
this._addAccount('d74aa6d18aa79a05f3473dd030a97d3305737cbc8337d940344345c1f6b72eea', '0x56BC75E2D63100000')
this._addAccount('71975fbf7fe448e004ac7ae54cad0a383c3906055a65468714156a07385e96ce', '0x56BC75E2D63100000')
executionContext.vm().stateManager.cache.flush(function () {})
}
// TODO: most params here can be refactored away in txRunner
this.txRunner = new TxRunner(this.accounts, {
// TODO: only used to check value of doNotShowTransactionConfirmationAgain property
config: this.config,
// TODO: to refactor, TxRunner already has access to executionContext
detectNetwork: (cb) => {
executionContext.detectNetwork(cb)
},
personalMode: () => {
return this.config.get('settings/personal-mode')
resetEnvironment () {
this.accounts = {}
if (executionContext.isVM()) {
this._addAccount('3cd7232cd6f3fc66a57a6bedc1a8ed6c228fff0a327e169c2bcc5e869ed49511', '0x56BC75E2D63100000')
this._addAccount('2ac6c190b09897cd8987869cc7b918cfea07ee82038d492abce033c75c1b1d0c', '0x56BC75E2D63100000')
this._addAccount('dae9801649ba2d95a21e688b56f77905e5667c44ce868ec83f82e838712a2c7a', '0x56BC75E2D63100000')
this._addAccount('d74aa6d18aa79a05f3473dd030a97d3305737cbc8337d940344345c1f6b72eea', '0x56BC75E2D63100000')
this._addAccount('71975fbf7fe448e004ac7ae54cad0a383c3906055a65468714156a07385e96ce', '0x56BC75E2D63100000')
executionContext.vm().stateManager.cache.flush(function () {})
}
})
this.txRunner.event.register('transactionBroadcasted', (txhash) => {
executionContext.detectNetwork((error, network) => {
if (error || !network) return
this.event.trigger('transactionBroadcasted', [txhash, network.name])
// TODO: most params here can be refactored away in txRunner
this.txRunner = new TxRunner(this.accounts, {
// TODO: only used to check value of doNotShowTransactionConfirmationAgain property
config: this.config,
// TODO: to refactor, TxRunner already has access to executionContext
detectNetwork: (cb) => {
executionContext.detectNetwork(cb)
},
personalMode: () => {
return this.config.get('settings/personal-mode')
}
})
})
}
UniversalDApp.prototype.resetAPI = function (transactionContextAPI) {
this.transactionContextAPI = transactionContextAPI
}
UniversalDApp.prototype.createVMAccount = function (privateKey, balance, cb) {
return new Promise((resolve, reject) => {
if (executionContext.getProvider() !== 'vm') return reject('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')
resolve('0x' + ethJSUtil.privateToAddress(privateKey).toString('hex'))
})
}
UniversalDApp.prototype.newAccount = function (password, passwordPromptCb, cb) {
if (!executionContext.isVM()) {
if (!this.config.get('settings/personal-mode')) {
return cb('Not running in personal mode')
}
passwordPromptCb((passphrase) => {
executionContext.web3().personal.newAccount(passphrase, cb)
this.txRunner.event.register('transactionBroadcasted', (txhash) => {
executionContext.detectNetwork((error, network) => {
if (error || !network) return
this.event.trigger('transactionBroadcasted', [txhash, network.name])
})
})
} else {
var privateKey
do {
privateKey = crypto.randomBytes(32)
} while (!ethJSUtil.isValidPrivate(privateKey))
this._addAccount(privateKey, '0x56BC75E2D63100000')
executionContext.vm().stateManager.cache.flush(function () {})
cb(null, '0x' + ethJSUtil.privateToAddress(privateKey).toString('hex'))
}
}
UniversalDApp.prototype._addAccount = function (privateKey, balance) {
var self = this
if (!executionContext.isVM()) {
throw new Error('_addAccount() cannot be called in non-VM mode')
resetAPI (transactionContextAPI) {
this.transactionContextAPI = transactionContextAPI
}
if (self.accounts) {
privateKey = Buffer.from(privateKey, 'hex')
var address = ethJSUtil.privateToAddress(privateKey)
// FIXME: we don't care about the callback, but we should still make this proper
executionContext.vm().stateManager.putAccountBalance(address, balance || '0xf00000000000000001', function cb () {})
self.accounts['0x' + address.toString('hex')] = { privateKey: privateKey, nonce: 0 }
createVMAccount (privateKey, balance, cb) {
return new Promise((resolve, reject) => {
if (executionContext.getProvider() !== 'vm') return reject('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')
resolve('0x' + ethJSUtil.privateToAddress(privateKey).toString('hex'))
})
}
}
// TODO should remove this cb
UniversalDApp.prototype.getAccounts = function (cb) {
var self = this
return new Promise((resolve, reject) => {
newAccount (password, passwordPromptCb, cb) {
if (!executionContext.isVM()) {
// Weirdness of web3: listAccounts() is sync, `getListAccounts()` is async
// See: https://github.com/ethereum/web3.js/issues/442
if (this._deps.config.get('settings/personal-mode')) {
return executionContext.web3().personal.getListAccounts((error, accounts) => {
if (cb) cb(error, accounts)
if (error) return reject(error)
resolve(accounts)
})
} else {
executionContext.web3().eth.getAccounts((error, accounts) => {
if (cb) cb(error, accounts)
if (error) return reject(error)
resolve(accounts)
})
if (!this.config.get('settings/personal-mode')) {
return cb('Not running in personal mode')
}
passwordPromptCb((passphrase) => {
executionContext.web3().personal.newAccount(passphrase, cb)
})
} else {
if (!self.accounts) {
if (cb) cb('No accounts?')
reject('No accounts?')
return
}
if (cb) cb(null, Object.keys(self.accounts))
resolve(Object.keys(self.accounts))
var privateKey
do {
privateKey = crypto.randomBytes(32)
} while (!ethJSUtil.isValidPrivate(privateKey))
this._addAccount(privateKey, '0x56BC75E2D63100000')
executionContext.vm().stateManager.cache.flush(function () {})
cb(null, '0x' + ethJSUtil.privateToAddress(privateKey).toString('hex'))
}
})
}
}
UniversalDApp.prototype.getBalance = function (address, cb) {
var self = this
_addAccount (privateKey, balance) {
if (!executionContext.isVM()) {
throw new Error('_addAccount() cannot be called in non-VM mode')
}
address = ethJSUtil.stripHexPrefix(address)
if (this.accounts) {
privateKey = Buffer.from(privateKey, 'hex')
const address = ethJSUtil.privateToAddress(privateKey)
if (!executionContext.isVM()) {
executionContext.web3().eth.getBalance(address, function (err, res) {
if (err) {
cb(err)
} else {
cb(null, res.toString(10))
}
})
} else {
if (!self.accounts) {
return cb('No accounts?')
// FIXME: we don't care about the callback, but we should still make this proper
executionContext.vm().stateManager.putAccountBalance(address, balance || '0xf00000000000000001', function cb () {})
this.accounts['0x' + address.toString('hex')] = { privateKey, nonce: 0 }
}
}
executionContext.vm().stateManager.getAccountBalance(Buffer.from(address, 'hex'), function (err, res) {
if (err) {
cb('Account not found')
getAccounts (cb) {
return new Promise((resolve, reject) => {
if (!executionContext.isVM()) {
// Weirdness of web3: listAccounts() is sync, `getListAccounts()` is async
// See: https://github.com/ethereum/web3.js/issues/442
if (this._deps.config.get('settings/personal-mode')) {
return executionContext.web3().personal.getListAccounts((error, accounts) => {
if (cb) cb(error, accounts)
if (error) return reject(error)
resolve(accounts)
})
} else {
executionContext.web3().eth.getAccounts((error, accounts) => {
if (cb) cb(error, accounts)
if (error) return reject(error)
resolve(accounts)
})
}
} else {
cb(null, new BN(res).toString(10))
if (!this.accounts) {
if (cb) cb('No accounts?')
reject('No accounts?')
return
}
if (cb) cb(null, Object.keys(this.accounts))
resolve(Object.keys(this.accounts))
}
})
}
}
UniversalDApp.prototype.getBalanceInEther = function (address, callback) {
var self = this
self.getBalance(address, (error, balance) => {
if (error) {
callback(error)
getBalance (address, cb) {
address = ethJSUtil.stripHexPrefix(address)
if (!executionContext.isVM()) {
executionContext.web3().eth.getBalance(address, (err, res) => {
if (err) {
cb(err)
} else {
cb(null, res.toString(10))
}
})
} else {
callback(null, executionContext.web3().fromWei(balance, 'ether'))
if (!this.accounts) {
return cb('No accounts?')
}
executionContext.vm().stateManager.getAccountBalance(Buffer.from(address, 'hex'), (err, res) => {
if (err) {
cb('Account not found')
} else {
cb(null, new BN(res).toString(10))
}
})
}
})
}
}
UniversalDApp.prototype.pendingTransactionsCount = function () {
return Object.keys(this.txRunner.pendingTxs).length
}
getBalanceInEther (address, callback) {
this.getBalance(address, (error, balance) => {
if (error) {
callback(error)
} else {
callback(null, executionContext.web3().fromWei(balance, 'ether'))
}
})
}
/**
* deploy the given contract
*
* @param {String} data - data to send with the transaction ( return of txFormat.buildData(...) ).
* @param {Function} callback - callback.
*/
UniversalDApp.prototype.createContract = function (data, confirmationCb, continueCb, promptCb, callback) {
this.runTx({data: data, useCall: false}, confirmationCb, continueCb, promptCb, (error, txResult) => {
// see universaldapp.js line 660 => 700 to check possible values of txResult (error case)
callback(error, txResult)
})
}
pendingTransactionsCount () {
return Object.keys(this.txRunner.pendingTxs).length
}
/**
* call the current given contract
*
* @param {String} to - address of the contract to call.
* @param {String} data - data to send with the transaction ( return of txFormat.buildData(...) ).
* @param {Object} funAbi - abi definition of the function to call.
* @param {Function} callback - callback.
*/
UniversalDApp.prototype.callFunction = function (to, data, funAbi, confirmationCb, continueCb, promptCb, callback) {
this.runTx({to: to, data: data, useCall: funAbi.constant}, confirmationCb, continueCb, promptCb, (error, txResult) => {
// see universaldapp.js line 660 => 700 to check possible values of txResult (error case)
callback(error, txResult)
})
}
/**
* deploy the given contract
*
* @param {String} data - data to send with the transaction ( return of txFormat.buildData(...) ).
* @param {Function} callback - callback.
*/
createContract (data, confirmationCb, continueCb, promptCb, callback) {
this.runTx({data: data, useCall: false}, confirmationCb, continueCb, promptCb, (error, txResult) => {
// see universaldapp.js line 660 => 700 to check possible values of txResult (error case)
callback(error, txResult)
})
}
UniversalDApp.prototype.context = function () {
return (executionContext.isVM() ? 'memory' : 'blockchain')
}
/**
* call the current given contract
*
* @param {String} to - address of the contract to call.
* @param {String} data - data to send with the transaction ( return of txFormat.buildData(...) ).
* @param {Object} funAbi - abi definition of the function to call.
* @param {Function} callback - callback.
*/
callFunction (to, data, funAbi, confirmationCb, continueCb, promptCb, callback) {
this.runTx({to: to, data: data, useCall: funAbi.constant}, confirmationCb, continueCb, promptCb, (error, txResult) => {
// see universaldapp.js line 660 => 700 to check possible values of txResult (error case)
callback(error, txResult)
})
}
UniversalDApp.prototype.getABI = function (contract) {
return txHelper.sortAbiFunction(contract.abi)
}
context () {
return (executionContext.isVM() ? 'memory' : 'blockchain')
}
UniversalDApp.prototype.getFallbackInterface = function (contractABI) {
return txHelper.getFallbackInterface(contractABI)
}
getABI (contract) {
return txHelper.sortAbiFunction(contract.abi)
}
UniversalDApp.prototype.getInputs = function (funABI) {
if (!funABI.inputs) {
return ''
getFallbackInterface (contractABI) {
return txHelper.getFallbackInterface(contractABI)
}
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.
*/
UniversalDApp.prototype.runTestTx = function (tx) {
return new Promise((resolve, reject) => {
executionContext.detectNetwork((error, network) => {
if (error) return reject(error)
if (network.name === 'Main' && network.id === '1') {
return reject(new Error('It is not allowed to make this action against mainnet'))
}
this.silentRunTx(tx, (error, result) => {
getInputs (funABI) {
if (!funABI.inputs) {
return ''
}
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.
*/
runTestTx (tx) {
return new Promise((resolve, reject) => {
executionContext.detectNetwork((error, network) => {
if (error) return reject(error)
resolve({
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
if (network.name === 'Main' && network.id === '1') {
return reject(new Error('It is not allowed to make this action against mainnet'))
}
this.silentRunTx(tx, (error, result) => {
if (error) return reject(error)
resolve({
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!
*
* @param {Object} tx - transaction.
* @param {Function} callback - callback.
*/
UniversalDApp.prototype.silentRunTx = function (tx, cb) {
if (!executionContext.isVM()) return cb('Cannot silently send transaction through a web3 provider')
this.txRunner.rawRun(
tx,
(network, tx, gasEstimation, continueTxExecution, cancelCb) => { continueTxExecution() },
(error, continueTxExecution, cancelCb) => { if (error) { cb(error) } else { continueTxExecution() } },
(okCb, cancelCb) => { okCb() },
cb)
}
/**
* This function send a tx without alerting the user (if mainnet or if gas estimation too high).
* SHOULD BE TAKEN CAREFULLY!
*
* @param {Object} tx - transaction.
* @param {Function} callback - callback.
*/
silentRunTx (tx, cb) {
if (!executionContext.isVM()) return cb('Cannot silently send transaction through a web3 provider')
this.txRunner.rawRun(
tx,
(network, tx, gasEstimation, continueTxExecution, cancelCb) => { continueTxExecution() },
(error, continueTxExecution, cancelCb) => { if (error) { cb(error) } else { continueTxExecution() } },
(okCb, cancelCb) => { okCb() },
cb
)
}
UniversalDApp.prototype.runTx = function (args, confirmationCb, continueCb, promptCb, cb) {
const self = this
async.waterfall([
function getGasLimit (next) {
if (self.transactionContextAPI.getGasLimit) {
return self.transactionContextAPI.getGasLimit(next)
}
next(null, 3000000)
},
function queryValue (gasLimit, next) {
if (args.value) {
return next(null, args.value, gasLimit)
}
if (args.useCall || !self.transactionContextAPI.getValue) {
return next(null, 0, gasLimit)
}
self.transactionContextAPI.getValue(function (err, value) {
next(err, value, gasLimit)
})
},
function getAccount (value, gasLimit, next) {
if (args.from) {
return next(null, args.from, value, gasLimit)
}
if (self.transactionContextAPI.getAddress) {
return self.transactionContextAPI.getAddress(function (err, address) {
next(err, address, value, gasLimit)
runTx (args, confirmationCb, continueCb, promptCb, cb) {
const self = this
async.waterfall([
function getGasLimit (next) {
if (self.transactionContextAPI.getGasLimit) {
return self.transactionContextAPI.getGasLimit(next)
}
next(null, 3000000)
},
function queryValue (gasLimit, next) {
if (args.value) {
return next(null, args.value, gasLimit)
}
if (args.useCall || !self.transactionContextAPI.getValue) {
return next(null, 0, gasLimit)
}
self.transactionContextAPI.getValue(function (err, value) {
next(err, value, gasLimit)
})
}
self.getAccounts(function (err, accounts) {
let address = accounts[0]
},
function getAccount (value, gasLimit, next) {
if (args.from) {
return next(null, args.from, value, gasLimit)
}
if (self.transactionContextAPI.getAddress) {
return self.transactionContextAPI.getAddress(function (err, address) {
next(err, address, value, gasLimit)
})
}
self.getAccounts(function (err, accounts) {
let address = accounts[0]
if (err) return next(err)
if (!address) return next('No accounts available')
if (executionContext.isVM() && !self.accounts[address]) {
return next('Invalid account selected')
if (err) return next(err)
if (!address) return next('No accounts available')
if (executionContext.isVM() && !self.accounts[address]) {
return next('Invalid account selected')
}
next(null, address, value, gasLimit)
})
},
function runTransaction (fromAddress, value, gasLimit, next) {
var tx = { to: args.to, data: args.data.dataHex, useCall: args.useCall, from: fromAddress, value: value, gasLimit: gasLimit, timestamp: args.data.timestamp }
var payLoad = { funAbi: args.data.funAbi, funArgs: args.data.funArgs, contractBytecode: args.data.contractBytecode, contractName: args.data.contractName, contractABI: args.data.contractABI, linkReferences: args.data.linkReferences }
var timestamp = Date.now()
if (tx.timestamp) {
timestamp = tx.timestamp
}
next(null, address, value, gasLimit)
})
},
function runTransaction (fromAddress, value, gasLimit, next) {
var tx = { to: args.to, data: args.data.dataHex, useCall: args.useCall, from: fromAddress, value: value, gasLimit: gasLimit, timestamp: args.data.timestamp }
var payLoad = { funAbi: args.data.funAbi, funArgs: args.data.funArgs, contractBytecode: args.data.contractBytecode, contractName: args.data.contractName, contractABI: args.data.contractABI, linkReferences: args.data.linkReferences }
var timestamp = Date.now()
if (tx.timestamp) {
timestamp = tx.timestamp
}
self.event.trigger('initiatingTransaction', [timestamp, tx, payLoad])
self.txRunner.rawRun(tx, confirmationCb, continueCb, promptCb,
function (error, result) {
let eventName = (tx.useCall ? 'callExecuted' : 'transactionExecuted')
self.event.trigger(eventName, [error, tx.from, tx.to, tx.data, tx.useCall, result, timestamp, payLoad])
self.event.trigger('initiatingTransaction', [timestamp, tx, payLoad])
self.txRunner.rawRun(tx, confirmationCb, continueCb, promptCb,
function (error, result) {
let eventName = (tx.useCall ? 'callExecuted' : 'transactionExecuted')
self.event.trigger(eventName, [error, tx.from, tx.to, tx.data, tx.useCall, result, timestamp, payLoad])
if (error && (typeof (error) !== 'string')) {
if (error.message) error = error.message
else {
try { error = 'error: ' + JSON.stringify(error) } catch (e) {}
if (error && (typeof (error) !== 'string')) {
if (error.message) error = error.message
else {
try { error = 'error: ' + JSON.stringify(error) } catch (e) {}
}
}
next(error, result)
}
next(error, result)
}
)
}
], cb)
)
}
], cb)
}
}
module.exports = UniversalDApp

Loading…
Cancel
Save