Merge pull request #1656 from ethereum/swap_it

Swap it
pull/1/head
yann300 6 years ago committed by GitHub
commit 63f7e02936
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .babelrc
  2. 6
      .circleci/config.yml
  3. 4
      assets/css/font-awesome.min.css
  4. BIN
      assets/fonts/FontAwesome.otf
  5. BIN
      assets/fonts/fontawesome-webfont.eot
  6. 2671
      assets/fonts/fontawesome-webfont.svg
  7. BIN
      assets/fonts/fontawesome-webfont.ttf
  8. BIN
      assets/fonts/fontawesome-webfont.woff
  9. BIN
      assets/fonts/fontawesome-webfont.woff2
  10. 11
      bin/remix-ide
  11. 2
      ci/deploy_from_travis_remix-alpha.sh
  12. 2
      ci/deploy_from_travis_remix-live.sh
  13. 2
      extensions/etherscan-general/index.js
  14. 3
      index.html
  15. 39
      package.json
  16. 354
      src/app.js
  17. 36
      src/app/compiler/compiler-imports.js
  18. 155
      src/app/components/local-plugin.js
  19. 191
      src/app/components/plugin-manager-component.js
  20. 36
      src/app/components/plugin-manager-proxy.js
  21. 43
      src/app/components/swap-panel-api.js
  22. 102
      src/app/components/swap-panel-component.js
  23. 20
      src/app/components/vertical-icons-api.js
  24. 428
      src/app/components/vertical-icons-component.js
  25. 96
      src/app/debugger/debuggerUI.js
  26. 26
      src/app/debugger/debuggerUI/ButtonNavigator.js
  27. 18
      src/app/debugger/debuggerUI/TxBrowser.js
  28. 10
      src/app/debugger/debuggerUI/VmDebugger.js
  29. 6
      src/app/debugger/debuggerUI/vmDebugger/CodeListView.js
  30. 41
      src/app/debugger/debuggerUI/vmDebugger/DropdownPanel.js
  31. 39
      src/app/editor/SourceHighlighters.js
  32. 114
      src/app/editor/contextView.js
  33. 122
      src/app/editor/contextualListener.js
  34. 613
      src/app/editor/editor.js
  35. 36
      src/app/editor/sourceHighlighter.js
  36. 21
      src/app/editor/styles/contextView-styles.js
  37. 8
      src/app/execution/confirmDialog.js
  38. 76
      src/app/execution/txLogger.js
  39. 96
      src/app/files/browser-files-tree.js
  40. 370
      src/app/files/file-explorer.js
  41. 251
      src/app/files/fileManager.js
  42. 106
      src/app/files/remixd-handle.js
  43. 24
      src/app/files/styles/file-explorer-styles.js
  44. 281
      src/app/panels/editor-panel.js
  45. 477
      src/app/panels/file-panel.js
  46. 164
      src/app/panels/righthand-panel.js
  47. 179
      src/app/panels/styles/editor-panel-styles.js
  48. 50
      src/app/panels/styles/file-panel-styles.js
  49. 124
      src/app/panels/styles/terminal-styles.js
  50. 177
      src/app/panels/tab-proxy.js
  51. 271
      src/app/panels/terminal.js
  52. 94
      src/app/plugin/bundle.js
  53. 55
      src/app/plugin/index.js
  54. 71
      src/app/plugin/package.json
  55. 53
      src/app/plugin/plugin.md
  56. 150
      src/app/plugin/pluginAPI.js
  57. 179
      src/app/plugin/pluginManager.js
  58. 38
      src/app/plugin/plugins.js
  59. 74
      src/app/staticanalysis/staticAnalysisView.js
  60. 19
      src/app/staticanalysis/styles/staticAnalysisView-styles.js
  61. 37
      src/app/tabs/analysis-tab.js
  62. 977
      src/app/tabs/compile-tab.js
  63. 105
      src/app/tabs/compileTab/compileTab.js
  64. 267
      src/app/tabs/compileTab/compilerContainer.js
  65. 17
      src/app/tabs/debugger-tab.js
  66. 66
      src/app/tabs/network-module.js
  67. 53
      src/app/tabs/run-tab.js
  68. 24
      src/app/tabs/runTab/contractDropdown.js
  69. 4
      src/app/tabs/runTab/model/dropdownlogic.js
  70. 8
      src/app/tabs/runTab/recorder.js
  71. 91
      src/app/tabs/runTab/settings.js
  72. 383
      src/app/tabs/settings-tab.js
  73. 2
      src/app/tabs/styles/analysis-tab-styles.js
  74. 219
      src/app/tabs/styles/compile-tab-styles.js
  75. 2
      src/app/tabs/styles/debugger-tab-styles.js
  76. 100
      src/app/tabs/styles/run-tab-styles.js
  77. 69
      src/app/tabs/styles/settings-tab-styles.js
  78. 4
      src/app/tabs/styles/support-tab-styles.js
  79. 16
      src/app/tabs/styles/test-tab-styles.js
  80. 77
      src/app/tabs/support-tab.js
  81. 93
      src/app/tabs/tabbed-menu.js
  82. 360
      src/app/tabs/test-tab.js
  83. 85
      src/app/tabs/testTab/testTab.js
  84. 63
      src/app/tabs/theme-module.js
  85. 7
      src/app/ui/TreeView.js
  86. 208
      src/app/ui/auto-complete-popup.js
  87. 25
      src/app/ui/card.js
  88. 44
      src/app/ui/contextMenu.js
  89. 7
      src/app/ui/copy-to-clipboard.js
  90. 15
      src/app/ui/draggableContent.js
  91. 18
      src/app/ui/dropdown.js
  92. 248
      src/app/ui/landing-page/landing-page.js
  93. 70
      src/app/ui/landing-page/section.js
  94. 44
      src/app/ui/landing-page/workspace.js
  95. 2
      src/app/ui/modal-dialog-custom.js
  96. 103
      src/app/ui/modaldialog.js
  97. 12
      src/app/ui/renderer.js
  98. 804
      src/app/ui/styles-guide/style-guide.js
  99. 796
      src/app/ui/styles-guide/styleGuideDark.js
  100. 36
      src/app/ui/styles-guide/theme-chooser.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -7,6 +7,7 @@
"transform-es2015-classes",
"transform-es2015-computed-properties",
"transform-es2015-destructuring",
"transform-object-rest-spread",
"transform-es2015-duplicate-keys",
"transform-es2015-for-of",
"transform-es2015-function-name",

@ -19,17 +19,17 @@ jobs:
- ENCRYPTION_LABEL3: "1b1c118ea62d"
- COMMIT_AUTHOR_EMAIL: "chris@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "assets background.js build icon.png index.html manifest.json README.md soljson.js"
- FILES_TO_PACKAGE: "assets background.js build icon.png index.html manifest.json README.md soljson.js package.json"
working_directory: ~/repo
steps:
- checkout
- restore_cache:
keys:
- dep-bundle-27-{{ checksum "package.json" }}
- dep-bundle-29-{{ checksum "package.json" }}
- run: npm install
- save_cache:
key: dep-bundle-27-{{ checksum "package.json" }}
key: dep-bundle-29-{{ checksum "package.json" }}
paths:
- ~/repo/node_modules
- run: npm run lint && npm run test && npm run make-mock-compiler && npm run build

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 434 KiB

@ -1,19 +1,20 @@
#!/usr/bin/env node
var path = require('path')
var httpServer = require('http-server')
var remixd = require('remixd')
var server = httpServer.createServer({
root: __dirname + '/../'
root: path.join(__dirname, '/../')
})
var folder = process.argv.length > 2 ? process.argv[2] : process.cwd()
server.listen(8080, '127.0.0.1', function () {})
var router = new remixd.Router(65520, remixd.services.sharedFolder, { remixIdeUrl: 'http://localhost:8080' }, (webSocket) => {
remixd.services.sharedFolder.setWebSocket(webSocket)
remixd.services.sharedFolder.setupNotifications(folder)
remixd.services.sharedFolder.sharedFolder(folder, false)
})
remixd.services.sharedFolder.setWebSocket(webSocket)
remixd.services.sharedFolder.setupNotifications(folder)
remixd.services.sharedFolder.sharedFolder(folder, false)
})
router.start()

@ -7,7 +7,7 @@ SHA=`git rev-parse --short --verify HEAD`
git config user.name "$COMMIT_AUTHOR"
git config user.email "$COMMIT_AUTHOR_EMAIL"
git checkout --orphan gh-pages
git rm --cached -r .
git rm --cached -r -f .
echo "# Automatic build" > README.md
echo "Built website from \`$SHA\`. See https://github.com/ethereum/remix-ide/ for details." >> README.md
echo "To use an offline copy, download \`remix-$SHA.zip\`." >> README.md

@ -7,7 +7,7 @@ SHA=`git rev-parse --short --verify HEAD`
git config user.name "$COMMIT_AUTHOR"
git config user.email "$COMMIT_AUTHOR_EMAIL"
git checkout --orphan gh-pages
git rm --cached -r .
git rm --cached -r -f .
echo "# Automatic build" > README.md
echo "Built website from \`$SHA\`. See https://github.com/ethereum/remix-ide/ for details." >> README.md
echo "To use an offline copy, download \`remix-$SHA.zip\`." >> README.md

@ -40,7 +40,7 @@ function load () {
}
})
setInterval(function () {
remix.call('app', 'detectNetWork', [], function (error, result) {
remix.call('network', 'detectNetWork', [], function (error, result) {
if (error) console.log(error)
if (network.innerHTML !== result[0].name + ' - ' + result[0].id) {
currentNetWork = result[0].name

@ -28,8 +28,9 @@
-->
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<title>Remix - Solidity IDE</title>
<link rel="stylesheet" id="theme-link"/>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
<link rel="stylesheet" href="assets/css/pygment_trac.css">
<link rel="stylesheet" href="assets/css/font-awesome.min.css">
<link rel="icon" type="x-icon" href="icon.png">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
</head>

@ -1,12 +1,16 @@
{
"name": "remix-ide",
"version": "v0.7.5",
"version": "v0.8.0-alpha+002",
"description": "Minimalistic browser-based Solidity IDE",
"devDependencies": {
"@fortawesome/fontawesome-free": "^5.8.1",
"@resolver-engine/imports": "^0.3.0",
"ace-mode-solidity": "^0.1.0",
"async": "^2.1.2",
"babel-eslint": "^7.1.1",
"babel-plugin-transform-modern-regexp": "0.0.6",
"babel-plugin-transform-object-assign": "^6.22.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-yo-yoify": "^0.3.3",
"babel-polyfill": "^6.22.0",
"babel-preset-env": "^1.6.1",
@ -21,7 +25,8 @@
"csjs-inject": "^1.0.1",
"csslint": "^1.0.2",
"deep-equal": "^1.0.1",
"ethereumjs-util": "^6.1.0",
"ethereumjs-util": "^5.1.2",
"events": "^3.0.0",
"execr": "^1.0.1",
"exorcist": "^0.4.0",
"fast-async": "6.3.1",
@ -38,11 +43,12 @@
"npm-link-local": "^1.1.0",
"npm-run-all": "^4.0.2",
"onchange": "^3.2.1",
"remix-debug": "0.3.1",
"remix-analyzer": "0.3.1",
"remix-lib": "0.4.1",
"remix-solidity": "0.3.1",
"remix-tests": "0.1.1",
"remix-analyzer": "0.3.5",
"remix-debug": "0.3.5",
"remix-lib": "0.4.5",
"remix-solidity": "0.3.5",
"remix-tabs": "^1.0.0",
"remix-tests": "0.1.6",
"remixd": "0.1.8-alpha.6",
"request": "^2.83.0",
"rimraf": "^2.6.1",
@ -60,7 +66,8 @@
"yo-yoify": "^3.7.3"
},
"dependencies": {
"http-server": "0.9.0",
"http-server-legacy": "latest",
"remix-plugin": "0.0.2-alpha.6",
"remixd": "0.1.8-alpha.6"
},
"repository": {
@ -105,7 +112,8 @@
"transform-es2015-spread",
"transform-es2015-parameters",
"transform-es2015-destructuring",
"transform-es2015-block-scoping"
"transform-es2015-block-scoping",
"transform-modern-regexp"
]
},
"browserify": {
@ -146,7 +154,7 @@
"remix-ide": "./bin/remix-ide"
},
"scripts": {
"setupremix": "npm run linkremixlib && npm run linkremixsolidity && npm run linkremixsimulator && npm run linkremixtests",
"setupremix": "npm run linkremixdebug && npm run linkremixlib && npm run linkremixsolidity && npm run linkremixsimulator && npm run linkremixtests",
"pullremix": "git clone https://github.com/ethereum/remix",
"linkremixlib": "cd node_modules && rm -rf remix-lib && ln -s ../../remix/remix-lib remix-lib && cd ..",
"linkremixsolidity": "cd node_modules && rm -rf remix-solidity && ln -s ../../remix/remix-solidity remix-solidity && cd ..",
@ -171,15 +179,16 @@
"nightwatch_remote_debugger_parallel": "nightwatch --config nightwatch_debugger.js --env safari,chrome,default",
"onchange": "onchange build/app.js -- npm-run-all lint",
"prepublish": "mkdirp build; npm-run-all -ls downloadsolc_root build",
"remixd": "./node_modules/remixd/bin/remixd -s ./contracts --remix-ide http://127.0.0.1:8080",
"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_debugger": "execr --silent http-server src/app/debugger/remix-debugger",
"serve": "npx http-server-legacy .",
"serve_debugger": "npx http-server-legacy src/app/debugger/remix-debugger",
"sourcemap": "exorcist --root ../ build/app.js.map > build/app.js",
"start": "npm-run-all -lpr serve watch onchange remixd",
"test": "npm run csslint; standard && node test/index.js",
"test": "csslint && standard && node test/index.js",
"test-browser": "npm-run-all -lpr selenium downloadsolc_root make-mock-compiler serve browsertest",
"watch": "watchify src/index.js -dv -p browserify-reload -o build/app.js --exclude solc"
"watch": "watchify src/index.js -dv -p browserify-reload -o build/app.js --exclude solc",
"reinstall": "rm ./node-modules/ -rf; rm package-lock.json; rm ./build/ -rf; npm install; npm run build"
}
}

@ -6,8 +6,6 @@ var yo = require('yo-yo')
var async = require('async')
var request = require('request')
var remixLib = require('remix-lib')
var EventManager = require('./lib/events')
var registry = require('./global/registry')
var UniversalDApp = require('./universal-dapp.js')
var UniversalDAppUI = require('./universal-dapp-ui.js')
@ -18,14 +16,11 @@ var GistHandler = require('./lib/gist-handler')
var helper = require('./lib/helper')
var Storage = remixLib.Storage
var Browserfiles = require('./app/files/browser-files')
var BrowserfilesTree = require('./app/files/browser-files-tree')
var SharedFolder = require('./app/files/shared-folder')
var Config = require('./config')
var Renderer = require('./app/ui/renderer')
var executionContext = require('./execution-context')
var FilePanel = require('./app/panels/file-panel')
var EditorPanel = require('./app/panels/editor-panel')
var RighthandPanel = require('./app/panels/righthand-panel')
var examples = require('./app/editor/example-contracts')
var modalDialogCustom = require('./app/ui/modal-dialog-custom')
var TxLogger = require('./app/execution/txLogger')
@ -37,29 +32,36 @@ var NotPersistedExplorer = require('./app/files/NotPersistedExplorer')
var toolTip = require('./app/ui/tooltip')
var TransactionReceiptResolver = require('./transactionReceiptResolver')
const CompilerAbstract = require('./app/compiler/compiler-abstract')
const PluginManager = require('./app/plugin/pluginManager')
const PluginManagerComponent = require('./app/components/plugin-manager-component')
const VerticalIconsComponent = require('./app/components/vertical-icons-component')
const VerticalIconsApi = require('./app/components/vertical-icons-api')
const SwapPanelComponent = require('./app/components/swap-panel-component')
const SwapPanelApi = require('./app/components/swap-panel-api')
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')
var styleGuide = require('./app/ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
import PanelsResize from './lib/panels-resize'
import { EntityStore } from './lib/store'
import { RemixAppManager } from './remixAppManager'
import { LandingPage } from './app/ui/landing-page/landing-page'
import framingService from './framingService'
import { ThemeModule } from './app/tabs/theme-module'
import { NetworkModule } from './app/tabs/network-module'
var css = csjs`
html { box-sizing: border-box; }
*, *:before, *:after { box-sizing: inherit; }
body {
font: 14px/1.5 Lato, "Helvetica Neue", Helvetica, Arial, sans-serif;
margin : 0;
padding : 0;
font-size : 12px;
color : ${styles.leftPanel.text_Primary};
font-weight : normal;
/* font: 14px/1.5 Lato, "Helvetica Neue", Helvetica, Arial, sans-serif; */
font-size : .8rem;
}
pre {
overflow-x: auto;
@ -70,8 +72,7 @@ var css = csjs`
height : 100vh;
overflow : hidden;
}
.centerpanel {
background-color : ${styles.colors.transparent};
.mainpanel {
display : flex;
flex-direction : column;
position : absolute;
@ -79,8 +80,7 @@ var css = csjs`
bottom : 0;
overflow : hidden;
}
.leftpanel {
background-color : ${styles.leftPanel.backgroundColor_Panel};
.iconpanel {
display : flex;
flex-direction : column;
position : absolute;
@ -88,26 +88,29 @@ var css = csjs`
bottom : 0;
left : 0;
overflow : hidden;
width : 50px;
user-select : none;
/* border-right : 1px solid var(--primary); */
}
.rightpanel {
background-color : ${styles.rightPanel.backgroundColor_Panel};
.swappanel {
display : flex;
flex-direction : column;
position : absolute;
top : 0;
right : 0;
left : 50px;
bottom : 0;
overflow : hidden;
overflow-y : auto;
}
.highlightcode {
position:absolute;
z-index:20;
background-color: ${styles.editor.backgroundColor_DebuggerMode};
background-color: var(--info);
}
.highlightcode_fullLine {
position:absolute;
z-index:20;
background-color: ${styles.editor.backgroundColor_DebuggerMode};
background-color: var(--info);
opacity: 0.5;
}
`
@ -115,30 +118,23 @@ var css = csjs`
class App {
constructor (api = {}, events = {}, opts = {}) {
var self = this
this.event = new EventManager()
self._components = {}
registry.put({api: self, name: 'app'})
var fileStorage = new Storage('sol:')
registry.put({api: fileStorage, name: 'fileStorage'})
var configStorage = new Storage('config:')
var configStorage = new Storage('config-v0.8:')
registry.put({api: configStorage, name: 'configStorage'})
self._components.config = new Config(fileStorage)
self._components.config = new Config(configStorage)
registry.put({api: self._components.config, name: 'config'})
executionContext.init(self._components.config)
executionContext.listenOnLastBlock()
self._components.gistHandler = new GistHandler()
self._components.filesProviders = {}
self._components.filesProviders['browser'] = new Browserfiles(fileStorage)
self._components.filesProviders['config'] = new BrowserfilesTree('config', configStorage)
self._components.filesProviders['config'].init()
registry.put({api: self._components.filesProviders['browser'], name: 'fileproviders/browser'})
registry.put({api: self._components.filesProviders['config'], name: 'fileproviders/config'})
var remixd = new Remixd(65520)
registry.put({api: remixd, name: 'remixd'})
@ -161,81 +157,47 @@ class App {
registry.put({api: self._components.filesProviders, name: 'fileproviders'})
self._view = {}
self.data = {
_layout: {
right: {
offset: self._components.config.get('right-offset') || 400,
show: true
}, // @TODO: adapt sizes proportionally to browser window size
left: {
offset: self._components.config.get('left-offset') || 200,
show: true
}
}
}
}
_adjustLayout (direction, delta) {
var self = this
var layout = self.data._layout[direction]
if (layout) {
if (delta === undefined) {
layout.show = !layout.show
if (layout.show) delta = layout.offset
else delta = 0
} else {
self._components.config.set(`${direction}-offset`, delta)
layout.offset = delta
}
}
if (direction === 'left') {
self._view.leftpanel.style.width = delta + 'px'
self._view.centerpanel.style.left = delta + 'px'
}
if (direction === 'right') {
self._view.rightpanel.style.width = delta + 'px'
self._view.centerpanel.style.right = delta + 'px'
}
}
init () {
var self = this
self._components.resizeFeature = new PanelsResize('#swap-panel', '#editor-container', { 'minWidth': 300, x: 450 })
run.apply(self)
}
render () {
var self = this
if (self._view.el) return self._view.el
self._view.leftpanel = yo`
<div id="filepanel" class=${css.leftpanel}>
${''}
// not resizable
self._view.iconpanel = yo`
<div id="icon-panel" class="${css.iconpanel} bg-light">
${''}
</div>
`
self._view.centerpanel = yo`
<div id="editor-container" class=${css.centerpanel}>
// center panel, resizable
self._view.swappanel = yo`
<div id="swap-panel" class=${css.swappanel}>
${''}
</div>
`
self._view.rightpanel = yo`
<div class=${css.rightpanel}>
// handle the editor + terminal
self._view.mainpanel = yo`
<div id="editor-container" class=${css.mainpanel}>
${''}
</div>
`
self._view.el = yo`
<div class=${css.browsersolidity}>
${self._view.leftpanel}
${self._view.centerpanel}
${self._view.rightpanel}
${self._view.iconpanel}
${self._view.swappanel}
${self._view.mainpanel}
</div>
`
// INIT
self._adjustLayout('left', self.data._layout.left.offset)
self._adjustLayout('right', self.data._layout.right.offset)
return self._view.el
}
startdebugging (txHash) {
const self = this
self.event.trigger('debuggingRequested', [])
self._components.righthandpanel.debugger().debug(txHash)
}
loadFromGist (params) {
const self = this
return self._components.gistHandler.handleLoad(params, function (gistId) {
@ -314,22 +276,22 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
registry.put({api: self._components.compilersArtefacts, name: 'compilersartefacts'})
// ----------------- UniversalDApp -----------------
var udapp = new UniversalDApp(registry)
const udapp = new UniversalDApp(registry)
// TODO: to remove when possible
registry.put({api: udapp, name: 'udapp'})
udapp.event.register('transactionBroadcasted', (txhash, networkName) => {
var txLink = executionContext.txDetailsLink(networkName, txhash)
if (txLink) registry.get('logCallback').api.logCallback(yo`<a href="${txLink}" target="_blank">${txLink}</a>`)
if (txLink) registry.get('logCallback').api(yo`<a href="${txLink}" target="_blank">${txLink}</a>`)
})
var udappUI = new UniversalDAppUI(udapp, registry)
const udappUI = new UniversalDAppUI(udapp, registry)
// TODO: to remove when possible
registry.put({api: udappUI, name: 'udappUI'})
// ----------------- Tx listener -----------------
var transactionReceiptResolver = new TransactionReceiptResolver()
const transactionReceiptResolver = new TransactionReceiptResolver()
var txlistener = new Txlistener({
const txlistener = new Txlistener({
api: {
contracts: function () {
if (self._components.compilersArtefacts['__last']) return self._components.compilersArtefacts['__last'].getContracts()
@ -343,8 +305,9 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
udapp: udapp.event
}})
registry.put({api: txlistener, name: 'txlistener'})
udapp.startListening(txlistener)
var eventsDecoder = new EventsDecoder({
const eventsDecoder = new EventsDecoder({
api: {
resolveReceipt: function (tx, cb) {
transactionReceiptResolver.resolve(tx, cb)
@ -357,42 +320,132 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
// TODO: There are still a lot of dep between editorpanel and filemanager
// ----------------- editor panel ----------------------
self._components.editorpanel = new EditorPanel()
registry.put({ api: self._components.editorpanel, name: 'editorpanel' })
let appStore = new EntityStore('module', 'name')
const appManager = new RemixAppManager(appStore)
registry.put({api: appManager, name: 'appmanager'})
const mainPanelComponent = new SwapPanelComponent('mainPanel', appStore, appManager, { default: false, displayHeader: false })
// ----------------- file manager ----------------------------
self._components.fileManager = new FileManager()
var fileManager = self._components.fileManager
const fileManager = self._components.fileManager
registry.put({api: fileManager, name: 'filemanager'})
// ---------------- Plugin Manager -------------------------------
let pluginManager = new PluginManager(
self,
self._components.compilersArtefacts,
txlistener,
self._components.fileProviders,
self._components.fileManager,
udapp)
registry.put({api: pluginManager, name: 'pluginmanager'})
pluginManager.event.register('sendCompilationResult', (file, source, languageVersion, data) => {
// TODO check whether the tab is configured
let compiler = new CompilerAbstract(languageVersion, data, source)
self._components.compilersArtefacts['__last'] = compiler
})
// ----------------- Network ----------------------------
const networkModule = new NetworkModule()
registry.put({api: networkModule, name: 'network'})
// ----------------- theme module ----------------------------
const themeModule = new ThemeModule(registry)
registry.put({api: themeModule, name: 'themeModule'})
// ----------------- editor panel ----------------------
self._components.editorpanel = new EditorPanel(appStore, appManager, mainPanelComponent)
registry.put({ api: self._components.editorpanel, name: 'editorpanel' })
// ----------------- Renderer -----------------
const renderer = new Renderer()
registry.put({api: renderer, name: 'renderer'})
// ----------------- app manager ----------------------------
/*
TODOs:
- for each activated plugin,
an internal module (associated only with the plugin) should be created for accessing specific part of the UI. detail to be discussed
- the current API is not optimal. For instance methods of `app` only refers to `executionContext`, wich does not make really sense.
*/
// TODOs those are instanciated before hand. should be instanciated on demand
const pluginManagerComponent = new PluginManagerComponent()
const swapPanelComponent = new SwapPanelComponent('swapPanel', appStore, appManager, { default: true, displayHeader: true })
registry.put({api: appManager.proxy(), name: 'pluginmanager'})
pluginManagerComponent.setApp(appManager)
pluginManagerComponent.setStore(appStore)
// Need to have Home initialized before VerticalIconComponent render to access profile of it for icon
const landingPage = new LandingPage(appManager, appStore)
// ----------------- Vertical Icon ----------------------------
const verticalIconsComponent = new VerticalIconsComponent('swapPanel', appStore, landingPage.profile)
const swapPanelApi = new SwapPanelApi(swapPanelComponent, verticalIconsComponent) // eslint-disable-line
const mainPanelApi = new SwapPanelApi(mainPanelComponent, verticalIconsComponent) // eslint-disable-line
const verticalIconsApi = new VerticalIconsApi(verticalIconsComponent) // eslint-disable-line
registry.put({api: verticalIconsApi, name: 'verticalicon'})
self._components.editorpanel.init()
self._components.fileManager.init()
self._components.editorpanel.event.register('resize', direction => self._adjustLayout(direction))
self._view.centerpanel.appendChild(self._components.editorpanel.render())
self._view.mainpanel.appendChild(self._components.editorpanel.render())
self._view.iconpanel.appendChild(verticalIconsComponent.render())
self._view.swappanel.appendChild(swapPanelComponent.render())
let filePanel = new FilePanel()
registry.put({api: filePanel, name: 'filepanel'})
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,
registry.get('config').api,
registry.get('filemanager').api,
registry.get('editor').api,
registry.get('logCallback').api,
registry.get('filepanel').api,
registry.get('pluginmanager').api,
registry.get('compilersartefacts').api
)
let settings = new SettingsTab(
registry.get('config').api,
registry.get('editor').api,
appManager
)
let analysis = new AnalysisTab(registry)
let debug = new DebuggerTab()
let test = new TestTab(
registry.get('filemanager').api,
registry.get('filepanel').api,
compileTab
)
let sourceHighlighters = registry.get('editor').api.sourceHighlighters
appManager.init([
landingPage.api(),
udapp.api(),
fileManager.api(),
sourceHighlighters.api(),
filePanel.api(),
// { profile: support.profile(), api: support },
settings.api(),
pluginManagerComponent.api(),
networkModule.api(),
themeModule.api()
])
appManager.registerMany([
compileTab.api(),
run.api(),
debug.api(),
analysis.api(),
test.api(),
filePanel.remixdHandle.api(),
...appManager.plugins()
])
framingService.start(appStore, swapPanelApi, verticalIconsApi, mainPanelApi, this._components.resizeFeature)
// The event listener needs to be registered as early as possible, because the
// parent will send the message upon the "load" event.
var filesToLoad = null
var loadFilesCallback = function (files) { filesToLoad = files } // will be replaced later
let filesToLoad = null
let loadFilesCallback = function (files) { filesToLoad = files } // will be replaced later
window.addEventListener('message', function (ev) {
if (typeof ev.data === typeof [] && ev.data[0] === 'loadFiles') {
@ -410,40 +463,11 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
self.loadFiles(filesToLoad)
}
// ---------------- FilePanel --------------------
self._components.filePanel = new FilePanel()
self._view.leftpanel.appendChild(self._components.filePanel.render())
self._components.filePanel.event.register('resize', delta => self._adjustLayout('left', delta))
registry.put({api: self._components.filePanel, name: 'filepanel'})
// ----------------- Renderer -----------------
var renderer = new Renderer()
registry.put({api: renderer, name: 'renderer'})
// ---------------- Tabs -------------------------------
let compileTab = new CompileTab(self._components.registry)
let tabs = {
compile: compileTab,
run: new RunTab(
registry.get('udapp').api,
registry.get('udappUI').api,
registry.get('config').api,
registry.get('filemanager').api,
registry.get('editor').api,
registry.get('logCallback').api,
registry.get('filepanel').api,
registry.get('pluginmanager').api,
registry.get('compilersartefacts').api
),
settings: new SettingsTab(self._components.registry),
analysis: new AnalysisTab(registry),
debug: new DebuggerTab(),
support: new SupportTab(),
test: new TestTab(self._components.registry, compileTab)
}
registry.get('app').api.event.register('tabChanged', (tabName) => {
if (tabName === 'Support') tabs.support.loadTab()
const txLogger = new TxLogger() // eslint-disable-line
txLogger.event.register('debuggingRequested', (hash) => {
if (!appStore.isActive('debugger')) appManager.activateOne('debugger')
debug.debugger().debug(hash)
verticalIconsApi.select('debugger')
})
let transactionContextAPI = {
@ -471,17 +495,9 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
}
udapp.resetAPI(transactionContextAPI)
// ---------------- Righthand-panel --------------------
self._components.righthandpanel = new RighthandPanel({ tabs, pluginManager })
self._view.rightpanel.appendChild(self._components.righthandpanel.render())
self._components.righthandpanel.init()
self._components.righthandpanel.event.register('resize', delta => self._adjustLayout('right', delta))
var txLogger = new TxLogger() // eslint-disable-line
const queryParams = new QueryParams()
var queryParams = new QueryParams()
var loadingFromGist = self.loadFromGist(queryParams.get())
const loadingFromGist = self.loadFromGist(queryParams.get())
if (!loadingFromGist) {
// insert ballot contract if there are no files to show
self._components.filesProviders['browser'].resolveDirectory('browser', (error, filesList) => {
@ -495,18 +511,4 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
}
})
}
// Open last opened file
var previouslyOpenedFile = self._components.config.get('currentFile')
if (previouslyOpenedFile) {
self._components.filesProviders['browser'].get(previouslyOpenedFile, (error, content) => {
if (!error && content) {
fileManager.switchFile(previouslyOpenedFile)
} else {
fileManager.switchFile()
}
})
} else {
fileManager.switchFile()
}
}

@ -1,6 +1,7 @@
'use strict'
var base64 = require('js-base64').Base64
var swarmgw = require('swarmgw')()
var resolver = require('@resolver-engine/imports').ImportsEngine()
var request = require('request')
module.exports = class CompilerImports {
@ -10,10 +11,20 @@ module.exports = class CompilerImports {
}
handleGithubCall (root, path, cb) {
var accessToken = this.githubAccessToken() ? '?access_token=' + this.githubAccessToken() : ''
let param = '?'
param += this.githubAccessToken() ? 'access_token=' + this.githubAccessToken() : ''
const regex = path.match(/blob\/([^/]+)\/(.*)/)
if (regex) {
// if we have /blob/master/+path we extract the branch name "master" and add it as a parameter to the github api
// the ref can be branch name, tag, commit id
const reference = regex[1]
param += 'ref=' + reference
path = path.replace(`blob/${reference}/`, '')
}
return request.get(
{
url: 'https://api.github.com/repos/' + root + '/contents/' + path + accessToken,
url: 'https://api.github.com/repos/' + root + '/contents/' + path + param,
json: true
},
(err, r, data) => {
@ -112,13 +123,22 @@ module.exports = class CompilerImports {
})
}
})
if (found) return
if (found) {
return
} else if (/^[^:]*:\/\//.exec(url)) {
cb('Unable to import "' + url + '": Unsupported URL schema')
} else {
resolver
.resolve(url)
.then(result => {
return resolver.require(url)
})
.then(result => {
if (url.indexOf(result.provider + ':') === 0) {
url = url.substring(result.provider.length + 1) // remove the github prefix
}
cb(null, result.source, url, result.provider, result.url)
})
.catch(err => {
err
cb('Unable to import "' + url + '": File not found')
}
})
}
}

@ -0,0 +1,155 @@
/* global localStorage */
const yo = require('yo-yo')
const modalDialog = require('../ui/modaldialog')
const unexposedEvents = ['statusChanged']
module.exports = class LocalPlugin {
/**
* Open a modal to create a local plugin
* @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) {
this.profile = JSON.parse(localStorage.getItem('plugins/local')) || { notifications: {} }
return new Promise((resolve, reject) => {
const onValidation = () => {
try {
const profile = this.create()
resolve(profile)
} catch (err) {
reject(err)
}
}
modalDialog('Local Plugin', this.form(plugins),
{ fn: () => onValidation() },
{ fn: () => resolve() }
)
})
}
/**
* Create the object to add to the plugin-list
*/
create () {
const profile = {
...this.profile,
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xMjYyIDEwNzVxLTM3IDEyMS0xMzggMTk1dC0yMjggNzQtMjI4LTc0LTEzOC0xOTVxLTgtMjUgNC00OC41dDM4LTMxLjVxMjUtOCA0OC41IDR0MzEuNSAzOHEyNSA4MCA5Mi41IDEyOS41dDE1MS41IDQ5LjUgMTUxLjUtNDkuNSA5Mi41LTEyOS41cTgtMjYgMzItMzh0NDktNCAzNyAzMS41IDQgNDguNXptLTQ5NC00MzVxMCA1My0zNy41IDkwLjV0LTkwLjUgMzcuNS05MC41LTM3LjUtMzcuNS05MC41IDM3LjUtOTAuNSA5MC41LTM3LjUgOTAuNSAzNy41IDM3LjUgOTAuNXptNTEyIDBxMCA1My0zNy41IDkwLjV0LTkwLjUgMzcuNS05MC41LTM3LjUtMzcuNS05MC41IDM3LjUtOTAuNSA5MC41LTM3LjUgOTAuNSAzNy41IDM3LjUgOTAuNXptMjU2IDI1NnEwLTEzMC01MS0yNDguNXQtMTM2LjUtMjA0LTIwNC0xMzYuNS0yNDguNS01MS0yNDguNSA1MS0yMDQgMTM2LjUtMTM2LjUgMjA0LTUxIDI0OC41IDUxIDI0OC41IDEzNi41IDIwNCAyMDQgMTM2LjUgMjQ4LjUgNTEgMjQ4LjUtNTEgMjA0LTEzNi41IDEzNi41LTIwNCA1MS0yNDguNXptMTI4IDBxMCAyMDktMTAzIDM4NS41dC0yNzkuNSAyNzkuNS0zODUuNSAxMDMtMzg1LjUtMTAzLTI3OS41LTI3OS41LTEwMy0zODUuNSAxMDMtMzg1LjUgMjc5LjUtMjc5LjUgMzg1LjUtMTAzIDM4NS41IDEwMyAyNzkuNSAyNzkuNSAxMDMgMzg1LjV6Ii8+PC9zdmc+',
methods: [],
hash: `local-${this.profile.name}`,
location: 'swapPanel'
}
profile.events = profile.events.filter((item) => { return item !== '' })
if (!profile.name) throw new Error('Plugin should have a name')
if (!profile.url) throw new Error('Plugin should have an URL')
localStorage.setItem('plugins/local', JSON.stringify(profile))
return profile
}
/**
* Add or remove a notification to/from the profile
* @param {Event} e The event when checkbox changes
* @param {string} pluginName The name of the plugin
* @param {string} eventName The name of the event to listen on
*/
toggleNotification (e, pluginName, eventName) {
const {checked} = e.target
if (checked) {
if (!this.profile.notifications[pluginName]) this.profile.notifications[pluginName] = []
this.profile.notifications[pluginName].push(eventName)
} else {
this.profile.notifications[pluginName].splice(this.profile.notifications[pluginName].indexOf(eventName), 1)
if (this.profile.notifications[pluginName].length === 0) delete this.profile.notifications[pluginName]
}
}
updateName ({target}) {
this.profile.name = target.value
}
updateUrl ({target}) {
this.profile.url = target.value
}
updateDisplayName ({target}) {
this.profile.displayName = target.value
}
updateEvents ({target}, index) {
if (this.profile.events[index] !== undefined) {
this.profile.events[index] = target.value
}
}
/**
* The checkbox for a couple module / event
* @param {string} plugin The name of the plugin
* @param {string} event The name of the event exposed by the plugin
*/
notificationCheckbox (plugin, event) {
const notifications = this.profile.notifications || {}
const checkbox = notifications[plugin] && notifications[plugin].includes(event)
? yo`<input type="checkbox" checked onchange="${e => this.toggleNotification(e, plugin, event)}">`
: yo`<input type="checkbox" onchange="${e => this.toggleNotification(e, plugin, event)}">`
return yo`<div>
${checkbox}
<label>${plugin} - ${event}</label>
</div>`
}
/**
* The form to create a local plugin
* @param {ProfileApi[]} plugins Liste of profile of the plugins
*/
form (plugins = []) {
const name = this.profile.name || ''
const url = this.profile.url || ''
const displayName = this.profile.displayName || ''
const profiles = plugins
.filter(({profile}) => profile.events && profile.events.length > 0)
.map(({profile}) => profile)
const eventsForm = (events) => {
return yo`<div>${events.map((event, i) => {
return yo`<input class="form-control" onchange="${e => this.updateEvents(e, i)}" value="${event}" />`
})}</div>`
}
const eventsEl = eventsForm(this.profile.events || [])
const pushEvent = () => {
if (!this.profile.events) this.profile.events = []
this.profile.events.push('')
yo.update(eventsEl, eventsForm(this.profile.events))
}
const addEvent = yo`<button type="button" class="btn btn-sm btn-light" onclick="${() => pushEvent()}">Add an event</button>`
return yo`
<form id="local-plugin-form">
<div class="form-group">
<label for="plugin-name">Plugin Name <small>(required)</small></label>
<input class="form-control" onchange="${e => this.updateName(e)}" value="${name}" id="plugin-name" placeholder="Should be camelCase">
</div>
<div class="form-group">
<label for="plugin-displayname">Display Name</label>
<input class="form-control" onchange="${e => this.updateDisplayName(e)}" value="${displayName}" id="plugin-displayname" placeholder="Name in the header">
</div>
<div class="form-group">
<label for="plugin-url">Url <small>(required)</small></label>
<input class="form-control" onchange="${e => this.updateUrl(e)}" value="${url}" id="plugin-url" placeholder="ex: https://localhost:8000">
</div>
<div class="form-group">
<label>Events</label>
${eventsEl}${addEvent}
</div>
<div class="form-group">
<label>Notifications</label>
${profiles.map(({name, events}) => {
return events
.filter(event => !unexposedEvents.includes(event))
.map(event => this.notificationCheckbox(name, event))
})}
</div>
</form>`
}
}

@ -0,0 +1,191 @@
const yo = require('yo-yo')
const csjs = require('csjs-inject')
const EventEmitter = require('events')
const LocalPlugin = require('./local-plugin')
import { Plugin, BaseApi } from 'remix-plugin'
const css = csjs`
.pluginSearch {
display: flex;
flex-direction: column;
align-items: center;
background-color: var(--light);
padding: 10px;
position: sticky;
top: 0;
z-index: 2;
margin-bottom: 0px;
}
.localPluginBtn {
margin-top: 15px;
}
.displayName {
text-transform: capitalize;
}
.description {
text-transform: capitalize;
}
.row {
display: flex;
flex-direction: row;
}
.isStuck {
background-color: var(--primary);
color:
}
`
const profile = {
name: 'pluginManager',
displayName: 'Plugin manager',
methods: [],
events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNzU1IDQ1M3EzNyAzOCAzNyA5MC41dC0zNyA5MC41bC00MDEgNDAwIDE1MCAxNTAtMTYwIDE2MHEtMTYzIDE2My0zODkuNSAxODYuNXQtNDExLjUtMTAwLjVsLTM2MiAzNjJoLTE4MXYtMTgxbDM2Mi0zNjJxLTEyNC0xODUtMTAwLjUtNDExLjV0MTg2LjUtMzg5LjVsMTYwLTE2MCAxNTAgMTUwIDQwMC00MDFxMzgtMzcgOTEtMzd0OTAgMzcgMzcgOTAuNS0zNyA5MC41bC00MDAgNDAxIDIzNCAyMzQgNDAxLTQwMHEzOC0zNyA5MS0zN3Q5MCAzN3oiLz48L3N2Zz4=',
description: 'Start/stop services, modules and plugins',
kind: 'settings',
location: 'swapPanel'
}
class PluginManagerComponent extends BaseApi {
constructor () {
super(profile)
this.event = new EventEmitter()
this.views = {
root: null,
items: {}
}
this.localPlugin = new LocalPlugin()
this.filter = ''
}
setApp (appManager) {
this.appManager = appManager
}
setStore (store) {
this.store = store
this.store.event.on('activate', (name) => { this.reRender() })
this.store.event.on('deactivate', (name) => { this.reRender() })
this.store.event.on('add', (api) => { this.reRender() })
this.store.event.on('remove', (api) => { this.reRender() })
}
renderItem (name) {
const api = this.store.getOne(name)
if (!api) return
const isActive = this.store.actives.includes(name)
const displayName = (api.profile.displayName) ? api.profile.displayName : name
const activationButton = isActive
? yo`
<button onclick="${_ => this.appManager.deactivateOne(name)}" class="btn btn-secondary btn-sm">
Deactivate
</button>`
: yo`
<button onclick="${_ => this.appManager.activateOne(name)}" class="btn btn-success btn-sm">
Activate
</button>`
return yo`
<article class="list-group-item py-1" title="${name}" >
<div class="${css.row} justify-content-between align-items-center">
<h6 class="${css.displayName}">${displayName}</h6>
${activationButton}
</div>
<p class="${css.description}">${api.profile.description}</p>
</article>
`
}
/***************
* SUB-COMPONENT
*/
/**
* Add a local plugin to the list of plugins
*/
async openLocalPlugin () {
try {
const profile = await this.localPlugin.open(this.store.getAll())
if (!profile) return
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
console.log(`Cannot create Plugin : ${err.message}`)
}
}
render () {
// Filtering helpers
const isFiltered = (api) => api.name.toLowerCase().includes(this.filter)
const isNotRequired = ({profile}) => !profile.required
const sortByName = (a, b) => {
const nameA = a.name.toUpperCase()
const nameB = b.name.toUpperCase()
return (nameA < nameB) ? -1 : (nameA > nameB) ? 1 : 0
}
// Filter all active and inactive modules that are not required
const { actives, inactives } = this.store.getAll()
.filter(isFiltered)
.filter(isNotRequired)
.sort(sortByName)
.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
? yo`
<nav class="navbar navbar-expand-lg navbar-light bg-light justify-content-between align-items-center">
<span class="navbar-brand">Active Modules</span>
<span class="badge badge-pill badge-primary">${actives.length}</span>
</nav>`
: ''
const inactiveTile = inactives.length !== 0
? yo`
<nav class="navbar navbar-expand-lg navbar-light bg-light justify-content-between align-items-center">
<span class="navbar-brand">Inactive Modules</span>
<span class="badge badge-pill badge-primary">${inactives.length}</span>
</nav>`
: ''
const rootView = yo`
<div id='pluginManager'>
<div class="form-group ${css.pluginSearch}">
<input onkeyup="${e => this.filterPlugins(e)}" class="form-control" placeholder="Search">
<button onclick="${_ => this.openLocalPlugin()}" class="btn btn-sm text-info ${css.localPluginBtn}">
Connect to a Local Plugin
</button>
</div>
<section>
${activeTile}
<div class="list-group list-group-flush">
${actives.map(name => this.renderItem(name))}
</div>
${inactiveTile}
<div class="list-group list-group-flush">
${inactives.map(name => this.renderItem(name))}
</div>
</section>
</div>
`
if (!this.views.root) this.views.root = rootView
return rootView
}
reRender () {
if (this.views.root) {
yo.update(this.views.root, this.render())
}
}
filterPlugins ({ target }) {
this.filter = target.value.toLowerCase()
this.reRender()
}
}
module.exports = PluginManagerComponent

@ -0,0 +1,36 @@
var registry = require('../../global/registry')
const CompilerAbstract = require('../compiler/compiler-abstract')
const EventManager = require('remix-lib').EventManager
class PluginManagerProxy {
constructor () {
this.event = new EventManager()
this._listeners = {}
this._listeners['vyper'] = (file, source, languageVersion, data) => {
registry.get('compilersartefacts').api['__last'] = new CompilerAbstract(languageVersion, data, source)
this.event.trigger('sendCompilationResult', [file, source, languageVersion, data])
}
this._listeners['solidity'] = (file, source, languageVersion, data) => {
registry.get('compilersartefacts').api['__last'] = new CompilerAbstract(languageVersion, data, source)
this.event.trigger('sendCompilationResult', [file, source, languageVersion, data])
}
}
register (name, instance) {
if (this._listeners[name]) {
instance.events.on('compilationFinished', this._listeners[name])
}
}
unregister (name, instance) {
if (this._listeners[name]) {
instance.events.removeListener('compilationFinished', this._listeners[name])
}
}
}
module.exports = PluginManagerProxy

@ -0,0 +1,43 @@
import EventEmmitter from 'events'
class SwapPanelApi {
constructor (swapPanelComponent, verticalIconsComponent) {
this.event = new EventEmmitter()
this.component = swapPanelComponent
this.currentContent
verticalIconsComponent.events.on('toggleContent', (moduleName) => {
if (!swapPanelComponent.contents[moduleName]) return
if (this.currentContent === moduleName) {
this.event.emit('toggle', moduleName)
return
}
this.showContent(moduleName)
this.event.emit('showing', moduleName)
})
verticalIconsComponent.events.on('showContent', (moduleName) => {
if (!swapPanelComponent.contents[moduleName]) return
this.showContent(moduleName)
this.event.emit('showing', moduleName)
})
}
showContent (moduleName) {
this.component.showContent(moduleName)
this.currentContent = moduleName
}
/*
content: DOM element
by appManager
*/
add (profile, content) {
return this.component.add(profile.name, content)
}
remove (profile) {
return this.component.remove(profile.name)
}
}
module.exports = SwapPanelApi

@ -0,0 +1,102 @@
var yo = require('yo-yo')
var csjs = require('csjs-inject')
class SwapPanelComponent {
constructor (name, appStore, appManager, opt) {
this.name = name
this.opt = opt
this.store = appStore
// list of contents
this.contents = {}
// name of the current displayed content
this.currentNode
this.store.event.on('activate', (name) => {
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())
}
})
this.store.event.on('deactivate', (name) => {
if (this.contents[name]) this.remove(name)
})
this.store.event.on('add', (api) => { })
this.store.event.on('remove', (api) => { })
}
showContent (moduleName) {
// hiding the current view and display the `moduleName`
if (this.contents[moduleName]) {
if (this.currentNode) {
this.contents[this.currentNode].style.display = 'none'
}
this.contents[moduleName].style.display = 'block'
this.currentNode = moduleName
var api = this.store.getOne(moduleName)
this.header.querySelector('h6').innerHTML = api.profile ? api.profile.displayName : ' - '
return
}
}
add (moduleName, content) {
content.style.height = '100%'
content.style.width = '100%'
content.style.border = '0'
this.contents[moduleName] = yo`<div class=${css.plugItIn} >${content}</div>`
this.view.appendChild(this.contents[moduleName])
}
remove (moduleName) {
let el = this.contents[moduleName]
if (el) el.parentElement.removeChild(el)
}
render () {
this.view = yo`
<div id='plugins' class=${css.plugins}>
</div>
`
this.header = yo`<header class="${css.swapitHeader}"><h6 class="${css.swapitTitle}"></h6></header>`
if (!this.opt.displayHeader) this.header.style.display = 'none'
return yo`<div class=${css.pluginsContainer}>
${this.header}
${this.view}
</div>`
}
}
module.exports = SwapPanelComponent
const css = csjs`
.plugins {
height : 95%;
}
.plugItIn {
display : none;
height : 100%;
}
.plugItIn > div {
overflow-y : auto;
height : 100%;
width : 100%;
}
.plugItIn.active {
display : block;
}
.pluginsContainer {
height: 100%;
overflow-y: hidden;
}
.swapitTitle {
text-transform: uppercase;
}
.swapitHeader {
height: 35px;
padding-top: 10px;
padding-left: 27px;
}
`

@ -0,0 +1,20 @@
// API
class VerticalIconsApi {
constructor (verticalIconsComponent) {
this.component = verticalIconsComponent
}
addIcon (mod) {
this.component.addIcon(mod)
}
removeIcon (mod) {
this.component.removeIcon(mod)
}
select (moduleName) {
this.component.select(moduleName)
}
}
module.exports = VerticalIconsApi

@ -0,0 +1,428 @@
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var helper = require('../../lib/helper')
let globalRegistry = require('../../global/registry')
const EventEmitter = require('events')
// Component
class VerticalIconComponent {
constructor (name, appStore, homeProfile) {
this.store = appStore
this.homeProfile = homeProfile
this.events = new EventEmitter()
this.icons = {}
this.iconKind = {}
this.iconStatus = {}
this.name = name
this.store.event.on('activate', (name) => {
const api = this.store.getOne(name)
if (!api.profile.icon) return
if (api.profile.location === this.name) {
this.addIcon(api.profile)
this.listenOnStatus(api)
}
})
this.store.event.on('deactivate', (name) => {
const api = this.store.getOne(name)
if (api && this.icons[name]) {
this.removeIcon(api.profile)
this.stopListenOnStatus(api)
}
})
this.store.event.on('add', (api) => { })
this.store.event.on('remove', (api) => { })
let themeModule = globalRegistry.get('themeModule').api
themeModule.events.on('themeChanged', (theme) => {
this.onThemeChanged(theme.quality)
})
}
stopListenOnStatus (api) {
if (!api.events) return
let fn = this.iconStatus[api.profile.name]
if (fn) {
api.events.removeListener('statusChanged', fn)
delete this.iconStatus[api.profile.name]
}
}
listenOnStatus (api) {
if (!api.events) return
// the list of supported keys. 'none' will remove the status
const keys = ['edited', 'succeed', 'none', 'loading', 'failed']
const types = ['error', 'warning', 'success', 'info', '']
const fn = (status) => {
if (!types.includes(status.type) && status.type) throw new Error(`type should be ${keys.join()}`)
if (!status.key) throw new Error(`status key should be defined`)
if (typeof status.key === 'string' && (!keys.includes(status.key))) {
throw new Error('key should contain either number or ' + keys.join())
}
this.setIconStatus(api.profile.name, status)
}
this.iconStatus[api.profile.name] = fn
api.events.on('statusChanged', this.iconStatus[api.profile.name])
}
/**
* Add an icon to the map
* @param {ModuleProfile} profile The profile of the module
*/
addIcon ({kind, name, icon, displayName, tooltip}) {
let title = (displayName || name)// + (tooltip ? tooltip : "")
this.icons[name] = yo`
<div
class="${css.icon}"
onclick="${(e) => { this._iconClick(name) }}"
plugin="${name}" title="${title}" >
<img class="image" src="${icon}" alt="${name}" />
</div>`
this.iconKind[kind || 'other'].appendChild(this.icons[name])
}
/**
* resolve a classes list for @arg key
* @param {Object} key
* @param {Object} type
*/
resolveClasses (key, type) {
let classes = css.status
switch (key) {
case 'succeed':
classes += ' fas fa-check-circle text-' + type + ' ' + css.statusCheck
break
case 'edited':
classes += ' fas fa-sync text-' + type + ' ' + css.statusCheck
break
case 'loading':
classes += ' fas fa-spinner text-' + type + ' ' + css.statusCheck
break
case 'failed':
classes += ' fas fa-exclamation-triangle text-' + type + ' ' + css.statusCheck
break
default: {
classes += ' badge badge-pill badge-' + type
}
}
return classes
}
/**
* Set a new status for the @arg name
* @param {String} name
* @param {Object} status
*/
setIconStatus (name, status) {
const el = this.icons[name]
if (!el) return
let statusEl = el.querySelector('span')
if (statusEl) {
el.removeChild(statusEl)
}
if (status.key === 'none') return // remove status
let text = ''
let key = ''
if (typeof status.key === 'number') {
key = status.key.toString()
text = key
} else key = helper.checkSpecialChars(status.key) ? '' : status.key
let type = ''
if (status.type === 'error') {
type = 'danger' // to use with bootstrap
} else type = helper.checkSpecialChars(status.type) ? '' : status.type
let title = helper.checkSpecialChars(status.title) ? '' : status.title
el.appendChild(yo`<span
title="${title}"
class="${this.resolveClasses(key, type)}"
aria-hidden="true"
>
${text}
</span>`)
el.classList.add(`${css.icon}`)
}
/**
* 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])
}
/**
* Remove active for the current activated icons
*/
removeActive () {
// reset filters
const images = this.view.querySelectorAll(`.image`)
images.forEach(function (im) {
im.style.setProperty('filter', 'invert(0.5)')
})
// remove active
const currentActive = this.view.querySelector(`.${css.active}`)
if (currentActive) {
currentActive.classList.remove(css.active)
}
}
/**
* Add active for the new activated icon
* @param {string} name Name of profile of the module to activate
*/
addActive (name) {
const themeType = globalRegistry.get('themeModule').api.currentTheme().quality
const invert = themeType === 'dark' ? 1 : 0
const nextActive = this.view.querySelector(`[plugin="${name}"]`)
if (nextActive) {
let image = nextActive.querySelector('.image')
nextActive.classList.add(css.active)
image.style.setProperty('filter', `invert(${invert})`)
}
}
/**
* Set an icon as active
* @param {string} name Name of profile of the module to activate
*/
select (name) {
this.removeActive()
this.addActive(name)
this.events.emit('showContent', name)
}
onThemeChanged (themeType) {
const invert = themeType === 'dark' ? 1 : 0
const active = this.view.querySelector(`.${css.active}`)
if (active) {
let image = active.querySelector('.image')
image.style.setProperty('filter', `invert(${invert})`)
}
}
_iconClick (name) {
this.removeActive()
this.addActive(name)
this.events.emit('toggleContent', name)
}
render () {
let home = yo`
<div
class="${css.homeIcon}"
onclick="${(e) => {
globalRegistry.get('appmanager').api.ensureActivated('home')
}}"
plugin="${this.homeProfile.name}" title="${this.homeProfile.displayName}"
>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="42px" height="42px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
<g>
<path fill="#414042" d="M70.582,428.904c0.811,0,1.622,0.285,2.437,0.853c0.811,0.571,1.218,1.34,1.218,2.314
c0,2.277-1.059,3.496-3.168,3.656c-5.038,0.814-9.381,2.356-13.037,4.63c-3.655,2.276-6.663,5.117-9.016,8.528
c-2.357,3.411-4.104,7.272-5.239,11.575c-1.139,4.307-1.706,8.814-1.706,13.524v32.653c0,2.273-1.139,3.411-3.412,3.411
c-2.277,0-3.412-1.138-3.412-3.411v-74.323c0-2.273,1.135-3.411,3.412-3.411c2.273,0,3.412,1.138,3.412,3.411v15.108
c1.462-2.437,3.206-4.752,5.239-6.945c2.029-2.193,4.264-4.143,6.701-5.848c2.437-1.706,5.076-3.085,7.919-4.143
C64.771,429.433,67.658,428.904,70.582,428.904z"/>
<path fill="#414042" d="M137.773,427.198c5.685,0,10.966,1.181,15.839,3.534c4.874,2.356,9.055,5.482,12.55,9.381
c3.492,3.899,6.214,8.407,8.164,13.524c1.949,5.117,2.924,10.44,2.924,15.961c0,0.976-0.366,1.79-1.097,2.438
c-0.731,0.65-1.583,0.975-2.559,0.975h-67.987c0.487,4.226,1.584,8.285,3.29,12.184c1.706,3.899,3.937,7.312,6.701,10.234
c2.761,2.925,6.008,5.281,9.748,7.067c3.735,1.789,7.877,2.681,12.428,2.681c12.021,0,21.36-4.79,28.023-14.377
c0.647-1.136,1.622-1.706,2.924-1.706c2.273,0,3.412,1.139,3.412,3.412c0,0.163-0.164,0.73-0.487,1.705
c-3.412,6.013-8.205,10.479-14.377,13.402c-6.176,2.924-12.671,4.387-19.495,4.387c-5.689,0-10.928-1.181-15.718-3.533
c-4.793-2.354-8.936-5.483-12.428-9.382c-3.495-3.899-6.214-8.407-8.163-13.524c-1.95-5.118-2.924-10.437-2.924-15.962
c0-5.521,0.975-10.844,2.924-15.961c1.949-5.117,4.668-9.625,8.163-13.524c3.492-3.898,7.634-7.024,12.428-9.381
C126.846,428.379,132.084,427.198,137.773,427.198z M169.94,466.188c-0.328-4.223-1.341-8.285-3.046-12.184
c-1.706-3.899-3.982-7.312-6.823-10.235c-2.844-2.924-6.175-5.277-9.991-7.067c-3.819-1.785-7.92-2.68-12.306-2.68
c-4.55,0-8.692,0.895-12.428,2.68c-3.739,1.79-6.987,4.144-9.748,7.067c-2.764,2.924-4.995,6.336-6.701,10.235
c-1.706,3.898-2.802,7.961-3.29,12.184H169.94z"/>
<path fill="#414042" d="M304.69,427.441c5.034,0,9.504,1.018,13.402,3.047c3.899,2.033,7.189,4.672,9.87,7.92
c2.68,3.251,4.709,7.066,6.092,11.452c1.379,4.387,2.07,8.856,2.07,13.402v43.62c0,0.975-0.365,1.789-1.097,2.438
c-0.73,0.646-1.503,0.975-2.313,0.975c-2.276,0-3.412-1.14-3.412-3.412v-43.62c0-3.571-0.529-7.104-1.584-10.6
c-1.059-3.491-2.602-6.618-4.63-9.382c-2.033-2.761-4.592-4.953-7.677-6.58c-3.088-1.621-6.662-2.436-10.722-2.436
c-5.2,0-9.587,1.218-13.159,3.654c-3.574,2.438-6.457,5.566-8.65,9.382c-2.193,3.819-3.818,8.042-4.874,12.672
c-1.059,4.63-1.584,9.058-1.584,13.28v33.629c0,0.975-0.365,1.789-1.096,2.438c-0.731,0.646-1.505,0.975-2.315,0.975
c-2.276,0-3.411-1.14-3.411-3.412v-43.62c0-3.571-0.53-7.104-1.585-10.6c-1.058-3.491-2.601-6.618-4.629-9.382
c-2.034-2.761-4.592-4.953-7.677-6.58c-3.087-1.621-6.663-2.436-10.722-2.436c-5.037,0-9.344,0.895-12.915,2.68
c-3.575,1.79-6.542,4.266-8.895,7.433c-2.357,3.167-4.063,6.944-5.117,11.331c-1.059,4.386-1.584,9.1-1.584,14.134v3.899v0.243
v32.897c0,2.272-1.138,3.412-3.412,3.412c-2.276,0-3.411-1.14-3.411-3.412v-74.567c0-2.273,1.135-3.411,3.411-3.411
c2.273,0,3.412,1.138,3.412,3.411v12.428c2.924-5.197,6.861-9.382,11.819-12.55c4.954-3.167,10.517-4.752,16.692-4.752
c6.983,0,12.995,1.991,18.032,5.97c5.033,3.983,8.688,9.223,10.966,15.719c2.76-6.336,6.739-11.533,11.94-15.596
C291.125,429.475,297.38,427.441,304.69,427.441z"/>
<path fill="#414042" d="M378.753,429.392c0.811,0,1.584,0.365,2.314,1.097c0.731,0.73,1.097,1.504,1.097,2.314v74.08
c0,0.814-0.365,1.584-1.097,2.315c-0.73,0.73-1.504,1.097-2.314,1.097c-0.975,0-1.79-0.366-2.438-1.097
c-0.65-0.731-0.975-1.501-0.975-2.315v-74.08c0-0.811,0.324-1.584,0.975-2.314C376.963,429.757,377.778,429.392,378.753,429.392z"
/>
<path fill="#414042" d="M473.34,428.66c2.273,0,3.412,1.139,3.412,3.411l-0.487,1.95l-24.368,35.334l24.368,35.577
c0.323,0.976,0.487,1.626,0.487,1.95c0,2.272-1.139,3.412-3.412,3.412c-1.302,0-2.193-0.488-2.68-1.463l-22.906-33.384
l-22.663,33.384c-0.814,0.975-1.79,1.463-2.924,1.463c-2.277,0-3.411-1.14-3.411-3.412c0-0.324,0.159-0.975,0.486-1.95
l24.369-35.577l-24.369-35.334l-0.486-1.95c0-2.272,1.134-3.411,3.411-3.411c1.134,0,2.109,0.487,2.924,1.462l22.663,33.141
l22.906-33.141C471.146,429.147,472.038,428.66,473.34,428.66z"/>
</g>
<g>
<g>
<g opacity="0.45">
<g>
<polygon fill="#010101" points="150.734,196.212 255.969,344.508 255.969,258.387"/>
</g>
</g>
<g opacity="0.8">
<g>
<polygon fill="#010101" points="255.969,258.387 255.969,344.508 361.267,196.212"/>
</g>
</g>
<g opacity="0.6">
<g>
<polygon fill="#010101" points="255.969,126.781 150.733,174.611 255.969,236.818 361.204,174.611"/>
</g>
</g>
<g opacity="0.45">
<g>
<polygon fill="#010101" points="150.734,174.612 255.969,236.818 255.969,126.782 255.969,0.001"/>
</g>
</g>
<g opacity="0.8">
<g>
<polygon fill="#010101" points="255.969,0 255.969,126.781 255.969,236.818 361.204,174.611"/>
</g>
</g>
</g>
</g>
</svg>
</div>`
this.iconKind['fileexplorer'] = yo`
<div id='fileExplorerIcons'>
</div>
`
this.iconKind['compile'] = yo`
<div id='compileIcons'>
</div>
`
this.iconKind['run'] = yo`
<div id='runIcons'>
</div>
`
this.iconKind['testing'] = yo`
<div id='testingIcons'>
</div>
`
this.iconKind['analysis'] = yo`
<div id='analysisIcons'>
</div>
`
this.iconKind['debugging'] = yo`
<div id='debuggingIcons'>
</div>
`
this.iconKind['other'] = yo`
<div id='otherIcons'>
</div>
`
this.iconKind['settings'] = yo`
<div id='settingsIcons'>
</div>
`
this.view = yo`
<div class=${css.icons}>
${home}
${this.iconKind['fileexplorer']}
${this.iconKind['compile']}
${this.iconKind['run']}
${this.iconKind['testing']}
${this.iconKind['analysis']}
${this.iconKind['debugging']}
${this.iconKind['other']}
${this.iconKind['settings']}
</div>
`
return this.view
}
}
module.exports = VerticalIconComponent
const css = csjs`
.homeIcon {
display: block;
width: 42px;
height: 42px;
margin-bottom: 20px;
margin-left: -5px;
cursor: pointer;
}
.homeIcon svg path {
fill: var(--primary);
}
.homeIcon svg polygon {
fill: var(--primary);
}
.icons {
margin-left: 10px;
margin-top: 15px;
}
.icon {
cursor: pointer;
margin-bottom: 12px;
width: 36px;
height: 36px;
padding: 3px;
position: relative;
border-radius: 8px;
}
.icon img {
width: 28px;
height: 28px;
padding: 4px;
filter: invert(0.5);
}
.image {
}
.icon svg {
width: 28px;
height: 28px;
padding: 4px;
}
.icon[title='Settings'] {
position: absolute;
bottom: 0;
}
.status {
position: absolute;
bottom: 0;
right: 0;
}
.statusCheck {
font-size: 1.2em;
}
.statusWithBG
border-radius: 8px;
background-color: var(--danger);
color: var(--light);
font-size: 12px;
height: 15px;
text-align: center;
font-weight: bold;
padding-left: 5px;
padding-right: 5px;
}
`

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

@ -3,8 +3,6 @@ var EventManager = require('../../../lib/events')
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var styleGuide = require('../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs`
.buttons {
@ -17,7 +15,6 @@ var css = csjs`
justify-content: center;
}
.stepButton {
${styles.rightPanel.debuggerTab.button_Debugger}
}
.jumpButtons {
width: 100%;
@ -25,13 +22,10 @@ var css = csjs`
justify-content: center;
}
.jumpButton {
${styles.rightPanel.debuggerTab.button_Debugger}
}
.navigator {
color: ${styles.rightPanel.debuggerTab.text_Primary};
}
.navigator:hover {
color: ${styles.rightPanel.debuggerTab.button_Debugger_icon_HoverColor};
}
`
@ -51,20 +45,20 @@ function ButtonNavigator () {
ButtonNavigator.prototype.render = function () {
var self = this
var view = yo`<div class="${css.buttons}">
<div class="${css.stepButtons}">
<button id='overback' title='Step over back' class='${css.navigator} ${css.stepButton} fa fa-reply' onclick=${function () { self.event.trigger('stepOverBack') }} disabled=${this.overBackDisabled} ></button>
<button id='intoback' title='Step back' class='${css.navigator} ${css.stepButton} fa fa-level-up' onclick=${function () { self.event.trigger('stepIntoBack') }} disabled=${this.intoBackDisabled} ></button>
<button id='intoforward' title='Step into' class='${css.navigator} ${css.stepButton} fa fa-level-down' onclick=${function () { self.event.trigger('stepIntoForward') }} disabled=${this.intoForwardDisabled} ></button>
<button id='overforward' title='Step over forward' class='${css.navigator} ${css.stepButton} fa fa-share' onclick=${function () { self.event.trigger('stepOverForward') }} disabled=${this.overForwardDisabled} ></button>
<div class="${css.stepButtons} btn-group p-1">
<button id='overback' class='btn btn-primary btn-sm' title='Step over back' class='${css.navigator} ${css.stepButton} fas fa-reply' onclick=${function () { self.event.trigger('stepOverBack') }} disabled=${this.overBackDisabled} ></button>
<button id='intoback' class='btn btn-primary btn-sm' title='Step back' class='${css.navigator} ${css.stepButton} fas fa-level-up-alt' onclick=${function () { self.event.trigger('stepIntoBack') }} disabled=${this.intoBackDisabled} ></button>
<button id='intoforward' class='btn btn-primary btn-sm' title='Step into' class='${css.navigator} ${css.stepButton} fas fa-level-down-alt' onclick=${function () { self.event.trigger('stepIntoForward') }} disabled=${this.intoForwardDisabled} ></button>
<button id='overforward' class='btn btn-primary btn-sm' title='Step over forward' class='${css.navigator} ${css.stepButton} fas fa-share' onclick=${function () { self.event.trigger('stepOverForward') }} disabled=${this.overForwardDisabled} ></button>
</div>
<div class="${css.jumpButtons}">
<button id='jumppreviousbreakpoint' title='Jump to the previous breakpoint' class='${css.navigator} ${css.jumpButton} fa fa-step-backward' onclick=${function () { self.event.trigger('jumpPreviousBreakpoint') }} disabled=${this.jumpPreviousBreakpointDisabled} ></button>
<button id='jumpout' title='Jump out' class='${css.navigator} ${css.jumpButton} fa fa-eject' onclick=${function () { self.event.trigger('jumpOut') }} disabled=${this.jumpOutDisabled} ></button>
<button id='jumpnextbreakpoint' title='Jump to the next breakpoint' class='${css.navigator} ${css.jumpButton} fa fa-step-forward' onclick=${function () { self.event.trigger('jumpNextBreakpoint') }} disabled=${this.jumpNextBreakpointDisabled} ></button>
<div class="${css.jumpButtons} btn-group p-1">
<button class='btn btn-primary btn-sm' id='jumppreviousbreakpoint' title='Jump to the previous breakpoint' class='${css.navigator} ${css.jumpButton} fas fa-step-backward' onclick=${function () { self.event.trigger('jumpPreviousBreakpoint') }} disabled=${this.jumpPreviousBreakpointDisabled} ></button>
<button class='btn btn-primary btn-sm' id='jumpout' title='Jump out' class='${css.navigator} ${css.jumpButton} fas fa-eject' onclick=${function () { self.event.trigger('jumpOut') }} disabled=${this.jumpOutDisabled} ></button>
<button class='btn btn-primary btn-sm' id='jumpnextbreakpoint' title='Jump to the next breakpoint' class='${css.navigator} ${css.jumpButton} fas fa-step-forward' onclick=${function () { self.event.trigger('jumpNextBreakpoint') }} disabled=${this.jumpNextBreakpointDisabled} ></button>
</div>
<div id='reverted' style="display:none">
<button id='jumptoexception' title='Jump to exception' class='${css.navigator} ${css.button} fa fa-exclamation-triangle' onclick=${function () { self.event.trigger('jumpToException') }} disabled=${this.jumpOutDisabled} >
<button class='btn btn-danger btn-sm' id='jumptoexception' title='Jump to exception' class='${css.navigator} ${css.button} fas fa-exclamation-triangle' onclick=${function () { self.event.trigger('jumpToException') }} disabled=${this.jumpOutDisabled} >
</button>
<span>State changes made during this call will be reverted.</span>
<span id='outofgas' style="display:none">This call will run out of gas.</span>

@ -1,8 +1,6 @@
var EventManager = require('../../../lib/events')
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var styleGuide = require('../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs`
.container {
@ -19,7 +17,6 @@ var css = csjs`
justify-content: center;
}
.txinput {
${styles.rightPanel.debuggerTab.input_Debugger}
margin: 3px;
width: inherit;
}
@ -29,14 +26,11 @@ var css = csjs`
justify-content: center;
}
.txbutton {
${styles.rightPanel.debuggerTab.button_Debugger}
width: inherit;
}
.txbuttonstart {
${styles.rightPanel.debuggerTab.button_Debugger}
}
.txbutton:hover {
color: ${styles.rightPanel.debuggerTab.button_Debugger_icon_HoverColor};
}
.vmargin {
margin-top: 10px;
@ -99,13 +93,13 @@ TxBrowser.prototype.render = function () {
var self = this
var view = yo`<div class="${css.container}">
<div class="${css.txContainer}">
<div class="${css.txinputs}">
<input class="${css.txinput}" onkeyup=${function () { self.updateBlockN(arguments[0]) }} type='text' placeholder=${'Block number'} />
<input class="${css.txinput}" id='txinput' onkeyup=${function () { self.updateTxN(arguments[0]) }} type='text' placeholder=${'Transaction index or hash'} />
<div class="${css.txinputs} p-1 input-group">
<input class="form-control" class="${css.txinput}" onkeyup=${function () { self.updateBlockN(arguments[0]) }} type='text' placeholder=${'Block number'} />
<input class="form-control" class="${css.txinput}" id='txinput' onkeyup=${function () { self.updateTxN(arguments[0]) }} type='text' placeholder=${'Transaction index or hash'} />
</div>
<div class="${css.txbuttons}">
<button id='load' class='${css.txbutton}' title='start debugging' onclick=${function () { self.submit() }}>Start debugging</button>
<button id='unload' class='${css.txbutton}' title='stop debugging' onclick=${function () { self.unload() }}>Stop</button>
<div class="${css.txbuttons} btn-group p-1">
<button class='btn btn-primary btn-sm' id='load' class='${css.txbutton}' title='start debugging' onclick=${function () { self.submit() }}>Start debugging</button>
<button class='btn btn-primary btn-sm' id='unload' class='${css.txbutton}' title='stop debugging' onclick=${function () { self.unload() }}>Stop</button>
</div>
</div>
<span id='error'></span>

@ -16,8 +16,6 @@ var DropdownPanel = require('./vmDebugger/DropdownPanel')
var css = csjs`
.asmCode {
float: left;
width: 50%;
}
.stepDetail {
}
@ -123,10 +121,10 @@ function VmDebugger (vmDebuggerLogic) {
}
VmDebugger.prototype.renderHead = function () {
var headView = yo`<div id='vmheadView' class=${css.vmheadView}>
<div>
<div class=${css.asmCode}>${this.asmCode.render()}</div>
<div class=${css.stepDetail}>${this.stepDetail.render()}</div>
var headView = yo`<div id='vmheadView' class="${css.vmheadView} container">
<div class="row" >
<div class="${css.asmCode} column">${this.asmCode.render()}</div>
<div class="${css.stepDetail} column">${this.stepDetail.render()}</div>
</div>
</div>`
if (!this.headView) {

@ -4,12 +4,9 @@ var yo = require('yo-yo')
var DropdownPanel = require('./DropdownPanel')
var EventManager = require('../../../../lib/events')
var csjs = require('csjs-inject')
var styleGuide = require('../../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs`
.instructions {
${styles.rightPanel.debuggerTab.box_Debugger}
overflow-y: scroll;
max-height: 150px;
}
@ -43,7 +40,8 @@ CodeListView.prototype.indexChanged = function (index) {
}
}
this.itemSelected = this.codeView.children[index]
this.itemSelected.setAttribute('style', 'background-color: ' + styles.rightPanel.debuggerTab.text_BgHighlight)
this.itemSelected.style.setProperty('background-color', 'var(--info)')
this.itemSelected.style.setProperty('color', 'var(--light)')
this.itemSelected.setAttribute('selected', 'selected')
if (this.itemSelected.firstChild) {
this.itemSelected.firstChild.setAttribute('style', 'margin-left: 2px')

@ -5,13 +5,9 @@ var EventManager = require('../../../../lib/events')
var TreeView = require('../../../ui/TreeView') // TODO setup a direct reference to the UI components
var csjs = require('csjs-inject')
var styleGuide = require('../../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs`
.title {
margin-top: 10px;
${styles.rightPanel.debuggerTab.dropdown_Debugger};
display: flex;
align-items: center;
}
@ -23,18 +19,16 @@ var css = csjs`
margin-left: 3px;
}
.icon {
color: ${styles.rightPanel.debuggerTab.button_Debugger_icon_Color};
margin-right: 5%;
}
.eyeButton {
margin: 3px;
}
.eyeButton:hover {
color: ${styles.rightPanel.debuggerTab.button_Debugger_icon_HoverColor};
}
.dropdownpanel {
${styles.rightPanel.debuggerTab.dropdown_Debugger};
width: 100%;
word-break: break-all;
}
.dropdownrawcontent {
padding: 2px;
@ -71,7 +65,7 @@ DropdownPanel.prototype.setMessage = function (message) {
if (!this.view) return
this.view.querySelector('.dropdownpanel .dropdownrawcontent').style.display = 'none'
this.view.querySelector('.dropdownpanel .dropdowncontent').style.display = 'none'
this.view.querySelector('.dropdownpanel .fa-refresh').style.display = 'none'
this.view.querySelector('.dropdownpanel > i').style.display = 'none'
this.message(message)
}
@ -79,20 +73,18 @@ DropdownPanel.prototype.setLoading = function () {
if (!this.view) return
this.view.querySelector('.dropdownpanel .dropdownrawcontent').style.display = 'none'
this.view.querySelector('.dropdownpanel .dropdowncontent').style.display = 'none'
this.view.querySelector('.dropdownpanel .fa-refresh').style.display = 'inline-block'
this.view.querySelector('.dropdownpanel > i').style.display = 'inline-block'
this.message('')
}
DropdownPanel.prototype.setUpdating = function () {
if (!this.view) return
this.view.querySelector('.dropdownpanel .dropdowncontent').style.color = styles.appProperties.greyedText_color
}
DropdownPanel.prototype.update = function (_data, _header) {
if (!this.view) return
this.view.querySelector('.dropdownpanel .fa-refresh').style.display = 'none'
this.view.querySelector('.dropdownpanel > i').style.display = 'none'
this.view.querySelector('.dropdownpanel .dropdowncontent').style.display = 'block'
this.view.querySelector('.dropdownpanel .dropdowncontent').style.color = styles.appProperties.mainText_Color
this.view.querySelector('.dropdownpanel .dropdownrawcontent').innerText = JSON.stringify(_data, null, '\t')
if (!this.displayContentOnly) {
this.view.querySelector('.title div.btn').style.display = 'block'
@ -106,11 +98,10 @@ DropdownPanel.prototype.update = function (_data, _header) {
DropdownPanel.prototype.setContent = function (node) {
if (!this.view) return
var parent = this.view.querySelector('.dropdownpanel div.dropdowncontent')
parent.replaceChild(node, parent.firstElementChild)
yo.update(this.view, this.render(null, node))
}
DropdownPanel.prototype.render = function (overridestyle) {
DropdownPanel.prototype.render = function (overridestyle, node) {
var content = yo`<div>Empty</div>`
if (this.json) {
content = this.treeView.render({})
@ -118,19 +109,19 @@ DropdownPanel.prototype.render = function (overridestyle) {
overridestyle === undefined ? {} : overridestyle
var self = this
var title = !self.displayContentOnly ? yo`<div class="${css.title} title">
<div class="${css.icon} fa fa-caret-right" onclick=${function () { self.toggle() }} ></div>
<div class="${css.icon} fas fa-caret-right" onclick=${function () { self.toggle() }} ></div>
<div class="${css.name}" onclick=${function () { self.toggle() }} >${this.name}</div><span class="${css.nameDetail}" onclick=${function () { self.toggle() }} ></span>
<div onclick=${function () { self.copyClipboard() }} title='raw' class="${css.eyeButton} btn fa fa-clipboard"></div>
<div onclick=${function () { self.copyClipboard() }} title='raw' class="${css.eyeButton} btn far fa-clipboard"></div>
</div>` : yo`<div></div>`
var contentNode = yo`<div class='dropdownpanel' style='display:none'>
<i class="${css.refresh} fa fa-refresh" aria-hidden="true"></i>
<div class='dropdowncontent'>${content}</div>
var contentNode = yo`<div class='dropdownpanel ${css.dropdownpanel}' style='display:none'>
<i class="${css.refresh} fas fa-sync" aria-hidden="true"></i>
<div class='dropdowncontent'>${node || content}</div>
<div class='dropdownrawcontent' style='display:none'></div>
<div class='message' style='display:none'></div>
</div>`
var view = yo`
<div>
<div class="border border-primary rounded p-1 m-1">
<style>
@-moz-keyframes spin {
to { -moz-transform: rotate(359deg); }
@ -162,11 +153,11 @@ DropdownPanel.prototype.toggle = function () {
var caret = this.view.querySelector('.title').firstElementChild
if (el.style.display === '') {
el.style.display = 'none'
caret.className = `${css.icon} fa fa-caret-right`
caret.className = `${css.icon} fas fa-caret-right`
this.event.trigger('hide', [])
} else {
el.style.display = ''
caret.className = `${css.icon} fa fa-caret-down`
caret.className = `${css.icon} fas fa-caret-down`
this.event.trigger('show', [])
}
}
@ -176,7 +167,7 @@ DropdownPanel.prototype.hide = function () {
var caret = this.view.querySelector('.title').firstElementChild
var el = this.view.querySelector('.dropdownpanel')
el.style.display = 'none'
caret.className = `${css.icon} fa fa-caret-right`
caret.className = `${css.icon} fas fa-caret-right`
this.event.trigger('hide', [])
}
@ -185,7 +176,7 @@ DropdownPanel.prototype.show = function () {
var caret = this.view.querySelector('.title').firstElementChild
var el = this.view.querySelector('.dropdownpanel')
el.style.display = ''
caret.className = `${css.icon} fa fa-caret-down`
caret.className = `${css.icon} fas fa-caret-down`
this.event.trigger('show', [])
}

@ -0,0 +1,39 @@
'use strict'
const SourceHighlighter = require('./sourceHighlighter')
import { EditorApi } from 'remix-plugin'
const profile = {
displayName: 'source highlighters',
name: 'editor',
description: 'service - highlight source code'
}
// EditorApi:
// - methods: ['highlight', 'discardHighlight'],
class SourceHighlighters extends EditorApi {
constructor () {
super(profile)
this.highlighters = {}
}
highlight (position, filePath, hexColor) {
const { from } = this.currentRequest
try {
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
}
}
discardHighlight () {
const { from } = this.currentRequest
if (this.highlighters[from]) this.highlighters[from].currentSourceLocation(null)
}
}
module.exports = SourceHighlighters

@ -1,10 +1,10 @@
'use strict'
var yo = require('yo-yo')
var remixLib = require('remix-lib')
var SourceMappingDecoder = remixLib.SourceMappingDecoder
var globalRegistry = require('../../global/registry')
const yo = require('yo-yo')
const remixLib = require('remix-lib')
const SourceMappingDecoder = remixLib.SourceMappingDecoder
const globalRegistry = require('../../global/registry')
var css = require('./styles/contextView-styles')
const css = require('./styles/contextView-styles')
/*
Display information about the current focused code:
@ -15,37 +15,38 @@ var css = require('./styles/contextView-styles')
*/
class ContextView {
constructor (opts, localRegistry) {
const self = this
self._components = {}
self._components.registry = localRegistry || globalRegistry
self.contextualListener = opts.contextualListener
self.editor = opts.editor
self._deps = {
compilersArtefacts: self._components.registry.get('compilersartefacts').api,
offsetToLineColumnConverter: self._components.registry.get('offsettolinecolumnconverter').api,
config: self._components.registry.get('config').api,
fileManager: self._components.registry.get('filemanager').api
this._components = {}
this._components.registry = localRegistry || globalRegistry
this.contextualListener = opts.contextualListener
this.editor = opts.editor
this._deps = {
compilersArtefacts: this._components.registry.get('compilersartefacts').api,
offsetToLineColumnConverter: this._components.registry.get('offsettolinecolumnconverter').api,
config: this._components.registry.get('config').api,
fileManager: this._components.registry.get('filemanager').api
}
this._view
this._nodes
this._current
this.sourceMappingDecoder = new SourceMappingDecoder()
this.previousElement = null
self.contextualListener.event.register('contextChanged', nodes => {
this.contextualListener.event.register('contextChanged', nodes => {
this.show()
this._nodes = nodes
this.update()
})
this.contextualListener.event.register('stopHighlighting', () => {
})
}
render () {
var view = yo`<div class=${css.contextview}>
const view = yo`<div class="${css.contextview} ${css.contextviewcontainer} badge bg-light border-0">
<div class=${css.container}>
${this._renderTarget()}
</div>
</div>`
if (!this._view) {
this._view = view
this.hide()
}
return view
}
@ -65,18 +66,18 @@ class ContextView {
update () {
if (this._view) {
yo.update(this._view, this.render())
this._view.style.display = this._current ? 'block' : 'none'
}
}
_renderTarget () {
var previous = this._current
let last
const previous = this._current
if (this._nodes && this._nodes.length) {
var last = this._nodes[this._nodes.length - 1]
last = this._nodes[this._nodes.length - 1]
if (isDefinition(last)) {
this._current = last
} else {
var target = this.contextualListener.declarationOf(last)
const target = this.contextualListener.declarationOf(last)
if (target) {
this._current = target
} else {
@ -91,27 +92,26 @@ class ContextView {
}
_jumpToInternal (position) {
var self = this
function jumpToLine (lineColumn) {
const jumpToLine = (lineColumn) => {
if (lineColumn.start && lineColumn.start.line && lineColumn.start.column) {
self.editor.gotoLine(lineColumn.start.line, lineColumn.end.column + 1)
this.editor.gotoLine(lineColumn.start.line, lineColumn.end.column + 1)
}
}
let lastCompilationResult = self._deps.compilersArtefacts['__last']
if (lastCompilationResult && lastCompilationResult.data) {
var lineColumn = self._deps.offsetToLineColumnConverter.offsetToLineColumn(
let lastCompilationResult = this._deps.compilersArtefacts['__last']
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) {
const lineColumn = this._deps.offsetToLineColumnConverter.offsetToLineColumn(
position,
position.file,
lastCompilationResult.getSourceCode().sources,
lastCompilationResult.getAsts())
var filename = lastCompilationResult.getSourceName(position.file)
const filename = lastCompilationResult.getSourceName(position.file)
// TODO: refactor with rendererAPI.errorClick
if (filename !== self._deps.config.get('currentFile')) {
var provider = self._deps.fileManager.fileProviderOf(filename)
if (filename !== this._deps.config.get('currentFile')) {
const provider = this._deps.fileManager.fileProviderOf(filename)
if (provider) {
provider.exists(filename, (error, exist) => {
if (error) return console.log(error)
self._deps.fileManager.switchFile(filename)
this._deps.fileManager.switchFile(filename)
jumpToLine(lineColumn)
})
}
@ -123,14 +123,13 @@ class ContextView {
_render (node, nodeAtCursorPosition) {
if (!node) return yo`<div></div>`
var self = this
var references = self.contextualListener.referencesOf(node)
var type = (node.attributes && node.attributes.type) ? node.attributes.type : node.name
let references = this.contextualListener.referencesOf(node)
const type = (node.attributes && node.attributes.type) ? node.attributes.type : node.name
references = `${references ? references.length : '0'} reference(s)`
var ref = 0
var nodes = self.contextualListener.getActiveHighlights()
for (var k in nodes) {
let ref = 0
const nodes = this.contextualListener.getActiveHighlights()
for (const k in nodes) {
if (nodeAtCursorPosition.id === nodes[k].nodeId) {
ref = k
break
@ -138,44 +137,43 @@ class ContextView {
}
// JUMP BETWEEN REFERENCES
function jump (e) {
const jump = (e) => {
e.target.dataset.action === 'next' ? ref++ : ref--
if (ref < 0) ref = nodes.length - 1
if (ref >= nodes.length) ref = 0
self._jumpToInternal(nodes[ref].position)
this._jumpToInternal(nodes[ref].position)
}
function jumpTo () {
const jumpTo = () => {
if (node && node.src) {
var position = self.sourceMappingDecoder.decode(node.src)
const position = this.sourceMappingDecoder.decode(node.src)
if (position) {
self._jumpToInternal(position)
this._jumpToInternal(position)
}
}
}
return yo`<div class=${css.line}>
<div title=${type} class=${css.type}>${type}</div>
<div title=${node.attributes.name} class=${css.name}>${node.attributes.name}</div>
<i class="fa fa-share ${css.jump}" aria-hidden="true" onclick=${jumpTo}></i>
<span class=${css.referencesnb}>${references}</span>
<i data-action='previous' class="fa fa-chevron-up ${css.jump}" aria-hidden="true" onclick=${jump}></i>
<i data-action='next' class="fa fa-chevron-down ${css.jump}" aria-hidden="true" onclick=${jump}></i>
${showGasEstimation()}
</div>`
function showGasEstimation () {
const showGasEstimation = () => {
if (node.name === 'FunctionDefinition') {
var result = self.contextualListener.gasEstimation(node)
var executionCost = 'Execution cost: ' + result.executionCost + ' gas'
var codeDepositCost = 'Code deposit cost: ' + result.codeDepositCost + ' gas'
var estimatedGas = result.codeDepositCost ? `${codeDepositCost}, ${executionCost}` : `${executionCost}`
const result = this.contextualListener.gasEstimation(node)
const executionCost = 'Execution cost: ' + result.executionCost + ' gas'
const codeDepositCost = 'Code deposit cost: ' + result.codeDepositCost + ' gas'
const estimatedGas = result.codeDepositCost ? `${codeDepositCost}, ${executionCost}` : `${executionCost}`
return yo`<div class=${css.gasEstimation}>
<img class=${css.gasStationIcon} title='Gas estimation' src='assets/img/gasStation_50.png'>
${estimatedGas}
</div>`
}
}
return yo`<div class=${css.line}>${showGasEstimation()}
<div title=${type} class=${css.type}>${type}</div>
<div title=${node.attributes.name} class=${css.name}>${node.attributes.name}</div>
<i class="fas fa-share ${css.jump}" aria-hidden="true" onclick=${jumpTo}></i>
<span class=${css.referencesnb}>${references}</span>
<i data-action='previous' class="fas fa-chevron-up ${css.jump}" aria-hidden="true" onclick=${jump}></i>
<i data-action='next' class="fas fa-chevron-down ${css.jump}" aria-hidden="true" onclick=${jump}></i>
</div>`
}
}

@ -1,25 +1,24 @@
'use strict'
var remixLib = require('remix-lib')
var SourceMappingDecoder = remixLib.SourceMappingDecoder
var AstWalker = remixLib.AstWalker
var EventManager = require('../../lib/events')
var globalRegistry = require('../../global/registry')
const remixLib = require('remix-lib')
const SourceMappingDecoder = remixLib.SourceMappingDecoder
const AstWalker = remixLib.AstWalker
const EventManager = require('../../lib/events')
const globalRegistry = require('../../global/registry')
/*
trigger contextChanged(nodes)
*/
class ContextualListener {
constructor (opts, localRegistry) {
var self = this
this.event = new EventManager()
self._components = {}
self._components.registry = localRegistry || globalRegistry
self.editor = opts.editor
self.pluginManager = opts.pluginManager
self._deps = {
compilersArtefacts: self._components.registry.get('compilersartefacts').api,
config: self._components.registry.get('config').api,
offsetToLineColumnConverter: self._components.registry.get('offsettolinecolumnconverter').api
this._components = {}
this._components.registry = localRegistry || globalRegistry
this.editor = opts.editor
this.pluginManager = opts.pluginManager
this._deps = {
compilersArtefacts: this._components.registry.get('compilersartefacts').api,
config: this._components.registry.get('config').api,
offsetToLineColumnConverter: this._components.registry.get('offsettolinecolumnconverter').api
}
this._index = {
Declarations: {},
@ -27,7 +26,8 @@ class ContextualListener {
}
this._activeHighlights = []
self.pluginManager.event.register('sendCompilationResult', (file, source, languageVersion, data) => {
this.pluginManager.event.register('sendCompilationResult', (file, source, languageVersion, data) => {
if (languageVersion.indexOf('soljson') !== 0) return
this._stopHighlighting()
this._index = {
Declarations: {},
@ -36,13 +36,13 @@ class ContextualListener {
this._buildIndex(data, source)
})
self.editor.event.register('contentChanged', () => { this._stopHighlighting() })
this.editor.event.register('contentChanged', () => { this._stopHighlighting() })
this.sourceMappingDecoder = new SourceMappingDecoder()
this.astWalker = new AstWalker()
setInterval(() => {
if (self._deps.compilersArtefacts['__last']) {
this._highlightItems(self.editor.getCursorPosition(), self._deps.compilersArtefacts['__last'], self._deps.config.get('currentFile'))
if (this._deps.compilersArtefacts['__last'] && this._deps.compilersArtefacts['__last'].languageversion.indexOf('soljson') === 0) {
this._highlightItems(this.editor.getCursorPosition(), this._deps.compilersArtefacts['__last'], this._deps.config.get('currentFile'))
}
}, 1000)
}
@ -73,7 +73,7 @@ class ContextualListener {
this.currentPosition = cursorPosition
this.currentFile = file
if (compilationResult && compilationResult.data && compilationResult.data.sources[file]) {
var nodes = this.sourceMappingDecoder.nodesAtPosition(null, cursorPosition, compilationResult.data.sources[file])
const nodes = this.sourceMappingDecoder.nodesAtPosition(null, cursorPosition, compilationResult.data.sources[file])
this.nodes = nodes
if (nodes && nodes.length && nodes[nodes.length - 1]) {
this._highlightExpressions(nodes[nodes.length - 1], compilationResult)
@ -84,19 +84,18 @@ class ContextualListener {
_buildIndex (compilationResult, source) {
if (compilationResult && compilationResult.sources) {
var self = this
var callback = {}
callback['*'] = function (node) {
const callback = {}
callback['*'] = (node) => {
if (node && node.attributes && node.attributes.referencedDeclaration) {
if (!self._index['Declarations'][node.attributes.referencedDeclaration]) {
self._index['Declarations'][node.attributes.referencedDeclaration] = []
if (!this._index['Declarations'][node.attributes.referencedDeclaration]) {
this._index['Declarations'][node.attributes.referencedDeclaration] = []
}
self._index['Declarations'][node.attributes.referencedDeclaration].push(node)
this._index['Declarations'][node.attributes.referencedDeclaration].push(node)
}
self._index['FlatReferences'][node.id] = node
this._index['FlatReferences'][node.id] = node
return true
}
for (var s in compilationResult.sources) {
for (const s in compilationResult.sources) {
this.astWalker.walk(compilationResult.sources[s].legacyAST, callback)
}
}
@ -104,21 +103,19 @@ class ContextualListener {
_highlight (node, compilationResult) {
if (!node) return
var self = this
var position = this.sourceMappingDecoder.decode(node.src)
var eventId = this._highlightInternal(position, node)
let lastCompilationResult = self._deps.compilersArtefacts['__last']
if (eventId && lastCompilationResult) {
const position = this.sourceMappingDecoder.decode(node.src)
const eventId = this._highlightInternal(position, node)
let lastCompilationResult = this._deps.compilersArtefacts['__last']
if (eventId && lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0) {
this._activeHighlights.push({ eventId, position, fileTarget: lastCompilationResult.getSourceName(position.file), nodeId: node.id })
}
}
_highlightInternal (position, node) {
var self = this
let lastCompilationResult = self._deps.compilersArtefacts['__last']
if (lastCompilationResult) {
var lineColumn = self._deps.offsetToLineColumnConverter.offsetToLineColumn(position, position.file, lastCompilationResult.getSourceCode().sources, lastCompilationResult.getAsts())
var css = 'highlightreference'
let lastCompilationResult = this._deps.compilersArtefacts['__last']
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0) {
let lineColumn = this._deps.offsetToLineColumnConverter.offsetToLineColumn(position, position.file, lastCompilationResult.getSourceCode().sources, lastCompilationResult.getAsts())
let css = 'highlightreference'
if (node.children && node.children.length) {
// If node has children, highlight the entire line. if not, just highlight the current source position of the node.
css = 'highlightreference'
@ -133,28 +130,27 @@ class ContextualListener {
}
}
}
var fileName = lastCompilationResult.getSourceName(position.file)
const fileName = lastCompilationResult.getSourceName(position.file)
if (fileName) {
return self.editor.addMarker(lineColumn, fileName, css)
return this.editor.addMarker(lineColumn, fileName, css)
}
}
return null
}
_highlightExpressions (node, compilationResult) {
var self = this
function highlights (id) {
if (self._index['Declarations'] && self._index['Declarations'][id]) {
var refs = self._index['Declarations'][id]
for (var ref in refs) {
var node = refs[ref]
self._highlight(node, compilationResult)
const highlights = (id) => {
if (this._index['Declarations'] && this._index['Declarations'][id]) {
const refs = this._index['Declarations'][id]
for (const ref in refs) {
const node = refs[ref]
this._highlight(node, compilationResult)
}
}
}
if (node.attributes && node.attributes.referencedDeclaration) {
highlights(node.attributes.referencedDeclaration)
var current = this._index['FlatReferences'][node.attributes.referencedDeclaration]
const current = this._index['FlatReferences'][node.attributes.referencedDeclaration]
this._highlight(current, compilationResult)
} else {
highlights(node.id)
@ -164,23 +160,22 @@ class ContextualListener {
}
_stopHighlighting () {
var self = this
for (var eventKey in this._activeHighlights) {
var event = this._activeHighlights[eventKey]
self.editor.removeMarker(event.eventId, event.fileTarget)
for (const eventKey in this._activeHighlights) {
const event = this._activeHighlights[eventKey]
this.editor.removeMarker(event.eventId, event.fileTarget)
}
this.event.trigger('stopHighlighting', [])
this._activeHighlights = []
}
gasEstimation (node) {
this._loadContractInfos(node)
var executionCost
var codeDepositCost
let executionCost, codeDepositCost
if (node.name === 'FunctionDefinition') {
var visibility = node.attributes.visibility
const visibility = node.attributes.visibility
if (!node.attributes.isConstructor) {
var fnName = node.attributes.name
var fn = fnName + this._getInputParams(node)
const fnName = node.attributes.name
const fn = fnName + this._getInputParams(node)
if (visibility === 'public' || visibility === 'external') {
executionCost = this.estimationObj.external[fn]
} else if (visibility === 'private' || visibility === 'internal') {
@ -197,9 +192,9 @@ class ContextualListener {
}
_loadContractInfos (node) {
for (var i in this.nodes) {
for (const i in this.nodes) {
if (this.nodes[i].id === node.attributes.scope) {
var contract = this.nodes[i]
const contract = this.nodes[i]
this.contract = this.results.data.contracts[this.results.source.target][contract.attributes.name]
this.estimationObj = this.contract.evm.gasEstimates
this.creationCost = this.estimationObj.creation.totalCost
@ -209,16 +204,17 @@ class ContextualListener {
}
_getInputParams (node) {
var params = []
for (var i in node.children) {
const params = []
let target
for (const i in node.children) {
if (node.children[i].name === 'ParameterList') {
var target = node.children[i]
target = node.children[i]
break
}
}
if (target) {
var children = target.children
for (var j in children) {
const children = target.children
for (const j in children) {
if (children[j].name === 'VariableDeclaration') {
params.push(children[j].attributes.type)
}

@ -1,37 +1,25 @@
'use strict'
var EventManager = require('../../lib/events')
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var ace = require('brace')
const EventManager = require('../../lib/events')
const yo = require('yo-yo')
const csjs = require('csjs-inject')
const ace = require('brace')
require('brace/theme/tomorrow_night_blue')
const globalRegistry = require('../../global/registry')
const SourceHighlighters = require('./SourceHighlighters')
var globalRegistry = require('../../global/registry')
var Range = ace.acequire('ace/range').Range
const Range = ace.acequire('ace/range').Range
require('brace/ext/language_tools')
require('brace/ext/searchbox')
var langTools = ace.acequire('ace/ext/language_tools')
const langTools = ace.acequire('ace/ext/language_tools')
require('ace-mode-solidity/build/remix-ide/mode-solidity')
require('brace/mode/javascript')
require('brace/mode/python')
require('brace/mode/json')
var styleGuide = require('../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
function setTheme (cb) {
if (styles.appProperties.aceTheme) {
cb('brace/theme/', styles.appProperties.aceTheme)
}
}
setTheme((path, theme) => {
require('brace/theme/tomorrow_night_blue')
})
require('brace/theme/chaos')
require('brace/theme/chrome')
var css = csjs`
const css = csjs`
.ace-editor {
background-color : ${styles.editor.backgroundColor_Editor};
width : 100%;
}
`
@ -40,121 +28,211 @@ document.head.appendChild(yo`
.ace-tm .ace_gutter,
.ace-tm .ace_gutter-active-line,
.ace-tm .ace_marker-layer .ace_active-line {
background-color: ${styles.editor.backgroundColor_Tabs_Highlights};
background-color: var(--secondary);
}
.ace_gutter-cell.ace_breakpoint{
background-color: ${styles.editor.backgroundColor_DebuggerMode};
background-color: var(--secondary);
}
.highlightreference {
position:absolute;
z-index:20;
background-color: ${styles.editor.backgroundColor_Editor_Context_Highlights};
opacity: 0.7
</style>
`)
class Editor {
constructor (opts = {}, localRegistry) {
// Dependancies
this._components = {}
this._components.registry = localRegistry || globalRegistry
this._deps = {
themeModule: this._components.registry.get('themeModule').api,
fileManager: this._components.registry.get('filemanager').api,
config: this._components.registry.get('config').api
}
.highlightreferenceline {
position:absolute;
z-index:20;
background-color: ${styles.editor.backgroundColor_Editor_Context_Highlights};
opacity: 0.7
this._themes = {
'light': 'chrome',
'dark': 'chaos'
}
this._deps.themeModule.events.on('themeChanged', (theme) => {
this.setTheme(theme.quality)
})
.highlightcode {
position:absolute;
z-index:20;
background-color: ${styles.editor.backgroundColor_Editor_Context_Error_Highlights};
}
</style>
`)
// Init
this.event = new EventManager()
this.sessions = {}
this.sourceAnnotations = []
this.readOnlySessions = {}
this.previousInput = ''
this.saveTimeout = null
this.sourceHighlighters = new SourceHighlighters()
this.emptySession = this._createSession('')
this.modes = {
sol: 'ace/mode/solidity',
js: 'ace/mode/javascript',
py: 'ace/mode/python',
vy: 'ace/mode/python',
txt: 'ace/mode/text',
json: 'ace/mode/json',
abi: 'ace/mode/json'
}
function Editor (opts = {}, localRegistry) {
var self = this
var el = yo`<div id="input"></div>`
var editor = ace.edit(el)
if (styles.appProperties.aceTheme) {
editor.setTheme('ace/theme/' + styles.appProperties.aceTheme)
}
self._components = {}
self._components.registry = localRegistry || globalRegistry
self._deps = {
fileManager: self._components.registry.get('filemanager').api,
config: self._components.registry.get('config').api
}
// Editor Setup
const el = yo`<div id="input"></div>`
this.editor = ace.edit(el)
ace.acequire('ace/ext/language_tools')
// Unmap ctrl-l & cmd-l
this.editor.commands.bindKeys({
'ctrl-L': null,
'Command-L': null
})
ace.acequire('ace/ext/language_tools')
editor.setOptions({
enableBasicAutocompletion: true,
enableLiveAutocompletion: true
})
var flowCompleter = {
getCompletions: function (editor, session, pos, prefix, callback) {
// @TODO add here other propositions
// shortcuts for "Ctrl-"" and "Ctrl+"" to increase/decrease font size of the editor
this.editor.commands.addCommand({
name: 'increasefontsizeEqual',
bindKey: {win: 'Ctrl-=', mac: 'Command-='},
exec: (editor) => {
this.editorFontSize(1)
},
readOnly: true
})
this.editor.commands.addCommand({
name: 'increasefontsizePlus',
bindKey: {win: 'Ctrl-+', mac: 'Command-+'},
exec: (editor) => {
this.editorFontSize(1)
},
readOnly: true
})
this.editor.commands.addCommand({
name: 'decreasefontsize',
bindKey: {win: 'Ctrl--', mac: 'Command--'},
exec: (editor) => {
this.editorFontSize(-1)
},
readOnly: true
})
this.editor.setShowPrintMargin(false)
this.editor.resize(true)
this.editor.setOptions({
enableBasicAutocompletion: true,
enableLiveAutocompletion: true
})
el.className += ' ' + css['ace-editor']
el.editor = this.editor // required to access the editor during tests
this.render = () => el
// Completer for editor
const flowCompleter = {
getCompletions: (editor, session, pos, prefix, callback) => {
// @TODO add here other propositions
}
}
langTools.addCompleter(flowCompleter)
// zoom with Ctrl+wheel
window.addEventListener('wheel', (e) => {
if (e.ctrlKey && Math.abs(e.wheelY) > 5) {
this.editorFontSize(e.wheelY > 0 ? 1 : -1)
}
})
// EVENTS LISTENERS
// Gutter Mouse down
this.editor.on('guttermousedown', e => {
const target = e.domEvent.target
if (target.className.indexOf('ace_gutter-cell') === -1) {
return
}
const row = e.getDocumentPosition().row
const breakpoints = e.editor.session.getBreakpoints()
for (const k in breakpoints) {
if (k === row.toString()) {
this.event.trigger('breakpointCleared', [this.currentSession, row])
e.editor.session.clearBreakpoint(row)
e.stop()
return
}
}
this.setBreakpoint(row)
this.event.trigger('breakpointAdded', [this.currentSession, row])
e.stop()
})
// Do setup on initialisation here
this.editor.on('changeSession', () => {
this._onChange()
this.event.trigger('sessionSwitched', [])
this.editor.getSession().on('change', () => {
this._onChange()
this.event.trigger('contentChanged', [])
})
})
}
langTools.addCompleter(flowCompleter)
el.className += ' ' + css['ace-editor']
el.editor = editor // required to access the editor during tests
self.render = function () { return el }
var event = new EventManager()
self.event = event
var sessions = {}
var sourceAnnotations = []
var readOnlySessions = {}
var currentSession
var emptySession = createSession('')
var modes = {
'sol': 'ace/mode/solidity',
'js': 'ace/mode/javascript',
'py': 'ace/mode/python',
'vy': 'ace/mode/python',
'txt': 'ace/mode/text',
'json': 'ace/mode/json',
'abi': 'ace/mode/json'
setTheme (type) {
this.editor.setTheme('ace/theme/' + this._themes[type])
}
editor.on('guttermousedown', function (e) {
var target = e.domEvent.target
if (target.className.indexOf('ace_gutter-cell') === -1) {
_onChange () {
const currentFile = this._deps.config.get('currentFile')
if (!currentFile) {
return
}
var row = e.getDocumentPosition().row
var breakpoints = e.editor.session.getBreakpoints()
for (var k in breakpoints) {
if (k === row.toString()) {
event.trigger('breakpointCleared', [currentSession, row])
e.editor.session.clearBreakpoint(row)
e.stop()
return
}
const input = this.get(currentFile)
if (!input) {
return
}
self.setBreakpoint(row)
event.trigger('breakpointAdded', [currentSession, row])
e.stop()
})
this.displayEmptyReadOnlySession = function () {
currentSession = null
editor.setSession(emptySession)
editor.setReadOnly(true)
}
// if there's no change, don't do anything
if (input === this.previousInput) {
return
}
this.previousInput = input
this.setBreakpoint = function (row, css) {
editor.session.setBreakpoint(row, css)
// fire storage update
// NOTE: save at most once per 5 seconds
if (this.saveTimeout) {
window.clearTimeout(this.saveTimeout)
}
this.saveTimeout = window.setTimeout(() => {
this._deps.fileManager.saveCurrentFile()
}, 5000)
}
this.editorFontSize = function (incr) {
editor.setFontSize(editor.getFontSize() + incr)
_switchSession (path) {
this.currentSession = path
this.editor.setSession(this.sessions[this.currentSession])
this.editor.setReadOnly(this.readOnlySessions[this.currentSession])
this.editor.focus()
}
this.setText = function (text) {
if (currentSession && sessions[currentSession]) {
sessions[currentSession].setValue(text)
}
/**
* Get Ace mode base of the extension of the session file
* @param {string} path Path of the file
*/
_getMode (path) {
let ext = path.indexOf('.') !== -1 ? /[^.]+/.exec(path) : null
if (ext) ext = path.replace(ext[0] + '.', '') // we get <ext>
ext = ext.split('#')
if (!ext.length) return this.modes['txt']
ext = ext[0]
return ext && this.modes[ext] ? this.modes[ext] : this.modes['txt']
}
function createSession (content, mode) {
var s = new ace.EditSession(content)
/**
* Create an Ace session
* @param {string} content Content of the file to open
* @param {string} mode Ace Mode for this file [Default is `text`]
*/
_createSession (content, mode) {
const s = new ace.EditSession(content)
s.setMode(mode || 'ace/mode/text')
s.setUndoManager(new ace.UndoManager())
s.setTabSize(4)
@ -162,98 +240,159 @@ function Editor (opts = {}, localRegistry) {
return s
}
function switchSession (path) {
currentSession = path
editor.setSession(sessions[currentSession])
editor.setReadOnly(readOnlySessions[currentSession])
editor.focus()
/**
* Attempts to find the string in the current document
* @param {string} string
*/
find (string) {
return this.editor.find(string)
}
/**
* Display an Empty read-only session
*/
displayEmptyReadOnlySession () {
this.currentSession = null
this.editor.setSession(this.emptySession)
this.editor.setReadOnly(true)
}
function getMode (path) {
var ext = path.indexOf('.') !== -1 ? /[^.]+$/.exec(path) : null
if (ext) ext = ext[0]
return ext && modes[ext] ? modes[ext] : modes['txt']
/**
* Sets a breakpoint on the row number
* @param {number} row Line index of the breakpoint
* @param {string} className Class of the breakpoint
*/
setBreakpoint (row, className) {
this.editor.session.setBreakpoint(row, className)
}
this.open = function (path, content) {
if (!sessions[path]) {
var session = createSession(content, getMode(path))
sessions[path] = session
readOnlySessions[path] = false
} else if (sessions[path].getValue() !== content) {
sessions[path].setValue(content)
/**
* Increment the font size (in pixels) for the editor text.
* @param {number} incr The amount of pixels to add to the font.
*/
editorFontSize (incr) {
let newSize = this.editor.getFontSize() + incr
if (newSize >= 6) {
this.editor.setFontSize(newSize)
}
}
/**
* Set the text in the current session, if any.
* @param {string} text New text to be place.
*/
setText (text) {
if (this.currentSession && this.sessions[this.currentSession]) {
this.sessions[this.currentSession].setValue(text)
}
switchSession(path)
}
this.openReadOnly = function (path, content) {
if (!sessions[path]) {
var session = createSession(content, getMode(path))
sessions[path] = session
readOnlySessions[path] = true
/**
* Upsert and open a session.
* @param {string} path Path of the session to open.
* @param {string} content Content of the document or update.
*/
open (path, content) {
if (!this.sessions[path]) {
const session = this._createSession(content, this._getMode(path))
this.sessions[path] = session
this.readOnlySessions[path] = false
} else if (this.sessions[path].getValue() !== content) {
this.sessions[path].setValue(content)
}
switchSession(path)
this._switchSession(path)
}
/**
* returns the content of the current session
*
* @return {String} content of the file referenced by @arg path
*/
this.currentContent = function () {
* Upsert and Open a session and set it as Read-only.
* @param {string} path Path of the session to open.
* @param {string} content Content of the document or update.
*/
openReadOnly (path, content) {
if (!this.sessions[path]) {
const session = this._createSession(content, this._getMode(path))
this.sessions[path] = session
this.readOnlySessions[path] = true
}
this._switchSession(path)
}
/**
* Content of the current session
* @return {String} content of the file referenced by @arg path
*/
currentContent () {
return this.get(this.current())
}
/**
* returns the content of the session targeted by @arg path
* if @arg path is null, the content of the current session is returned
*
* @return {String} content of the file referenced by @arg path
*/
this.get = function (path) {
if (!path || currentSession === path) {
return editor.getValue()
} else if (sessions[path]) {
return sessions[path].getValue()
* Content of the session targeted by @arg path
* if @arg path is null, the content of the current session is returned
* @param {string} path Path of the session to get.
* @return {String} content of the file referenced by @arg path
*/
get (path) {
if (!path || this.currentSession === path) {
return this.editor.getValue()
} else if (this.sessions[path]) {
return this.sessions[path].getValue()
}
}
/**
* returns the path of the currently editing file
* returns `undefined` if no session is being editer
*
* @return {String} path of the current session
*/
this.current = function () {
if (editor.getSession() === emptySession) {
* Path of the currently editing file
* returns `undefined` if no session is being editer
* @return {String} path of the current session
*/
current () {
if (this.editor.getSession() === this.emptySession) {
return
}
return currentSession
return this.currentSession
}
this.getCursorPosition = function () {
return editor.session.doc.positionToIndex(editor.getCursorPosition(), 0)
/**
* The position of the cursor
*/
getCursorPosition () {
return this.editor.session.doc.positionToIndex(
this.editor.getCursorPosition(),
0
)
}
this.discardCurrentSession = function () {
if (sessions[currentSession]) {
delete sessions[currentSession]
currentSession = null
/**
* Remove the current session from the list of sessions.
*/
discardCurrentSession () {
if (this.sessions[this.currentSession]) {
delete this.sessions[this.currentSession]
this.currentSession = null
}
}
this.discard = function (path) {
if (sessions[path]) delete sessions[path]
if (currentSession === path) currentSession = null
/**
* Remove a session based on its path.
* @param {string} path
*/
discard (path) {
if (this.sessions[path]) delete this.sessions[path]
if (this.currentSession === path) this.currentSession = null
}
this.resize = function (useWrapMode) {
editor.resize()
var session = editor.getSession()
/**
* Resize the editor, and sets whether or not line wrapping is enabled.
* @param {boolean} useWrapMode Enable (or disable) wrap mode
*/
resize (useWrapMode) {
this.editor.resize()
const session = this.editor.getSession()
session.setUseWrapMode(useWrapMode)
if (session.getUseWrapMode()) {
var characterWidth = editor.renderer.characterWidth
var contentWidth = editor.container.ownerDocument.getElementsByClassName('ace_scroller')[0].clientWidth
const characterWidth = this.editor.renderer.characterWidth
const contentWidth = this.editor.container.ownerDocument.getElementsByClassName(
'ace_scroller'
)[0].clientWidth
if (contentWidth > 0) {
session.setWrapLimit(parseInt(contentWidth / characterWidth, 10))
@ -261,87 +400,81 @@ function Editor (opts = {}, localRegistry) {
}
}
this.addMarker = function (lineColumnPos, source, cssClass) {
var currentRange = new Range(lineColumnPos.start.line, lineColumnPos.start.column, lineColumnPos.end.line, lineColumnPos.end.column)
if (sessions[source]) {
return sessions[source].addMarker(currentRange, cssClass)
/**
* Adds a new marker to the given `Range`.
* @param {*} lineColumnPos
* @param {string} source Path of the session to add the mark on.
* @param {string} cssClass css to apply to the mark.
*/
addMarker (lineColumnPos, source, cssClass) {
const currentRange = new Range(
lineColumnPos.start.line,
lineColumnPos.start.column,
lineColumnPos.end.line,
lineColumnPos.end.column
)
if (this.sessions[source]) {
return this.sessions[source].addMarker(currentRange, cssClass)
}
return null
}
this.scrollToLine = function (line, center, animate, callback) {
editor.scrollToLine(line, center, animate, callback)
/**
* Scrolls to a line. If center is true, it puts the line in middle of screen (or attempts to).
* @param {number} line The line to scroll to
* @param {boolean} center If true
* @param {boolean} animate If true animates scrolling
* @param {Function} callback Function to be called when the animation has finished
*/
scrollToLine (line, center, animate, callback) {
this.editor.scrollToLine(line, center, animate, callback)
}
this.removeMarker = function (markerId, source) {
if (sessions[source]) {
sessions[source].removeMarker(markerId)
/**
* Remove a marker from the session
* @param {string} markerId Id of the marker
* @param {string} source Path of the session
*/
removeMarker (markerId, source) {
if (this.sessions[source]) {
this.sessions[source].removeMarker(markerId)
}
}
this.clearAnnotations = function () {
sourceAnnotations = []
editor.getSession().clearAnnotations()
}
this.addAnnotation = function (annotation) {
sourceAnnotations[sourceAnnotations.length] = annotation
this.setAnnotations(sourceAnnotations)
}
this.setAnnotations = function (sourceAnnotations) {
editor.getSession().setAnnotations(sourceAnnotations)
/**
* Clears all the annotations for the current session.
*/
clearAnnotations () {
this.sourceAnnotations = []
this.editor.getSession().clearAnnotations()
}
this.gotoLine = function (line, col) {
editor.focus()
editor.gotoLine(line + 1, col - 1, true)
/**
* Add an annotation to the current session.
* @param {Object} annotation
*/
addAnnotation (annotation) {
this.sourceAnnotations[this.sourceAnnotations.length] = annotation
this.setAnnotations(this.sourceAnnotations)
}
this.find = (string) => editor.find(string)
this.previousInput = ''
this.saveTimeout = null
// Do setup on initialisation here
editor.on('changeSession', function () {
editorOnChange(self)
event.trigger('sessionSwitched', [])
editor.getSession().on('change', function () {
editorOnChange(self)
event.trigger('contentChanged', [])
})
})
// Unmap ctrl-t & ctrl-f
editor.commands.bindKeys({ 'ctrl-t': null })
editor.setShowPrintMargin(false)
editor.resize(true)
}
function editorOnChange (self) {
var currentFile = self._deps.config.get('currentFile')
if (!currentFile) {
return
}
var input = self.get(currentFile)
if (!input) {
return
}
// if there's no change, don't do anything
if (input === self.previousInput) {
return
/**
* Set a list of annotations to the current session.
* @param {Array<Object>} annotation
*/
setAnnotations (sourceAnnotations) {
this.editor.getSession().setAnnotations(sourceAnnotations)
}
self.previousInput = input
// fire storage update
// NOTE: save at most once per 5 seconds
if (self.saveTimeout) {
window.clearTimeout(self.saveTimeout)
/**
* Moves the cursor and focus to the specified line and column number
* @param {number} line
* @param {number} col
*/
gotoLine (line, col) {
this.editor.focus()
this.editor.gotoLine(line + 1, col - 1, true)
}
self.saveTimeout = window.setTimeout(() => {
self._deps.fileManager.saveCurrentFile()
}, 5000)
}
module.exports = Editor

@ -1,20 +1,17 @@
'use strict'
var csjs = require('csjs-inject')
var globlalRegistry = require('../../global/registry')
var styleGuide = require('../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
const csjs = require('csjs-inject')
const globlalRegistry = require('../../global/registry')
class SourceHighlighter {
constructor (localRegistry) {
const self = this
self._components = {}
self._components.registry = localRegistry || globlalRegistry
this._components = {}
this._components.registry = localRegistry || globlalRegistry
// dependencies
self._deps = {
editor: self._components.registry.get('editor').api,
config: self._components.registry.get('config').api,
fileManager: self._components.registry.get('filemanager').api,
compilerArtefacts: self._components.registry.get('compilersartefacts').api
this._deps = {
editor: this._components.registry.get('editor').api,
config: this._components.registry.get('config').api,
fileManager: this._components.registry.get('filemanager').api,
compilerArtefacts: this._components.registry.get('compilersartefacts').api
}
this.statementMarker = null
this.fullLineMarker = null
@ -26,7 +23,7 @@ class SourceHighlighter {
if (this.fullLineMarker) this._deps.editor.removeMarker(this.fullLineMarker, this.source)
let lastCompilationResult = this._deps.compilerArtefacts['__last']
if (location && location.file !== undefined && lastCompilationResult) {
var path = lastCompilationResult.getSourceName(location.file)
const path = lastCompilationResult.getSourceName(location.file)
if (path) {
this.currentSourceLocationFromfileName(lineColumnPos, path)
}
@ -45,21 +42,24 @@ class SourceHighlighter {
this._deps.fileManager.switchFile(this.source)
}
var css = csjs`
const css = csjs`
.highlightcode {
position:absolute;
z-index:20;
background-color: ${style || styles.editor.backgroundColor_DebuggerMode};
background-color: ${style || 'var(--info)'};
}
.highlightcode_fullLine {
position:absolute;
z-index:20;
background-color: ${style || styles.editor.backgroundColor_DebuggerMode};
opacity: 0.5;
background-color: ${style || 'var(--info)'};
}
.customBackgroundColor {
background-color: ${style || 'var(--info)'};
}
`
this.statementMarker = this._deps.editor.addMarker(lineColumnPos, this.source, css.highlightcode)
this.statementMarker = this._deps.editor.addMarker(lineColumnPos, this.source, css.highlightcode.className + ' ' + css.customBackgroundColor.className)
this._deps.editor.scrollToLine(lineColumnPos.start.line, true, true, function () {})
if (lineColumnPos.start.line === lineColumnPos.end.line) {
@ -72,7 +72,7 @@ class SourceHighlighter {
line: lineColumnPos.start.line + 1,
column: 0
}
}, this.source, css.highlightcode_fullLine)
}, this.source, css.highlightcode_fullLine.className)
}
}
}

@ -1,11 +1,11 @@
var csjs = require('csjs-inject')
var styleGuide = require('../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs`
.contextview {
opacity : 0.8;
}
opacity : 1;
position : relative;
height : 20px;
}
.container {
padding : 1px 15px;
}
@ -16,8 +16,7 @@ var css = csjs`
text-overflow : ellipsis;
overflow : hidden;
white-space : nowrap;
color : ${styles.editor.text_Primary};
font-size : 11px;
font-size : 13px;
}
.type {
font-style : italic;
@ -29,17 +28,16 @@ var css = csjs`
.jump {
cursor : pointer;
margin : 0 5px;
color : ${styles.editor.icon_Color_Editor};
}
.jump:hover {
color : ${styles.editor.icon_HoverColor_Editor};
color : var(--secondary);
}
.referencesnb {
float : right;
margin-left : 15px;
}
.gasEstimation {
margin-left: 15px;
margin-right: 15px;
display: flex;
align-items: center;
}
@ -47,6 +45,11 @@ var css = csjs`
height: 13px;
margin-right: 5px;
}
.contextviewcontainer{
z-index : 50;
border-radius : 1px;
border : 2px solid var(--secondary);
}
`
module.exports = css

@ -1,11 +1,9 @@
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var styleGuide = require('../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
const copyToClipboard = require('../ui/copy-to-clipboard')
var css = csjs`
.txInfoBox {
${styles.rightPanel.compileTab.box_CompileContainer}; // add askToConfirmTXContainer to Remix and then replace this styling
}
.wrapword {
white-space: pre-wrap; /* Since CSS 2.1 */
@ -38,11 +36,11 @@ function confirmDialog (tx, amount, gasEstimation, self, newGasPriceCb, initialP
<div>Gas price: <input id='gasprice' oninput=${onGasPriceChange} /> Gwei <span> (visit <a target='_blank' href='https://ethgasstation.info'>ethgasstation.info</a> to get more info about gas price)</span></div>
<div>Max transaction fee:<span id='txfee'></span></div>
<div>Data:</div>
<pre class=${css.wrapword}>${tx.data}</pre>
<pre class=${css.wrapword}>${tx.data && tx.data.length > 50 ? tx.data.substring(0, 49) + '...' : tx.data} ${copyToClipboard(() => { return tx.data })}</pre>
</div>
<div class=${css.checkbox}>
<input id='confirmsetting' type="checkbox">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Do not ask for confirmation again. (the setting will not be persisted for the next page reload)
<i class="fas fa-exclamation-triangle" aria-hidden="true"></i> Do not ask for confirmation again. (the setting will not be persisted for the next page reload)
</div>
</div>
`

@ -5,8 +5,6 @@ var copyToClipboard = require('../ui/copy-to-clipboard')
// -------------- styling ----------------------
var csjs = require('csjs-inject')
var remixLib = require('remix-lib')
var styleGuide = require('../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var EventManager = require('../../lib/events')
var helper = require('../../lib/helper')
@ -26,14 +24,14 @@ var css = csjs`
opacity: 0.8;
}
.arrow {
color: ${styles.terminal.icon_Color_Menu};
color: var(--text-info);
font-size: 20px;
cursor: pointer;
display: flex;
margin-left: 10px;
}
.arrow:hover {
color: ${styles.terminal.icon_HoverColor_Menu};
color: var(--secondary);
}
.txLog {
}
@ -44,28 +42,27 @@ var css = csjs`
float: left;
}
.succeeded {
color: ${styles.terminal.icon_Color_Log_Succeed};
color: var(--success);
}
.failed {
color: ${styles.terminal.icon_Color_Log_Failed};
color: var(--danger);
}
.notavailable {
}
.call {
font-size: 7px;
background-color: ${styles.terminal.icon_BackgroundColor_Log_Call};
border-radius: 50%;
min-width: 20px;
min-height: 20px;
display: flex;
justify-content: center;
align-items: center;
color: ${styles.terminal.icon_Color_Log_Call};
color: var(--text-info);
text-transform: uppercase;
font-weight: bold;
}
.txItem {
color: ${styles.terminal.text_Primary};
color: var(--text-info);
margin-right: 5px;
float: left;
}
@ -73,7 +70,7 @@ var css = csjs`
font-weight: bold;
}
.tx {
color: ${styles.terminal.text_Title_TransactionLog};
color: var(--text-info);
font-weight: bold;
float: left;
margin-right: 10px;
@ -83,8 +80,8 @@ var css = csjs`
.td {
border-collapse: collapse;
font-size: 10px;
color: ${styles.terminal.text_Primary};
border: 1px solid ${styles.terminal.text_Secondary};
color: var(--text-info);
border: 1px solid var(--text-info);
}
#txTable {
margin-top: 1%;
@ -110,12 +107,7 @@ var css = csjs`
margin-left: auto;
}
.debug {
${styles.terminal.button_Log_Debug}
width: 55px;
min-width: 55px;
min-height: 20px;
max-height: 20px;
font-size: 11px;
white-space: nowrap;
}
.debug:hover {
opacity: 0.8;
@ -142,8 +134,7 @@ class TxLogger {
editorPanel: this._components.registry.get('editorpanel').api,
txListener: this._components.registry.get('txlistener').api,
eventsDecoder: this._components.registry.get('eventsdecoder').api,
compilersArtefacts: this._components.registry.get('compilersartefacts').api,
app: this._components.registry.get('app').api
compilersArtefacts: this._components.registry.get('compilersartefacts').api
}
this.logKnownTX = this._deps.editorPanel.registerCommand('knownTransaction', (args, cmds, append) => {
@ -171,24 +162,8 @@ class TxLogger {
append(el)
}, { activate: true })
this._deps.editorPanel.event.register('terminalFilterChanged', (type, label) => {
if (type === 'deselect') {
if (label === 'only remix transactions') {
this._deps.editorPanel.updateTerminalFilter({ type: 'select', value: 'unknownTransaction' })
} else if (label === 'all transactions') {
this._deps.editorPanel.updateTerminalFilter({ type: 'deselect', value: 'unknownTransaction' })
}
} else if (type === 'select') {
if (label === 'only remix transactions') {
this._deps.editorPanel.updateTerminalFilter({ type: 'deselect', value: 'unknownTransaction' })
} else if (label === 'all transactions') {
this._deps.editorPanel.updateTerminalFilter({ type: 'select', value: 'unknownTransaction' })
}
}
})
this._deps.txListener.event.register('newBlock', (block) => {
if (!block.transactions.length) {
if (!block.transactions || block.transactions && !block.transactions.length) {
this.logEmptyBlock({ block: block })
}
})
@ -200,6 +175,9 @@ class TxLogger {
this._deps.txListener.event.register('newCall', (tx) => {
log(this, tx, null)
})
this._deps.editorPanel.updateTerminalFilter({ type: 'select', value: 'unknownTransaction' })
this._deps.editorPanel.updateTerminalFilter({ type: 'select', value: 'knownTransaction' })
}
}
@ -208,7 +186,7 @@ function debug (e, data, self) {
if (data.tx.isCall && data.tx.envMode !== 'vm') {
modalDialog.alert('Cannot debug this call. Debugging calls is only possible in JavaScript VM mode.')
} else {
self._deps.app.startdebugging(data.tx.hash)
self.event.trigger('debuggingRequested', [data.tx.hash])
}
}
@ -241,9 +219,9 @@ function renderKnownTransaction (self, data) {
${checkTxStatus(data.receipt, txType)}
${context(self, {from, to, data})}
<div class=${css.buttons}>
<div class=${css.debug} onclick=${(e) => debug(e, data, self)}>Debug</div>
<button class="${css.debug} btn btn-primary btn-sm" onclick=${(e) => debug(e, data, self)}>Debug</div>
</div>
<i class="${css.arrow} fa fa-angle-down"></i>
<i class="${css.arrow} fas fa-angle-down"></i>
</div>
</span>
`
@ -267,9 +245,9 @@ function renderCall (self, data) {
<div class=${css.txItem}><span class=${css.txItemTitle}>data:</span> ${input}</div>
</span>
<div class=${css.buttons}>
<div class=${css.debug} onclick=${(e) => debug(e, data, self)}>Debug</div>
<div class="${css.debug} btn btn-primary btn-sm" onclick=${(e) => debug(e, data, self)}>Debug</div>
</div>
<i class="${css.arrow} fa fa-angle-down"></i>
<i class="${css.arrow} fas fa-angle-down"></i>
</div>
</span>
`
@ -287,9 +265,9 @@ function renderUnknownTransaction (self, data) {
${checkTxStatus(data.receipt || data.tx, txType)}
${context(self, {from, to, data})}
<div class=${css.buttons}>
<div class=${css.debug} onclick=${(e) => debug(e, data, self)}>Debug</div>
<div class="${css.debug} btn btn-primary btn-sm" onclick=${(e) => debug(e, data, self)}>Debug</div>
</div>
<i class="${css.arrow} fa fa-angle-down"></i>
<i class="${css.arrow} fas fa-angle-down"></i>
</div>
</span>
`
@ -305,14 +283,14 @@ function renderEmptyBlock (self, data) {
function checkTxStatus (tx, type) {
if (tx.status === '0x1') {
return yo`<i class="${css.txStatus} ${css.succeeded} fa fa-check-circle"></i>`
return yo`<i class="${css.txStatus} ${css.succeeded} fas fa-check-circle"></i>`
}
if (type === 'call' || type === 'unknownCall') {
return yo`<i class="${css.txStatus} ${css.call}">call</i>`
} else if (tx.status === '0x0') {
return yo`<i class="${css.txStatus} ${css.failed} fa fa-times-circle"></i>`
return yo`<i class="${css.txStatus} ${css.failed} fas fa-times-circle"></i>`
} else {
return yo`<i class="${css.txStatus} ${css.notavailable} fa fa-circle-thin" title='Status not available' ></i>`
return yo`<i class="${css.txStatus} ${css.notavailable} fas fa-circle-thin" title='Status not available' ></i>`
}
}
@ -379,8 +357,8 @@ function txDetails (e, tx, data, obj) {
var to = obj.to
var log = document.querySelector(`#${tx.id} [class^='log']`)
var arrow = document.querySelector(`#${tx.id} [class^='arrow']`)
var arrowUp = yo`<i class="${css.arrow} fa fa-angle-up"></i>`
var arrowDown = yo`<i class="${css.arrow} fa fa-angle-down"></i>`
var arrowUp = yo`<i class="${css.arrow} fas fa-angle-up"></i>`
var arrowDown = yo`<i class="${css.arrow} fas fa-angle-down"></i>`
if (table && table.parentNode) {
tx.removeChild(table)
log.removeChild(arrow)

@ -2,21 +2,30 @@
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 { BaseApi } from 'remix-plugin'
class FilesTree extends BaseApi {
constructor (name, storage) {
super({
name: name,
methods: ['get', 'set', 'remove'],
description:
'service - read/write file to the `config` explorer without need of additionnal permission.'
})
this.event = new EventManager()
this.storage = storage
this.type = name
this.structFile = '.' + name + '.tree'
this.tree = {}
}
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 +39,94 @@ 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,7 +137,7 @@ 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

@ -1,19 +1,21 @@
/* 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 +24,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: 'fas fa-plus-circle'
},
{ action: 'publishToGist',
title: 'Publish all [browser] explorer files to a github gist',
icon: 'fab fa-github'
},
{ action: 'copyFiles',
title: 'Copy all files to another instance of Remix IDE',
icon: 'far fa-copy'
},
{ action: 'uploadFile',
title: 'Add Local file to the Browser Storage Explorer',
icon: 'far fa-folder-open'
},
{ action: 'updateGist',
title: 'Update the current [gist] explorer',
icon: 'fab 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
@ -31,6 +62,8 @@ function fileExplorer (localRegistry, files) {
fileManager: self._components.registry.get('filemanager').api
}
self._components.registry.put({ api: self, name: `fileexplorer/${self.files.type}` })
// warn if file changed outside of Remix
function remixdDialog () {
return yo`<div>This file has been changed outside of Remix IDE.</div>`
@ -79,8 +112,8 @@ function fileExplorer (localRegistry, files) {
self.focusElement = self.treeView.labelAt(self.focusPath)
// TODO: here we update the selected file (it applicable)
// cause we are refreshing the interface of the whole directory when there's a new file.
if (self.focusElement && !self.focusElement.classList.contains(css.hasFocus)) {
self.focusElement.classList.add(css.hasFocus)
if (self.focusElement && !self.focusElement.classList.contains('bg-secondary')) {
self.focusElement.classList.add('bg-secondary')
}
})
}
@ -122,12 +155,19 @@ function fileExplorer (localRegistry, files) {
},
formatSelf: function formatSelf (key, data, li) {
var isRoot = data.path === self.files.type
return yo`<label class="${data.children ? css.folder : css.file}"
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>
`
}
})
@ -136,13 +176,13 @@ function fileExplorer (localRegistry, files) {
MENU_HANDLE && MENU_HANDLE.hide(null, true)
MENU_HANDLE = contextMenu(event, {
'Rename': () => {
if (self.files.readonly) { return addTooltip('cannot rename folder. ' + self.files.type + ' is a read only explorer') }
if (self.files.readonly) { return tooltip('cannot rename folder. ' + self.files.type + ' is a read only explorer') }
var name = label.querySelector('label[data-path="' + key + '"]')
if (name) editModeOn(name)
},
'Delete': () => {
if (self.files.readonly) { return addTooltip('cannot delete folder. ' + self.files.type + ' is a read only explorer') }
modalDialogCustom.confirm(null, 'Do you want to delete this folder?', () => { files.remove(key) }, () => {})
if (self.files.readonly) { return tooltip('cannot delete folder. ' + self.files.type + ' is a read only explorer') }
modalDialogCustom.confirm('Confirm to delete a folder', 'Are you sure you want to delete this folder?', () => { files.remove(key) }, () => {})
}
})
})
@ -152,56 +192,47 @@ function fileExplorer (localRegistry, files) {
MENU_HANDLE && MENU_HANDLE.hide(null, true)
MENU_HANDLE = contextMenu(event, {
'Rename': () => {
if (self.files.readonly) { return addTooltip('cannot rename file. ' + self.files.type + ' is a read only explorer') }
if (self.files.readonly) { return tooltip('cannot rename file. ' + self.files.type + ' is a read only explorer') }
var name = label.querySelector('label[data-path="' + key + '"]')
if (name) editModeOn(name)
},
'Delete': () => {
if (self.files.readonly) { return addTooltip('cannot delete file. ' + self.files.type + ' is a read only explorer') }
modalDialogCustom.confirm(null, 'Do you want to delete this file?', () => { files.remove(key) }, () => {})
if (self.files.readonly) { return tooltip('cannot delete file. ' + self.files.type + ' is a read only explorer') }
modalDialogCustom.confirm('Delete a file', 'Are you sure you want to delete this file?', () => { files.remove(key) }, () => {})
}
})
})
self.treeView.event.register('leafClick', function (key, data, label) {
if (self.focusElement) {
self.focusElement.classList.remove(css.hasFocus)
self.focusElement = null
self.focusPath = null
}
self.focusElement = self.treeView.labelAt(key)
if (self.focusElement) {
self.focusElement.classList.add(css.hasFocus)
self.focusPath = key
self.events.trigger('focus', [key])
}
self.events.trigger('focus', [key])
})
self.treeView.event.register('nodeClick', function (path, childrenContainer) {
if (!childrenContainer) return
if (childrenContainer.style.display === 'none') return
files.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error)
if (!fileTree) return
var newTree = normalize(path, fileTree)
self.treeView.updateNodeFromJSON(path, newTree, true)
})
self.updatePath(path)
})
function normalize (path, filesList) {
var prefix = path.split('/')[0]
var newList = {}
Object.keys(filesList).forEach(key => {
newList[prefix + '/' + key] = filesList[key].isDirectory ? {} : { '/content': true }
})
return newList
}
// register to main app, trigger when the current file in the editor changed
self._deps.fileManager.event.register('currentFileChanged', (newFile, explorer) => {
if (self.focusElement && (!explorer || explorer.type !== files.type) && self.focusPath !== newFile) {
self.focusElement.classList.remove(css.hasFocus)
self._deps.fileManager.events.on('currentFileChanged', (newFile) => {
const explorer = self._deps.fileManager.fileProviderOf(newFile)
if (self.focusElement && self.focusPath !== newFile) {
self.focusElement.classList.remove('bg-secondary')
self.focusElement = null
self.focusPath = null
}
if (explorer && (explorer.type === files.type)) {
self.focusElement = self.treeView.labelAt(newFile)
if (self.focusElement) {
self.focusElement.classList.add('bg-secondary')
self.focusPath = newFile
}
}
})
self._deps.fileManager.events.on('noFileSelected', () => {
if (self.focusElement) {
self.focusElement.classList.remove('bg-secondary')
self.focusElement = null
self.focusPath = null
}
@ -220,7 +251,7 @@ function fileExplorer (localRegistry, files) {
function editModeOn (label) {
textUnderEdit = label.innerText
label.setAttribute('contenteditable', true)
label.classList.add(css.rename)
label.classList.add('bg-light')
label.focus()
selectElementContents(label)
}
@ -256,14 +287,23 @@ function fileExplorer (localRegistry, files) {
var isFolder = label.className.indexOf('folder') !== -1
var save = textUnderEdit !== label.innerText
if (save) {
modalDialogCustom.confirm(null, 'Do you want to rename?', () => { rename() }, () => { label.innerText = textUnderEdit })
modalDialogCustom.confirm('Confirm to rename a file', 'Are you sure you want to rename this file?', () => { rename() }, () => { label.innerText = textUnderEdit })
}
label.removeAttribute('contenteditable')
label.classList.remove(css.rename)
label.classList.remove('bg-light')
}
}
}
fileExplorer.prototype.updatePath = function (path) {
this.files.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error)
if (!fileTree) return
var newTree = normalize(path, fileTree)
this.treeView.updateNodeFromJSON(path, newTree, true)
})
}
fileExplorer.prototype.hide = function () {
if (this.container) this.container.style.display = 'none'
}
@ -277,6 +317,216 @@ 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('Confirm overwrite', `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('Gist is ready', `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.length) {
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(
'Copy files from browser explorer',
'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('Create new file', '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}" title="${title}">
<input type="file" onchange=${(event) => {
event.stopPropagation()
this.uploadFile(event)
}} multiple />
</label>
`
} else {
return yo`
<span onclick=${(event) => { event.stopPropagation(); this[ action ]() }} class="newFile ${icon} ${css.newFile}" title=${title}></span>
`
}
})
}
return yo`<span class=" ${css.menu}">${items}</span>`
}
fileExplorer.prototype.ensureRoot = function (cb) {
cb = cb || (() => {})
var self = this
@ -285,13 +535,23 @@ fileExplorer.prototype.ensureRoot = function (cb) {
self.files.resolveDirectory('/', (error, files) => {
if (error) console.error(error)
var element = self.treeView.render(files, false)
element.className = css.fileexplorer
element.classList.add(css.fileexplorer)
element.events = self.events
element.api = self.api
self.container.appendChild(element)
self.element = element
if (cb) cb()
self.treeView.expand(self.files.type)
})
}
function normalize (path, filesList) {
var prefix = path.split('/')[0]
var newList = {}
Object.keys(filesList).forEach(key => {
newList[prefix + '/' + key] = filesList[key].isDirectory ? {} : { '/content': true }
})
return newList
}
module.exports = fileExplorer

@ -1,67 +1,76 @@
'use strict'
var $ = require('jquery')
var yo = require('yo-yo')
var EventManager = require('../../lib/events')
import yo from 'yo-yo'
const EventEmitter = require('events')
var globalRegistry = require('../../global/registry')
var CompilerImport = require('../compiler/compiler-imports')
var toaster = require('../ui/tooltip')
import { FileSystemApi } from 'remix-plugin'
/*
attach to files event (removed renamed)
trigger: currentFileChanged
*/
class FileManager {
const profile = {
name: 'fileManager',
displayName: 'File manager',
description: 'Service - read/write to any files or folders, require giving permissions',
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNjk2IDM4NHE0MCAwIDY4IDI4dDI4IDY4djEyMTZxMCA0MC0yOCA2OHQtNjggMjhoLTk2MHEtNDAgMC02OC0yOHQtMjgtNjh2LTI4OGgtNTQ0cS00MCAwLTY4LTI4dC0yOC02OHYtNjcycTAtNDAgMjAtODh0NDgtNzZsNDA4LTQwOHEyOC0yOCA3Ni00OHQ4OC0yMGg0MTZxNDAgMCA2OCAyOHQyOCA2OHYzMjhxNjgtNDAgMTI4LTQwaDQxNnptLTU0NCAyMTNsLTI5OSAyOTloMjk5di0yOTl6bS02NDAtMzg0bC0yOTkgMjk5aDI5OXYtMjk5em0xOTYgNjQ3bDMxNi0zMTZ2LTQxNmgtMzg0djQxNnEwIDQwLTI4IDY4dC02OCAyOGgtNDE2djY0MGg1MTJ2LTI1NnEwLTQwIDIwLTg4dDQ4LTc2em05NTYgODA0di0xMTUyaC0zODR2NDE2cTAgNDAtMjggNjh0LTY4IDI4aC00MTZ2NjQwaDg5NnoiLz48L3N2Zz4=',
permission: true
}
// File System profile
// - events: ['currentFileChanged']
// - methods: ['getFolder', 'getCurrentFile', 'getFile', 'setFile']
class FileManager extends FileSystemApi {
constructor (localRegistry) {
this.tabbedFiles = {}
this.event = new EventManager()
super(profile)
this.openedFiles = {} // list all opened files
this.events = new EventEmitter()
this._components = {}
this._components.compilerImport = new CompilerImport()
this._components.registry = localRegistry || globalRegistry
}
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,
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.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.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) })
}
fileRenamedEvent (oldName, newName, isFolder) {
var self = this
if (!isFolder) {
self._deps.config.set('currentFile', '')
self._deps.editor.discard(oldName)
if (this.tabbedFiles[oldName]) {
delete this.tabbedFiles[oldName]
this.tabbedFiles[newName] = newName
this._deps.config.set('currentFile', '')
this._deps.editor.discard(oldName)
if (this.openedFiles[oldName]) {
delete this.openedFiles[oldName]
this.openedFiles[newName] = newName
}
this.switchFile(newName)
} else {
var newFocus
for (var k in this.tabbedFiles) {
for (var k in this.openedFiles) {
if (k.indexOf(oldName + '/') === 0) {
var newAbsolutePath = k.replace(oldName, newName)
this.tabbedFiles[newAbsolutePath] = newAbsolutePath
delete this.tabbedFiles[k]
if (self._deps.config.get('currentFile') === k) {
this.openedFiles[newAbsolutePath] = newAbsolutePath
delete this.openedFiles[k]
if (this._deps.config.get('currentFile') === k) {
newFocus = newAbsolutePath
}
}
@ -70,7 +79,7 @@ class FileManager {
this.switchFile(newFocus)
}
}
this.refreshTabs()
this.events.emit('fileRenamed', oldName, newName)
}
currentFileProvider () {
@ -82,20 +91,87 @@ class FileManager {
}
currentFile () {
var self = this
return self._deps.config.get('currentFile')
return this._deps.config.get('currentFile')
}
closeFile (name) {
delete this.openedFiles[name]
if (Object.keys(this.openedFiles).length) {
this.switchFile(Object.keys(this.openedFiles)[0])
} else {
this._deps.editor.displayEmptyReadOnlySession()
this._deps.config.set('currentFile', '')
this.events.emit('noFileSelected')
}
this.events.emit('fileClosed', name)
}
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
}
async getCurrentFile () {
const path = this.currentFile()
if (!path) throw new Error('no file selected')
console.log('Get current File', path)
return path
}
getFile (path) {
const provider = this.fileProviderOf(path)
if (!provider) throw new Error(`${path} not available`)
// TODO: change provider to Promise
return new Promise((resolve, reject) => {
provider.get(path, (err, content) => {
if (err) reject(err)
resolve(content)
})
})
}
async setFile (path, content) {
if (this.currentRequest) {
let reject = false
let savedAsAnotherFile = false
let warnToaster
const actions = yo`<div class="container ml-1">
<button class="btn btn-primary btn-sm m-1" onclick=${(e) => { reject = true; e.target.innerHTML = 'Canceled'; warnToaster.hide() }}>Cancel</button>
<button class="btn btn-primary btn-sm m-1" onclick=${(e) => {
if (savedAsAnotherFile) return
savedAsAnotherFile = true
const newPath = path + '.' + this.currentRequest.from
this._setFileInternal(newPath, content)
this.switchFile(newPath)
e.target.innerHTML = 'Saved'
warnToaster.hide()
}}>Save As Copy</button>
</div>`
warnToaster = await toaster(yo`<div><span class="text-primary">${this.currentRequest.from}</span> is modyfing <span class="text-primary">${path}</span></div>`, actions, { time: 6000 })
if (reject) throw new Error(`set file operation on ${path} aborted by user.`)
if (savedAsAnotherFile) return
}
this._setFileInternal(path, content)
}
_setFileInternal (path, content) {
const provider = this.fileProviderOf(path)
if (!provider) throw new Error(`${path} not availble`)
// TODO : Add permission
// TODO : Change Provider to Promise
return new Promise((resolve, reject) => {
provider.set(path, content, (error) => {
if (error) reject(error)
this.syncEditor(path)
resolve(true)
})
})
}
removeTabsOf (provider) {
for (var tab in this.tabbedFiles) {
for (var tab in this.openedFiles) {
if (this.fileProviderOf(tab).type === provider.type) {
this.fileRemovedEvent(tab)
}
@ -103,78 +179,60 @@ class FileManager {
}
fileRemovedEvent (path) {
var self = this
if (!this.tabbedFiles[path]) return
if (path === self._deps.config.get('currentFile')) {
self._deps.config.set('currentFile', '')
if (!this.openedFiles[path]) return
if (path === this._deps.config.get('currentFile')) {
this._deps.config.set('currentFile', '')
}
self._deps.editor.discard(path)
delete this.tabbedFiles[path]
this.refreshTabs()
this._deps.editor.discard(path)
delete this.openedFiles[path]
this.events.emit('fileRemoved', path)
this.switchFile()
}
// Display files that have already been selected
refreshTabs (newfile) {
if (newfile) {
this.tabbedFiles[newfile] = newfile
}
var $filesEl = $('#files')
$filesEl.find('.file').remove()
for (var file in this.tabbedFiles) {
$filesEl.append(yo`<li class="file"><span class="name">${file}</span><span class="remove"><i class="fa fa-close"></i></span></li>`)
}
var active = $('#files .file').filter(function () {
return $(this).find('.name').text() === newfile
})
if (active.length) active.addClass('active')
$('#output').toggle(active)
}
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.event.trigger('currentFileChanged', [])
self._deps.editor.displayEmptyReadOnlySession()
}
})
}
function _switchFile (file) {
self.saveCurrentFile()
self._deps.config.set('currentFile', file)
self.refreshTabs(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.event.trigger('currentFileChanged', [file, self.fileProviderOf(file)])
this._deps.editor.displayEmptyReadOnlySession()
this.events.emit('noFileSelected')
}
})
}
}
filesFromPath (path, cb) {
var provider = this.fileProviderOf(path)
if (provider) {
return provider.resolveDirectory(path, (error, filesTree) => { cb(error, filesTree) })
}
cb(`provider for path ${path} not found`)
getFolder (path) {
// 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)
})
})
}
fileProviderOf (file) {
@ -208,7 +266,6 @@ class FileManager {
}
syncEditor (path) {
var self = this
var currentFile = this._deps.config.get('currentFile')
if (path !== currentFile) return
@ -216,7 +273,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')

@ -0,0 +1,106 @@
let globalRegistry = require('../../global/registry')
import { BaseApi } from 'remix-plugin'
var yo = require('yo-yo')
var modalDialog = require('../ui/modaldialog')
var modalDialogCustom = require('../ui/modal-dialog-custom')
var csjs = require('csjs-inject')
var css = csjs`
.dialog {
display: flex;
flex-direction: column;
}
.dialogParagraph {
margin-bottom: 2em;
word-break: break-word;
}
`
const profile = {
name: 'remixd',
methods: [],
events: [],
description: 'using Remixd daemon, allow to access file system',
kind: 'other'
}
export class RemixdHandle extends BaseApi {
constructor (fileSystemExplorer, locahostProvider) {
super(profile)
this.fileSystemExplorer = fileSystemExplorer
this.locahostProvider = locahostProvider
}
deactivate () {
this.locahostProvider.close((error) => {
if (error) console.log(error)
})
}
activate () {
this.connectToLocalhost()
}
canceled () {
let appManager = globalRegistry.get('appmanager').api
appManager.ensureDeactivated('remixd')
}
/**
* connect to localhost if no connection and render the explorer
* disconnect from localhost if connected and remove the explorer
*
* @param {String} txHash - hash of the transaction
*/
connectToLocalhost () {
if (this.locahostProvider.isConnected()) {
this.locahostProvider.close((error) => {
if (error) console.log(error)
})
} else {
modalDialog(
'Connect to localhost',
remixdDialog(),
{ label: 'Connect',
fn: () => {
this.locahostProvider.init((error) => {
if (error) {
console.log(error)
modalDialogCustom.alert(
'Cannot connect to the remixd daemon.' +
'Please make sure you have the remixd running in the background.'
)
this.canceled()
} else {
this.fileSystemExplorer.ensureRoot()
}
})
}
},
{ label: 'Cancel',
fn: () => {
this.canceled()
}
}
)
}
}
}
function remixdDialog () {
return yo`
<div class=${css.dialog}>
<div class=${css.dialogParagraph}>Interact with your file system from Remix. Click connect and find shared folder in the Remix file explorer (under localhost).
Before you get started, check out <a target="_blank" href="https://remix.readthedocs.io/en/latest/tutorial_remixd_filesystem.html">Tutorial_remixd_filesystem</a>
to find out how to run Remixd.
</div>
<div class=${css.dialogParagraph}>Connection will start a session between <em>${window.location.href}</em> and your local file system <i>ws://127.0.0.1:65520</i>
so please make sure your system is secured enough (port 65520 neither opened nor forwarded).
<i class="fas fa-link"></i> will show you current connection status.
</div>
<div class=${css.dialogParagraph}>This feature is still in Alpha, so we recommend you to keep a copy of the shared folder.</div>
</div>
`
}

@ -1,8 +1,9 @@
var csjs = require('csjs-inject')
var styleGuide = require('../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs`
.label {
margin-top : 4px
}
.fileexplorer {
box-sizing : border-box;
}
@ -15,13 +16,26 @@ var css = csjs`
cursor : pointer;
}
.file {
color : ${styles.leftPanel.text_Teriary};
padding : 4px;
}
.newFile {
padding-right : 10px;
}
.newFile i {
cursor : pointer;
}
.newFile:hover {
transform : scale(1.3);
}
.menu {
margin-left : 20px;
}
.items {
display : inline
}
.hasFocus {
background-color : ${styles.leftPanel.backgroundColor_FileExplorer};
}
.rename {
background-color : ${styles.leftPanel.backgroundColor_Panel};
}
.remove {
margin-left : auto;

@ -1,23 +1,44 @@
var yo = require('yo-yo')
var EventManager = require('../../lib/events')
var $ = require('jquery')
var Terminal = require('./terminal')
var Editor = require('../editor/editor')
var globalRegistry = require('../../global/registry')
var { TabProxy } = require('./tab-proxy.js')
var ContextualListener = require('../editor/contextualListener')
var ContextView = require('../editor/contextView')
var styles = require('./styles/editor-panel-styles')
var cssTabs = styles.cssTabs
var css = styles.css
var csjs = require('csjs-inject')
var css = csjs`
.editorpanel {
display : flex;
flex-direction : column;
height : 100%;
}
.content {
position : relative;
display : flex;
flex-direction : column;
height : 100%;
width : 100%;
}
`
class EditorPanel {
constructor (localRegistry) {
constructor (appStore, appManager, mainPanelComponent) {
var self = this
self._components = {}
self._components.registry = localRegistry || globalRegistry
self.event = new EventManager()
self._view = {}
self._components = {}
self._components.registry = globalRegistry
self._components.editor = new Editor({})
self._components.registry.put({api: self._components.editor, name: 'editor'})
self.appStore = appStore
self.appManager = appManager
self.mainPanelComponent = mainPanelComponent
}
init () {
var self = this
@ -28,55 +49,79 @@ class EditorPanel {
udapp: self._components.registry.get('udapp').api,
pluginManager: self._components.registry.get('pluginmanager').api
}
self.tabProxy = new TabProxy(self._deps.fileManager, self._components.editor, self.appStore, self.appManager)
let showApp = function (name) {
self.mainPanelComponent.showContent(name)
self._view.editor.style.display = 'none'
self._components.contextView.hide()
self._view.mainPanel.style.display = 'block'
self.tabProxy.sh
}
self.appManager.event.on('ensureActivated', (name) => {
if (name === 'home') {
showApp(name)
self.tabProxy.showTab('home')
}
})
/*
We listen here on event from the tab component to display / hide the editor and mainpanel
depending on the content that should be displayed
*/
self._deps.fileManager.events.on('currentFileChanged', (file) => {
// we check upstream for "fileChanged"
self._view.editor.style.display = 'block'
self._view.mainPanel.style.display = 'none'
self._components.contextView.show()
})
self.tabProxy.event.on('switchFile', (file) => {
self._view.editor.style.display = 'block'
self._view.mainPanel.style.display = 'none'
self._components.contextView.show()
})
self.tabProxy.event.on('closeFile', (file) => {
})
self.tabProxy.event.on('switchApp', showApp)
self.tabProxy.event.on('closeApp', (name) => {
self._view.editor.style.display = 'block'
self._components.contextView.show()
self._view.mainPanel.style.display = 'none'
})
self.data = {
_FILE_SCROLL_DELTA: 200,
_layout: {
top: {
offset: self._deps.config.get('terminal-top-offset') || 500,
offset: self._deps.config.get('terminal-top-offset') || 150,
show: true
}
}
}
self._view = {}
var editor = new Editor({})
self._components.registry.put({api: editor, name: 'editor'})
var contextualListener = new ContextualListener({editor, pluginManager: self._deps.pluginManager})
var contextView = new ContextView({contextualListener, editor})
var contextualListener = new ContextualListener({editor: self._components.editor, pluginManager: self._deps.pluginManager})
var contextView = new ContextView({contextualListener, editor: self._components.editor})
self._components = {
editor: editor,
contextualListener: contextualListener,
contextView: contextView,
// TODO list of compilers is always empty; should find a path to add plugin compiler here
terminal: new Terminal({
udapp: self._deps.udapp,
compilers: {}
},
{
getPosition: (event) => {
var limitUp = 36
var limitDown = 20
var height = window.innerHeight
var newpos = (event.pageY < limitUp) ? limitUp : event.pageY
newpos = (newpos < height - limitDown) ? newpos : height - limitDown
return newpos
}
})
}
self._components.contextualListener = contextualListener
self._components.contextView = contextView
self._components.terminal = new Terminal({
udapp: self._deps.udapp,
appStore: self.appStore,
appManager: self.appManager
},
{
getPosition: (event) => {
var limitUp = 36
var limitDown = 20
var height = window.innerHeight
var newpos = (event.pageY < limitUp) ? limitUp : event.pageY
newpos = (newpos < height - limitDown) ? newpos : height - limitDown
return height - newpos
}
})
self._components.terminal.event.register('filterChanged', (type, value) => {
this.event.trigger('terminalFilterChanged', [type, value])
})
self._components.terminal.event.register('resize', delta => self._adjustLayout('top', delta))
if (self._deps.txListener) {
self._components.terminal.event.register('listenOnNetWork', (listenOnNetWork) => {
self._deps.txListener.setListenOnNetwork(listenOnNetWork)
})
}
if (document && document.head) {
document.head.appendChild(cssTabs)
}
}
_adjustLayout (direction, delta) {
var limitUp = 0
@ -88,7 +133,7 @@ class EditorPanel {
if (delta === undefined) {
layout.show = !layout.show
if (layout.show) delta = layout.offset
else delta = containerHeight
else delta = 0
} else {
layout.show = true
self._deps.config.set(`terminal-${direction}-offset`, delta)
@ -98,10 +143,11 @@ class EditorPanel {
var tmp = delta - limitDown
delta = tmp > 0 ? tmp : 0
if (direction === 'top') {
var height = containerHeight - delta
height = height < 0 ? 0 : height
self._view.editor.style.height = `${delta}px`
self._view.terminal.style.height = `${height}px` // - menu bar height
var mainPanelHeight = containerHeight - delta
mainPanelHeight = mainPanelHeight < 0 ? 0 : mainPanelHeight
self._view.editor.style.height = `${mainPanelHeight}px`
self._view.mainPanel.style.height = `${mainPanelHeight}px`
self._view.terminal.style.height = `${delta}px` // - menu bar height
self._components.editor.resize((document.querySelector('#editorWrap') || {}).checked)
self._components.terminal.scroll2bottom()
}
@ -131,14 +177,15 @@ class EditorPanel {
var self = this
if (self._view.el) return self._view.el
self._view.editor = self._components.editor.render()
self._view.editor.style.display = 'none'
self._view.mainPanel = self.mainPanelComponent.render()
self._view.terminal = self._components.terminal.render()
self._view.content = yo`
<div class=${css.content}>
${self._renderTabsbar()}
<div class=${css.contextviewcontainer}>
${self._components.contextView.render()}
</div>
${self.tabProxy.renderTabsbar()}
${self._view.editor}
${self._view.mainPanel}
${self._components.contextView.render()}
${self._view.terminal}
</div>
`
@ -149,6 +196,11 @@ class EditorPanel {
`
// INIT
self._adjustLayout('top', self.data._layout.top.offset)
document.addEventListener('keydown', (e) => {
if (e.altKey && e.keyCode === 84) self.tabProxy.switchNextTab() // alt + t
})
return self._view.el
}
registerCommand (name, command, opts) {
@ -158,137 +210,6 @@ class EditorPanel {
updateTerminalFilter (filter) {
this._components.terminal.updateJournal(filter)
}
_renderTabsbar () {
var self = this
if (self._view.tabsbar) return self._view.tabsbar
self._view.filetabs = yo`<ul id="files" class="${css.files} nav nav-tabs"></ul>`
self._view.tabs = yo`
<div class=${css.tabs} onmouseenter=${toggleScrollers} onmouseleave=${toggleScrollers}>
<div onclick=${scrollLeft} class="${css.scroller} ${css.hide} ${css.scrollerleft}">
<i class="fa fa-chevron-left "></i>
</div>
${self._view.filetabs}
<div onclick=${scrollRight} class="${css.scroller} ${css.hide} ${css.scrollerright}">
<i class="fa fa-chevron-right "></i>
</div>
</div>
`
self._view.tabsbar = yo`
<div class=${css.tabsbar}>
<div class=${css.buttons}>
<span class=${css.toggleLHP} onclick=${toggleLHP} title="Toggle left hand panel">
<i class="fa fa-angle-double-left"></i>
</span>
<span class=${css.changeeditorfontsize} >
<i class="increditorsize fa fa-plus" onclick=${increase} aria-hidden="true" title="increase editor font size"></i>
<i class="decreditorsize fa fa-minus" onclick=${decrease} aria-hidden="true" title="decrease editor font size"></i>
</span>
</div>
${self._view.tabs}
<span class="${css.toggleRHP}" onclick=${toggleRHP} title="Toggle right hand panel">
<i class="fa fa-angle-double-right"></i>
</span>
</div>
`
// tabs
var $filesEl = $(self._view.filetabs)
// Switch tab
$filesEl.on('click', '.file:not(.active)', function (ev) {
ev.preventDefault()
self._deps.fileManager.switchFile($(this).find('.name').text())
return false
})
// Remove current tab
$filesEl.on('click', '.file .remove', function (ev) {
ev.preventDefault()
var name = $(this).parent().find('.name').text()
delete self._deps.fileManager.tabbedFiles[name]
self._deps.fileManager.refreshTabs()
if (Object.keys(self._deps.fileManager.tabbedFiles).length) {
self._deps.fileManager.switchFile(Object.keys(self._deps.fileManager.tabbedFiles)[0])
} else {
self._components.editor.displayEmptyReadOnlySession()
self._deps.config.set('currentFile', '')
}
return false
})
return self._view.tabsbar
function toggleScrollers (event = {}) {
if (event.type) self.data._focus = event.type
var isMouseEnter = self.data._focus === 'mouseenter'
var leftArrow = this.children[0]
var rightArrow = this.children[2]
if (isMouseEnter && this.children[1].offsetWidth > this.offsetWidth) {
var hiddenLength = self._view.filetabs.offsetWidth - self._view.tabs.offsetWidth
var currentLeft = self._view.filetabs.offsetLeft || 0
var hiddenRight = hiddenLength + currentLeft
if (currentLeft < 0) {
leftArrow.classList.add(css.show)
leftArrow.classList.remove(css.hide)
}
if (hiddenRight > 0) {
rightArrow.classList.add(css.show)
rightArrow.classList.remove(css.hide)
}
} else {
leftArrow.classList.remove(css.show)
leftArrow.classList.add(css.hide)
rightArrow.classList.remove(css.show)
rightArrow.classList.add(css.hide)
}
}
function toggleLHP (event) {
this.children[0].classList.toggle('fa-angle-double-right')
this.children[0].classList.toggle('fa-angle-double-left')
self.event.trigger('resize', ['left'])
}
function toggleRHP (event) {
this.children[0].classList.toggle('fa-angle-double-right')
this.children[0].classList.toggle('fa-angle-double-left')
self.event.trigger('resize', ['right'])
}
function increase () { self._components.editor.editorFontSize(1) }
function decrease () { self._components.editor.editorFontSize(-1) }
function scrollLeft (event) {
var leftArrow = this
var rightArrow = this.nextElementSibling.nextElementSibling
var currentLeft = self._view.filetabs.offsetLeft || 0
if (currentLeft < 0) {
rightArrow.classList.add(css.show)
rightArrow.classList.remove(css.hide)
if (currentLeft < -self.data._FILE_SCROLL_DELTA) {
self._view.filetabs.style.left = `${currentLeft + self.data._FILE_SCROLL_DELTA}px`
} else {
self._view.filetabs.style.left = `${currentLeft - currentLeft}px`
leftArrow.classList.remove(css.show)
leftArrow.classList.add(css.hide)
}
}
}
function scrollRight (event) {
var rightArrow = this
var leftArrow = this.previousElementSibling.previousElementSibling
var hiddenLength = self._view.filetabs.offsetWidth - self._view.tabs.offsetWidth
var currentLeft = self._view.filetabs.offsetLeft || 0
var hiddenRight = hiddenLength + currentLeft
if (hiddenRight > 0) {
leftArrow.classList.add(css.show)
leftArrow.classList.remove(css.hide)
if (hiddenRight > self.data._FILE_SCROLL_DELTA) {
self._view.filetabs.style.left = `${currentLeft - self.data._FILE_SCROLL_DELTA}px`
} else {
self._view.filetabs.style.left = `${currentLeft - hiddenRight}px`
rightArrow.classList.remove(css.show)
rightArrow.classList.add(css.hide)
}
}
}
}
}
module.exports = EditorPanel

@ -1,28 +1,14 @@
/* 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 modalDialog = require('../ui/modaldialog')
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 styleGuide = require('../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = require('./styles/file-panel-styles')
var limit = 60
import { BaseApi } from 'remix-plugin'
var canUpload = window.File || window.FileReader || window.FileList || window.Blob
var ghostbar = yo`<div class=${css.ghostbar}></div>`
/*
Overview of APIs:
@ -41,386 +27,121 @@ var ghostbar = yo`<div class=${css.ghostbar}></div>`
- 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'])
const profile = {
name: 'fileExplorers',
displayName: 'File explorers',
methods: [],
events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNjk2IDM4NHE0MCAwIDY4IDI4dDI4IDY4djEyMTZxMCA0MC0yOCA2OHQtNjggMjhoLTk2MHEtNDAgMC02OC0yOHQtMjgtNjh2LTI4OGgtNTQ0cS00MCAwLTY4LTI4dC0yOC02OHYtNjcycTAtNDAgMjAtODh0NDgtNzZsNDA4LTQwOHEyOC0yOCA3Ni00OHQ4OC0yMGg0MTZxNDAgMCA2OCAyOHQyOCA2OHYzMjhxNjgtNDAgMTI4LTQwaDQxNnptLTU0NCAyMTNsLTI5OSAyOTloMjk5di0yOTl6bS02NDAtMzg0bC0yOTkgMjk5aDI5OXYtMjk5em0xOTYgNjQ3bDMxNi0zMTZ2LTQxNmgtMzg0djQxNnEwIDQwLTI4IDY4dC02OCAyOGgtNDE2djY0MGg1MTJ2LTI1NnEwLTQwIDIwLTg4dDQ4LTc2em05NTYgODA0di0xMTUyaC0zODR2NDE2cTAgNDAtMjggNjh0LTY4IDI4aC00MTZ2NjQwaDg5NnoiLz48L3N2Zz4=',
description: ' - ',
kind: 'fileexplorer',
location: 'swapPanel'
}
// ----------------- editor panel ----------------------
self._compilerMetadata = new CompilerMetadata(
{
fileManager: self._deps.fileManager,
pluginManager: self._deps.pluginManager,
config: self._deps.config
module.exports = class Filepanel extends BaseApi {
constructor (localRegistry) {
super(profile)
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 }
var dragbar = yo`<div onmousedown=${mousedown} class=${css.dragbar}></div>`
function remixdDialog () {
return yo`
<div class=${css.dialog}>
<div class=${css.dialogParagraph}>Interact with your file system from Remix. Click connect and find shared folder in the Remix file explorer (under localhost).
Before you get started, check out <a target="_blank" href="https://remix.readthedocs.io/en/latest/tutorial_remixd_filesystem.html">Tutorial_remixd_filesystem</a>
to find out how to run Remixd.
</div>
<div class=${css.dialogParagraph}>Connection will start a session between <em>${window.location.href}</em> and your local file system <i>ws://127.0.0.1:65520</i>
so please make sure your system is secured enough (port 65520 neither opened nor forwarded).
<i class="fa fa-link"></i> will show you current connection status.
</div>
<div class=${css.dialogParagraph}>This feature is still in Alpha, so we recommend you to keep a copy of the shared folder.</div>
</div>
`
}
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>
<span onclick=${connectToLocalhost} class="${css.connectToLocalhost}">
<i class="websocketconn fa fa-link" title="Connect to Localhost"></i>
</span>
</div>
<div class=${css.treeviews}>
<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 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="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>
${dragbar}
</div>
`
}
var event = new EventManager()
self.event = event
var element = template()
fileExplorer.ensureRoot()
configExplorer.ensureRoot()
var websocketconn = element.querySelector('.websocketconn')
self._deps.fileProviders['localhost'].event.register('connecting', (event) => {
websocketconn.style.color = styles.colors.yellow
websocketconn.setAttribute('title', 'Connecting to localhost. ' + JSON.stringify(event))
})
self._deps.fileProviders['localhost'].event.register('connected', (event) => {
websocketconn.style.color = styles.colors.green
websocketconn.setAttribute('title', 'Connected to localhost. ' + JSON.stringify(event))
fileSystemExplorer.show()
})
self._deps.fileProviders['localhost'].event.register('errored', (event) => {
websocketconn.style.color = styles.colors.red
websocketconn.setAttribute('title', 'localhost connection errored. ' + JSON.stringify(event))
fileSystemExplorer.hide()
})
self._deps.fileProviders['localhost'].event.register('closed', (event) => {
websocketconn.style.color = styles.colors.black
websocketconn.setAttribute('title', 'localhost connection closed. ' + JSON.stringify(event))
fileSystemExplorer.hide()
})
fileExplorer.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)
})
swarmExplorer.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)
})
httpExplorer.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 }
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() })
}
})
var event = new EventManager()
self.event = event
var element = template()
fileExplorer.ensureRoot()
self._deps.fileProviders['localhost'].event.register('connecting', (event) => {
})
}
// ----------------- resizeable ui ---------------
function mousedown (event) {
event.preventDefault()
if (event.which === 1) {
moveGhostbar(event)
document.body.appendChild(ghostbar)
document.addEventListener('mousemove', moveGhostbar)
document.addEventListener('mouseup', removeGhostbar)
document.addEventListener('keydown', cancelGhostbar)
}
}
function cancelGhostbar (event) {
if (event.keyCode === 27) {
document.body.removeChild(ghostbar)
document.removeEventListener('mousemove', moveGhostbar)
document.removeEventListener('mouseup', removeGhostbar)
document.removeEventListener('keydown', cancelGhostbar)
}
}
function getPosition (event) {
var rhp = document.body.offsetWidth - window['righthand-panel'].offsetWidth
var newpos = (event.pageX < limit) ? limit : event.pageX
newpos = (newpos < (rhp - limit)) ? newpos : (rhp - limit)
return newpos
}
function moveGhostbar (event) { // @NOTE VERTICAL ghostbar
ghostbar.style.left = getPosition(event) + 'px'
}
function removeGhostbar (event) {
document.body.removeChild(ghostbar)
document.removeEventListener('mousemove', moveGhostbar)
document.removeEventListener('mouseup', removeGhostbar)
document.removeEventListener('keydown', cancelGhostbar)
self.event.trigger('resize', [getPosition(event)])
}
self._deps.fileProviders['localhost'].event.register('connected', (event) => {
fileSystemExplorer.show()
})
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)
}
self._deps.fileProviders['localhost'].event.register('errored', (event) => {
fileSystemExplorer.hide()
})
/**
* connect to localhost if no connection and render the explorer
* disconnect from localhost if connected and remove the explorer
*
* @param {String} txHash - hash of the transaction
*/
function connectToLocalhost () {
if (self._deps.fileProviders['localhost'].isConnected()) {
self._deps.fileProviders['localhost'].close((error) => {
if (error) console.log(error)
})
} else {
modalDialog('Connect to localhost', remixdDialog(),
{ label: 'Connect',
fn: () => {
self._deps.fileProviders['localhost'].init((error) => {
if (error) {
console.log(error)
} else {
fileSystemExplorer.ensureRoot()
}
})
}})
}
}
self._deps.fileProviders['localhost'].event.register('closed', (event) => {
fileSystemExplorer.hide()
})
// ------------------ gist publish --------------
fileExplorer.events.register('focus', function (path) {
self._deps.fileManager.switchFile(path)
})
function updateGist () {
var gistId = self._deps.fileProviders['gist'].id
if (!gistId) {
tooltip('no gist content is currently loaded.')
} else {
toGist('gist', gistId)
}
}
fileSystemExplorer.events.register('focus', function (path) {
self._deps.fileManager.switchFile(path)
})
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)
swarmExplorer.events.register('focus', function (path) {
self._deps.fileManager.switchFile(path)
})
}
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)
})
}
}
}
githubExplorer.events.register('focus', function (path) {
self._deps.fileManager.switchFile(path)
})
}
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'))
}
}
}
gistExplorer.events.register('focus', function (path) {
self._deps.fileManager.switchFile(path)
})
// ------------------ copy files --------------
httpExplorer.events.register('focus', function (path) {
self._deps.fileManager.switchFile(path)
})
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)
httpsExplorer.events.register('focus', function (path) {
self._deps.fileManager.switchFile(path)
})
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)
})
}
})
self.render = function render () { return element }
}
}
module.exports = filepanel

@ -1,164 +0,0 @@
const yo = require('yo-yo')
const csjs = require('csjs-inject')
const EventManager = require('../../lib/events')
var globalRegistry = require('../../global/registry')
const styleguide = require('../ui/styles-guide/theme-chooser')
const TabbedMenu = require('../tabs/tabbed-menu')
const PluginTab = require('../tabs/plugin-tab')
const DraggableContent = require('../ui/draggableContent')
const styles = styleguide.chooser()
const css = csjs`
.righthandpanel {
display : flex;
flex-direction : column;
top : 0;
right : 0;
bottom : 0;
box-sizing : border-box;
overflow : hidden;
height : 100%;
}
.header {
height : 100%;
}
.dragbar {
position : absolute;
width : ${styles.rightPanel.dragbarWidth};
top : 3em;
bottom : 0;
cursor : col-resize;
background-color : ${styles.rightPanel.dragbarBackgroundColor};
z-index : 999;
border-left : 2px solid ${styles.rightPanel.bar_Dragging};
}
.ghostbar {
width : 3px;
background-color : ${styles.rightPanel.bar_Ghost};
opacity : 0.5;
position : absolute;
cursor : col-resize;
z-index : 9999;
top : 0;
bottom : 0;
}
`
class RighthandPanel {
constructor ({pluginManager, tabs}, localRegistry) {
const self = this
self._components = {}
self._components.registry = localRegistry || globalRegistry
self._components.registry.put({api: this, name: 'righthandpanel'})
self.event = new EventManager()
self._view = {
element: null,
tabbedMenu: null,
tabbedMenuViewport: null,
dragbar: null
}
var tabbedMenu = new TabbedMenu(self._components.registry)
self._components = {
tabbedMenu: tabbedMenu,
tabs
}
self._components.tabs.settings.event.register('plugin-loadRequest', json => {
self.loadPlugin(json)
})
self.loadPlugin = function (json) {
var modal = new DraggableContent(() => {
pluginManager.unregister(json)
})
var tab = new PluginTab(json)
var content = tab.render()
document.querySelector('body').appendChild(modal.render(json.title, json.url, content))
pluginManager.register(json, modal, content)
}
self._view.dragbar = yo`<div id="dragbar" class=${css.dragbar}></div>`
self._view.element = yo`
<div id="righthand-panel" class=${css.righthandpanel}>
${self._view.dragbar}
<div id="header" class=${css.header}>
${self._components.tabbedMenu.render()}
${self._components.tabbedMenu.renderViewport()}
</div>
</div>`
const { compile, run, settings, analysis, debug, support, test } = tabs
self._components.tabbedMenu.addTab('Compile', 'compileView', compile.render())
self._components.tabbedMenu.addTab('Run', 'runView', run.render())
self._components.tabbedMenu.addTab('Analysis', 'staticanalysisView', analysis.render())
self._components.tabbedMenu.addTab('Testing', 'testView', test.render())
self._components.tabbedMenu.addTab('Debugger', 'debugView', debug.render())
self._components.tabbedMenu.addTab('Settings', 'settingsView', settings.render())
self._components.tabbedMenu.addTab('Support', 'supportView', support.render())
self._components.tabbedMenu.selectTabByTitle('Compile')
}
render () {
const self = this
if (self._view.element) return self._view.element
return self._view.element
}
debugger () {
return this._components.tabs.debug.debugger()
}
focusOn (x) {
if (this._components.tabbedMenu) this._components.tabbedMenu.selectTabByClassName(x)
}
init () {
// @TODO: init is for resizable drag bar only and should be refactored in the future
const self = this
const limit = 60
self._view.dragbar.addEventListener('mousedown', mousedown)
const ghostbar = yo`<div class=${css.ghostbar}></div>`
function mousedown (event) {
event.preventDefault()
if (event.which === 1) {
moveGhostbar(event)
document.body.appendChild(ghostbar)
document.addEventListener('mousemove', moveGhostbar)
document.addEventListener('mouseup', removeGhostbar)
document.addEventListener('keydown', cancelGhostbar)
}
}
function cancelGhostbar (event) {
if (event.keyCode === 27) {
document.body.removeChild(ghostbar)
document.removeEventListener('mousemove', moveGhostbar)
document.removeEventListener('mouseup', removeGhostbar)
document.removeEventListener('keydown', cancelGhostbar)
}
}
function getPosition (event) {
const lhp = window['filepanel'].offsetWidth
const max = document.body.offsetWidth - limit
var newpos = (event.pageX > max) ? max : event.pageX
newpos = (newpos > (lhp + limit)) ? newpos : lhp + limit
return newpos
}
function moveGhostbar (event) { // @NOTE VERTICAL ghostbar
ghostbar.style.left = getPosition(event) + 'px'
}
function removeGhostbar (event) {
document.body.removeChild(ghostbar)
document.removeEventListener('mousemove', moveGhostbar)
document.removeEventListener('mouseup', removeGhostbar)
document.removeEventListener('keydown', cancelGhostbar)
self.event.trigger('resize', [document.body.offsetWidth - getPosition(event)])
}
}
}
module.exports = RighthandPanel

@ -1,179 +0,0 @@
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var styleGuide = require('../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var cssTabs = yo`
<style>
#files .file {
padding: 0 0.6em;
box-sizing: border-box;
background-color: ${styles.editor.backgroundColor_Tabs_Highlights};
cursor: pointer;
margin-right: 10px;
margin-top: 5px;
position: relative;
display: table-cell;
text-align: center;
vertical-align: middle;
color: ${styles.editor.text_Teriary};
}
#files .file.active {
color: ${styles.editor.text_Primary};
font-weight: bold;
border-bottom: 0 none;
padding-right: 1.5em;
}
#files .file .remove {
font-size: 12px;
display: flex;
color: ${styles.editor.text_Primary};
position: absolute;
top: -7px;
right: 5px;
display: none;
}
#files .file input {
background-color: ${styles.colors.transparent};
border: 0 none;
border-bottom: 1px dotted ${styles.editor.text_Primary};
line-height: 1em;
margin: 0.5em 0;
}
#files .file.active .remove {
display: inline-block;
color: ${styles.editor.text_Primary};
}
</style>
`
var css = csjs`
.editorpanel {
display : flex;
flex-direction : column;
height : 100%;
}
.tabsbar {
background-color : ${styles.editor.backgroundColor_Panel};
display : flex;
overflow : hidden;
height : 30px;
}
.tabs {
position : relative;
display : flex;
margin : 0;
left : 10px;
margin-right : 10px;
width : 100%;
overflow : hidden;
}
.files {
display : flex;
position : relative;
list-style : none;
margin : 0;
font-size : 15px;
height : 2.5em;
box-sizing : border-box;
line-height : 2em;
top : 0;
border-bottom : 0 none;
}
.changeeditorfontsize {
margin : 0;
font-size : 9px;
margin-top : 0.5em;
}
.changeeditorfontsize i {
cursor : pointer;
display : block;
color : ${styles.editor.icon_Color_Editor};
}
.changeeditorfontsize i {
cursor : pointer;
}
.changeeditorfontsize i:hover {
color : ${styles.editor.icon_HoverColor_Editor};
}
.buttons {
display : flex;
flex-direction : row;
justify-content : space-around;
align-items : center;
min-width : 45px;
}
.toggleLHP {
display : flex;
padding : 10px;
width : 100%;
font-weight : bold;
color : ${styles.leftPanel.icon_Color_TogglePanel};
}
.toggleLHP i {
cursor : pointer;
font-size : 14px;
font-weight : bold;
}
.toggleLHP i:hover {
color : ${styles.leftPanel.icon_HoverColor_TogglePanel};
}
.scroller {
position : absolute;
z-index : 999;
text-align : center;
cursor : pointer;
vertical-align : middle;
background-color : ${styles.colors.general_BackgroundColor};
height : 100%;
font-size : 1.3em;
color : orange;
}
.scrollerright {
right : 0;
margin-right : 15px;
}
.scrollerleft {
left : 0;
}
.toggleRHP {
margin : 0.5em;
font-weight : bold;
color : ${styles.rightPanel.icon_Color_TogglePanel};
right : 0;
}
.toggleRHP i {
cursor : pointer;
font-size : 14px;
font-weight : bold;
}
.toggleRHP i:hover {
color : ${styles.rightPanel.icon_HoverColor_TogglePanel};
}
.show {
opacity : 1;
transition : .3s opacity ease-in;
}
.hide {
opacity : 0;
pointer-events : none;
transition : .3s opacity ease-in;
}
.content {
position : relative;
display : flex;
flex-direction : column;
height : 100%;
width : 100%;
}
.contextviewcontainer{
width : 100%;
height : 20px;
background-color : ${styles.editor.backgroundColor_Tabs_Highlights};
}
`
module.exports = {
cssTabs: cssTabs,
css: css
}

@ -1,6 +1,4 @@
var csjs = require('csjs-inject')
var styleGuide = require('../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs`
.container {
@ -15,22 +13,8 @@ var css = csjs`
flex-direction : column;
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 : ${styles.colors.orange};
padding-left : 6px;
padding-top : 6px;
}
.gist {
padding : 10px;
@ -57,49 +41,25 @@ var css = csjs`
cursor : pointer;
}
.connectToLocalhost i:hover {
color : ${styles.colors.orange};
color : var(--secondary)
}
.uploadFile {
padding : 10px;
}
.uploadFile label:hover {
color : ${styles.colors.orange};
color : var(--secondary)
}
.uploadFile label {
cursor : pointer;
}
.treeview {
background-color : ${styles.colors.general_BackgroundColor};
}
.treeviews {
overflow-y : auto;
}
.dragbar {
position : absolute;
top : 29px;
width : 0.5em;
right : 0;
bottom : 0;
cursor : col-resize;
z-index : 999;
border-right : ${styles.leftPanel.dragbarBorderRight};
}
.ghostbar {
width : 3px;
background-color : ${styles.colors.lightBlue};
opacity : 0.5;
position : absolute;
cursor : col-resize;
z-index : 9999;
top : 0;
bottom : 0;
}
}
.dialog {
display: flex;
flex-direction: column;
}
.dialogParagraph {
${styles.infoTextBox}
margin-bottom: 2em;
word-break: break-word;
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,177 @@
var yo = require('yo-yo')
var $ = require('jquery')
const EventEmitter = require('events')
require('remix-tabs')
export class TabProxy {
constructor (fileManager, editor, appStore, appManager) {
this.event = new EventEmitter()
this.fileManager = fileManager
this.appManager = appManager
this.editor = editor
this.data = {}
this._view = {}
this._handlers = {}
fileManager.events.on('fileRemoved', (name) => {
this.removeTab(name)
})
fileManager.events.on('fileClosed', (name) => {
this.removeTab(name)
})
fileManager.events.on('currentFileChanged', (file) => {
if (this._handlers[file]) {
this._view.filetabs.activateTab(file)
return
}
this.addTab(file, '', () => {
this.fileManager.switchFile(file)
this.event.emit('switchFile', file)
},
() => {
this.fileManager.closeFile(file)
this.event.emit('closeFile', file)
})
})
fileManager.events.on('fileRenamed', (oldName, newName) => {
this.removeTab(oldName)
this.addTab(newName, '', () => {
this.fileManager.switchFile(newName)
this.event.emit('switchFile', newName)
},
() => {
this.fileManager.closeFile(newName)
this.event.emit('closeFile', newName)
})
})
appStore.event.on('activate', (name) => {
const { profile } = appStore.getOne(name)
if (profile.location === 'mainPanel') {
this.addTab(
name,
profile.displayName,
() => this.event.emit('switchApp', name),
() => {
this.event.emit('closeApp', name)
this.appManager.deactivateOne(name)
}
)
this.switchTab(name)
}
})
appStore.event.on('deactivate', (name) => {
this.removeTab(name)
})
}
switchTab (tabName) {
if (this._handlers[tabName]) {
this._handlers[tabName].switchTo()
this._view.filetabs.activateTab(tabName)
}
}
switchNextTab () {
const active = this._view.filetabs.active
if (active && this._handlers[active]) {
const handlers = Object.keys(this._handlers)
let i = handlers.indexOf(active)
if (i >= 0) {
i = handlers[i + 1] ? i + 1 : 0
this.switchTab(handlers[i])
}
}
}
switchPreviousTab () {
const active = this._view.filetabs.active
if (active && this._handlers[active]) {
const handlers = Object.keys(this._handlers)
let i = handlers.indexOf(active)
if (i >= 0) {
i = handlers[i - 1] ? i - 1 : handlers.length - 1
this.switchTab(handlers[i])
}
}
}
showTab (name) {
this._view.filetabs.activateTab(name)
}
addTab (name, title, switchTo, close, kind) {
if (this._handlers[name]) return
var slash = name.split('/')
if (!title) {
title = name.indexOf('/') !== -1 ? slash[slash.length - 1] : name
}
this._view.filetabs.addTab({
id: name,
title,
icon: '',
tooltip: name
})
this._handlers[name] = { switchTo, close }
}
removeTab (name) {
this._view.filetabs.removeTab(name)
delete this._handlers[name]
}
addHandler (type, fn) {
this.handlers[type] = fn
}
renderTabsbar () {
this._view.filetabs = yo`<remix-tabs></remix-tabs>`
this._view.filetabs.addEventListener('tabClosed', (event) => {
if (this._handlers[event.detail]) this._handlers[event.detail].close()
})
this._view.filetabs.addEventListener('tabActivated', (event) => {
if (this._handlers[event.detail]) this._handlers[event.detail].switchTo()
})
this._view.filetabs.canAdd = false
this._view.tabs = yo`
<div style="width: 100%; height: 100%;">
${this._view.filetabs}
</div>
`
let tabsbar = yo`
<div class="d-flex align-items-center" style="max-height: 35px; height: 100%">
${this._view.tabs}
</div>
`
// tabs
var $filesEl = $(this._view.filetabs)
// Switch tab
var self = this
$filesEl.on('click', '.file:not(.active)', function (ev) {
ev.preventDefault()
var name = $(this).find('.name').text()
self._handlers[name].switchTo()
return false
})
// Remove current tab
$filesEl.on('click', '.file .remove', function (ev) {
ev.preventDefault()
var name = $(this).parent().find('.name').text()
self._handlers[name].close()
return false
})
return tabsbar
}
}

@ -11,24 +11,33 @@ var swarmgw = require('swarmgw')()
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')
var styleGuide = require('../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = require('./styles/terminal-styles')
import { BaseApi } from 'remix-plugin'
var packageV = require('../../../package.json')
var KONSOLES = []
function register (api) { KONSOLES.push(api) }
var ghostbar = yo`<div class=${css.ghostbar}></div>`
var ghostbar = yo`<div class=${css.ghostbar} bg-secondary></div>`
const profile = {
displayName: 'Terminal',
name: 'terminal',
methods: [],
events: [],
description: ' - ',
required: false
}
class Terminal {
class Terminal extends BaseApi {
constructor (opts, api) {
super(profile)
var self = this
self.event = new EventManager()
self._api = api
@ -42,40 +51,14 @@ class Terminal {
self._view = { el: null, bar: null, input: null, term: null, journal: null, cli: null }
self._components = {}
self._components.cmdInterpreter = new CommandInterpreterAPI(this)
self._components.dropdown = new Dropdown({
options: [
'only remix transactions',
'all transactions',
'script'
],
defaults: ['only remix transactions', 'script'],
dependencies: {'all transactions': ['only remix transactions'], 'only remix transactions': ['all transactions']}
})
self._components.dropdown.event.register('deselect', function (label) {
self.event.trigger('filterChanged', ['deselect', label])
if (label === 'script') {
self.updateJournal({ type: 'deselect', value: label })
}
})
self._components.dropdown.event.register('select', function (label) {
self.event.trigger('filterChanged', ['select', label])
if (label === 'script') {
self.updateJournal({ type: 'select', value: label })
}
})
self._components.autoCompletePopup = new AutoCompletePopup()
self._components.autoCompletePopup = new AutoCompletePopup(self._opts)
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.innerText = textList
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 = {}
@ -109,64 +92,98 @@ class Terminal {
self._jsSandboxContext = {}
self._jsSandboxRegistered = {}
self.externalApi = this.api()
self.externalApi.notifs = {'theme': ['switchTheme']}
opts.appManager.init([self.externalApi])
opts.appManager.activateRequestAndNotification(self.externalApi)
if (opts.shell) self._shell = opts.shell
register(self)
}
focus () {
if (this._view.input) this._view.input.focus()
}
render () {
var self = this
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" onload=${() => { this.focus() }} contenteditable="true" onpaste=${paste} onkeydown=${change}></span>
`
self._view.input.innerText = '\n'
self._view.cli = yo`
<div class=${css.cli}>
<div class="${css.cli}">
<span class=${css.prompt}>${'>'}</span>
${self._view.input}
</div>
`
self._view.icon = yo`
<i onmouseenter=${hover} onmouseleave=${hover} onmousedown=${minimize}
class="${css.toggleTerminal} fa fa-angle-double-down"></i>`
class="btn btn-secondary btn-sm align-items-center ${css.toggleTerminal} fas fa-angle-double-down"></i>`
self._view.dragbar = yo`
<div onmousedown=${mousedown} class=${css.dragbarHorizontal}></div>`
self._view.dropdown = self._components.dropdown.render()
self._view.pendingTxCount = yo`<div class=${css.pendingTx} title='Pending Transactions'>0</div>`
self._view.inputSearch = yo`<input
spellcheck="false"
type="text"
class="${css.filter} form-control"
id="searchInput"
onkeydown=${filter}
placeholder="Search with transaction hash or address">
</input>`
self._view.bar = yo`
<div class=${css.bar}>
<div class="${css.bar}">
${self._view.dragbar}
<div class=${css.menu}>
<div class="${css.menu} border-top bg-light">
${self._view.icon}
<div class=${css.clear} onclick=${clear}>
<i class="fa fa-ban" aria-hidden="true" title="Clear console"
<i class="fas fa-ban" aria-hidden="true" title="Clear console"
onmouseenter=${hover} onmouseleave=${hover}></i>
</div>
${self._view.pendingTxCount}
<div class=${css.verticalLine}></div>
<div class=${css.listen}>
<input onchange=${listenOnNetwork} type="checkbox"
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you">
<div class="form-check">
<input
id="listenNetworkCheck"
onchange=${listenOnNetwork}
type="checkbox" class="form-check-input "
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you"
>
<label
class="form-check-label"
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you"
for="listenNetworkCheck"
>
listen on network
</label>
</div>
${self._view.dropdown}
<div class=${css.search}>
<i class="fa fa-search ${css.searchIcon}" aria-hidden="true"></i>
<input type="text" class=${css.filter} onkeydown=${filter} placeholder="Search transactions">
<i class="fas fa-search ${css.searchIcon} bg-light" aria-hidden="true"></i>
${self._view.inputSearch}
</div>
</div>
</div>
`
self._view.term = yo`
<div class=${css.terminal_container} onscroll=${throttle(reattach, 10)} onclick=${focusinput}>
<div class="${css.terminal_container}" onscroll=${throttle(reattach, 10)} onclick=${focusinput}>
<div style="
background-color: grey;
position: absolute;
height: 100%;
width: 100%;
opacity: 0.1;
z-index: -1;
"></div>
<div class=${css.terminal}>
${self._view.journal}
${self._view.cli}
</div>
</div>
`
self._view.autoCompletePopup = self._components.autoCompletePopup.render()
self._view.el = yo`
<div class=${css.panel}>
<div class="${css.panel}" style="height: 180px;">
${self._view.bar}
${self._view.term}
</div>
@ -273,7 +290,7 @@ class Terminal {
if (inserted) {
text.innerText = ''
background.onclick = undefined
self._view.journal.removeChild(placeholder)
if (placeholder.parentElement) self._view.journal.removeChild(placeholder)
}
inserted = false
delete self.scroll2bottom
@ -340,7 +357,8 @@ class Terminal {
clearTimeout(filtertimeout)
}
filtertimeout = setTimeout(() => {
self.updateJournal({ type: 'search', value: document.querySelector('.' + event.target.className).value })
self.updateJournal({ type: 'search', value: self._view.inputSearch.value })
self.scroll2bottom()
}, 500)
}
function clear (event) {
@ -385,7 +403,7 @@ class Terminal {
self._cmdIndex = -1
self._cmdTemp = ''
var intro = yo`<div><div> - Welcome to Remix v0.7.5 - </div><br>
var intro = yo`<div><div> - Welcome to Remix ${packageV.version} - </div><br>
<div>You can use this terminal for: </div>
<ul class=${css2.ul}>
<li>Checking transactions details and start debugging.</li>
@ -394,7 +412,7 @@ class Terminal {
<li><a target="_blank" href="https://web3js.readthedocs.io/en/1.0/">web3 version 1.0.0</a></li>
<li><a target="_blank" href="https://docs.ethers.io/ethers.js/html/">ethers.js</a> </li>
<li><a target="_blank" href="https://www.npmjs.com/package/swarmgw">swarmgw</a> </li>
<li>compilers - contains currently loaded compiler</li>
<li>remix (run remix.help() for more info)</li>
</ul>
</li>
<li>Executing common command to interact with the Remix interface (see list of commands above). Note that these commands can also be included and run from a JavaScript script.</li>
@ -407,14 +425,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 = ''
@ -425,102 +445,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
@ -537,8 +510,8 @@ class Terminal {
commands[value] = false
if (!self._INDEX.commandsMain[value]) return
self._INDEX.commandsMain[value].forEach(item => {
item.root.steps.forEach(item => { self._JOURNAL[item.gidx] = undefined })
self._JOURNAL[item.gidx] = undefined
item.root.steps.forEach(item => { self._JOURNAL[item.gidx].hide = true })
self._JOURNAL[item.gidx].hide = true
})
} else if (filterEvent.type === 'search') {
if (value !== self.data.activeFilters.input) {
@ -557,8 +530,8 @@ class Terminal {
self._JOURNAL.forEach(item => {
if (item && item.el && !item.hide) df.appendChild(item.el)
})
self._view.journal.innerHTML = ''
requestAnimationFrame(function updateDOM () {
self._view.journal.innerHTML = ''
self._view.journal.appendChild(df)
})
}
@ -573,7 +546,7 @@ class Terminal {
self._jobs = []
})
}
self._jobs.push(el)
if (self.data.activeFilters.commands[item.cmd]) self._jobs.push(el)
}
scroll2bottom () {
var self = this
@ -587,7 +560,12 @@ class Terminal {
if (args.length) append(args[0])
}
}
mode = { log: styles.terminal.text_RegularLog, info: styles.terminal.text_InfoLog, warn: styles.terminal.text_WarnLog, error: styles.terminal.text_ErrorLog }[mode] // defaults
mode = {
log: 'text-info',
info: 'text-info',
warn: 'text-warning',
error: 'text-danger' }[mode] // defaults
if (mode) {
return function logger (args, scopedCommands, append) {
var types = args.map(type)
@ -596,7 +574,7 @@ class Terminal {
if (types[idx] === 'element') val = jsbeautify.html(val)
return val
})
append(yo`<span style="color: ${mode};">${values}</span>`)
append(yo`<span class="${mode}" >${values}</span>`)
}
} else {
throw new Error('mode is not supported')
@ -681,7 +659,6 @@ class Terminal {
function domTerminalFeatures (self, scopedCommands) {
return {
compilers: self._opts.compilers,
swarmgw,
ethers,
remix: self._components.cmdInterpreter,

@ -1,94 +0,0 @@
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
'use strict';
var _createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);
}
}return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;
};
}();
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var RemixExtension = function () {
function RemixExtension() {
var _this = this;
_classCallCheck(this, RemixExtension);
this._notifications = {};
this._pendingRequests = {};
this._id = 0;
window.addEventListener('message', function (event) {
return _this._newMessage(event);
}, false);
}
_createClass(RemixExtension, [{
key: 'listen',
value: function listen(key, type, callback) {
if (!this._notifications[key]) this._notifications[key] = {};
this._notifications[key][type] = callback;
}
}, {
key: 'call',
value: function call(key, type, params, callback) {
this._id++;
this._pendingRequests[this._id] = callback;
window.parent.postMessage(JSON.stringify({
action: 'request',
key: key,
type: type,
value: params,
id: this._id
}), '*');
}
}, {
key: '_newMessage',
value: function _newMessage(event) {
if (!event.data) return;
if (typeof event.data !== 'string') return;
var msg;
try {
msg = JSON.parse(event.data);
} catch (e) {
return console.log('unable to parse data');
}
var _msg = msg,
action = _msg.action,
key = _msg.key,
type = _msg.type,
value = _msg.value;
if (action === 'notification') {
if (this._notifications[key] && this._notifications[key][type]) {
this._notifications[key][type](value);
}
} else if (action === 'response') {
var _msg2 = msg,
id = _msg2.id,
error = _msg2.error;
if (this._pendingRequests[id]) {
this._pendingRequests[id](error, value);
delete this._pendingRequests[id];
}
}
}
}]);
return RemixExtension;
}();
if (window) window.RemixExtension = RemixExtension;
if (module && module.exports) module.exports = RemixExtension;
},{}]},{},[1]);

@ -1,55 +0,0 @@
'use strict'
class RemixExtension {
constructor () {
this._notifications = {}
this._pendingRequests = {}
this._id = 0
window.addEventListener('message', (event) => this._newMessage(event), false)
}
listen (key, type, callback) {
if (!this._notifications[key]) this._notifications[key] = {}
this._notifications[key][type] = callback
}
call (key, type, params, callback) {
this._id++
this._pendingRequests[this._id] = callback
window.parent.postMessage(JSON.stringify({
action: 'request',
key,
type,
value: params,
id: this._id
}), '*')
}
_newMessage (event) {
if (!event.data) return
if (typeof event.data !== 'string') return
var msg
try {
msg = JSON.parse(event.data)
} catch (e) {
return console.log('unable to parse data')
}
const {action, key, type, value} = msg
if (action === 'notification') {
if (this._notifications[key] && this._notifications[key][type]) {
this._notifications[key][type](value)
}
} else if (action === 'response') {
const {id, error} = msg
if (this._pendingRequests[id]) {
this._pendingRequests[id](error, value)
delete this._pendingRequests[id]
}
}
}
}
if (window) window.RemixExtension = RemixExtension
if (module && module.exports) module.exports = RemixExtension

@ -1,71 +0,0 @@
{
"name": "remix-extension",
"version": "0.0.1",
"description": "Ethereum IDE and tools for the web",
"contributors": [
{
"name": "Yann Levreau",
"email": "yann@ethdev.com"
}
],
"main": "./index.js",
"dependencies": {
"babel-eslint": "^7.1.1",
"babel-plugin-transform-object-assign": "^6.22.0",
"babel-preset-es2015": "^6.24.0",
"babelify": "^7.3.0",
"standard": "^7.0.1",
"tape": "^4.6.0"
},
"scripts": {
"browserify": "browserify index.js -o bundle.js"
},
"standard": {
"ignore": [
"node_modules/*"
],
"parser": "babel-eslint"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ethereum/remix-ide.git"
},
"author": "cpp-ethereum team",
"license": "MIT",
"bugs": {
"url": "https://github.com/ethereum/remix-ide/issues"
},
"homepage": "https://github.com/ethereum/remix-ide#readme",
"browserify": {
"transform": [
[
"babelify",
{
"plugins": [
[
"fast-async",
{
"runtimePatten": null,
"compiler": {
"promises": true,
"es7": true,
"noRuntime": true,
"wrapAwait": true
}
}
],
"transform-object-assign"
]
}
],
[
"babelify",
{
"presets": [
"es2015"
]
}
]
]
}
}

@ -1,53 +0,0 @@
plugin api
# current APIs:
## 1) notifications
### app (key: app)
- unfocus `[]`
- focus `[]`
### compiler (key: compiler)
- compilationFinished `[success (bool), data (obj), source (obj)]`
- compilationData `[compilationResult]`
### transaction listener (key: txlistener)
- newTransaction `tx (obj)`
## 2) interactions
### app
- getExecutionContextProvider `@return {String} provider (injected | web3 | vm)`
- getProviderEndpoint `@return {String} url`
- updateTitle `@param {String} title`
- detectNetWork `@return {Object} {name, id}`
- addProvider `@param {String} name, @param {String} url`
- removeProvider `@return {String} name`
### config
- setConfig `@param {String} path, @param {String} content`
- getConfig `@param {String} path`
- removeConfig `@param {String} path`
### compiler
- getCompilationResult `@return {Object} compilation result`
### udapp (only VM)
- runTx `@param {Object} tx`
- getAccounts `@return {Array} acccounts`
- createVMAccount `@param {String} privateKey, @param {String} balance (hex)`
### editor
- getFilesFromPath `@param {Array} [path]`
- getCurrentFile `@return {String} path`
- getFile `@param {String} path`
- setFile `@param {String} path, @param {String} content`
- highlight `@param {Object} lineColumnPos {start: {line, column}, end: {line, column}}, @param {String} path, @param {String} hexColor`
- discardHighlight

@ -1,150 +0,0 @@
'use strict'
var executionContext = require('../../execution-context')
var SourceHighlighter = require('../editor/sourceHighlighter')
/*
Defines available API. `key` / `type`
*/
module.exports = (pluginManager, fileProviders, fileManager, compilesrArtefacts, udapp) => {
let highlighters = {}
return {
app: {
getExecutionContextProvider: (mod, cb) => {
cb(null, executionContext.getProvider())
},
getProviderEndpoint: (mod, cb) => {
if (executionContext.getProvider() === 'web3') {
cb(null, executionContext.web3().currentProvider.host)
} else {
cb('no endpoint: current provider is either injected or vm')
}
},
updateTitle: (mod, title, cb) => {
pluginManager.plugins[mod].modal.setTitle(title)
if (cb) cb()
},
detectNetWork: (mod, cb) => {
executionContext.detectNetwork((error, network) => {
cb(error, network)
})
},
addProvider: (mod, name, url, cb) => {
executionContext.addProvider({ name, url })
cb()
},
removeProvider: (mod, name, cb) => {
executionContext.removeProvider(name)
cb()
}
},
config: {
setConfig: (mod, path, content, cb) => {
fileProviders['config'].set(mod + '/' + path, content)
cb()
},
getConfig: (mod, path, cb) => {
cb(null, fileProviders['config'].get(mod + '/' + path))
},
removeConfig: (mod, path, cb) => {
cb(null, fileProviders['config'].remove(mod + '/' + path))
if (cb) cb()
}
},
compiler: {
getCompilationResult: (mod, cb) => {
cb(null, compilesrArtefacts['__last'])
},
sendCompilationResult: (mod, file, source, languageVersion, data, cb) => {
pluginManager.receivedDataFrom('sendCompilationResult', mod, [file, source, languageVersion, data])
}
},
udapp: {
runTx: (mod, tx, cb) => {
executionContext.detectNetwork((error, network) => {
if (error) return cb(error)
if (network.name === 'Main' && network.id === '1') {
return cb('It is not allowed to make this action against mainnet')
}
udapp.silentRunTx(tx, (error, result) => {
if (error) return cb(error)
cb(null, {
transactionHash: result.transactionHash,
status: result.result.status,
gasUsed: '0x' + result.result.gasUsed.toString('hex'),
error: result.result.vm.exceptionError,
return: result.result.vm.return ? '0x' + result.result.vm.return.toString('hex') : '0x',
createdAddress: result.result.createdAddress ? '0x' + result.result.createdAddress.toString('hex') : undefined
})
})
})
},
getAccounts: (mod, cb) => {
executionContext.detectNetwork((error, network) => {
if (error) return cb(error)
if (network.name === 'Main' && network.id === '1') {
return cb('It is not allowed to make this action against mainnet')
}
udapp.getAccounts(cb)
})
},
createVMAccount: (mod, privateKey, balance, cb) => {
if (executionContext.getProvider() !== 'vm') return cb('plugin API does not allow creating a new account through web3 connection. Only vm mode is allowed')
udapp.createVMAccount(privateKey, balance, (error, address) => {
cb(error, address)
})
}
},
editor: {
getFilesFromPath: (mod, path, cb) => {
fileManager.filesFromPath(path, cb)
},
getCurrentFile: (mod, cb) => {
var path = fileManager.currentFile()
if (!path) {
cb('no file selected')
} else {
cb(null, path)
}
},
getFile: (mod, path, cb) => {
var provider = fileManager.fileProviderOf(path)
if (provider) {
// TODO add approval to user for external plugin to get the content of the given `path`
provider.get(path, (error, content) => {
cb(error, content)
})
} else {
cb(path + ' not available')
}
},
setFile: (mod, path, content, cb) => {
var provider = fileManager.fileProviderOf(path)
if (provider) {
// TODO add approval to user for external plugin to set the content of the given `path`
provider.set(path, content, (error) => {
if (error) return cb(error)
fileManager.syncEditor(path)
cb()
})
} else {
cb(path + ' not available')
}
},
highlight: (mod, lineColumnPos, filePath, hexColor, cb) => {
var position
try {
position = JSON.parse(lineColumnPos)
} catch (e) {
return cb(e.message)
}
if (!highlighters[mod]) highlighters[mod] = new SourceHighlighter()
highlighters[mod].currentSourceLocation(null)
highlighters[mod].currentSourceLocationFromfileName(position, filePath, hexColor)
cb()
},
discardHighlight: (mod, cb) => {
if (highlighters[mod]) highlighters[mod].currentSourceLocation(null)
cb()
}
}
}
}

@ -1,179 +0,0 @@
'use strict'
var remixLib = require('remix-lib')
var EventManager = remixLib.EventManager
const PluginAPI = require('./pluginAPI')
/**
* Register and Manage plugin:
*
* Plugin registration is done in the settings tab,
* using the following format:
* {
* "title": "<plugin name>",
* "url": "<plugin url>"
* }
*
* structure of messages:
*
* - Notification sent by Remix:
*{
* action: 'notification',
* key: <string>,
* type: <string>,
* value: <array>
*}
*
* - Request sent by the plugin:
*{
* id: <number>,
* action: 'request',
* key: <string>,
* type: <string>,
* value: <array>
*}
*
* - Response sent by Remix and receive by the plugin:
*{
* id: <number>,
* action: 'response',
* key: <string>,
* type: <string>,
* value: <array>,
* error: (see below)
*}
* => The `error` property is `undefined` if no error happened.
* => In case of error (due to permission, system error, API error, etc...):
* error: { code, msg (optional), data (optional), stack (optional)
* => possible error code are still to be defined, but the generic one would be 500.
*
* Plugin receive 4 types of message:
* - focus (when he get focus)
* - unfocus (when he loose focus - is hidden)
* - compilationData (that is triggered just after a focus - and send the current compilation data or null)
* - compilationFinished (that is only sent to the plugin that has focus)
*
* Plugin can emit messages and receive response.
*
* CONFIG:
* - getConfig(filename). The data to send should be formatted like:
* {
* id: <requestid>,
* action: 'request',
* key: 'config',
* type: 'getConfig',
* value: ['filename.ext']
* }
* the plugin will reveice a response like:
* {
* id: <requestid>,
* action: 'response',
* key: 'config',
* type: 'getConfig',
* error,
* value: ['content of filename.ext']
* }
* same apply for the other call
* - setConfig(filename, content)
* - removeConfig
*
* See index.html and remix.js in test-browser folder for sample
*
*/
module.exports = class PluginManager {
constructor (app, compilersArtefacts, txlistener, fileProviders, fileManager, udapp) {
const self = this
self.event = new EventManager()
var pluginAPI = new PluginAPI(
this,
fileProviders,
fileManager,
compilersArtefacts,
udapp
)
self._components = { pluginAPI }
self.plugins = {}
self.origins = {}
self.inFocus
fileManager.event.register('currentFileChanged', (file, provider) => {
self.broadcast(JSON.stringify({
action: 'notification',
key: 'editor',
type: 'currentFileChanged',
value: [ file ]
}))
})
txlistener.event.register('newTransaction', (tx) => {
self.broadcast(JSON.stringify({
action: 'notification',
key: 'txlistener',
type: 'newTransaction',
value: [tx]
}))
})
window.addEventListener('message', (event) => {
if (event.type !== 'message') return
var extension = self.origins[event.origin]
if (!extension) return
function response (key, type, callid, error, result) {
self.postToOrigin(event.origin, JSON.stringify({
id: callid,
action: 'response',
key: key,
type: type,
error: error,
value: [ result ]
}))
}
var data = JSON.parse(event.data)
data.value.unshift(extension)
data.value.push((error, result) => {
response(data.key, data.type, data.id, error, result)
})
if (pluginAPI[data.key] && pluginAPI[data.key][data.type]) {
pluginAPI[data.key][data.type].apply({}, data.value)
} else {
response(data.key, data.type, data.id, `Endpoint ${data.key}/${data.type} not present`, null)
}
}, false)
}
unregister (desc) {
const self = this
self._components.pluginAPI.editor.discardHighlight(desc.title, () => {})
delete self.plugins[desc.title]
delete self.origins[desc.url]
}
register (desc, modal, content) {
const self = this
self.plugins[desc.title] = { content, modal, origin: desc.url }
self.origins[desc.url] = desc.title
}
broadcast (value) {
for (var plugin in this.plugins) {
this.post(plugin, value)
}
}
postToOrigin (origin, value) {
if (this.origins[origin]) {
this.post(this.origins[origin], value)
}
}
receivedDataFrom (methodName, mod, argumentsArray) {
// TODO check whether 'mod' as right to do that
console.log(argumentsArray)
this.event.trigger(methodName, argumentsArray) // forward to internal modules
this.broadcast(JSON.stringify({ // forward to plugins
action: 'notification',
key: mod,
type: methodName,
value: argumentsArray
}))
}
post (name, value) {
const self = this
if (self.plugins[name]) {
self.plugins[name].content.querySelector('iframe').contentWindow.postMessage(value, self.plugins[name].origin)
}
}
}

@ -1,38 +0,0 @@
'use strict'
module.exports = {
'oraclize': {
url: 'https://remix-plugin.oraclize.it',
title: 'Oraclize'
},
'solium': {
url: 'https://two-water.surge.sh',
title: 'Solium'
},
'ethdoc': {
url: 'https://30400.swarm-gateways.net/bzz:/ethdoc.remixide.eth',
title: 'Ethdoc'
},
'openzeppelin snippet': {
url: 'https://left-edge.surge.sh',
title: 'Openzeppelin snippet'
},
'vyper': {
url: 'https://plugin.vyper.live',
title: 'Vyper'
},
'slither/mythril': {
url: 'http://jittery-space.surge.sh',
title: 'Slither/Mythril'
},
'pipeline': {
url: 'https://pipeline.pipeos.one',
title: 'Pipeline'
}
/*
'etherscan-general': {
url: 'http://127.0.0.1:8081',
title: 'Etherscan-general'
}
*/
}

@ -4,10 +4,6 @@ var yo = require('yo-yo')
var $ = require('jquery')
var remixLib = require('remix-lib')
var utils = remixLib.util
var styleGuide = require('../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = require('./styles/staticAnalysisView-styles')
var globlalRegistry = require('../../global/registry')
@ -34,6 +30,7 @@ function staticAnalysisView (localRegistry) {
self.lastCompilationResult = null
self.lastCompilationSource = null
$('#staticanalysisresult').empty()
if (languageVersion.indexOf('soljson') !== 0) return
self.lastCompilationResult = data
self.lastCompilationSource = source
if (self.view.querySelector('#autorunstaticanalysis').checked) {
@ -46,30 +43,34 @@ staticAnalysisView.prototype.render = function () {
var self = this
var view = yo`
<div class="${css.analysis}">
<div id="staticanalysismodules">
${this.modulesView}
</div>
<div class="${css.buttons}">
<button class="${css.buttonRun}" onclick="${function () { self.run() }}" >Run</button>
<label class="${css.label}" for="autorunstaticanalysis">
<input id="autorunstaticanalysis"
type="checkbox"
style="vertical-align:bottom"
checked="true"
>
Auto run
</label>
<label class="${css.label}" for="checkAllEntries">
<input id="checkAllEntries"
type="checkbox"
onclick="${function (event) { self.checkAll(event) }}"
style="vertical-align:bottom"
checked="true"
>
Check/Uncheck all
</label>
</div>
<div class="${css.result}" "id='staticanalysisresult'></div>
<div class="${css.buttonsInner}">
<button class="${css.buttonRun} btn btn-sm btn-primary" onclick="${function () { self.run() }}" >Run</button>
<label class="${css.label}" for="autorunstaticanalysis">
<input id="autorunstaticanalysis"
type="checkbox"
style="vertical-align:bottom"
checked="true"
>
Auto run
</label>
<label class="${css.label}" for="checkAllEntries">
<input id="checkAllEntries"
type="checkbox"
onclick="${function (event) { self.checkAll(event) }}"
style="vertical-align:bottom"
checked="true"
>
Check/Uncheck all
</label>
</div>
</div>
<div id="staticanalysismodules" class="list-group list-group-flush ${css.container}">
${this.modulesView}
</div>
<hr>
<div><h6>Results:</h6></div>
<div class="${css.result}" id='staticanalysisresult'></div>
</div>
`
if (!this.view) {
@ -97,8 +98,8 @@ staticAnalysisView.prototype.run = function () {
var selected = this.selectedModules()
var warningContainer = $('#staticanalysisresult')
warningContainer.empty()
if (this.lastCompilationResult) {
var self = this
var self = this
if (this.lastCompilationResult && selected.length) {
var warningCount = 0
this.runner.run(this.lastCompilationResult, selected, function (results) {
results.map(function (result, i) {
@ -119,19 +120,16 @@ staticAnalysisView.prototype.run = function () {
}
warningCount++
var msg = yo`<span>${location} ${item.warning} ${item.more ? yo`<span><br><a href="${item.more}" target="blank">more</a></span>` : yo`<span></span>`}</span>`
self._deps.renderer.error(msg, warningContainer, {type: 'staticAnalysisWarning', useSpan: true})
self._deps.renderer.error(msg, warningContainer, {type: 'staticAnalysisWarning alert alert-warning', useSpan: true})
})
})
if (warningContainer.html() === '') {
$('#righthand-panel #menu .staticanalysisView').css('color', '')
warningContainer.html('No warning to report')
} else {
$('#righthand-panel #menu .staticanalysisView').css('color', styles.colors.red)
}
self.event.trigger('staticAnaysisWarning', [warningCount])
})
} else {
warningContainer.html('No compiled AST available')
if (selected.length) {
warningContainer.html('No compiled AST available')
}
self.event.trigger('staticAnaysisWarning', [-1])
}
}
@ -176,7 +174,7 @@ staticAnalysisView.prototype.renderModules = function () {
</label>
`
})
return yo`<div class="${css.analysisModulesContainer}">
return yo`<div class="${css.analysisModulesContainer} list-group-item py-1">
<label class="${css.label}"><b>${category[0].categoryDisplayName}</b></label>
${entriesDom}
</div>`

@ -1,8 +1,5 @@
var csjs = require('csjs-inject')
var styleGuide = require('../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs`
.analysis {
display: flex;
@ -10,27 +7,29 @@ var css = csjs`
}
.result {
margin-top: 1%;
max-height: 300px;
}
.buttons {
${styles.rightPanel.analysisTab.box_AnalysisContainer}
margin: 1rem 0;
}
.buttonsInner {
display: flex;
align-items: center;
justify-content: space-around;
}
.buttonRun {
${styles.rightPanel.analysisTab.button_Run_AnalysisTab}
margin-right: 1%;
}
.analysisModulesContainer {
${styles.rightPanel.analysisTab.box_AnalysisContainer}
margin-bottom: 1%;
line-height: 2em;
display: flex;
flex-direction: column;
}
.label {
display: flex;
align-items: center;
}
.container {
max-height: 300px;
overflow-y: auto;
}
`
module.exports = css

@ -3,21 +3,46 @@ var StaticAnalysis = require('../staticanalysis/staticAnalysisView')
var EventManager = require('../../lib/events')
var css = require('./styles/analysis-tab-styles')
class AnalysisTab {
import { BaseApi } from 'remix-plugin'
import { EventEmitter } from 'events'
const profile = {
name: 'solidityStaticAnalysis',
displayName: 'Solidity static analysis',
methods: [],
events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMjA0OCIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMjA0OCAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0yMDQ4IDE1MzZ2MTI4aC0yMDQ4di0xNTM2aDEyOHYxNDA4aDE5MjB6bS0xMjgtMTI0OHY0MzVxMCAyMS0xOS41IDI5LjV0LTM1LjUtNy41bC0xMjEtMTIxLTYzMyA2MzNxLTEwIDEwLTIzIDEwdC0yMy0xMGwtMjMzLTIzMy00MTYgNDE2LTE5Mi0xOTIgNTg1LTU4NXExMC0xMCAyMy0xMHQyMyAxMGwyMzMgMjMzIDQ2NC00NjQtMTIxLTEyMXEtMTYtMTYtNy41LTM1LjV0MjkuNS0xOS41aDQzNXExNCAwIDIzIDl0OSAyM3oiLz48L3N2Zz4=',
description: 'Checks the contract code for security vulnerabilities and bad practices.',
kind: 'analysis',
location: 'swapPanel'
}
class AnalysisTab extends BaseApi {
constructor (registry) {
super(profile)
this.event = new EventManager()
this.events = new EventEmitter()
this.registry = registry
}
render () {
var staticanalysis = new StaticAnalysis()
this.registry.put({api: staticanalysis, name: 'staticanalysis'})
if (!this.staticanalysis) this.staticanalysis = new StaticAnalysis()
this.staticanalysis.event.register('staticAnaysisWarning', (count) => {
if (count > 0) {
this.events.emit('statusChanged', {key: count, title: `${count} warning${count === 1 ? '' : 's'}`, type: 'warning'})
} else if (count === 0) {
this.events.emit('statusChanged', {key: 'succeed', title: 'no warning', type: 'success'})
} else {
// count ==-1 no compilation result
this.events.emit('statusChanged', {key: 'none'})
}
})
this.registry.put({api: this.staticanalysis, name: 'staticanalysis'})
if (this.el) return this.el
this.el = yo`<div class="${css.analysisTabView} "id="staticanalysisView">${staticanalysis.render()}</div>`
return this.el
return yo`<div class="${css.analysisTabView}" id="staticanalysisView">${this.staticanalysis.render()}</div>`
}
}
module.exports = AnalysisTab

File diff suppressed because it is too large Load Diff

@ -0,0 +1,105 @@
const async = require('async')
const EventEmitter = require('events')
var remixTests = require('remix-tests')
var Compiler = require('remix-solidity').Compiler
var CompilerImport = require('../../compiler/compiler-imports')
// TODO: move this to the UI
const addTooltip = require('../../ui/tooltip')
class CompileTab {
constructor (queryParams, fileManager, editor, config, fileProviders) {
this.event = new EventEmitter()
this.queryParams = queryParams
this.compilerImport = new CompilerImport()
this.compiler = new Compiler((url, cb) => this.importFileCb(url, cb))
this.fileManager = fileManager
this.editor = editor
this.config = config
this.fileProviders = fileProviders
}
init () {
this.optimize = this.queryParams.get().optimize
this.optimize = this.optimize === 'true'
this.queryParams.update({ optimize: this.optimize })
this.compiler.setOptimize(this.optimize)
}
setOptimize (newOptimizeValue) {
this.optimize = newOptimizeValue
this.queryParams.update({ optimize: this.optimize })
this.compiler.setOptimize(this.optimize)
}
runCompiler () {
this.fileManager.saveCurrentFile()
this.editor.clearAnnotations()
var currentFile = this.config.get('currentFile')
if (!currentFile) return
if (!/\.sol/.exec(currentFile)) return
// only compile *.sol file.
var target = currentFile
var sources = {}
var provider = this.fileManager.fileProviderOf(currentFile)
if (!provider) return console.log('cannot compile ' + currentFile + '. Does not belong to any explorer')
provider.get(target, (error, content) => {
if (error) return console.log(error)
sources[target] = { content }
this.event.emit('startingCompilation')
setTimeout(() => {
// setTimeout fix the animation on chrome... (animation triggered by 'staringCompilation')
this.compiler.compile(sources, target)
}, 100)
})
}
importExternal (url, cb) {
this.compilerImport.import(url,
// TODO: move to an event that is generated, the UI shouldn't be here
(loadingMsg) => { addTooltip(loadingMsg) },
(error, content, cleanUrl, type, url) => {
if (error) return cb(error)
if (this.fileProviders[type]) {
this.fileProviders[type].addReadOnly(cleanUrl, content, url)
}
cb(null, content)
})
}
importFileCb (url, filecb) {
if (url.indexOf('/remix_tests.sol') !== -1) return filecb(null, remixTests.assertLibCode)
var provider = this.fileManager.fileProviderOf(url)
if (provider) {
if (provider.type === 'localhost' && !provider.isConnected()) {
return filecb(`file provider ${provider.type} not available while trying to resolve ${url}`)
}
return provider.exists(url, (error, exist) => {
if (error) return filecb(error)
if (exist) {
return provider.get(url, filecb)
}
this.importExternal(url, filecb)
})
}
if (this.compilerImport.isRelativeImport(url)) {
// try to resolve localhost modules (aka truffle imports)
var splitted = /([^/]+)\/(.*)$/g.exec(url)
return async.tryEach([
(cb) => { this.importFileCb('localhost/installed_contracts/' + url, cb) },
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.importFileCb('localhost/installed_contracts/' + splitted[1] + '/contracts/' + splitted[2], cb) } },
(cb) => { this.importFileCb('localhost/node_modules/' + url, cb) },
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.importFileCb('localhost/node_modules/' + splitted[1] + '/contracts/' + splitted[2], cb) } }],
(error, result) => { filecb(error, result) }
)
}
this.importExternal(url, filecb)
}
}
module.exports = CompileTab

@ -0,0 +1,267 @@
/* global Worker */
const yo = require('yo-yo')
var minixhr = require('minixhr')
var helper = require('../../../lib/helper')
const addTooltip = require('../../ui/tooltip')
var css = require('../styles/compile-tab-styles')
class CompilerContainer {
constructor (compileTabLogic, editor, config, queryParams) {
this._view = {}
this.compileTabLogic = compileTabLogic
this.editor = editor
this.config = config
this.queryParams = queryParams
this.data = {
hideWarnings: config.get('hideWarnings') || false,
autoCompile: config.get('autoCompile'),
compileTimeout: null,
timeout: 300,
allversions: null,
selectedVersion: null,
defaultVersion: 'soljson-v0.5.1+commit.c8a2cb62.js', // this default version is defined: in makeMockCompiler (for browser test) and in package.json (downloadsolc_root) for the builtin compiler
baseurl: 'https://solc-bin.ethereum.org/bin'
}
}
/**
* Update the compilation button with the name of the current file
*/
set currentFile (name = '') {
if (!this._view.compilationButton) return
const button = this.compilationButton(name.split('/').pop())
yo.update(this._view.compilationButton, button)
}
deactivate () {
}
activate () {
this.currentFile = this.config.get('currentFile')
this.listenToEvents()
}
listenToEvents () {
this.editor.event.register('contentChanged', this.scheduleCompilation.bind(this))
this.editor.event.register('sessionSwitched', this.scheduleCompilation.bind(this))
this.compileTabLogic.event.on('startingCompilation', () => {
if (!this._view.compileIcon) return
this._view.compileIcon.setAttribute('title', 'compiling...')
this._view.compileIcon.classList.remove(`${css.bouncingIcon}`)
this._view.compileIcon.classList.add(`${css.spinningIcon}`)
})
this.compileTabLogic.compiler.event.register('compilationDuration', (speed) => {
if (!this._view.warnCompilationSlow) return
if (speed > 1000) {
const msg = `Last compilation took ${speed}ms. We suggest to turn off autocompilation.`
this._view.warnCompilationSlow.setAttribute('title', msg)
this._view.warnCompilationSlow.style.visibility = 'visible'
} else {
this._view.warnCompilationSlow.style.visibility = 'hidden'
}
})
this.editor.event.register('contentChanged', () => {
if (!this._view.compileIcon) return
this._view.compileIcon.classList.add(`${css.bouncingIcon}`) // @TODO: compileView tab
})
this.compileTabLogic.compiler.event.register('loadingCompiler', () => {
if (!this._view.compileIcon) return
this._view.compileIcon.setAttribute('title', 'compiler is loading, please wait a few moments.')
this._view.compileIcon.classList.add(`${css.spinningIcon}`)
this._view.warnCompilationSlow.style.visibility = 'hidden'
})
this.compileTabLogic.compiler.event.register('compilerLoaded', () => {
if (!this._view.compileIcon) return
this._view.compileIcon.setAttribute('title', '')
this._view.compileIcon.classList.remove(`${css.spinningIcon}`)
})
this.compileTabLogic.compiler.event.register('compilationFinished', (success, data, source) => {
if (!this._view.compileIcon) return
this._view.compileIcon.setAttribute('title', 'idle')
this._view.compileIcon.classList.remove(`${css.spinningIcon}`)
this._view.compileIcon.classList.remove(`${css.bouncingIcon}`)
})
}
/**************
* SUBCOMPONENT
*/
compilationButton (name) {
if (!name) name = ''
var displayed = name === '' ? '<no file selected>' : name
var el = yo`
<div class="${css.compilerArticle}">
<button class="btn btn-primary btn-block ${name === '' ? 'disabled' : ''}" title="Compile" onclick="${this.compile.bind(this)}">
<span>${this._view.compileIcon} Compile ${displayed}</span>
</button>
</div>`
if (name === '') {
el.setAttribute('disabled', 'true')
}
return el
}
render () {
this.compileTabLogic.compiler.event.register('compilerLoaded', (version) => this.setVersionText(version))
this.fetchAllVersion((allversions, selectedVersion) => {
this.data.allversions = allversions
this.data.selectedVersion = selectedVersion
if (this._view.versionSelector) this._updateVersionSelector()
})
this._view.warnCompilationSlow = yo`<i title="Compilation Slow" style="visibility:hidden" class="${css.warnCompilationSlow} fas fa-exclamation-triangle" aria-hidden="true"></i>`
this._view.compileIcon = yo`<i class="fas fa-sync ${css.icon}" aria-hidden="true"></i>`
this._view.autoCompile = yo`<input class="${css.autocompile}" onchange=${this.updateAutoCompile.bind(this)} id="autoCompile" type="checkbox" title="Auto compile">`
this._view.hideWarningsBox = yo`<input class="${css.autocompile}" onchange=${this.hideWarnings.bind(this)} id="hideWarningsBox" type="checkbox" title="Hide warnings">`
if (this.data.autoCompile) this._view.autoCompile.setAttribute('checked', '')
if (this.data.hideWarnings) this._view.hideWarningsBox.setAttribute('checked', '')
this._view.optimize = yo`<input onchange=${this.onchangeOptimize.bind(this)} id="optimize" type="checkbox">`
if (this.compileTabLogic.optimize) this._view.optimize.setAttribute('checked', '')
this._view.versionSelector = yo`
<select onchange="${this.onchangeLoadVersion.bind(this)}" class="custom-select" id="versionSelector" disabled>
<option disabled selected>Select new compiler version</option>
</select>`
this._view.version = yo`<span id="version"></span>`
this._view.compilationButton = this.compilationButton()
this._view.compileContainer = yo`
<section>
<!-- Select Compiler Version -->
<article>
<header class="navbar navbar-light bg-light input-group mb-3">
<div class="input-group-prepend">
<label class="input-group-text border-0" for="versionSelector">Compiler</label>
</div>
${this._view.versionSelector}
</header>
${this._view.compilationButton}
</article>
<!-- Config -->
<article>
<small class="${css.compilerSm}">Compiler Configuration</small>
<ul class="list-group list-group-flush">
<li class="list-group-item form-group ${css.compilerConfig}">
${this._view.autoCompile}
<label for="autoCompile">Auto compile</label>
</li>
<li class="list-group-item form-group ${css.compilerConfig}">
${this._view.optimize}
<label for="optimize">Enable Optimization</label>
</li>
<li class="list-group-item form-group ${css.compilerConfig}">
${this._view.hideWarningsBox}
<label for="hideWarningsBox">Hide warnings</label>
</li>
</ul>
</article>
</section>`
return this._view.compileContainer
}
updateAutoCompile (event) {
this.config.set('autoCompile', this._view.autoCompile.checked)
}
compile (event) {
this.compileTabLogic.runCompiler()
}
hideWarnings (event) {
this.config.set('hideWarnings', this._view.hideWarningsBox.checked)
this.compile()
}
onchangeOptimize () {
this.compileTabLogic.setOptimize(!!this._view.optimize.checked)
this.compileTabLogic.runCompiler()
}
onchangeLoadVersion (event) {
this.data.selectedVersion = this._view.versionSelector.value
this._updateVersionSelector()
}
_updateVersionSelector () {
this._view.versionSelector.innerHTML = ''
this.data.allversions.forEach(build => {
const option = build.path === this.data.selectedVersion
? yo`<option value="${build.path}" selected>${build.longVersion}</option>`
: yo`<option value="${build.path}">${build.longVersion}</option>`
this._view.versionSelector.appendChild(option)
})
this._view.versionSelector.removeAttribute('disabled')
this.queryParams.update({ version: this.data.selectedVersion })
let url
if (this.data.selectedVersion === 'builtin') {
let location = window.document.location
location = `${location.protocol}//${location.host}/${location.pathname}`
if (location.endsWith('index.html')) location = location.substring(0, location.length - 10)
if (!location.endsWith('/')) location += '/'
url = location + 'soljson.js'
} else {
if (this.data.selectedVersion.indexOf('soljson') !== 0 || helper.checkSpecialChars(this.data.selectedVersion)) {
return console.log('loading ' + this.data.selectedVersion + ' not allowed')
}
url = `${this.data.baseurl}/${this.data.selectedVersion}`
}
const isFirefox = typeof InstallTrigger !== 'undefined'
if (document.location.protocol !== 'file:' && Worker !== undefined && isFirefox) {
// Workers cannot load js on "file:"-URLs and we get a
// "Uncaught RangeError: Maximum call stack size exceeded" error on Chromium,
// resort to non-worker version in that case.
this.compileTabLogic.compiler.loadVersion(true, url)
this.setVersionText('(loading using worker)')
} else {
this.compileTabLogic.compiler.loadVersion(false, url)
this.setVersionText('(loading)')
}
}
setVersionText (text) {
this.data.version = text
if (this._view.version) this._view.version.innerText = text
}
fetchAllVersion (callback) {
minixhr(`${this.data.baseurl}/list.json`, (json, event) => {
// @TODO: optimise and cache results to improve app loading times
var allversions, selectedVersion
if (event.type !== 'error') {
try {
const data = JSON.parse(json)
allversions = data.builds.slice().reverse()
selectedVersion = this.data.defaultVersion
if (this.queryParams.get().version) selectedVersion = this.queryParams.get().version
} catch (e) {
addTooltip('Cannot load compiler version list. It might have been blocked by an advertisement blocker. Please try deactivating any of them from this page and reload.')
}
} else {
allversions = [{ path: 'builtin', longVersion: 'latest local version' }]
selectedVersion = 'builtin'
}
callback(allversions, selectedVersion)
})
}
scheduleCompilation () {
if (!this.config.get('autoCompile')) return
if (this.data.compileTimeout) window.clearTimeout(this.data.compileTimeout)
this.data.compileTimeout = window.setTimeout(() => this.compileTabLogic.runCompiler(), this.data.timeout)
}
}
module.exports = CompilerContainer

@ -3,8 +3,23 @@ var css = require('./styles/debugger-tab-styles')
var DebuggerUI = require('../debugger/debuggerUI')
class DebuggerTab {
import { BaseApi } from 'remix-plugin'
const profile = {
name: 'debugger',
displayName: 'Debugger',
methods: [],
events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNjk2IDk2MHEwIDI2LTE5IDQ1dC00NSAxOWgtMjI0cTAgMTcxLTY3IDI5MGwyMDggMjA5cTE5IDE5IDE5IDQ1dC0xOSA0NXEtMTggMTktNDUgMTl0LTQ1LTE5bC0xOTgtMTk3cS01IDUtMTUgMTN0LTQyIDI4LjUtNjUgMzYuNS04MiAyOS05NyAxM3YtODk2aC0xMjh2ODk2cS01MSAwLTEwMS41LTEzLjV0LTg3LTMzLTY2LTM5LTQzLjUtMzIuNWwtMTUtMTQtMTgzIDIwN3EtMjAgMjEtNDggMjEtMjQgMC00My0xNi0xOS0xOC0yMC41LTQ0LjV0MTUuNS00Ni41bDIwMi0yMjdxLTU4LTExNC01OC0yNzRoLTIyNHEtMjYgMC00NS0xOXQtMTktNDUgMTktNDUgNDUtMTloMjI0di0yOTRsLTE3My0xNzNxLTE5LTE5LTE5LTQ1dDE5LTQ1IDQ1LTE5IDQ1IDE5bDE3MyAxNzNoODQ0bDE3My0xNzNxMTktMTkgNDUtMTl0NDUgMTkgMTkgNDUtMTkgNDVsLTE3MyAxNzN2Mjk0aDIyNHEyNiAwIDQ1IDE5dDE5IDQ1em0tNDgwLTU3NmgtNjQwcTAtMTMzIDkzLjUtMjI2LjV0MjI2LjUtOTMuNSAyMjYuNSA5My41IDkzLjUgMjI2LjV6Ii8+PC9zdmc+',
description: 'Debug transactions',
kind: 'debugging',
location: 'swapPanel'
}
class DebuggerTab extends BaseApi {
constructor () {
super(profile)
this.el = null
}

@ -0,0 +1,66 @@
const executionContext = require('../../execution-context')
import { NetworkApi } from 'remix-plugin'
export const profile = {
name: 'network',
description: 'Manage the network (mainnet, ropsten, goerli...) and the provider (web3, vm, injected)'
}
// Network API has :
// - events: ['providerChanged']
// - methods: ['getNetworkProvider', 'getEndpoint', 'detectNetwork', 'addNetwork', 'removeNetwork']
export class NetworkModule extends NetworkApi {
constructor () {
super(profile)
// TODO: See with remix-lib to make sementic coherent
executionContext.event.register('contextChanged', (provider) => {
this.events.emit('providerChanged', provider)
})
/*
// Events that could be implemented later
executionContext.event.register('removeProvider', (provider) => {
this.events.emit('networkRemoved', provider)
})
executionContext.event.register('addProvider', (provider) => {
this.events.emit('networkAdded', provider)
})
executionContext.event.register('web3EndpointChanged', (provider) => {
this.events.emit('web3EndpointChanged', provider)
})
*/
}
/** Return the current network provider (web3, vm, injected) */
getNetworkProvider () {
return executionContext.getProvider()
}
/** Return the current network */
detectNetwork () {
return new Promise((resolve, reject) => {
executionContext.detectNetwork((error, network) => {
error ? reject(error) : resolve(network)
})
})
}
/** Return the url only if network provider is 'web3' */
getEndpoint () {
const provider = executionContext.getProvider()
if (provider !== 'web3') {
throw new Error('no endpoint: current provider is either injected or vm')
}
return provider.web3().currentProvider.host
}
/** Add a custom network to the list of available networks */
addNetwork (customNetwork) {
executionContext.addProvider(customNetwork)
}
/** Remove a network to the list of availble networks */
removeNetwork (name) {
executionContext.removeProvider(name)
}
}

@ -12,24 +12,42 @@ var ContractDropdownUI = require('./runTab/contractDropdown.js')
var Recorder = require('./runTab/model/recorder.js')
var RecorderUI = require('./runTab/recorder.js')
class RunTab {
const executionContext = require('../../execution-context')
import { BaseApi } from 'remix-plugin'
const profile = {
name: 'run',
displayName: 'Deploy and run transactions',
methods: [],
events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzFfY29weSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4Ig0KCSB5PSIwcHgiIHdpZHRoPSI3NDIuNTQ1cHgiIGhlaWdodD0iNjc2Ljg4NnB4IiB2aWV3Qm94PSIwIC0wLjIwNCA3NDIuNTQ1IDY3Ni44ODYiDQoJIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAtMC4yMDQgNzQyLjU0NSA2NzYuODg2IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnPg0KCTxwb2x5Z29uIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBwb2ludHM9IjI5NS45MTEsMC43MTEgNDg4LjkxMSwzMDQuMTg2IDQ4OC45MTEsMzk3LjE4MSAyOTMuOTExLDY3Ni41NTYgDQoJCTc0MS43ODYsMzQ5Ljk0MyAJIi8+DQoJPHBvbHlnb24gc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50cz0iNDE3LjA4Myw0MDYuNTg5IDIwOS43OTEsNTE5LjQ5NCAxLjg0Niw0MDYuMjM0IDIwOS43OTEsNjc1Ljg2MyAJIi8+DQoJPHBvbHlnb24gc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50cz0iNDE3LjA4MywzMTguNzA3IDIwOS43OTEsMC43MTEgMS44NDYsMzE4LjQyOCAyMDkuNzkxLDQzMS42ODkgCSIvPg0KPC9nPg0KPC9zdmc+DQo=',
description: 'execute and save transactions',
kind: 'run',
location: 'swapPanel'
}
class RunTab extends BaseApi {
constructor (udapp, udappUI, config, fileManager, editor, logCallback, filePanel, pluginManager, compilersArtefacts) {
super(profile)
this.event = new EventManager()
this.renderInstanceContainer()
this.renderSettings(udapp)
this.renderDropdown(udappUI, fileManager, pluginManager, compilersArtefacts, config, editor, udapp, filePanel, logCallback)
this.renderRecorder(udapp, udappUI, fileManager, config, logCallback)
this.renderRecorderCard()
this.renderContainer()
this.config = config
this.udapp = udapp
this.udappUI = udappUI
this.fileManager = fileManager
this.editor = editor
this.logCallback = logCallback
this.filePanel = filePanel
this.pluginManager = pluginManager
this.compilersArtefacts = compilersArtefacts
}
renderContainer () {
this.container = yo`<div class="${css.runTabView}" id="runTabView" ></div>`
var el = yo`
<div>
<div class="list-group list-group-flush">
${this.settingsUI.render()}
${this.contractDropdownUI.render()}
${this.recorderCard.render()}
@ -37,6 +55,7 @@ class RunTab {
</div>
`
this.container.appendChild(el)
return this.container
}
renderInstanceContainer () {
@ -46,7 +65,7 @@ class RunTab {
<div class=${css.instanceContainerTitle}
title="Autogenerated generic user interfaces for interaction with deployed contracts">
Deployed Contracts
<i class="${css.clearinstance} ${css.icon} fa fa-trash" onclick=${() => this.event.trigger('clearInstance', [])}
<i class="${css.clearinstance} ${css.icon} far fa-trash-alt" onclick=${() => this.event.trigger('clearInstance', [])}
title="Clear instances list and reset recorder" aria-hidden="true">
</i>
</div>`
@ -114,7 +133,7 @@ class RunTab {
renderRecorderCard () {
const collapsedView = yo`
<div class=${css.recorderCollapsedView}>
<div class=${css.recorderCount}>${this.recorderCount}</div>
<div class="${css.recorderCount} badge badge-pill badge-primary">${this.recorderCount}</div>
</div>`
const expandedView = yo`
@ -144,8 +163,18 @@ class RunTab {
}
render () {
return this.container
executionContext.init(this.config)
executionContext.stopListenOnLastBlock()
executionContext.listenOnLastBlock()
this.udapp.resetEnvironment()
this.renderInstanceContainer()
this.renderSettings(this.udapp)
this.renderDropdown(this.udappUI, this.fileManager, this.pluginManager, this.compilersArtefacts, this.config, this.editor, this.udapp, this.filePanel, this.logCallback)
this.renderRecorder(this.udapp, this.udappUI, this.fileManager, this.config, this.logCallback)
this.renderRecorderCard()
return this.renderContainer()
}
}
module.exports = RunTab

@ -18,6 +18,7 @@ class ContractDropdownUI {
listenToEvents () {
this.dropdownLogic.event.register('newlyCompiled', (success, data, source, compiler, compilerFullName) => {
if (!document.querySelector(`.${css.contractNames.classNames[0]}`)) return
var contractNames = document.querySelector(`.${css.contractNames.classNames[0]}`)
contractNames.innerHTML = ''
if (success) {
@ -43,11 +44,11 @@ class ContractDropdownUI {
}
render () {
this.compFails = yo`<i title="Contract compilation failed. Please check the compile tab for more information." class="fa fa-times-circle ${css.errorIcon}" ></i>`
var info = yo`<i class="fa fa-info ${css.infoDeployAction}" aria-hidden="true" title="*.sol files allows deploying and accessing contracts. *.abi files only allows accessing contracts."></i>`
this.compFails = yo`<i title="No contract compiled yet or compilation failed. Please check the compile tab for more information." class="fas fa-times-circle ${css.errorIcon}" ></i>`
var info = yo`<i class="fas fa-info ${css.infoDeployAction}" aria-hidden="true" title="*.sol files allows deploying and accessing contracts. *.abi files only allows accessing contracts."></i>`
this.atAddressButtonInput = yo`<input class="${css.input} ataddressinput" placeholder="Load contract from Address" title="atAddress" />`
this.selectContractNames = yo`<select class="${css.contractNames}" disabled></select>`
this.atAddressButtonInput = yo`<input class="${css.input} ${css.ataddressinput} ataddressinput form-control" placeholder="Load contract from Address" title="atAddress" />`
this.selectContractNames = yo`<select class="${css.contractNames} custom-select" disabled></select>`
this.createPanel = yo`<div class="${css.button}"></div>`
this.orLabel = yo`<div class="${css.orLabel}">or</div>`
@ -60,18 +61,19 @@ class ContractDropdownUI {
${this.createPanel}
${this.orLabel}
<div class="${css.button} ${css.atAddressSect}">
<div class="${css.atAddress}" onclick=${this.loadFromAddress.bind(this)}>At Address</div>
<div class="${css.atAddress} btn btn-sm btn-info" onclick=${this.loadFromAddress.bind(this)}>At Address</div>
${this.atAddressButtonInput}
</div>
</div>
</div>
`
this.selectContractNames.addEventListener('change', this.setInputParamsPlaceHolder.bind(this))
this.setInputParamsPlaceHolder()
return el
}
changeCurrentFile (currentFile) {
if (!document.querySelector(`.${css.contractNames}`)) return
document.querySelector(`.${css.contractNames}`).classList.remove(css.contractNamesError)
var contractNames = document.querySelector(`.${css.contractNames.classNames[0]}`)
contractNames.innerHTML = ''
@ -139,7 +141,7 @@ class ContractDropdownUI {
}
var promptCb = (okCb, cancelCb) => {
modalDialogCustom.promptPassphrase(null, 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb)
modalDialogCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb)
}
var statusCb = (msg) => {
@ -153,7 +155,7 @@ class ContractDropdownUI {
return this.logCallback(error)
}
this.event.trigger('newContractInstanceAdded', [contractObject, address, this.selectContractNames.value])
this.event.trigger('newContractInstanceAdded', [contractObject, address, contractObject.name])
}
if (selectedContract.isOverSizeLimit()) {
@ -163,7 +165,7 @@ class ContractDropdownUI {
{
label: 'Force Send',
fn: () => {
this.dropdownLogic.forceSend(selectedContract, args, continueCb, promptCb, modalDialogCustom, confirmDialog, statusCb, finalCb)
this.dropdownLogic.forceSend(selectedContract, args, continueCb, promptCb, modalDialog, confirmDialog, statusCb, finalCb)
}}, {
label: 'Cancel',
fn: () => {
@ -171,7 +173,7 @@ class ContractDropdownUI {
}
})
}
this.dropdownLogic.forceSend(selectedContract, args, continueCb, promptCb, modalDialogCustom, confirmDialog, statusCb, finalCb)
this.dropdownLogic.forceSend(selectedContract, args, continueCb, promptCb, modalDialog, confirmDialog, statusCb, finalCb)
}
loadFromAddress () {
@ -180,7 +182,7 @@ class ContractDropdownUI {
var address = this.atAddressButtonInput.value
this.dropdownLogic.loadContractFromAddress(address,
(cb) => {
modalDialogCustom.confirm(null, 'Do you really want to interact with ' + address + ' using the current ABI definition ?', cb)
modalDialogCustom.confirm(null, 'Do you really want to interact with ' + address + ' using the current ABI definition?', cb)
},
(error, loadType, abi) => {
if (error) {

@ -21,7 +21,7 @@ class DropdownLogic {
this.listenToCompilationEvents()
fileManager.event.register('currentFileChanged', (currentFile) => {
fileManager.events.on('currentFileChanged', (currentFile) => {
this.event.trigger('currentFileChanged', [currentFile])
})
}
@ -121,7 +121,7 @@ class DropdownLogic {
}
// TODO: check if selectedContract and data can be joined
createContract (selectedContract, data, continueCb, promptCb, confirmDialog, modalDialog, finalCb) {
createContract (selectedContract, data, continueCb, promptCb, modalDialog, confirmDialog, finalCb) {
if (data) {
data.contractName = selectedContract.name
data.linkReferences = selectedContract.bytecodeLinkReferences

@ -23,9 +23,9 @@ class RecorderUI {
.recorder {}
`
this.runButton = yo`<i class="fa fa-play runtransaction ${css2.runTxs} ${css.icon}" title="Run Transactions" aria-hidden="true"></i>`
this.runButton = yo`<i class="fas fa-play runtransaction ${css2.runTxs} ${css.icon}" title="Run Transactions" aria-hidden="true"></i>`
this.recordButton = yo`
<i class="fa fa-floppy-o savetransaction ${css2.recorder} ${css.icon}"
<i class="fas fa-save savetransaction ${css2.recorder} ${css.icon}"
onclick=${this.triggerRecordButton.bind(this)} title="Save Transactions" aria-hidden="true">
</i>`
@ -56,7 +56,7 @@ class RecorderUI {
}
var promptCb = (okCb, cancelCb) => {
modalDialogCustom.promptPassphrase(null, 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb)
modalDialogCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb)
}
var alertCb = (msg) => {
@ -76,7 +76,7 @@ class RecorderUI {
triggerRecordButton () {
this.recorder.saveScenario(
(path, cb) => {
modalDialogCustom.prompt(null, 'Transactions will be saved in a file under ' + path, 'scenario.json', cb)
modalDialogCustom.prompt('Save transactions as scenario', 'Transactions will be saved in a file under ' + path, 'scenario.json', cb)
},
(error) => {
if (error) return modalDialogCustom.alert(error)

@ -1,18 +1,20 @@
var $ = require('jquery')
var yo = require('yo-yo')
var remixLib = require('remix-lib')
var EventManager = remixLib.EventManager
var css = require('../styles/run-tab-styles')
var copyToClipboard = require('../../ui/copy-to-clipboard')
var modalDialogCustom = require('../../ui/modal-dialog-custom')
var addTooltip = require('../../ui/tooltip')
var helper = require('../../../lib/helper.js')
const $ = require('jquery')
const yo = require('yo-yo')
const remixLib = require('remix-lib')
const EventManager = remixLib.EventManager
const css = require('../styles/run-tab-styles')
const copyToClipboard = require('../../ui/copy-to-clipboard')
const modalDialogCustom = require('../../ui/modal-dialog-custom')
const addTooltip = require('../../ui/tooltip')
const helper = require('../../../lib/helper.js')
const globalRegistry = require('../../../global/registry')
class SettingsUI {
constructor (settings) {
this.settings = settings
this.event = new EventManager()
this._components = {}
this.settings.event.register('transactionExecuted', (error, from, to, data, lookupOnly, txResult) => {
if (error) return
@ -20,6 +22,11 @@ class SettingsUI {
this.updateAccountBalances()
})
this._components.registry = globalRegistry
this._deps = {
networkModule: this._components.registry.get('network').api
}
setInterval(() => {
this.updateAccountBalances()
}, 10 * 1000)
@ -40,7 +47,7 @@ class SettingsUI {
}
render () {
this.netUI = yo`<span class=${css.network}></span>`
this.netUI = yo`<span class="${css.network} badge badge-secondary"></span>`
var environmentEl = yo`
<div class="${css.crow}">
@ -48,8 +55,7 @@ class SettingsUI {
Environment
</div>
<div class=${css.environment}>
${this.netUI}
<select id="selectExEnvOptions" onchange=${() => { this.updateNetwork() }} class="${css.select}">
<select id="selectExEnvOptions" onchange=${() => { this.updateNetwork() }} class="form-control ${css.select}">
<option id="vm-mode"
title="Execution environment does not connect to any node, everything is local and in memory only."
value="vm" checked name="executionContext"> JavaScript VM
@ -64,48 +70,59 @@ class SettingsUI {
value="web3" name="executionContext"> Web3 Provider
</option>
</select>
<a href="https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md" target="_blank"><i class="${css.icon} fa fa-info"></i></a>
<a href="https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md" target="_blank"><i class="${css.infoDeployAction} fas fa-info"></i></a>
</div>
</div>
`
var accountEl = yo`
const networkEl = yo`
<div class="${css.crow}">
<div class="${css.col1_1}">
</div>
<div class="${css.environment}">
${this.netUI}
</div>
</div>
`
const accountEl = yo`
<div class="${css.crow}">
<div class="${css.col1_1}">
Account
<i class="fa fa-plus-circle ${css.icon}" aria-hidden="true" onclick=${this.newAccount.bind(this)} title="Create a new account"></i>
<i class="fas fa-plus-circle ${css.icon}" aria-hidden="true" onclick=${this.newAccount.bind(this)} title="Create a new account"></i>
</div>
<div class=${css.account}>
<select name="txorigin" class="${css.select}" id="txorigin"></select>
<select name="txorigin" class="form-control ${css.select}" id="txorigin"></select>
${copyToClipboard(() => document.querySelector('#runTabView #txorigin').value)}
<i class="fa fa-pencil-square-o ${css.icon}" aria-hiden="true" onclick=${this.signMessage.bind(this)} title="Sign a message using this account key"></i>
<i class="fas fa-edit ${css.icon}" aria-hiden="true" onclick=${this.signMessage.bind(this)} title="Sign a message using this account key"></i>
</div>
</div>
`
var gasPriceEl = yo`
const gasPriceEl = yo`
<div class="${css.crow}">
<div class="${css.col1_1}">Gas limit</div>
<input type="number" class="${css.col2}" id="gasLimit" value="3000000">
<input type="number" class="form-control ${css.gasNval} ${css.col2}" id="gasLimit" value="3000000">
</div>
`
var valueEl = yo`
const valueEl = yo`
<div class="${css.crow}">
<div class="${css.col1_1}">Value</div>
<input type="text" class="${css.col2_1}" id="value" value="0" title="Enter the value and choose the unit">
<select name="unit" class="${css.col2_2}" id="unit">
<option data-unit="wei">wei</option>
<option data-unit="gwei">gwei</option>
<option data-unit="finney">finney</option>
<option data-unit="ether">ether</option>
</select>
<div class="${css.gasValueContainer}">
<input type="text" class="form-control ${css.gasNval} ${css.col2}" id="value" value="0" title="Enter the value and choose the unit">
<select name="unit" class="form-control ${css.gasNvalUnit} ${css.col2_2}" id="unit">
<option data-unit="wei">wei</option>
<option data-unit="gwei">gwei</option>
<option data-unit="finney">finney</option>
<option data-unit="ether">ether</option>
</select>
</div>
</div>
`
var el = yo`
const el = yo`
<div class="${css.settings}">
${environmentEl}
${networkEl}
${accountEl}
${gasPriceEl}
${valueEl}
@ -133,9 +150,12 @@ class SettingsUI {
this.settings.event.register('addProvider', (network) => {
selectExEnv.appendChild(yo`<option
title="Manually added environment: ${network.url}"
value="${network.name}" name="executionContext"> ${network.name}
</option>`)
title="Manually added environment: ${network.url}"
value="${network.name}"
name="executionContext"
>
${network.name}
</option>`)
addTooltip(`${network.name} [${network.url}] added`)
})
@ -150,8 +170,8 @@ class SettingsUI {
selectExEnv.addEventListener('change', (event) => {
let context = selectExEnv.options[selectExEnv.selectedIndex].value
this.settings.changeExecutionContext(context, () => {
modalDialogCustom.confirm(null, 'Are you sure you want to connect to an ethereum node?', () => {
modalDialogCustom.prompt(null, 'Web3 Provider Endpoint', 'http://localhost:8545', (target) => {
modalDialogCustom.confirm('External node request', 'Are you sure you want to connect to an ethereum node?', () => {
modalDialogCustom.prompt('External node request', 'Web3 Provider Endpoint', 'http://localhost:8545', (target) => {
this.settings.setProviderFromEndpoint(target, context, (alertMsg) => {
if (alertMsg) {
modalDialogCustom.alert(alertMsg)
@ -229,7 +249,8 @@ class SettingsUI {
this.netUI.innerHTML = 'can\'t detect network '
return
}
this.netUI.innerHTML = `<i class="${css.networkItem} fa fa-plug" aria-hidden="true"></i> ${name} (${id || '-'})`
let network = this._deps.networkModule.getNetworkProvider
this.netUI.innerHTML = (network() !== 'vm') ? `${name} (${id || '-'}) network` : ''
})
}

@ -1,318 +1,157 @@
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var remixLib = require('remix-lib')
const defaultPlugins = require('../plugin/plugins')
var globalRegistry = require('../../global/registry')
const 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 styles = styleGuide.chooser()
var Storage = remixLib.Storage
var EventManager = require('../../lib/events')
var css = require('./styles/settings-tab-styles')
import { BaseApi } 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
const profile = {
name: 'settings',
displayName: 'Settings',
methods: [],
events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xMTUyIDg5NnEwLTEwNi03NS0xODF0LTE4MS03NS0xODEgNzUtNzUgMTgxIDc1IDE4MSAxODEgNzUgMTgxLTc1IDc1LTE4MXptNTEyLTEwOXYyMjJxMCAxMi04IDIzdC0yMCAxM2wtMTg1IDI4cS0xOSA1NC0zOSA5MSAzNSA1MCAxMDcgMTM4IDEwIDEyIDEwIDI1dC05IDIzcS0yNyAzNy05OSAxMDh0LTk0IDcxcS0xMiAwLTI2LTlsLTEzOC0xMDhxLTQ0IDIzLTkxIDM4LTE2IDEzNi0yOSAxODYtNyAyOC0zNiAyOGgtMjIycS0xNCAwLTI0LjUtOC41dC0xMS41LTIxLjVsLTI4LTE4NHEtNDktMTYtOTAtMzdsLTE0MSAxMDdxLTEwIDktMjUgOS0xNCAwLTI1LTExLTEyNi0xMTQtMTY1LTE2OC03LTEwLTctMjMgMC0xMiA4LTIzIDE1LTIxIDUxLTY2LjV0NTQtNzAuNXEtMjctNTAtNDEtOTlsLTE4My0yN3EtMTMtMi0yMS0xMi41dC04LTIzLjV2LTIyMnEwLTEyIDgtMjN0MTktMTNsMTg2LTI4cTE0LTQ2IDM5LTkyLTQwLTU3LTEwNy0xMzgtMTAtMTItMTAtMjQgMC0xMCA5LTIzIDI2LTM2IDk4LjUtMTA3LjV0OTQuNS03MS41cTEzIDAgMjYgMTBsMTM4IDEwN3E0NC0yMyA5MS0zOCAxNi0xMzYgMjktMTg2IDctMjggMzYtMjhoMjIycTE0IDAgMjQuNSA4LjV0MTEuNSAyMS41bDI4IDE4NHE0OSAxNiA5MCAzN2wxNDItMTA3cTktOSAyNC05IDEzIDAgMjUgMTAgMTI5IDExOSAxNjUgMTcwIDcgOCA3IDIyIDAgMTItOCAyMy0xNSAyMS01MSA2Ni41dC01NCA3MC41cTI2IDUwIDQxIDk4bDE4MyAyOHExMyAyIDIxIDEyLjV0OCAyMy41eiIvPjwvc3ZnPg==',
description: 'Remix-IDE settings',
kind: 'settings',
location: 'swapPanel'
}
module.exports = class SettingsTab extends BaseApi {
constructor (config, editor, appManager) {
super(profile)
this.config = config
this.editor = editor
this.appManager = appManager
this._components = {}
this._deps = {
themeModule: globalRegistry.get('themeModule').api
}
self._view = { /* eslint-disable */
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()
}
createThemeCheckies () {
let themes = this._deps.themeModule.getThemes()
const onswitchTheme = (event, name) => {
this._deps.themeModule.switchTheme(name)
}
if (themes) {
return yo`<div class="card-text themes-container">
${themes.map((aTheme) => {
let el = yo`<div class="${css.frow} form-check ${css.crow}">
<input type="radio" onchange=${event => { onswitchTheme(event, aTheme.name) }} class="align-middle form-check-input" name="theme" id="${aTheme.name}" >
<label class="form-check-label" for="${aTheme.name}">${aTheme.name} (${aTheme.quality})</label>
</div>`
if (this._deps.themeModule.active === aTheme.name) el.querySelector('input').setAttribute('checked', 'checked')
return el
})}
</div>`
}
}
render () {
const self = this
if (self._view.el) return self._view.el
// Gist settings
var gistAccessToken = yo`<input id="gistaccesstoken" type="password">`
var token = self._deps.config.get('settings/gist-access-token')
var gistAccessToken = yo`<input id="gistaccesstoken" type="password" class="form-control mb-2 ${css.inline}" placeholder="Token">`
var token = this.config.get('settings/gist-access-token')
if (token) gistAccessToken.value = token
var gistAddToken = yo`<input class="${css.savegisttoken}" 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 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>`
//
self._view.optionVM = yo`<input onchange=${onchangeOption} 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">`
if (self._deps.config.get('settings/personal-mode')) self._view.personal.setAttribute('checked', '')
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>`
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" aria-hidden="true"></i>`
self._view.generateContractMetadata = yo`<input onchange=${onchangeGenerateContractMetadata} id="generatecontractmetadata" type="checkbox">`
if (self._deps.config.get('settings/generate-contract-metadata')) self._view.generateContractMetadata.setAttribute('checked', '')
self._view.pluginInput = yo`<textarea rows="4" cols="70" id="plugininput" type="text" class="${css.pluginTextArea}" ></textarea>`
this._view.warnPersonalMode = yo`<i title=${warnText} class="${css.icon} fas 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 (this.config.get('settings/generate-contract-metadata')) this._view.generateContractMetadata.setAttribute('checked', '')
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="${css.col1}" name="theme" id="themeLight" type="radio">`
self._view.theme.dark = yo`<input onchange=${onswitch2darkTheme} class="${css.col1}" name="theme" id="themeDark" type="radio">`
self._view.theme.clean = yo`<input onchange=${onswitch2cleanTheme} class="${css.col1}" name="theme" id="themeClean" type="radio">`
self._view.theme[self.data.currentTheme].setAttribute('checked', 'checked')
this._view.themes = this._deps.themeModule.getThemes()
this._view.themesCheckBoxes = this.createThemeCheckies()
this._view.config.homePage = yo`
self._view.config.general = yo`
<div class="${css.info}">
<div class=${css.title}>General settings</div>
<div class="${css.crow}">
<div>${self._view.generateContractMetadata}</div>
<span class="${css.checkboxText}">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.</span>
<div class="${css.info} card">
<div class="card-body">
<h6 class="${css.title} card-title">Have a question?</h6>
<button class="btn btn-primary sm-1" onclick="${() => { window.open('https://gitter.im/ethereum/remix') }}">Gitter Channel</button>
</div>
</div>`
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>${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="${css.crow}">
<div>${self._view.optionVM}</div>
<span class="${css.checkboxText}">Always use Ethereum VM at Load</span>
<div class="form-check ${css.frow}">
<div>${this._view.optionVM}</div>
<label class="form-check-label align-middle" for="alwaysUseVM">Always use Ethereum VM at Load</label>
</div>
<div class="${css.crow}">
<div><input id="editorWrap" type="checkbox" onchange=${function () { self._deps.editor.resize(this.checked) }}></div>
<span class="${css.checkboxText}">Text Wrap</span>
<div class="form-check ${css.frow}">
<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="${css.crow}">
<div>${self._view.personal}></div>
<span class="${css.checkboxText}">Enable Personal Mode ${self._view.warnPersonalMode}></span>
<div class="form-check ${css.frow}">
<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`
<div class="${css.info}">
<div class=${css.title}>Gist Access Token</div>
<div class="${css.crowNoFlex}">Manage the access token used to publish to Gist and retrieve Github contents.</div>
<div class="${css.crowNoFlex}">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.</div>
<div class="${css.crowNoFlex}"><a target="_blank" href="https://github.com/settings/tokens">https://github.com/settings/tokens</a></div>
<div class="${css.crowNoFlex}">${self._view.gistToken}</div>
</div>`
self._view.config.themes = yo`
<div class="${css.info}">
<div class=${css.title}>Themes</div>
<div class=${css.attention}>
<i title="Select the theme" class="${css.icon} fa fa-exclamation-triangle" aria-hidden="true"></i>
<span>Selecting a theme will trigger a page reload</span>
</div>
<div class="${css.crow}">
${self._view.theme.light}
<label for="themeLight">Light Theme</label>
</div>
<div class="${css.crow}">
${self._view.theme.dark}
<label for="themeDark">Dark Theme</label>
</div>
<div class="${css.crow}">
${self._view.theme.clean}
<label for="themeClean">Clean Theme</label>
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}">${this._view.gistToken}</div>
</div>
</div>`
self._view.config.plugins = yo`<div></div>`
self._view.config.plugin = yo`
<div class="${css.info}">
<div class=${css.title}>Plugin <i title="This section is still under heavy development, please use it carefully" class="${css.icon} fa fa-exclamation-triangle" aria-hidden="true"></i> </div>
<div class="${css.crowNoFlex}">
<div>Load plugin from JSON description: </div>
${self._view.pluginInput}
<input onclick=${onloadPluginJson} type="button" value="Load" class="${css.initPlugin}">
${self._view.config.plugins}
this._view.config.themes = yo`
<div class="${css.info} card">
<div class="card-body">
<h6 class="${css.title} card-title">Themes</h6>
${this._view.themesCheckBoxes}
</div>
</div>`
self._view.el = yo`
this._view.el = yo`
<div class="${css.settingsTabView}" id="settingsView">
${self._view.config.general}
${self._view.config.plugin}
${self._view.gistToken}
${self._view.config.themes}
${this._view.config.homePage}
${this._view.config.general}
${this._view.gistToken}
${this._view.config.themes}
</div>`
function loadPlugins (plugins, opt) {
for (var k in plugins) {
(function (plugin) {
if (!self._view.plugins[plugin.title]) self._view.plugins[plugin.title] = {}
self._view.plugins[plugin.title].json = plugin
self._view.plugins[plugin.title].el = yo`<div title=${plugin.title} class="${css.pluginLoad}">
<div class="${css.aPlugin}" onclick=${() => { onLoadPlugin(plugin.title) }}>${plugin.title}</div>
${opt.removable ? yo`<span class="${css.removePlugin}" onclick=${() => { onRemovePlugin(plugin.title) }}><i class="fa fa-close"></i></span>` : yo`<span></span>`}
</div>`
self._view.config.plugins.appendChild(self._view.plugins[plugin.title].el)
})(plugins[k])
}
}
function getSavedPlugin () {
var savedPlugin = self._deps.config.get('settings/plugins-list')
return savedPlugin ? JSON.parse(savedPlugin) : {}
}
function setSavedPlugin (savedPlugins) {
self._deps.config.set('settings/plugins-list', JSON.stringify(savedPlugins))
}
loadPlugins(defaultPlugins, {removable: false})
loadPlugins(getSavedPlugin(), {removable: true})
function onLoadPlugin (name) {
self.event.trigger('plugin-loadRequest', [self._view.plugins[name].json])
}
function onRemovePlugin (name) {
var savedPlugin = getSavedPlugin()
delete savedPlugin[name]
setSavedPlugin(savedPlugin)
if (self._view.plugins[name]) {
self._view.plugins[name].el.parentNode.removeChild(self._view.plugins[name].el)
delete self._view.plugins[name]
}
}
function onloadPluginJson (event) {
try {
var json = JSON.parse(self._view.pluginInput.value)
} catch (e) {
return tooltip('cannot parse the plugin definition to JSON')
}
var savedPlugin = getSavedPlugin()
if (self._view.plugins[json.title]) return tooltip('Plugin already loaded')
savedPlugin[json.title] = json
setSavedPlugin(savedPlugin)
loadPlugins([json], {removable: true})
}
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'))
}
function onswitch2darkTheme (event) {
styleGuide.switchTheme('dark')
window.location.reload()
}
function onswitch2lightTheme (event) {
styleGuide.switchTheme('light')
window.location.reload()
}
function onswitch2cleanTheme (event) {
styleGuide.switchTheme('clean')
window.location.reload()
self.config.set('settings/always-use-vm', !self.config.get('settings/always-use-vm'))
}
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'))
}
return self._view.el
}
}
const css = csjs`
.settingsTabView {
padding: 2%;
display: flex;
}
.info {
${styles.rightPanel.settingsTab.box_SolidityVersionInfo};
margin-bottom: 1em;
word-break: break-word;
}
.title {
font-size: 1.1em;
font-weight: bold;
margin-bottom: 1em;
}
.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;
this._deps.themeModule.switchTheme()
return this._view.el
}
input[type=radio] {
margin-top: 2px;
}
.pluginTextArea {
font-family: unset;
}
.pluginLoad {
vertical-align: top;
${styles.rightPanel.settingsTab.button_LoadPlugin};
width: inherit;
display: inline-block;
}
.initPlugin {
vertical-align: top;
${styles.rightPanel.settingsTab.button_initPlugin};
width: inherit;
display: block;
max-height: inherit;
padding:7px;
}
.removePlugin {
cursor: pointer;
}
i.warnIt {
color: ${styles.appProperties.warningText_Color};
}
.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;
}
.pluginLoad {
vertical-align: top;
max-height: inherit;
margin: 2px;
}
.removePlugin{
padding-left: 7px;
padding-right: 7px;
border-left: 2px solid ${styles.appProperties.primary_BackgroundColor};
margin-left: 10px;
}
`
}

@ -4,8 +4,6 @@ const css = csjs`
.analysisTabView {
padding: 2%;
padding-bottom: 3em;
display: flex;
flex-direction: column;
}
`

@ -0,0 +1,219 @@
const csjs = require('csjs-inject')
const css = csjs`
.compilerArticle {
padding: 10px;
}
.title {
font-size: 1.1em;
font-weight: bold;
margin-bottom: 1em;
}
.panicError {
color: red;
font-size: 20px;
}
.crow {
display: flex;
overflow: auto;
clear: both;
padding: .2em;
}
.checkboxText {
font-weight: normal;
}
.crow label {
cursor:pointer;
}
.crowNoFlex {
overflow: auto;
clear: both;
}
.info {
padding: 10px;
word-break: break-word;
}
.contract {
display: block;
margin: 3% 0;
}
.autocompileContainer {
display: flex;
align-items: center;
}
.hideWarningsContainer {
display: flex;
align-items: center;
}
.autocompile {}
.autocompileTitle {
font-weight: bold;
margin: 1% 0;
}
.autocompileText {
margin: 1% 0;
font-size: 12px;
overflow: hidden;
word-break: normal;
line-height: initial;
}
.warnCompilationSlow {
margin-left: 1%;
}
.compilerConfig {
display: flex;
align-items: center;
}
.compilerConfig label {
margin: 0;
}
.compilerSm {
padding-left: 1.25rem;
}
.name {
display: flex;
}
.size {
display: flex;
}
.checkboxes {
display: flex;
width: 100%;
justify-content: space-between;
flex-wrap: wrap;
}
.compileButton {
width: 100%;
margin: 15px 0 10px 0;
font-size: 12px;
}
.container {
margin: 0;
margin-bottom: 2%;
}
.optimizeContainer {
display: flex;
}
.noContractAlert {
display: flex;
justify-content: center;
align-items: center;
}
.contractHelperButtons {
margin-top: 10px;
display: flex;
align-items: center;
justify-content: space-between;
float: right;
}
.copyToClipboard {
font-size: 1rem;
}
.copyIcon {
margin-right: 5px;
}
.log {
display: flex;
flex-direction: column;
margin-bottom: 5%;
overflow: visible;
}
.key {
margin-right: 5px;
text-transform: uppercase;
width: 100%;
}
.value {
display: flex;
width: 100%;
margin-top: 1.5%;
}
.questionMark {
margin-left: 2%;
cursor: pointer;
}
.questionMark:hover {
}
.detailsJSON {
padding: 8px 0;
border: none;
}
.icon {
margin-right: 0.3em;
}
.errorBlobs {
padding-left: 5px;
padding-right: 5px;
}
.spinningIcon {
display: inline-block;
position: relative;
animation: spin 2s infinite linear;
-moz-animation: spin 2s infinite linear;
-o-animation: spin 2s infinite linear;
-webkit-animation: spin 2s infinite linear;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@-webkit-keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@-moz-keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@-o-keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@-ms-keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.bouncingIcon {
display: inline-block;
position: relative;
-moz-animation: bounce 2s infinite linear;
-o-animation: bounce 2s infinite linear;
-webkit-animation: bounce 2s infinite linear;
animation: bounce 2s infinite linear;
}
@-webkit-keyframes bounce {
0% { top: 0; }
50% { top: -0.2em; }
70% { top: -0.3em; }
100% { top: 0; }
}
@-moz-keyframes bounce {
0% { top: 0; }
50% { top: -0.2em; }
70% { top: -0.3em; }
100% { top: 0; }
}
@-o-keyframes bounce {
0% { top: 0; }
50% { top: -0.2em; }
70% { top: -0.3em; }
100% { top: 0; }
}
@-ms-keyframes bounce {
0% { top: 0; }
50% { top: -0.2em; }
70% { top: -0.3em; }
100% { top: 0; }
}
@keyframes bounce {
0% { top: 0; }
50% { top: -0.2em; }
70% { top: -0.3em; }
100% { top: 0; }
}
`
module.exports = css

@ -1,5 +1,4 @@
var csjs = require('csjs-inject')
var styles = require('../../ui/styles-guide/theme-chooser').chooser()
const css = csjs`
.debuggerTabView {
@ -7,7 +6,6 @@ const css = csjs`
}
.debugger {
margin-bottom: 1%;
${styles.rightPanel.debuggerTab.box_Debugger}
}
`

@ -1,6 +1,4 @@
var csjs = require('csjs-inject')
var styleGuide = require('../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs`
.runTabView {
@ -14,28 +12,25 @@ var css = csjs`
font-size: 12px;
display: flex;
justify-content: space-between;
padding-left: 15px;
}
.settings {
${styles.rightPanel.runTab.box_RunTab}
margin-bottom: 2%;
padding: 10px 15px 15px 15px;
padding: 10px 0px 15px 15px;
}
.recorderCount {
border: 1px solid ${styles.rightPanel.runTab.icon_HoverColor};
border-radius: 50%;
margin-right: 30px;
min-width: 13px;
height: 13px;
display: flex;
justify-content: center;
align-items: center;
font-size: 10px;
/* margin-right: 30px; */
/* min-width: 13px; */
/* display: flex; */
/* justify-content: center; */
/* align-items: center; */
/* font-size: 10px; */
}
.crow {
margin-top: .5em;
display: flex;
align-items: center;
width: 500px;
/*width: 500px;*/
}
.col1 {
width: 30%;
@ -44,7 +39,6 @@ var css = csjs`
}
.col1_1 {
font-size: 12px;
width: 15%;
min-width: 75px;
float: left;
align-self: center;
@ -53,43 +47,36 @@ var css = csjs`
display: flex;
align-items: center;
position: relative;
width: 259px;
width: 100%;
padding-right: 25px;
}
.account {
display: flex;
align-items: center;
width: 266px;
width: 90%;
}
.col2 {
${styles.rightPanel.runTab.input_RunTab}
border-radius: 3px;
}
.col2_1 {
${styles.rightPanel.runTab.input_RunTab}
width: 164px;
min-width: 164px;
}
.col2_2 {
${styles.rightPanel.runTab.dropdown_RunTab}
width: 82px;
min-width: 82px;
}
.select {
${styles.rightPanel.runTab.dropdown_RunTab}
font-weight: normal;
width: 250px;
width: 100%;
}
.instanceContainer {
${styles.rightPanel.runTab.box_Instance}
display: flex;
flex-direction: column;
margin-bottom: 2%;
border: none;
text-align: center;
padding: 10px 0px 15px 15px;
padding: 10px 0px 15px 0px;
}
.pendingTxsContainer {
${styles.rightPanel.runTab.box_Instance}
display: flex;
flex-direction: column;
margin-top: 2%;
@ -97,8 +84,8 @@ var css = csjs`
text-align: center;
}
.container {
${styles.rightPanel.runTab.box_RunTab}
margin-bottom: 2%;
margin-bottom: 4%;
padding-left: 15px;
}
.recorderCollapsedView,
.recorderExpandedView {
@ -109,12 +96,10 @@ var css = csjs`
margin: 0 15px 15px 0;
}
.contractNames {
${styles.rightPanel.runTab.dropdown_RunTab}
width: 100%;
border: 1px solid
}
.contractNamesError {
border: 1px solid ${styles.appProperties.errorText_Color}
}
.subcontainer {
display: flex;
@ -127,18 +112,16 @@ var css = csjs`
margin-top: 13px;
}
.transaction {
${styles.rightPanel.runTab.button_transaction}
}
.atAddress {
margin: 0;
min-width: 100px;
width: 100px;
font-size: 10px;
/* font-size: 10px; */
word-break: inherit;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: 0;
${styles.rightPanel.runTab.button_atAddress}
}
.atAddressSect {
margin-top: 6px;
@ -147,20 +130,20 @@ var css = csjs`
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.ataddressinput {
padding: .25rem;
}
.create {
${styles.rightPanel.runTab.button_Create}
}
.input {
${styles.rightPanel.runTab.input_RunTab};
font-size: 10px;
}
.noInstancesText {
${styles.rightPanel.runTab.box_Instance}
font-style: italic;
text-align: left;
padding-left: 15px;
}
.pendingTxsText {
${styles.rightPanel.runTab.borderBox_Instance}
font-style: italic;
display: flex;
justify-content: space-evenly;
@ -173,15 +156,15 @@ var css = csjs`
align-items: center;
}
.transact {
color: ${styles.colors.lightRed};
color: var(--warning);
margin-right: .3em;
}
.payable {
color: ${styles.colors.red};
color: var(--warning);
margin-right: .3em;
}
.call {
color: ${styles.colors.lightBlue};
color: var(--info);
margin-right: .3em;
}
.pendingContainer {
@ -199,31 +182,23 @@ var css = csjs`
cursor: pointer;
font-size: 12px;
cursor: pointer;
color: ${styles.rightPanel.runTab.icon_Color};
margin-left: 5px;
}
.icon:hover {
font-size: 12px;
color: ${styles.rightPanel.runTab.icon_HoverColor};
color: var(--warning);
}
.errorIcon {
color: ${styles.appProperties.errorText_Color};
color: var(--warning);
margin-left: 15px;
}
.failDesc {
color: ${styles.appProperties.errorText_Color};
color: var(--warning);
padding-left: 10px;
display: inline;
}
.network {
display: flex;
justify-content: flex-end;
align-items: center;
position: absolute;
color: grey;
width: 100%;
height: 100%;
padding-right: 28px;
margin-left: 8px;
pointer-events: none;
}
.networkItem {
@ -235,7 +210,6 @@ var css = csjs`
.transactionActions {
display: flex;
justify-content: space-evenly;
${styles.rightPanel.runTab.box_Info_RunTab};
width: 145px;
}
.orLabel {
@ -244,6 +218,24 @@ var css = csjs`
.infoDeployAction {
margin-left: 5px;
font-size: 13px;
color: var(--info);
}
.gasValueContainer {
flex-direction: row;
display: flex;
}
.gasNval {
/* transform: scale(0.7); */
/* transform-origin: left; */
margin-right: 10px;
width: 100px;
font-size: 0.8rem;
}
.gasNvalUnit {
/* transform: scale(0.7); */
/* transform-origin: left; */
margin-right: 10px;
font-size: 0.8rem;
}
`

@ -1,27 +1,30 @@
var csjs = require('csjs-inject')
var styleGuide = require('../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs`
const css = csjs`
.settingsTabView {
padding: 2%;
display: flex;
}
.info {
${styles.rightPanel.settingsTab.box_SolidityVersionInfo}
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;
@ -38,11 +41,6 @@ var css = csjs`
padding: .5em;
font-weight: bold;
}
.select {
font-weight: bold;
margin-top: 1em;
${styles.rightPanel.settingsTab.dropdown_SelectCompiler}
}
.heading {
margin-bottom: 0;
}
@ -53,6 +51,7 @@ var css = csjs`
input {
margin-right: 5px;
cursor: pointer;
width: inherit;
}
input[type=radio] {
margin-top: 2px;
@ -60,24 +59,36 @@ var css = csjs`
.pluginTextArea {
font-family: unset;
}
.pluginLoad {
vertical-align: top;
}
i.warnIt {
color: ${styles.appProperties.warningText_Color};
.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,5 +1,4 @@
const csjs = require('csjs-inject')
const styles = require('../../ui/styles-guide/theme-chooser').chooser()
const css = csjs`
.supportTabView {
@ -12,7 +11,6 @@ const css = csjs`
overflow-y: auto;
}
.chat {
${styles.rightPanel.supportTab.box_IframeContainer}
display: flex;
flex-direction: column;
align-items: center;
@ -49,7 +47,6 @@ const css = csjs`
border: none;
}
.infoBox {
${styles.rightPanel.supportTab.box_SupportInfo}
}
.remixdinstallation {
padding: 3px;
@ -57,7 +54,6 @@ const css = csjs`
margin-left: 5px;
}
.info {
${styles.rightPanel.settingsTab.box_SolidityVersionInfo};
margin-top: 1em;
word-break: break-word;
}

@ -1,25 +1,25 @@
var csjs = require('csjs-inject')
var styleGuide = require('../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs`
.testTabView {}
.infoBox {
${styles.rightPanel.testTab.box_listTests};
margin: 2%;
}
.tests {}
.testList {
${styles.rightPanel.testTab.box_listTests};
line-height: 2em;
display: flex;
flex-direction: column;
margin: 2%;
max-height: 300px;
overflow-y: auto;
}
.container {
${styles.rightPanel.testTab.box_listTests};
margin: 2%;
padding-bottom: 5%;
max-height: 300px;
overflow-y: auto;
}
.outputTitle {
font-weight: bold;
@ -29,7 +29,6 @@ var css = csjs`
font-weight: bold;
}
.testPass {
background-color: ${styles.rightPanel.testTab.color_testPass};
}
.testLog {
margin-bottom: 1%;
@ -37,22 +36,17 @@ var css = csjs`
padding: 1% 1% 1% 5%;
}
.testFailure {
background-color: ${styles.rightPanel.testTab.color_testFail};
}
.testFailureSummary {
color: ${styles.appProperties.errorText_Color};
}
.buttons {
${styles.rightPanel.testTab.box_listTests};
margin: 2%;
display: flex;
align-items: center;
}
.runButton {
${styles.rightPanel.testTab.button_runTests};
}
.generateTestFile {
${styles.rightPanel.testTab.button_generateTestFile};
min-width: 100px
}
.title {

@ -1,77 +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
}
loadTab () {
if (this.gitterIsLoaded) return
const iframe = yo`<iframe class="${css.chatIframe}" src='https://gitter.im/ethereum/remix/~embed'>`
this.gitterIframe.parentNode.replaceChild(iframe, this.gitterIframe)
this.gitterIframe = iframe
this.el.style.display = 'block'
this.gitterIsLoaded = true
}
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,93 +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: {} }
self._deps.app.event.register('debuggingRequested', () => {
self.selectTabByTitle('Debugger')
})
}
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,193 +1,172 @@
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')
import { BaseApi } from 'remix-plugin'
const TestTabLogic = require('./testTab/testTab')
const profile = {
name: 'solidityUnitTesting',
displayName: 'Solidity unit testing',
methods: [],
events: [],
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4wUDEhQZ0zbrmQAAAfNJREFUWMPF17lrFVEUx/EPaIKovfAScSndjUtULFQSYhHF0r/Dwsa/RywUiTaWgvaChWsiKkSMZte4o7G5A49x7r0zLy/PA6eZOef3PXebuYfu2xCmcQ9b9NgOYw6rwR9ia6/gR7HQBi/8PjavN/w4FivghV9bT/gwlhLwHzjTVPQ8rqAvE3ciA/+O8abwy/gVBG4lijiJ5czIL64FXvhNbCzFnaoBv9AUPo7fEcEb2BDiTuNTAv4NYxX6u/EIM7GZuZoQXcX1sJk+J2K+YrRCexfetsX9xKVyUB9uZ4r4k3j3BSMR+JvIMv2zQfsxkSkiBj9XAd8ZgRf+vmop+nGnAXwlcs534HUm93FsQ9YtIjby7XiVyZ3BntSpyBWxgrMR+FQG/gF76xzNftxtMO1rgo+G5AdBqLBN4d9eCCyHD1En8Oi0j4UPSBE4hcFSERN4Fz7BZRvEZKcjHynBC5/EQI1lGqgJ3xcTmE4kvswUMRBiUvCPKTg8zQi8QKsirxXe5eD7c1N4ALMZoeelIlrhWSpnNmjXsoM1iihmYhueZGIXcKTp7/hQ6UZb5c+Cp2LmglZHVqeIlC+G2/GarNMiFnGsWzfdpkV0Fd7e5czXgC+FvmDdWq35/wVvbzbnI/DhXvV9Q6W+r6fw9hZsKnjX4H8B0Aamri7CrBsAAAAASUVORK5CYII=',
description: 'Fast tool to generate unit tests for your contracts',
location: 'swapPanel'
}
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>`
module.exports = class TestTab extends BaseApi {
constructor (fileManager, filePanel, compileTab) {
super(profile)
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>`
}
render () {
const self = this
var testsOutput = yo`<div class=${css.container} hidden='true' id="tests"></div>`
var testsSummary = yo`<div class=${css.container} 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}'>✓ (${result.value})</div>`)
} else if (result.type === 'testFailure') {
testsOutput.appendChild(yo`<div class='${css.testFailure} ${css.testLog}'>✘ (${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} >${_err.message}</div>`)
return
}
testsSummary.appendChild(yo`<div class=${css.summaryTitle}> ${filename} </div>`)
if (result.totalPassing > 0) {
testsSummary.appendChild(yo`<div>${result.totalPassing} passing (${result.totalTime}s)</div>`)
testsSummary.appendChild(yo`<br>`)
}
if (result.totalFailing > 0) {
testsSummary.appendChild(yo`<div>${result.totalFailing} failing</div>`)
testsSummary.appendChild(yo`<br>`)
}
result.errors.forEach((error, index) => {
testsSummary.appendChild(yo`<div>${error.context} - ${error.value} </div>`)
testsSummary.appendChild(yo`<div class=${css.testFailureSummary} >${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.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.filesFromPath(path, (error, files) => {
if (error) return cb(error)
if (!error) {
for (var file in files) {
if (/.(_test.sol)$/.exec(file)) tests.push(provider.type + '/' + file)
}
cb(null, tests)
}
})
}
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.event.register('currentFileChanged', (file, provider) => {
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 = ''
testsSummary.innerHTML = ''
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.loading.hidden = false
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)
this.loading.hidden = true
callback(error)
}, (url, cb) => {
return this.compileTab.compileTabLogic.importFileCb(url, cb)
})
})
}
runTests () {
this.loading.hidden = false
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>`
this.loading = yo`<span class='text-info ml-1'>Running tests...</span>`
this.loading.hidden = true
var el = yo`
<div class="${css.testTabView}" id="testView">
<div class="${css.testTabView} card" id="testView">
<div class="${css.infoBox}">
<div class="${css.title}">Unit Testing</div>
Test your smart contract by creating a foo_test.sol file (open ballot_test.sol to see the example).
<br/>
You will find more informations in the <a href="https://remix.readthedocs.io/en/latest/unittesting_tab.html">documentation</a>
@ -196,69 +175,30 @@ module.exports = class TestTab {
<br/>
For more details, see
How to test smart contracts guide in our documentation.
<div class="${css.generateTestFile}" onclick="${generateTestFile}">Generate test file</div>
<div class="${css.generateTestFile} btn btn-primary m-1" onclick="${this.testTabLogic.generateTestFile.bind(this.testTabLogic)}">Generate test file</div>
</div>
<div class="${css.tests}">
${self.testList}
<div class="${css.tests}">
<div class="${css.buttons}">
<div class="${css.runButton}" onclick="${runTests}">Run Tests</div>
<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.testList}
<hr>
<div class="${css.buttons}" ><h6>Results:${this.loading}</h6></div>
${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.getFolder(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,63 @@
import { BaseApi } from 'remix-plugin'
import { EventEmitter } from 'events'
const themes = [
{name: 'Cerulean', quality: 'light', url: 'https://bootswatch.com/4/cerulean/bootstrap.min.css'},
{name: 'Flatly', quality: 'light', url: 'https://bootswatch.com/4/flatly/bootstrap.min.css'},
{name: 'Lumen', quality: 'light', url: 'https://bootswatch.com/4/lumen/bootstrap.min.css'},
{name: 'Minty', quality: 'light', url: 'https://bootswatch.com/4/minty/bootstrap.min.css'},
{name: 'Pulse', quality: 'light', url: 'https://bootswatch.com/4/pulse/bootstrap.min.css'},
{name: 'Sandstone', quality: 'light', url: 'https://bootswatch.com/4/sandstone/bootstrap.min.css'},
{name: 'Spacelab', quality: 'light', url: 'https://bootswatch.com/4/spacelab/bootstrap.min.css'},
{name: 'Yeti', quality: 'light', url: 'https://bootswatch.com/4/yeti/bootstrap.min.css'},
{name: 'Cyborg', quality: 'dark', url: 'https://bootswatch.com/4/cyborg/bootstrap.min.css'},
{name: 'Darkly', quality: 'dark', url: 'https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/darkly/bootstrap.min.css'},
{name: 'Slate', quality: 'light', url: 'https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/slate/bootstrap.min.css'},
{name: 'Superhero', quality: 'dark', url: 'https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/superhero/bootstrap.min.css'}
]
const profile = {
name: 'theme',
events: ['themeChanged'],
methods: ['switchTheme', 'getThemes', 'currentTheme']
}
export class ThemeModule extends BaseApi {
constructor (registry) {
super(profile)
this.events = new EventEmitter()
this._deps = {
config: registry.get('config').api
}
this.themes = themes.reduce((acc, theme) => ({ ...acc, [theme.name]: theme }), {})
this.active = this._deps.config.get('settings/theme') ? this._deps.config.get('settings/theme') : 'Flatly'
}
/** Return the active theme */
currentTheme () {
return this.themes[this.active]
}
/** Returns all themes as an array */
getThemes () {
return Object.keys(this.themes).map(key => this.themes[key])
}
/**
* Change the current theme
* @param {string} [themeName] - The name of the theme
*/
switchTheme (themeName) {
if (themeName && !Object.keys(this.themes).includes(themeName)) {
throw new Error(`Theme ${themeName} doesn't exist`)
}
const next = themeName || this.active // Name
const nextTheme = this.themes[next] // Theme
this._deps.config.set('settings/theme', next)
document.getElementById('theme-link').setAttribute('href', nextTheme.url)
document.documentElement.style.setProperty('--theme', nextTheme.quality)
if (themeName) this.active = themeName
this.events.emit('themeChanged', nextTheme)
}
}

@ -24,7 +24,6 @@ var css = csjs`
flex-shrink: 0;
}
.label_tv {
display: flex;
align-items: center;
}
`
@ -78,7 +77,7 @@ class TreeView {
formatData (key, data, children, expand, keyPath) {
var self = this
var li = yo`<li key=${keyPath} class=${css.li_tv}></li>`
var caret = yo`<div class="fa fa-caret-right caret ${css.caret_tv}"></div>`
var caret = yo`<div class="fas fa-caret-right caret ${css.caret_tv}"></div>`
var label = yo`
<div key=${keyPath} class=${css.label_tv}>
${caret}
@ -88,7 +87,7 @@ class TreeView {
if (data.children) {
var list = yo`<ul key=${keyPath} class=${css.ul_tv}>${children}</ul>`
list.style.display = 'none'
caret.className = list.style.display === 'none' ? `fa fa-caret-right caret ${css.caret_tv}` : `fa fa-caret-down caret ${css.caret_tv}`
caret.className = list.style.display === 'none' ? `fas fa-caret-right caret ${css.caret_tv}` : `fas fa-caret-down caret ${css.caret_tv}`
label.onclick = function () {
self.expand(keyPath)
}
@ -121,7 +120,7 @@ class TreeView {
var node = this.nodeAt(path)
if (node) {
node.style.display = node.style.display === 'none' ? 'block' : 'none'
caret.className = node.style.display === 'none' ? `fa fa-caret-right caret ${css.caret_tv}` : `fa fa-caret-down caret ${css.caret_tv}`
caret.className = node.style.display === 'none' ? `fas fa-caret-right caret ${css.caret_tv}` : `fas fa-caret-down caret ${css.caret_tv}`
this.event.trigger('nodeClick', [path, node])
}
}

@ -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,114 +22,204 @@ class AutoCompletePopup {
constructor (opts = {}) {
var self = this
self.event = new EventManager()
self.isOpen = false
self.opts = opts
self.data = {
_options: opts.options || []
_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.extraCommands = []
this.render()
this.extendAutocompletion()
}
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
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]))
}
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 => {
const 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 => {
const command = getKeyOf(item)
if (command.includes(autoCompleteInput.trim())) {
this.data._options.push(item)
}
})
}
} else {
if (self._startingElement > 0) {
self._startingElement -= self._elementsToShow
})
this.extraCommands.forEach(item => {
const 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
}
self.event.trigger('updateList')
}
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())
}
extendAutocompletion () {
// TODO: this is not using the appManager interface. Terminal should be put as module
this.opts.appStore.event.on('activate', (id) => {
const profile = this.opts.appStore.getOne(id).profile
if (!profile.methods) return
profile.methods.forEach((method) => {
const key = `remix.call({name: '${id}', key:'${method}', payload: []}).then((result) => { console.log(result) }).catch((error) => { console.log(error) })`
const keyValue = {}
keyValue[key] = `call ${id} - ${method}`
if (this.extraCommands.includes(keyValue)) return
this.extraCommands.push(keyValue)
})
})
}
}
function getKeyOf (item) {
return Object.keys(item)[0]
}
function getValueOf (item) {
return Object.values(item)[0]
}
module.exports = AutoCompletePopup

@ -1,7 +1,5 @@
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var styleGuide = require('./styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var EventManager = require('../../lib/events')
module.exports = class Card {
@ -18,17 +16,15 @@ module.exports = class Card {
if (self._view.el) return self._view.el
self._view.cardBody = yo`<div class=${css.cardBody}></div>`
self._view.arrow = yo`<i class="${css.arrow} fa fa-angle-down"
onclick=${(ev) => trigger(ev.target)}></i>`
self._view.arrow = yo`<i class="${css.arrow} fas fa-angle-down" onclick="${() => trigger(this)}"></i>`
self._view.expandCollapseButton = yo`
<div class=${css.expandCollapseButton}>${self._view.arrow}</div>`
self._view.statusBar = yo`<div class=${css.statusBar}>${self._opts.collapsedView}</div>`
self._view.cardHeader = yo`
<div class=${css.cardHeader}>
<div class=${css.cardTitles}>
<div class=${css.cardHeader} onclick=${() => trigger(self._view.arrow)}>
<div class="p-1 ${css.cardTitles}">
<div class=${css.cardTitle}>${self._opts.title}</div>
${self._view.statusBar}
</div>
@ -38,14 +34,16 @@ module.exports = class Card {
function trigger (el) {
var body = self._view.cardBody
var status = self._view.statusBar
el.classList.toggle('fa-angle-up')
var arrow = el.classList.toggle('fa-angle-down') ? 'up' : 'down'
self.event.trigger('expandCollapseCard', [arrow, body, status])
if (el.classList) {
el.classList.toggle('fa-angle-up')
var arrow = el.classList.toggle('fa-angle-down') ? 'up' : 'down'
self.event.trigger('expandCollapseCard', [arrow, body, status])
}
}
// HTML
self._view.el = yo`
<div class=${css.cardContainer}>
<div class="${css.cardContainer} p-2 list-group-item">
${self._view.cardHeader}
${self._view.cardBody}
</div>`
@ -57,7 +55,7 @@ module.exports = class Card {
const css = csjs`
.cardContainer {
${styles.remix.solidBox};
padding : 10px 15px 15px 0;
margin-bottom : 2%;
}
.cardHeader {
@ -74,18 +72,15 @@ const css = csjs`
.cardTitle {
font-size : 13px;
font-weight : bold;
color : ${styles.appProperties.mainText_Color};
margin-right : 5px;
}
.expandCollapseButton {}
.arrow {
color : ${styles.appProperties.icon_Color};
font-weight : bold;
cursor : pointer;
font-size : 14px;
}
.arrow:hover {
color : ${styles.appProperties.icon_HoverColor};
}
`

@ -1,43 +1,33 @@
var yo = require('yo-yo')
// -------------- copyToClipboard ----------------------
var csjs = require('csjs-inject')
var styleGuide = require('./styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs`
.container
{
display: none;
position: fixed;
border: 1px solid ${styles.appProperties.solidBorderBox_BorderColor};
width:150px;
background: ${styles.appProperties.solidBorderBox_BackgroundColor};
border-radius: 2px;
z-index: 1000;
display: none;
position: fixed;
width:100px;
border-radius: 2px;
z-index: 1000;
box-shadow: 0 0 4px var(--dark);
}
.liitem
{
padding: 3px;
padding-left: 10px;
cursor: pointer;
padding: 2px;
padding-left: 6px;
cursor: pointer;
color: var(--text-dark);
background-color: var(--light);
}
#menuitems
.liitem:hover
{
list-style: none;
margin: 0px;
margin-top: 4px;
padding-left: 5px;
padding-right: 5px;
padding-bottom: 3px;
color: ${styles.appProperties.solidBorderBox_TextColor};
background-color: var(--secondary);
}
#menuitems :hover
#menuitems
{
background: ${styles.appProperties.highlight_BackgroundColor};
border-radius: 2px;
list-style: none;
margin: 0px;
}
`
@ -56,7 +46,7 @@ module.exports = (event, items) => {
current.onclick = () => { hide(null, true); items[item]() }
return current
})
var container = yo`<div class=${css.container}><ul id='menuitems'>${menu}</ul></div>`
var container = yo`<div class="${css.container} bg-light"><ul id='menuitems'>${menu}</ul></div>`
container.style.left = event.pageX + 'px'
container.style.top = event.pageY + 'px'
container.style.display = 'block'

@ -4,8 +4,6 @@ const copy = require('copy-text-to-clipboard')
var addTooltip = require('./tooltip')
// -------------- styling ----------------------
var csjs = require('csjs-inject')
var styleGuide = require('./styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs`
.copyIcon {
@ -15,10 +13,7 @@ var css = csjs`
`
module.exports = function copyToClipboard (getContent, tip = 'Copy value to clipboard', icon = 'fa-clipboard') {
var copyIcon = yo`<i title="${tip}" class="${css.copyIcon} fa ${icon}" aria-hidden="true"></i>`
copyIcon.style.color = styles.remix.icon_Color_CopyToClipboard
copyIcon.onmouseenter = function (event) { copyIcon.style.color = styles.remix.icon_HoverColor_CopyToClipboard }
copyIcon.onmouseleave = function (event) { copyIcon.style.color = styles.remix.icon_Color_CopyToClipboard }
var copyIcon = yo`<i title="${tip}" class="${css.copyIcon} far ${icon}" aria-hidden="true"></i>`
copyIcon.onclick = (event) => {
event.stopPropagation()
var copiableContent

@ -1,41 +1,32 @@
'use strict'
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var styleGuide = require('./styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs`
.containerDraggableModal {
position: absolute;
z-index: 1000;
background-color: ${styles.appProperties.light_BackgroundColor};
text-align: center;
width: 500px;
height: 500px;
border: 1px solid ${styles.appProperties.solidBorderBox_BorderColor};
overflow-y: hidden;
}
.headerDraggableModal {
cursor: move;
z-index: 10;
color: ${styles.appProperties.mainText_Color};
background-color: ${styles.appProperties.primary_BackgroundColor};
border-bottom: 1px solid ${styles.appProperties.solidBorderBox_BorderColor};
text-overflow: ellipsis;
overflow-x: hidden;
}
.modalActions {
float: right;
color: ${styles.appProperties.solidBorderBox_BorderColor};
}
.modalAction {
padding-right: 1px;
padding-left: 1px;
cursor: pointer;
color: ${styles.appProperties.solidBorderBox_BorderColor};
}
`
@ -53,9 +44,9 @@ module.exports =
<div>
<div class="${css.headerDraggableModal} title" title=${title}><span title="${title}" >${title}</span><span title="${url}" > - ${url}</span>
<div class=${css.modalActions}>
<i onclick=${() => { this.minimize() }} class="fa fa-window-minimize ${css.modalAction}"></i>
<i onclick=${() => { this.maximise() }} class="fa fa-window-maximize ${css.modalAction}"></i>
<i onclick=${() => { this.close() }} class="fa fa-window-close-o ${css.modalAction}"></i>
<i onclick=${() => { this.minimize() }} class="fas fa-window-minimize ${css.modalAction}"></i>
<i onclick=${() => { this.maximise() }} class="fas fa-window-maximize ${css.modalAction}"></i>
<i onclick=${() => { this.close() }} class="fas fa-window-close-o ${css.modalAction}"></i>
</div>
</div>
</div>

@ -41,15 +41,23 @@ class Dropdown {
self._view.selected = yo`
<div class=${css.selectbox}>
<span class=${css.selected}> [${self.data.selected.length}] ${self.data.selected.join(', ')}</span>
<i class="${css.icon} fa fa-caret-down"></i>
<i class="${css.icon} fas fa-caret-down"></i>
</div>
`
self._view.el = yo`
<div class=${css.dropdown} onclick=${show}>
<div name="dropdown" class="${css.dropdown} form-control form-control-sm" onclick=${show}>
${self._view.selected}
<div class=${css.options} style="display: none;">
<div class="${css.options} bg-light" style="display: none;}">
${self.data._options.map(label => {
var input = yo`<input data-idx=${self.data._elements.length} onchange=${emit} type="checkbox" />`
let index = self.data._elements.length
var input = yo`
<input
data-idx=${index}
onchange=${emit}
type="${index === 2 ? 'checkbox' : 'radio'}"
id="${label}"
/>
`
if (self.data.selected.indexOf(label) !== -1) {
input.checked = true
self.event.trigger('select', [label])
@ -58,7 +66,7 @@ class Dropdown {
return yo`
<div class=${css.option}>
${input}
<label>${label}</label>
<label class="text-dark" for="${label}">${label}</label>
</div>
`
})}

File diff suppressed because one or more lines are too long

@ -0,0 +1,70 @@
let yo = require('yo-yo')
let csjs = require('csjs-inject')
var css = csjs`
.text {
cursor: pointer;
font-weight: normal;
max-width: 300px;
user-select: none;
padding-left: 14px;
}
.text:hover {
font-weight: bold;
}
.link {
cursor: pointer;
font-weight: normal;
text-decoration : none;
user-select: none;
padding-left: 14px;
}
.link:hover {
font-weight: bold;
text-decoration : none;
}
`
class Section {
constructor (title, actions) {
this.title = title
this.actions = actions
}
render () {
let sectionLook = yo`
<div class="card border-0 bg-light text-dark p-1" style="min-width: 300px; min-height: 180px;">
<div class="card-header h5 font-weight-bold" style="user-select: none;">${this.title}</div>
<p></p>
</div>
`
for (var i = 0; i < this.actions.length; i++) {
if (this.actions[i].type === `callback`) {
sectionLook.appendChild(yo`
<div>
<span class="${css.text} h6 text-dark" onclick=${this.actions[i].payload} >
${this.actions[i].label}
</span>
</div>
`)
} else if (this.actions[i].type === `link`) {
sectionLook.appendChild(yo`
<div >
<a class="${css.link} text-dark h6 text-decoration-none" href=${this.actions[i].payload} target="_blank" >
${this.actions[i].label}
</a>
</div>
`)
}
}
if (!this._view) {
this._view = sectionLook
}
return this._view
}
}
module.exports = Section

@ -0,0 +1,44 @@
let globalRegistry = require('../../../global/registry')
export class Workspace {
constructor (title, description, isMain, activate, deactivate) {
this.title = title
this.description = description
this.isMain = isMain
this.activate = activate
this.deactivate = deactivate
}
}
export const defaultWorkspaces = (appManager) => {
return [
new Workspace(
'Solidity',
'Writing smart contracts. It is used for implementing smart contracts on various blockchain platforms',
true,
() => {
appManager.ensureActivated('solidity')
appManager.ensureActivated('run')
appManager.ensureActivated('solidityStaticAnalysis')
appManager.ensureActivated('solidityUnitTesting')
globalRegistry.get('verticalicon').api.select('solidity')
}, () => {}),
new Workspace(
'Vyper',
'Vyper is a contract-oriented, pythonic programming language that targets the Ethereum Virtual Machine (EVM)',
true,
() => {
appManager.ensureActivated('vyper')
appManager.ensureActivated('run')
globalRegistry.get('verticalicon').api.select('vyper')
}, () => {}),
new Workspace('Debugger', 'Debug transactions with remix', false, () => {
appManager.ensureActivated('debugger')
}, () => {}),
new Workspace('Pipeline', '', false, () => {
appManager.ensureActivated('solidity')
appManager.ensureActivated('pipeline')
appManager.ensureActivated('run')
})
]
}

@ -66,7 +66,7 @@ module.exports = {
function prompt (title, text, hidden, inputValue, ok, cancel, focus) {
if (!inputValue) inputValue = ''
var type = hidden ? 'password' : 'text'
var input = yo`<input type=${type} name='prompt_text' id='prompt_text' class="${css['prompt_text']}" value='${inputValue}' >`
var input = yo`<input type=${type} name='prompt_text' id='prompt_text' class="${css['prompt_text']} form-control" value='${inputValue}' >`
modal(title, yo`<div>${text}<div>${input}</div></div>`,
{
fn: () => { if (typeof ok === 'function') ok(document.getElementById('prompt_text').value) }

@ -1,61 +1,94 @@
var yo = require('yo-yo')
var css = require('./styles/modaldialog-styles')
module.exports = (title, content, ok, cancel, focusSelector) => {
var container = document.querySelector(`.${css.modal}`)
module.exports = (title, content, ok, cancel, focusSelector, opts) => {
let agreed = true
let footerIsActive = true
opts = opts || {}
var container = document.querySelector(`.modal`)
if (!container) {
document.querySelector('body').appendChild(html())
container = document.querySelector(`.${css.modal}`)
document.querySelector('body').appendChild(html(opts))
container = document.querySelector(`.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'
cancelDiv.style.display = cancelDiv.innerHTML === '' ? 'none' : 'inline-block'
var modal = document.querySelector(`.${css.modalBody}`)
var modalTitle = document.querySelector(`.${css.modalHeader} h2`)
var modal = document.querySelector(`.modal-body`)
var modalTitle = document.querySelector(`.modal-header h6`)
modalTitle.innerHTML = ''
if (title) modalTitle.innerHTML = title
if (title) modalTitle.innerText = title
modal.innerHTML = ''
if (content) modal.appendChild(content)
setFocusOn('ok')
show()
function setFocusOn (btn) {
var okDiv = document.getElementById('modal-footer-ok')
var cancelDiv = document.getElementById('modal-footer-cancel')
if (btn === 'ok') {
okDiv.className = okDiv.className.replace(/\bbtn-light\b/g, 'btn-dark')
cancelDiv.className = cancelDiv.className.replace(/\bbtn-dark\b/g, 'btn-light')
} else {
cancelDiv.className = cancelDiv.className.replace(/\bbtn-light\b/g, 'btn-dark')
okDiv.className = okDiv.className.replace(/\bbtn-dark\b/g, 'btn-light')
}
}
function okListener () {
removeEventListener()
hide()
if (ok && ok.fn) ok.fn()
if (ok && ok.fn && agreed) ok.fn()
}
function cancelListener () {
removeEventListener()
hide()
if (cancel && cancel.fn) cancel.fn()
if (container) {
container.class = `modal`
container = null
}
}
function modalKeyEvent (e) {
if (e.keyCode === 27) {
if (e.keyCode === 27) { // Esc
cancelListener()
} else if (e.keyCode === 13) {
} else if (e.keyCode === 13) { // Enter
e.preventDefault()
okListener()
} else if (e.keyCode === 37 && footerIsActive) { // Arrow Left
e.preventDefault()
agreed = true
setFocusOn('ok')
} else if (e.keyCode === 39 && footerIsActive) { // Arrow Right
e.preventDefault()
agreed = false
setFocusOn('cancel')
}
}
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}`)
const focusTarget = document.querySelector(`.modal ${focusSelector}`)
if (focusTarget) {
focusTarget.focus()
if (typeof focusTarget.setSelectionRange === 'function') {
@ -70,28 +103,44 @@ 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)
let modalDialog = document.getElementById('modal-dialog')
if (modalDialog) {
modalDialog.addEventListener('click', (e) => {
footerIsActive = document.activeElement === modalDialog
if (e.toElement === modalDialog) {
cancelListener() // click is outside of modal-content
}
})
}
return { container, okListener, cancelListener }
}
function html () {
return yo`<div id="modal-dialog" class="${css.modal}">
<div id="modal-background" class="${css['modalBackground']}"> </div>
<div class="${css['modalContent']}">
<div class="${css['modalHeader']}">
<h2></h2>
<i id="modal-close" title="Close" class="fa fa-times ${css['modalClose']}" aria-hidden="true"></i>
</div>
<div class="${css['modalBody']}"> -
</div>
<div class="${css['modalFooter']}">
<span id="modal-footer-ok" class=${css['modalFooterOk']}>OK</span><span id="modal-footer-cancel" class=${css['modalFooterCancel']}>Cancel</span>
function html (opts) {
return yo`
<div id="modal-dialog" class="modal" tabindex="-1" role="dialog">
<div id="modal-background" class="modal-dialog" role="document">
<div class="modal-content ${css.modalContent} ${opts.class}">
<div class="modal-header">
<h6 class="modal-title"></h6>
<span class="modal-close">
<i id="modal-close" title="Close" class="fas fa-times" aria-hidden="true"></i>
</span>
</div>
<div class="modal-body ${css.modalBody}"> - </div>
<div class="modal-footer" autofocus>
<span id="modal-footer-ok" class="${css['modalFooterOk']} modal-ok btn btn-sm btn-light" tabindex='5'>OK</span>
<span id="modal-footer-cancel" class="${css['modalFooterCancel']} modal-cancel btn btn-sm btn-light" tabindex='10' data-dismiss="modal">Cancel</span>
</div>
</div>
</div>
</div>
</div>`
}

@ -16,7 +16,6 @@ function Renderer (localRegistry) {
self._components.registry = localRegistry || globlalRegistry
// dependencies
self._deps = {
editor: self._components.registry.get('editor').api,
fileManager: self._components.registry.get('filemanager').api,
config: self._components.registry.get('config').api
}
@ -27,13 +26,15 @@ function Renderer (localRegistry) {
Renderer.prototype._error = function (file, error) {
const self = this
const editor = self._components.registry.get('editor').api
if (file === self._deps.config.get('currentFile')) {
self._deps.editor.addAnnotation(error)
editor.addAnnotation(error)
}
}
Renderer.prototype._errorClick = function (errFile, errLine, errCol) {
const self = this
const editor = self._components.registry.get('editor').api
if (errFile !== self._deps.config.get('currentFile')) {
// TODO: refactor with this._components.contextView.jumpTo
var provider = self._deps.fileManager.fileProviderOf(errFile)
@ -41,11 +42,11 @@ Renderer.prototype._errorClick = function (errFile, errLine, errCol) {
provider.exists(errFile, (error, exist) => {
if (error) return console.log(error)
self._deps.fileManager.switchFile(errFile)
self._deps.editor.gotoLine(errLine, errCol)
editor.gotoLine(errLine, errCol)
})
}
} else {
self._deps.editor.gotoLine(errLine, errCol)
editor.gotoLine(errLine, errCol)
}
}
@ -88,7 +89,8 @@ Renderer.prototype.error = function (message, container, opt) {
var $pre = $(opt.useSpan ? yo`<span></span>` : yo`<pre></pre>`).html(message)
var $error = $(yo`<div class="sol ${opt.type}"><div class="close"><i class="fa fa-close"></i></div></div>`).prepend($pre)
let classList = opt.type === 'error' ? 'alert alert-danger' : 'alert alert-warning'
var $error = $(yo`<div class="sol ${opt.type} ${classList}"><div class="close"><i class="fas fa-times"></i></div></div>`).prepend($pre)
$(container).append($error)
$error.click((ev) => {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,36 +0,0 @@
var styleGuideLight = require('./style-guide')
var styleGuideDark = require('./styleGuideDark')
var styleGuideClean = require('./styleGuideClean')
var Storage = require('remix-lib').Storage
module.exports = {
chooser: function () {
var themeStorage = new Storage('style:')
if (themeStorage.exists('theme')) {
if (themeStorage.get('theme') === 'dark') {
return styleGuideDark()
} else if (themeStorage.get('theme') === 'clean') {
return styleGuideClean()
} else {
return styleGuideLight()
}
} else {
return styleGuideLight()
}
},
switchTheme: function (theme) {
var themeStorage = new Storage('style:')
themeStorage.set('theme', theme)
if (theme === 'dark') {
return styleGuideDark()
} else if (theme === 'light') {
return styleGuideLight()
} else if (theme === 'clean') {
return styleGuideClean()
} else {
return styleGuideLight()
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save