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. 3
      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. 350
      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. 76
      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. 567
      src/app/editor/editor.js
  35. 36
      src/app/editor/sourceHighlighter.js
  36. 19
      src/app/editor/styles/contextView-styles.js
  37. 8
      src/app/execution/confirmDialog.js
  38. 76
      src/app/execution/txLogger.js
  39. 88
      src/app/files/browser-files-tree.js
  40. 358
      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. 259
      src/app/panels/editor-panel.js
  45. 333
      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. 48
      src/app/panels/styles/file-panel-styles.js
  49. 70
      src/app/panels/styles/terminal-styles.js
  50. 177
      src/app/panels/tab-proxy.js
  51. 211
      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. 34
      src/app/staticanalysis/staticAnalysisView.js
  60. 19
      src/app/staticanalysis/styles/staticAnalysisView-styles.js
  61. 37
      src/app/tabs/analysis-tab.js
  62. 905
      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. 22
      src/app/tabs/runTab/contractDropdown.js
  69. 4
      src/app/tabs/runTab/model/dropdownlogic.js
  70. 8
      src/app/tabs/runTab/recorder.js
  71. 77
      src/app/tabs/runTab/settings.js
  72. 379
      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. 67
      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. 320
      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. 204
      src/app/ui/auto-complete-popup.js
  87. 19
      src/app/ui/card.js
  88. 32
      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. 95
      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-classes",
"transform-es2015-computed-properties", "transform-es2015-computed-properties",
"transform-es2015-destructuring", "transform-es2015-destructuring",
"transform-object-rest-spread",
"transform-es2015-duplicate-keys", "transform-es2015-duplicate-keys",
"transform-es2015-for-of", "transform-es2015-for-of",
"transform-es2015-function-name", "transform-es2015-function-name",

@ -19,17 +19,17 @@ jobs:
- ENCRYPTION_LABEL3: "1b1c118ea62d" - ENCRYPTION_LABEL3: "1b1c118ea62d"
- COMMIT_AUTHOR_EMAIL: "chris@ethereum.org" - COMMIT_AUTHOR_EMAIL: "chris@ethereum.org"
- COMMIT_AUTHOR: "Circle CI" - 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 working_directory: ~/repo
steps: steps:
- checkout - checkout
- restore_cache: - restore_cache:
keys: keys:
- dep-bundle-27-{{ checksum "package.json" }} - dep-bundle-29-{{ checksum "package.json" }}
- run: npm install - run: npm install
- save_cache: - save_cache:
key: dep-bundle-27-{{ checksum "package.json" }} key: dep-bundle-29-{{ checksum "package.json" }}
paths: paths:
- ~/repo/node_modules - ~/repo/node_modules
- run: npm run lint && npm run test && npm run make-mock-compiler && npm run build - 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,9 +1,10 @@
#!/usr/bin/env node #!/usr/bin/env node
var path = require('path')
var httpServer = require('http-server') var httpServer = require('http-server')
var remixd = require('remixd') var remixd = require('remixd')
var server = httpServer.createServer({ var server = httpServer.createServer({
root: __dirname + '/../' root: path.join(__dirname, '/../')
}) })
var folder = process.argv.length > 2 ? process.argv[2] : process.cwd() var folder = process.argv.length > 2 ? process.argv[2] : process.cwd()

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

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

@ -28,8 +28,9 @@
--> -->
<meta http-equiv="X-UA-Compatible" content="chrome=1"> <meta http-equiv="X-UA-Compatible" content="chrome=1">
<title>Remix - Solidity IDE</title> <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/pygment_trac.css">
<link rel="stylesheet" href="assets/css/font-awesome.min.css">
<link rel="icon" type="x-icon" href="icon.png"> <link rel="icon" type="x-icon" href="icon.png">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
</head> </head>

@ -1,12 +1,16 @@
{ {
"name": "remix-ide", "name": "remix-ide",
"version": "v0.7.5", "version": "v0.8.0-alpha+002",
"description": "Minimalistic browser-based Solidity IDE", "description": "Minimalistic browser-based Solidity IDE",
"devDependencies": { "devDependencies": {
"@fortawesome/fontawesome-free": "^5.8.1",
"@resolver-engine/imports": "^0.3.0",
"ace-mode-solidity": "^0.1.0", "ace-mode-solidity": "^0.1.0",
"async": "^2.1.2", "async": "^2.1.2",
"babel-eslint": "^7.1.1", "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-assign": "^6.22.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-yo-yoify": "^0.3.3", "babel-plugin-yo-yoify": "^0.3.3",
"babel-polyfill": "^6.22.0", "babel-polyfill": "^6.22.0",
"babel-preset-env": "^1.6.1", "babel-preset-env": "^1.6.1",
@ -21,7 +25,8 @@
"csjs-inject": "^1.0.1", "csjs-inject": "^1.0.1",
"csslint": "^1.0.2", "csslint": "^1.0.2",
"deep-equal": "^1.0.1", "deep-equal": "^1.0.1",
"ethereumjs-util": "^6.1.0", "ethereumjs-util": "^5.1.2",
"events": "^3.0.0",
"execr": "^1.0.1", "execr": "^1.0.1",
"exorcist": "^0.4.0", "exorcist": "^0.4.0",
"fast-async": "6.3.1", "fast-async": "6.3.1",
@ -38,11 +43,12 @@
"npm-link-local": "^1.1.0", "npm-link-local": "^1.1.0",
"npm-run-all": "^4.0.2", "npm-run-all": "^4.0.2",
"onchange": "^3.2.1", "onchange": "^3.2.1",
"remix-debug": "0.3.1", "remix-analyzer": "0.3.5",
"remix-analyzer": "0.3.1", "remix-debug": "0.3.5",
"remix-lib": "0.4.1", "remix-lib": "0.4.5",
"remix-solidity": "0.3.1", "remix-solidity": "0.3.5",
"remix-tests": "0.1.1", "remix-tabs": "^1.0.0",
"remix-tests": "0.1.6",
"remixd": "0.1.8-alpha.6", "remixd": "0.1.8-alpha.6",
"request": "^2.83.0", "request": "^2.83.0",
"rimraf": "^2.6.1", "rimraf": "^2.6.1",
@ -60,7 +66,8 @@
"yo-yoify": "^3.7.3" "yo-yoify": "^3.7.3"
}, },
"dependencies": { "dependencies": {
"http-server": "0.9.0", "http-server-legacy": "latest",
"remix-plugin": "0.0.2-alpha.6",
"remixd": "0.1.8-alpha.6" "remixd": "0.1.8-alpha.6"
}, },
"repository": { "repository": {
@ -105,7 +112,8 @@
"transform-es2015-spread", "transform-es2015-spread",
"transform-es2015-parameters", "transform-es2015-parameters",
"transform-es2015-destructuring", "transform-es2015-destructuring",
"transform-es2015-block-scoping" "transform-es2015-block-scoping",
"transform-modern-regexp"
] ]
}, },
"browserify": { "browserify": {
@ -146,7 +154,7 @@
"remix-ide": "./bin/remix-ide" "remix-ide": "./bin/remix-ide"
}, },
"scripts": { "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", "pullremix": "git clone https://github.com/ethereum/remix",
"linkremixlib": "cd node_modules && rm -rf remix-lib && ln -s ../../remix/remix-lib remix-lib && cd ..", "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 ..", "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", "nightwatch_remote_debugger_parallel": "nightwatch --config nightwatch_debugger.js --env safari,chrome,default",
"onchange": "onchange build/app.js -- npm-run-all lint", "onchange": "onchange build/app.js -- npm-run-all lint",
"prepublish": "mkdirp build; npm-run-all -ls downloadsolc_root build", "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": "execr --silent selenium-standalone start",
"selenium-install": "selenium-standalone install", "selenium-install": "selenium-standalone install",
"serve": "execr --silent http-server .", "serve": "npx http-server-legacy .",
"serve_debugger": "execr --silent http-server src/app/debugger/remix-debugger", "serve_debugger": "npx http-server-legacy src/app/debugger/remix-debugger",
"sourcemap": "exorcist --root ../ build/app.js.map > build/app.js", "sourcemap": "exorcist --root ../ build/app.js.map > build/app.js",
"start": "npm-run-all -lpr serve watch onchange remixd", "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", "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 async = require('async')
var request = require('request') var request = require('request')
var remixLib = require('remix-lib') var remixLib = require('remix-lib')
var EventManager = require('./lib/events')
var registry = require('./global/registry') var registry = require('./global/registry')
var UniversalDApp = require('./universal-dapp.js') var UniversalDApp = require('./universal-dapp.js')
var UniversalDAppUI = require('./universal-dapp-ui.js') var UniversalDAppUI = require('./universal-dapp-ui.js')
@ -18,14 +16,11 @@ var GistHandler = require('./lib/gist-handler')
var helper = require('./lib/helper') var helper = require('./lib/helper')
var Storage = remixLib.Storage var Storage = remixLib.Storage
var Browserfiles = require('./app/files/browser-files') var Browserfiles = require('./app/files/browser-files')
var BrowserfilesTree = require('./app/files/browser-files-tree')
var SharedFolder = require('./app/files/shared-folder') var SharedFolder = require('./app/files/shared-folder')
var Config = require('./config') var Config = require('./config')
var Renderer = require('./app/ui/renderer') var Renderer = require('./app/ui/renderer')
var executionContext = require('./execution-context') var executionContext = require('./execution-context')
var FilePanel = require('./app/panels/file-panel')
var EditorPanel = require('./app/panels/editor-panel') var EditorPanel = require('./app/panels/editor-panel')
var RighthandPanel = require('./app/panels/righthand-panel')
var examples = require('./app/editor/example-contracts') var examples = require('./app/editor/example-contracts')
var modalDialogCustom = require('./app/ui/modal-dialog-custom') var modalDialogCustom = require('./app/ui/modal-dialog-custom')
var TxLogger = require('./app/execution/txLogger') var TxLogger = require('./app/execution/txLogger')
@ -37,29 +32,36 @@ var NotPersistedExplorer = require('./app/files/NotPersistedExplorer')
var toolTip = require('./app/ui/tooltip') var toolTip = require('./app/ui/tooltip')
var TransactionReceiptResolver = require('./transactionReceiptResolver') var TransactionReceiptResolver = require('./transactionReceiptResolver')
const CompilerAbstract = require('./app/compiler/compiler-abstract') const PluginManagerComponent = require('./app/components/plugin-manager-component')
const PluginManager = require('./app/plugin/pluginManager')
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 CompileTab = require('./app/tabs/compile-tab')
const SettingsTab = require('./app/tabs/settings-tab') const SettingsTab = require('./app/tabs/settings-tab')
const AnalysisTab = require('./app/tabs/analysis-tab') const AnalysisTab = require('./app/tabs/analysis-tab')
const DebuggerTab = require('./app/tabs/debugger-tab') const DebuggerTab = require('./app/tabs/debugger-tab')
const SupportTab = require('./app/tabs/support-tab')
const TestTab = require('./app/tabs/test-tab') const TestTab = require('./app/tabs/test-tab')
const RunTab = require('./app/tabs/run-tab') const RunTab = require('./app/tabs/run-tab')
const FilePanel = require('./app/panels/file-panel')
var styleGuide = require('./app/ui/styles-guide/theme-chooser') import PanelsResize from './lib/panels-resize'
var styles = styleGuide.chooser() 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` var css = csjs`
html { box-sizing: border-box; } html { box-sizing: border-box; }
*, *:before, *:after { box-sizing: inherit; } *, *:before, *:after { box-sizing: inherit; }
body { body {
font: 14px/1.5 Lato, "Helvetica Neue", Helvetica, Arial, sans-serif; /* font: 14px/1.5 Lato, "Helvetica Neue", Helvetica, Arial, sans-serif; */
margin : 0; font-size : .8rem;
padding : 0;
font-size : 12px;
color : ${styles.leftPanel.text_Primary};
font-weight : normal;
} }
pre { pre {
overflow-x: auto; overflow-x: auto;
@ -70,8 +72,7 @@ var css = csjs`
height : 100vh; height : 100vh;
overflow : hidden; overflow : hidden;
} }
.centerpanel { .mainpanel {
background-color : ${styles.colors.transparent};
display : flex; display : flex;
flex-direction : column; flex-direction : column;
position : absolute; position : absolute;
@ -79,8 +80,7 @@ var css = csjs`
bottom : 0; bottom : 0;
overflow : hidden; overflow : hidden;
} }
.leftpanel { .iconpanel {
background-color : ${styles.leftPanel.backgroundColor_Panel};
display : flex; display : flex;
flex-direction : column; flex-direction : column;
position : absolute; position : absolute;
@ -88,26 +88,29 @@ var css = csjs`
bottom : 0; bottom : 0;
left : 0; left : 0;
overflow : hidden; overflow : hidden;
width : 50px;
user-select : none;
/* border-right : 1px solid var(--primary); */
} }
.rightpanel { .swappanel {
background-color : ${styles.rightPanel.backgroundColor_Panel};
display : flex; display : flex;
flex-direction : column; flex-direction : column;
position : absolute; position : absolute;
top : 0; top : 0;
right : 0; left : 50px;
bottom : 0; bottom : 0;
overflow : hidden; overflow : hidden;
overflow-y : auto;
} }
.highlightcode { .highlightcode {
position:absolute; position:absolute;
z-index:20; z-index:20;
background-color: ${styles.editor.backgroundColor_DebuggerMode}; background-color: var(--info);
} }
.highlightcode_fullLine { .highlightcode_fullLine {
position:absolute; position:absolute;
z-index:20; z-index:20;
background-color: ${styles.editor.backgroundColor_DebuggerMode}; background-color: var(--info);
opacity: 0.5; opacity: 0.5;
} }
` `
@ -115,30 +118,23 @@ var css = csjs`
class App { class App {
constructor (api = {}, events = {}, opts = {}) { constructor (api = {}, events = {}, opts = {}) {
var self = this var self = this
this.event = new EventManager()
self._components = {} self._components = {}
registry.put({api: self, name: 'app'}) registry.put({api: self, name: 'app'})
var fileStorage = new Storage('sol:') var fileStorage = new Storage('sol:')
registry.put({api: fileStorage, name: 'fileStorage'}) registry.put({api: fileStorage, name: 'fileStorage'})
var configStorage = new Storage('config:') var configStorage = new Storage('config-v0.8:')
registry.put({api: configStorage, name: 'configStorage'}) 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'}) registry.put({api: self._components.config, name: 'config'})
executionContext.init(self._components.config)
executionContext.listenOnLastBlock()
self._components.gistHandler = new GistHandler() self._components.gistHandler = new GistHandler()
self._components.filesProviders = {} self._components.filesProviders = {}
self._components.filesProviders['browser'] = new Browserfiles(fileStorage) 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['browser'], name: 'fileproviders/browser'})
registry.put({api: self._components.filesProviders['config'], name: 'fileproviders/config'})
var remixd = new Remixd(65520) var remixd = new Remixd(65520)
registry.put({api: remixd, name: 'remixd'}) registry.put({api: remixd, name: 'remixd'})
@ -161,81 +157,47 @@ class App {
registry.put({api: self._components.filesProviders, name: 'fileproviders'}) registry.put({api: self._components.filesProviders, name: 'fileproviders'})
self._view = {} 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 () { init () {
var self = this var self = this
self._components.resizeFeature = new PanelsResize('#swap-panel', '#editor-container', { 'minWidth': 300, x: 450 })
run.apply(self) run.apply(self)
} }
render () { render () {
var self = this var self = this
if (self._view.el) return self._view.el if (self._view.el) return self._view.el
self._view.leftpanel = yo` // not resizable
<div id="filepanel" class=${css.leftpanel}> self._view.iconpanel = yo`
<div id="icon-panel" class="${css.iconpanel} bg-light">
${''} ${''}
</div> </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> </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> </div>
` `
self._view.el = yo` self._view.el = yo`
<div class=${css.browsersolidity}> <div class=${css.browsersolidity}>
${self._view.leftpanel} ${self._view.iconpanel}
${self._view.centerpanel} ${self._view.swappanel}
${self._view.rightpanel} ${self._view.mainpanel}
</div> </div>
` `
// INIT
self._adjustLayout('left', self.data._layout.left.offset)
self._adjustLayout('right', self.data._layout.right.offset)
return self._view.el return self._view.el
} }
startdebugging (txHash) {
const self = this
self.event.trigger('debuggingRequested', [])
self._components.righthandpanel.debugger().debug(txHash)
}
loadFromGist (params) { loadFromGist (params) {
const self = this const self = this
return self._components.gistHandler.handleLoad(params, function (gistId) { 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'}) registry.put({api: self._components.compilersArtefacts, name: 'compilersartefacts'})
// ----------------- UniversalDApp ----------------- // ----------------- UniversalDApp -----------------
var udapp = new UniversalDApp(registry) const udapp = new UniversalDApp(registry)
// TODO: to remove when possible // TODO: to remove when possible
registry.put({api: udapp, name: 'udapp'}) registry.put({api: udapp, name: 'udapp'})
udapp.event.register('transactionBroadcasted', (txhash, networkName) => { udapp.event.register('transactionBroadcasted', (txhash, networkName) => {
var txLink = executionContext.txDetailsLink(networkName, txhash) 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 // TODO: to remove when possible
registry.put({api: udappUI, name: 'udappUI'}) registry.put({api: udappUI, name: 'udappUI'})
// ----------------- Tx listener ----------------- // ----------------- Tx listener -----------------
var transactionReceiptResolver = new TransactionReceiptResolver() const transactionReceiptResolver = new TransactionReceiptResolver()
var txlistener = new Txlistener({ const txlistener = new Txlistener({
api: { api: {
contracts: function () { contracts: function () {
if (self._components.compilersArtefacts['__last']) return self._components.compilersArtefacts['__last'].getContracts() 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 udapp: udapp.event
}}) }})
registry.put({api: txlistener, name: 'txlistener'}) registry.put({api: txlistener, name: 'txlistener'})
udapp.startListening(txlistener)
var eventsDecoder = new EventsDecoder({ const eventsDecoder = new EventsDecoder({
api: { api: {
resolveReceipt: function (tx, cb) { resolveReceipt: function (tx, cb) {
transactionReceiptResolver.resolve(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 // TODO: There are still a lot of dep between editorpanel and filemanager
// ----------------- editor panel ---------------------- let appStore = new EntityStore('module', 'name')
self._components.editorpanel = new EditorPanel() const appManager = new RemixAppManager(appStore)
registry.put({ api: self._components.editorpanel, name: 'editorpanel' }) registry.put({api: appManager, name: 'appmanager'})
const mainPanelComponent = new SwapPanelComponent('mainPanel', appStore, appManager, { default: false, displayHeader: false })
// ----------------- file manager ---------------------------- // ----------------- file manager ----------------------------
self._components.fileManager = new FileManager() self._components.fileManager = new FileManager()
var fileManager = self._components.fileManager const fileManager = self._components.fileManager
registry.put({api: fileManager, name: 'filemanager'}) registry.put({api: fileManager, name: 'filemanager'})
// ---------------- Plugin Manager ------------------------------- // ----------------- Network ----------------------------
const networkModule = new NetworkModule()
let pluginManager = new PluginManager( registry.put({api: networkModule, name: 'network'})
self,
self._components.compilersArtefacts, // ----------------- theme module ----------------------------
txlistener, const themeModule = new ThemeModule(registry)
self._components.fileProviders, registry.put({api: themeModule, name: 'themeModule'})
self._components.fileManager,
udapp) // ----------------- editor panel ----------------------
registry.put({api: pluginManager, name: 'pluginmanager'}) self._components.editorpanel = new EditorPanel(appStore, appManager, mainPanelComponent)
registry.put({ api: self._components.editorpanel, name: 'editorpanel' })
pluginManager.event.register('sendCompilationResult', (file, source, languageVersion, data) => {
// TODO check whether the tab is configured // ----------------- Renderer -----------------
let compiler = new CompilerAbstract(languageVersion, data, source) const renderer = new Renderer()
self._components.compilersArtefacts['__last'] = compiler 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.editorpanel.init()
self._components.fileManager.init() self._components.fileManager.init()
self._view.mainpanel.appendChild(self._components.editorpanel.render())
self._view.iconpanel.appendChild(verticalIconsComponent.render())
self._view.swappanel.appendChild(swapPanelComponent.render())
self._components.editorpanel.event.register('resize', direction => self._adjustLayout(direction)) let filePanel = new FilePanel()
self._view.centerpanel.appendChild(self._components.editorpanel.render()) 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 // The event listener needs to be registered as early as possible, because the
// parent will send the message upon the "load" event. // parent will send the message upon the "load" event.
var filesToLoad = null let filesToLoad = null
var loadFilesCallback = function (files) { filesToLoad = files } // will be replaced later let loadFilesCallback = function (files) { filesToLoad = files } // will be replaced later
window.addEventListener('message', function (ev) { window.addEventListener('message', function (ev) {
if (typeof ev.data === typeof [] && ev.data[0] === 'loadFiles') { 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) self.loadFiles(filesToLoad)
} }
// ---------------- FilePanel -------------------- const txLogger = new TxLogger() // eslint-disable-line
self._components.filePanel = new FilePanel() txLogger.event.register('debuggingRequested', (hash) => {
self._view.leftpanel.appendChild(self._components.filePanel.render()) if (!appStore.isActive('debugger')) appManager.activateOne('debugger')
self._components.filePanel.event.register('resize', delta => self._adjustLayout('left', delta)) debug.debugger().debug(hash)
registry.put({api: self._components.filePanel, name: 'filepanel'}) verticalIconsApi.select('debugger')
// ----------------- 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()
}) })
let transactionContextAPI = { let transactionContextAPI = {
@ -471,17 +495,9 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
} }
udapp.resetAPI(transactionContextAPI) udapp.resetAPI(transactionContextAPI)
// ---------------- Righthand-panel -------------------- const queryParams = new QueryParams()
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
var queryParams = new QueryParams() const loadingFromGist = self.loadFromGist(queryParams.get())
var loadingFromGist = self.loadFromGist(queryParams.get())
if (!loadingFromGist) { if (!loadingFromGist) {
// insert ballot contract if there are no files to show // insert ballot contract if there are no files to show
self._components.filesProviders['browser'].resolveDirectory('browser', (error, filesList) => { 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' 'use strict'
var base64 = require('js-base64').Base64 var base64 = require('js-base64').Base64
var swarmgw = require('swarmgw')() var swarmgw = require('swarmgw')()
var resolver = require('@resolver-engine/imports').ImportsEngine()
var request = require('request') var request = require('request')
module.exports = class CompilerImports { module.exports = class CompilerImports {
@ -10,10 +11,20 @@ module.exports = class CompilerImports {
} }
handleGithubCall (root, path, cb) { 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( 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 json: true
}, },
(err, r, data) => { (err, r, data) => {
@ -112,13 +123,22 @@ module.exports = class CompilerImports {
}) })
} }
}) })
if (found) return
if (found) { resolver
return .resolve(url)
} else if (/^[^:]*:\/\//.exec(url)) { .then(result => {
cb('Unable to import "' + url + '": Unsupported URL schema') return resolver.require(url)
} else { })
cb('Unable to import "' + url + '": File not found') .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 globalRegistry = require('../../global/registry')
var remixLib = require('remix-lib') var remixLib = require('remix-lib')
var Web3Providers = remixLib.vm.Web3Providers
var DummyProvider = remixLib.vm.DummyProvider
var init = remixLib.init 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 { class DebuggerUI {
constructor (container) { constructor (container) {
this.registry = globalRegistry this.registry = globalRegistry
this.event = new EventManager() 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.isActive = false
this.sourceHighlighter = new SourceHighlighter() this.sourceHighlighter = new SourceHighlighter()
@ -173,20 +111,30 @@ class DebuggerUI {
if (compilers['__last']) lastCompilationResult = compilers['__last'] if (compilers['__last']) lastCompilationResult = compilers['__last']
// TODO debugging with source highlight is disabled. see line 98 // TODO debugging with source highlight is disabled. see line 98
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({ this.debugger = new Debugger({
web3: this.contextManager.getWeb3(), web3,
offsetToLineColumnConverter: this.registry.get('offsettolinecolumnconverter').api, offsetToLineColumnConverter: this.registry.get('offsettolinecolumnconverter').api,
compiler: { lastCompilationResult } compiler: { lastCompilationResult }
}) })
this.listenToEvents() this.listenToEvents()
this.debugger.debugger.updateWeb3(this.executionContext.web3())
this.debugger.debug(blockNumber, txNumber, tx, () => { this.debugger.debug(blockNumber, txNumber, tx, () => {
self.stepManager = new StepManagerUI(this.debugger.step_manager) self.stepManager = new StepManagerUI(this.debugger.step_manager)
self.vmDebugger = new VmDebugger(this.debugger.vmDebuggerLogic) self.vmDebugger = new VmDebugger(this.debugger.vmDebuggerLogic)
self.renderDebugger() self.renderDebugger()
}) })
})
} }
debug (txHash) { debug (txHash) {

@ -3,8 +3,6 @@ var EventManager = require('../../../lib/events')
var yo = require('yo-yo') var yo = require('yo-yo')
var csjs = require('csjs-inject') var csjs = require('csjs-inject')
var styleGuide = require('../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs` var css = csjs`
.buttons { .buttons {
@ -17,7 +15,6 @@ var css = csjs`
justify-content: center; justify-content: center;
} }
.stepButton { .stepButton {
${styles.rightPanel.debuggerTab.button_Debugger}
} }
.jumpButtons { .jumpButtons {
width: 100%; width: 100%;
@ -25,13 +22,10 @@ var css = csjs`
justify-content: center; justify-content: center;
} }
.jumpButton { .jumpButton {
${styles.rightPanel.debuggerTab.button_Debugger}
} }
.navigator { .navigator {
color: ${styles.rightPanel.debuggerTab.text_Primary};
} }
.navigator:hover { .navigator:hover {
color: ${styles.rightPanel.debuggerTab.button_Debugger_icon_HoverColor};
} }
` `
@ -51,20 +45,20 @@ function ButtonNavigator () {
ButtonNavigator.prototype.render = function () { ButtonNavigator.prototype.render = function () {
var self = this var self = this
var view = yo`<div class="${css.buttons}"> var view = yo`<div class="${css.buttons}">
<div class="${css.stepButtons}"> <div class="${css.stepButtons} btn-group p-1">
<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='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' title='Step back' class='${css.navigator} ${css.stepButton} fa fa-level-up' onclick=${function () { self.event.trigger('stepIntoBack') }} disabled=${this.intoBackDisabled} ></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' title='Step into' class='${css.navigator} ${css.stepButton} fa fa-level-down' onclick=${function () { self.event.trigger('stepIntoForward') }} disabled=${this.intoForwardDisabled} ></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' title='Step over forward' class='${css.navigator} ${css.stepButton} fa fa-share' onclick=${function () { self.event.trigger('stepOverForward') }} disabled=${this.overForwardDisabled} ></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>
<div class="${css.jumpButtons}"> <div class="${css.jumpButtons} btn-group p-1">
<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 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 id='jumpout' title='Jump out' class='${css.navigator} ${css.jumpButton} fa fa-eject' onclick=${function () { self.event.trigger('jumpOut') }} disabled=${this.jumpOutDisabled} ></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 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> <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>
<div id='reverted' style="display:none"> <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> </button>
<span>State changes made during this call will be reverted.</span> <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> <span id='outofgas' style="display:none">This call will run out of gas.</span>

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

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

@ -4,12 +4,9 @@ var yo = require('yo-yo')
var DropdownPanel = require('./DropdownPanel') var DropdownPanel = require('./DropdownPanel')
var EventManager = require('../../../../lib/events') var EventManager = require('../../../../lib/events')
var csjs = require('csjs-inject') var csjs = require('csjs-inject')
var styleGuide = require('../../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs` var css = csjs`
.instructions { .instructions {
${styles.rightPanel.debuggerTab.box_Debugger}
overflow-y: scroll; overflow-y: scroll;
max-height: 150px; max-height: 150px;
} }
@ -43,7 +40,8 @@ CodeListView.prototype.indexChanged = function (index) {
} }
} }
this.itemSelected = this.codeView.children[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') this.itemSelected.setAttribute('selected', 'selected')
if (this.itemSelected.firstChild) { if (this.itemSelected.firstChild) {
this.itemSelected.firstChild.setAttribute('style', 'margin-left: 2px') 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 TreeView = require('../../../ui/TreeView') // TODO setup a direct reference to the UI components
var csjs = require('csjs-inject') var csjs = require('csjs-inject')
var styleGuide = require('../../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs` var css = csjs`
.title { .title {
margin-top: 10px;
${styles.rightPanel.debuggerTab.dropdown_Debugger};
display: flex; display: flex;
align-items: center; align-items: center;
} }
@ -23,18 +19,16 @@ var css = csjs`
margin-left: 3px; margin-left: 3px;
} }
.icon { .icon {
color: ${styles.rightPanel.debuggerTab.button_Debugger_icon_Color};
margin-right: 5%; margin-right: 5%;
} }
.eyeButton { .eyeButton {
margin: 3px; margin: 3px;
} }
.eyeButton:hover { .eyeButton:hover {
color: ${styles.rightPanel.debuggerTab.button_Debugger_icon_HoverColor};
} }
.dropdownpanel { .dropdownpanel {
${styles.rightPanel.debuggerTab.dropdown_Debugger};
width: 100%; width: 100%;
word-break: break-all;
} }
.dropdownrawcontent { .dropdownrawcontent {
padding: 2px; padding: 2px;
@ -71,7 +65,7 @@ DropdownPanel.prototype.setMessage = function (message) {
if (!this.view) return if (!this.view) return
this.view.querySelector('.dropdownpanel .dropdownrawcontent').style.display = 'none' this.view.querySelector('.dropdownpanel .dropdownrawcontent').style.display = 'none'
this.view.querySelector('.dropdownpanel .dropdowncontent').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) this.message(message)
} }
@ -79,20 +73,18 @@ DropdownPanel.prototype.setLoading = function () {
if (!this.view) return if (!this.view) return
this.view.querySelector('.dropdownpanel .dropdownrawcontent').style.display = 'none' this.view.querySelector('.dropdownpanel .dropdownrawcontent').style.display = 'none'
this.view.querySelector('.dropdownpanel .dropdowncontent').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('') this.message('')
} }
DropdownPanel.prototype.setUpdating = function () { DropdownPanel.prototype.setUpdating = function () {
if (!this.view) return if (!this.view) return
this.view.querySelector('.dropdownpanel .dropdowncontent').style.color = styles.appProperties.greyedText_color
} }
DropdownPanel.prototype.update = function (_data, _header) { DropdownPanel.prototype.update = function (_data, _header) {
if (!this.view) return 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.display = 'block'
this.view.querySelector('.dropdownpanel .dropdowncontent').style.color = styles.appProperties.mainText_Color
this.view.querySelector('.dropdownpanel .dropdownrawcontent').innerText = JSON.stringify(_data, null, '\t') this.view.querySelector('.dropdownpanel .dropdownrawcontent').innerText = JSON.stringify(_data, null, '\t')
if (!this.displayContentOnly) { if (!this.displayContentOnly) {
this.view.querySelector('.title div.btn').style.display = 'block' this.view.querySelector('.title div.btn').style.display = 'block'
@ -106,11 +98,10 @@ DropdownPanel.prototype.update = function (_data, _header) {
DropdownPanel.prototype.setContent = function (node) { DropdownPanel.prototype.setContent = function (node) {
if (!this.view) return if (!this.view) return
var parent = this.view.querySelector('.dropdownpanel div.dropdowncontent') yo.update(this.view, this.render(null, node))
parent.replaceChild(node, parent.firstElementChild)
} }
DropdownPanel.prototype.render = function (overridestyle) { DropdownPanel.prototype.render = function (overridestyle, node) {
var content = yo`<div>Empty</div>` var content = yo`<div>Empty</div>`
if (this.json) { if (this.json) {
content = this.treeView.render({}) content = this.treeView.render({})
@ -118,19 +109,19 @@ DropdownPanel.prototype.render = function (overridestyle) {
overridestyle === undefined ? {} : overridestyle overridestyle === undefined ? {} : overridestyle
var self = this var self = this
var title = !self.displayContentOnly ? yo`<div class="${css.title} title"> 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 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>` </div>` : yo`<div></div>`
var contentNode = yo`<div class='dropdownpanel' style='display:none'> var contentNode = yo`<div class='dropdownpanel ${css.dropdownpanel}' style='display:none'>
<i class="${css.refresh} fa fa-refresh" aria-hidden="true"></i> <i class="${css.refresh} fas fa-sync" aria-hidden="true"></i>
<div class='dropdowncontent'>${content}</div> <div class='dropdowncontent'>${node || content}</div>
<div class='dropdownrawcontent' style='display:none'></div> <div class='dropdownrawcontent' style='display:none'></div>
<div class='message' style='display:none'></div> <div class='message' style='display:none'></div>
</div>` </div>`
var view = yo` var view = yo`
<div> <div class="border border-primary rounded p-1 m-1">
<style> <style>
@-moz-keyframes spin { @-moz-keyframes spin {
to { -moz-transform: rotate(359deg); } to { -moz-transform: rotate(359deg); }
@ -162,11 +153,11 @@ DropdownPanel.prototype.toggle = function () {
var caret = this.view.querySelector('.title').firstElementChild var caret = this.view.querySelector('.title').firstElementChild
if (el.style.display === '') { if (el.style.display === '') {
el.style.display = 'none' el.style.display = 'none'
caret.className = `${css.icon} fa fa-caret-right` caret.className = `${css.icon} fas fa-caret-right`
this.event.trigger('hide', []) this.event.trigger('hide', [])
} else { } else {
el.style.display = '' el.style.display = ''
caret.className = `${css.icon} fa fa-caret-down` caret.className = `${css.icon} fas fa-caret-down`
this.event.trigger('show', []) this.event.trigger('show', [])
} }
} }
@ -176,7 +167,7 @@ DropdownPanel.prototype.hide = function () {
var caret = this.view.querySelector('.title').firstElementChild var caret = this.view.querySelector('.title').firstElementChild
var el = this.view.querySelector('.dropdownpanel') var el = this.view.querySelector('.dropdownpanel')
el.style.display = 'none' el.style.display = 'none'
caret.className = `${css.icon} fa fa-caret-right` caret.className = `${css.icon} fas fa-caret-right`
this.event.trigger('hide', []) this.event.trigger('hide', [])
} }
@ -185,7 +176,7 @@ DropdownPanel.prototype.show = function () {
var caret = this.view.querySelector('.title').firstElementChild var caret = this.view.querySelector('.title').firstElementChild
var el = this.view.querySelector('.dropdownpanel') var el = this.view.querySelector('.dropdownpanel')
el.style.display = '' el.style.display = ''
caret.className = `${css.icon} fa fa-caret-down` caret.className = `${css.icon} fas fa-caret-down`
this.event.trigger('show', []) 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' 'use strict'
var yo = require('yo-yo') const yo = require('yo-yo')
var remixLib = require('remix-lib') const remixLib = require('remix-lib')
var SourceMappingDecoder = remixLib.SourceMappingDecoder const SourceMappingDecoder = remixLib.SourceMappingDecoder
var globalRegistry = require('../../global/registry') const globalRegistry = require('../../global/registry')
var css = require('./styles/contextView-styles') const css = require('./styles/contextView-styles')
/* /*
Display information about the current focused code: Display information about the current focused code:
@ -15,37 +15,38 @@ var css = require('./styles/contextView-styles')
*/ */
class ContextView { class ContextView {
constructor (opts, localRegistry) { constructor (opts, localRegistry) {
const self = this this._components = {}
self._components = {} this._components.registry = localRegistry || globalRegistry
self._components.registry = localRegistry || globalRegistry this.contextualListener = opts.contextualListener
self.contextualListener = opts.contextualListener this.editor = opts.editor
self.editor = opts.editor this._deps = {
self._deps = { compilersArtefacts: this._components.registry.get('compilersartefacts').api,
compilersArtefacts: self._components.registry.get('compilersartefacts').api, offsetToLineColumnConverter: this._components.registry.get('offsettolinecolumnconverter').api,
offsetToLineColumnConverter: self._components.registry.get('offsettolinecolumnconverter').api, config: this._components.registry.get('config').api,
config: self._components.registry.get('config').api, fileManager: this._components.registry.get('filemanager').api
fileManager: self._components.registry.get('filemanager').api
} }
this._view this._view
this._nodes this._nodes
this._current this._current
this.sourceMappingDecoder = new SourceMappingDecoder() this.sourceMappingDecoder = new SourceMappingDecoder()
this.previousElement = null this.previousElement = null
self.contextualListener.event.register('contextChanged', nodes => { this.contextualListener.event.register('contextChanged', nodes => {
this.show()
this._nodes = nodes this._nodes = nodes
this.update() this.update()
}) })
this.contextualListener.event.register('stopHighlighting', () => {
})
} }
render () { 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}> <div class=${css.container}>
${this._renderTarget()} ${this._renderTarget()}
</div> </div>
</div>` </div>`
if (!this._view) { if (!this._view) {
this._view = view this._view = view
this.hide()
} }
return view return view
} }
@ -65,18 +66,18 @@ class ContextView {
update () { update () {
if (this._view) { if (this._view) {
yo.update(this._view, this.render()) yo.update(this._view, this.render())
this._view.style.display = this._current ? 'block' : 'none'
} }
} }
_renderTarget () { _renderTarget () {
var previous = this._current let last
const previous = this._current
if (this._nodes && this._nodes.length) { if (this._nodes && this._nodes.length) {
var last = this._nodes[this._nodes.length - 1] last = this._nodes[this._nodes.length - 1]
if (isDefinition(last)) { if (isDefinition(last)) {
this._current = last this._current = last
} else { } else {
var target = this.contextualListener.declarationOf(last) const target = this.contextualListener.declarationOf(last)
if (target) { if (target) {
this._current = target this._current = target
} else { } else {
@ -91,27 +92,26 @@ class ContextView {
} }
_jumpToInternal (position) { _jumpToInternal (position) {
var self = this const jumpToLine = (lineColumn) => {
function jumpToLine (lineColumn) {
if (lineColumn.start && lineColumn.start.line && lineColumn.start.column) { 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'] let lastCompilationResult = this._deps.compilersArtefacts['__last']
if (lastCompilationResult && lastCompilationResult.data) { if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) {
var lineColumn = self._deps.offsetToLineColumnConverter.offsetToLineColumn( const lineColumn = this._deps.offsetToLineColumnConverter.offsetToLineColumn(
position, position,
position.file, position.file,
lastCompilationResult.getSourceCode().sources, lastCompilationResult.getSourceCode().sources,
lastCompilationResult.getAsts()) lastCompilationResult.getAsts())
var filename = lastCompilationResult.getSourceName(position.file) const filename = lastCompilationResult.getSourceName(position.file)
// TODO: refactor with rendererAPI.errorClick // TODO: refactor with rendererAPI.errorClick
if (filename !== self._deps.config.get('currentFile')) { if (filename !== this._deps.config.get('currentFile')) {
var provider = self._deps.fileManager.fileProviderOf(filename) const provider = this._deps.fileManager.fileProviderOf(filename)
if (provider) { if (provider) {
provider.exists(filename, (error, exist) => { provider.exists(filename, (error, exist) => {
if (error) return console.log(error) if (error) return console.log(error)
self._deps.fileManager.switchFile(filename) this._deps.fileManager.switchFile(filename)
jumpToLine(lineColumn) jumpToLine(lineColumn)
}) })
} }
@ -123,14 +123,13 @@ class ContextView {
_render (node, nodeAtCursorPosition) { _render (node, nodeAtCursorPosition) {
if (!node) return yo`<div></div>` if (!node) return yo`<div></div>`
var self = this let references = this.contextualListener.referencesOf(node)
var references = self.contextualListener.referencesOf(node) const type = (node.attributes && node.attributes.type) ? node.attributes.type : node.name
var type = (node.attributes && node.attributes.type) ? node.attributes.type : node.name
references = `${references ? references.length : '0'} reference(s)` references = `${references ? references.length : '0'} reference(s)`
var ref = 0 let ref = 0
var nodes = self.contextualListener.getActiveHighlights() const nodes = this.contextualListener.getActiveHighlights()
for (var k in nodes) { for (const k in nodes) {
if (nodeAtCursorPosition.id === nodes[k].nodeId) { if (nodeAtCursorPosition.id === nodes[k].nodeId) {
ref = k ref = k
break break
@ -138,44 +137,43 @@ class ContextView {
} }
// JUMP BETWEEN REFERENCES // JUMP BETWEEN REFERENCES
function jump (e) { const jump = (e) => {
e.target.dataset.action === 'next' ? ref++ : ref-- e.target.dataset.action === 'next' ? ref++ : ref--
if (ref < 0) ref = nodes.length - 1 if (ref < 0) ref = nodes.length - 1
if (ref >= nodes.length) ref = 0 if (ref >= nodes.length) ref = 0
self._jumpToInternal(nodes[ref].position) this._jumpToInternal(nodes[ref].position)
} }
function jumpTo () { const jumpTo = () => {
if (node && node.src) { if (node && node.src) {
var position = self.sourceMappingDecoder.decode(node.src) const position = this.sourceMappingDecoder.decode(node.src)
if (position) { if (position) {
self._jumpToInternal(position) this._jumpToInternal(position)
} }
} }
} }
return yo`<div class=${css.line}> const showGasEstimation = () => {
<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 () {
if (node.name === 'FunctionDefinition') { if (node.name === 'FunctionDefinition') {
var result = self.contextualListener.gasEstimation(node) const result = this.contextualListener.gasEstimation(node)
var executionCost = 'Execution cost: ' + result.executionCost + ' gas' const executionCost = 'Execution cost: ' + result.executionCost + ' gas'
var codeDepositCost = 'Code deposit cost: ' + result.codeDepositCost + ' gas' const codeDepositCost = 'Code deposit cost: ' + result.codeDepositCost + ' gas'
var estimatedGas = result.codeDepositCost ? `${codeDepositCost}, ${executionCost}` : `${executionCost}` const estimatedGas = result.codeDepositCost ? `${codeDepositCost}, ${executionCost}` : `${executionCost}`
return yo`<div class=${css.gasEstimation}> return yo`<div class=${css.gasEstimation}>
<img class=${css.gasStationIcon} title='Gas estimation' src='assets/img/gasStation_50.png'> <img class=${css.gasStationIcon} title='Gas estimation' src='assets/img/gasStation_50.png'>
${estimatedGas} ${estimatedGas}
</div>` </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' 'use strict'
var remixLib = require('remix-lib') const remixLib = require('remix-lib')
var SourceMappingDecoder = remixLib.SourceMappingDecoder const SourceMappingDecoder = remixLib.SourceMappingDecoder
var AstWalker = remixLib.AstWalker const AstWalker = remixLib.AstWalker
var EventManager = require('../../lib/events') const EventManager = require('../../lib/events')
var globalRegistry = require('../../global/registry') const globalRegistry = require('../../global/registry')
/* /*
trigger contextChanged(nodes) trigger contextChanged(nodes)
*/ */
class ContextualListener { class ContextualListener {
constructor (opts, localRegistry) { constructor (opts, localRegistry) {
var self = this
this.event = new EventManager() this.event = new EventManager()
self._components = {} this._components = {}
self._components.registry = localRegistry || globalRegistry this._components.registry = localRegistry || globalRegistry
self.editor = opts.editor this.editor = opts.editor
self.pluginManager = opts.pluginManager this.pluginManager = opts.pluginManager
self._deps = { this._deps = {
compilersArtefacts: self._components.registry.get('compilersartefacts').api, compilersArtefacts: this._components.registry.get('compilersartefacts').api,
config: self._components.registry.get('config').api, config: this._components.registry.get('config').api,
offsetToLineColumnConverter: self._components.registry.get('offsettolinecolumnconverter').api offsetToLineColumnConverter: this._components.registry.get('offsettolinecolumnconverter').api
} }
this._index = { this._index = {
Declarations: {}, Declarations: {},
@ -27,7 +26,8 @@ class ContextualListener {
} }
this._activeHighlights = [] 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._stopHighlighting()
this._index = { this._index = {
Declarations: {}, Declarations: {},
@ -36,13 +36,13 @@ class ContextualListener {
this._buildIndex(data, source) this._buildIndex(data, source)
}) })
self.editor.event.register('contentChanged', () => { this._stopHighlighting() }) this.editor.event.register('contentChanged', () => { this._stopHighlighting() })
this.sourceMappingDecoder = new SourceMappingDecoder() this.sourceMappingDecoder = new SourceMappingDecoder()
this.astWalker = new AstWalker() this.astWalker = new AstWalker()
setInterval(() => { setInterval(() => {
if (self._deps.compilersArtefacts['__last']) { if (this._deps.compilersArtefacts['__last'] && this._deps.compilersArtefacts['__last'].languageversion.indexOf('soljson') === 0) {
this._highlightItems(self.editor.getCursorPosition(), self._deps.compilersArtefacts['__last'], self._deps.config.get('currentFile')) this._highlightItems(this.editor.getCursorPosition(), this._deps.compilersArtefacts['__last'], this._deps.config.get('currentFile'))
} }
}, 1000) }, 1000)
} }
@ -73,7 +73,7 @@ class ContextualListener {
this.currentPosition = cursorPosition this.currentPosition = cursorPosition
this.currentFile = file this.currentFile = file
if (compilationResult && compilationResult.data && compilationResult.data.sources[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 this.nodes = nodes
if (nodes && nodes.length && nodes[nodes.length - 1]) { if (nodes && nodes.length && nodes[nodes.length - 1]) {
this._highlightExpressions(nodes[nodes.length - 1], compilationResult) this._highlightExpressions(nodes[nodes.length - 1], compilationResult)
@ -84,19 +84,18 @@ class ContextualListener {
_buildIndex (compilationResult, source) { _buildIndex (compilationResult, source) {
if (compilationResult && compilationResult.sources) { if (compilationResult && compilationResult.sources) {
var self = this const callback = {}
var callback = {} callback['*'] = (node) => {
callback['*'] = function (node) {
if (node && node.attributes && node.attributes.referencedDeclaration) { if (node && node.attributes && node.attributes.referencedDeclaration) {
if (!self._index['Declarations'][node.attributes.referencedDeclaration]) { if (!this._index['Declarations'][node.attributes.referencedDeclaration]) {
self._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 return true
} }
for (var s in compilationResult.sources) { for (const s in compilationResult.sources) {
this.astWalker.walk(compilationResult.sources[s].legacyAST, callback) this.astWalker.walk(compilationResult.sources[s].legacyAST, callback)
} }
} }
@ -104,21 +103,19 @@ class ContextualListener {
_highlight (node, compilationResult) { _highlight (node, compilationResult) {
if (!node) return if (!node) return
var self = this const position = this.sourceMappingDecoder.decode(node.src)
var position = this.sourceMappingDecoder.decode(node.src) const eventId = this._highlightInternal(position, node)
var eventId = this._highlightInternal(position, node) let lastCompilationResult = this._deps.compilersArtefacts['__last']
let lastCompilationResult = self._deps.compilersArtefacts['__last'] if (eventId && lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0) {
if (eventId && lastCompilationResult) {
this._activeHighlights.push({ eventId, position, fileTarget: lastCompilationResult.getSourceName(position.file), nodeId: node.id }) this._activeHighlights.push({ eventId, position, fileTarget: lastCompilationResult.getSourceName(position.file), nodeId: node.id })
} }
} }
_highlightInternal (position, node) { _highlightInternal (position, node) {
var self = this let lastCompilationResult = this._deps.compilersArtefacts['__last']
let lastCompilationResult = self._deps.compilersArtefacts['__last'] if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0) {
if (lastCompilationResult) { let lineColumn = this._deps.offsetToLineColumnConverter.offsetToLineColumn(position, position.file, lastCompilationResult.getSourceCode().sources, lastCompilationResult.getAsts())
var lineColumn = self._deps.offsetToLineColumnConverter.offsetToLineColumn(position, position.file, lastCompilationResult.getSourceCode().sources, lastCompilationResult.getAsts()) let css = 'highlightreference'
var css = 'highlightreference'
if (node.children && node.children.length) { 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. // If node has children, highlight the entire line. if not, just highlight the current source position of the node.
css = 'highlightreference' css = 'highlightreference'
@ -133,28 +130,27 @@ class ContextualListener {
} }
} }
} }
var fileName = lastCompilationResult.getSourceName(position.file) const fileName = lastCompilationResult.getSourceName(position.file)
if (fileName) { if (fileName) {
return self.editor.addMarker(lineColumn, fileName, css) return this.editor.addMarker(lineColumn, fileName, css)
} }
} }
return null return null
} }
_highlightExpressions (node, compilationResult) { _highlightExpressions (node, compilationResult) {
var self = this const highlights = (id) => {
function highlights (id) { if (this._index['Declarations'] && this._index['Declarations'][id]) {
if (self._index['Declarations'] && self._index['Declarations'][id]) { const refs = this._index['Declarations'][id]
var refs = self._index['Declarations'][id] for (const ref in refs) {
for (var ref in refs) { const node = refs[ref]
var node = refs[ref] this._highlight(node, compilationResult)
self._highlight(node, compilationResult)
} }
} }
} }
if (node.attributes && node.attributes.referencedDeclaration) { if (node.attributes && node.attributes.referencedDeclaration) {
highlights(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) this._highlight(current, compilationResult)
} else { } else {
highlights(node.id) highlights(node.id)
@ -164,23 +160,22 @@ class ContextualListener {
} }
_stopHighlighting () { _stopHighlighting () {
var self = this for (const eventKey in this._activeHighlights) {
for (var eventKey in this._activeHighlights) { const event = this._activeHighlights[eventKey]
var event = this._activeHighlights[eventKey] this.editor.removeMarker(event.eventId, event.fileTarget)
self.editor.removeMarker(event.eventId, event.fileTarget)
} }
this.event.trigger('stopHighlighting', [])
this._activeHighlights = [] this._activeHighlights = []
} }
gasEstimation (node) { gasEstimation (node) {
this._loadContractInfos(node) this._loadContractInfos(node)
var executionCost let executionCost, codeDepositCost
var codeDepositCost
if (node.name === 'FunctionDefinition') { if (node.name === 'FunctionDefinition') {
var visibility = node.attributes.visibility const visibility = node.attributes.visibility
if (!node.attributes.isConstructor) { if (!node.attributes.isConstructor) {
var fnName = node.attributes.name const fnName = node.attributes.name
var fn = fnName + this._getInputParams(node) const fn = fnName + this._getInputParams(node)
if (visibility === 'public' || visibility === 'external') { if (visibility === 'public' || visibility === 'external') {
executionCost = this.estimationObj.external[fn] executionCost = this.estimationObj.external[fn]
} else if (visibility === 'private' || visibility === 'internal') { } else if (visibility === 'private' || visibility === 'internal') {
@ -197,9 +192,9 @@ class ContextualListener {
} }
_loadContractInfos (node) { _loadContractInfos (node) {
for (var i in this.nodes) { for (const i in this.nodes) {
if (this.nodes[i].id === node.attributes.scope) { 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.contract = this.results.data.contracts[this.results.source.target][contract.attributes.name]
this.estimationObj = this.contract.evm.gasEstimates this.estimationObj = this.contract.evm.gasEstimates
this.creationCost = this.estimationObj.creation.totalCost this.creationCost = this.estimationObj.creation.totalCost
@ -209,16 +204,17 @@ class ContextualListener {
} }
_getInputParams (node) { _getInputParams (node) {
var params = [] const params = []
for (var i in node.children) { let target
for (const i in node.children) {
if (node.children[i].name === 'ParameterList') { if (node.children[i].name === 'ParameterList') {
var target = node.children[i] target = node.children[i]
break break
} }
} }
if (target) { if (target) {
var children = target.children const children = target.children
for (var j in children) { for (const j in children) {
if (children[j].name === 'VariableDeclaration') { if (children[j].name === 'VariableDeclaration') {
params.push(children[j].attributes.type) params.push(children[j].attributes.type)
} }

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

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

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

@ -1,11 +1,9 @@
var yo = require('yo-yo') var yo = require('yo-yo')
var csjs = require('csjs-inject') var csjs = require('csjs-inject')
var styleGuide = require('../ui/styles-guide/theme-chooser') const copyToClipboard = require('../ui/copy-to-clipboard')
var styles = styleGuide.chooser()
var css = csjs` var css = csjs`
.txInfoBox { .txInfoBox {
${styles.rightPanel.compileTab.box_CompileContainer}; // add askToConfirmTXContainer to Remix and then replace this styling
} }
.wrapword { .wrapword {
white-space: pre-wrap; /* Since CSS 2.1 */ 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>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>Max transaction fee:<span id='txfee'></span></div>
<div>Data:</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>
<div class=${css.checkbox}> <div class=${css.checkbox}>
<input id='confirmsetting' type="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>
</div> </div>
` `

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

@ -2,21 +2,30 @@
var EventManager = require('../../lib/events') var EventManager = require('../../lib/events')
function FilesTree (name, storage) { import { BaseApi } from 'remix-plugin'
var self = this
var event = new EventManager() class FilesTree extends BaseApi {
this.event = event 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.type = name
this.structFile = '.' + name + '.tree' this.structFile = '.' + name + '.tree'
this.tree = {} this.tree = {}
}
this.exists = function (path, cb) { exists (path, cb) {
cb(null, this._exists(path)) cb(null, this._exists(path))
} }
function updateRefs (path, type) { updateRefs (path, type) {
var split = path.split('/') // this should be unprefixed path var split = path.split('/') // this should be unprefixed path
var crawlpath = self.tree var crawlpath = this.tree
var intermediatePath = '' var intermediatePath = ''
split.forEach((pathPart, index) => { split.forEach((pathPart, index) => {
intermediatePath += pathPart intermediatePath += pathPart
@ -30,91 +39,94 @@ function FilesTree (name, storage) {
delete crawlpath[intermediatePath] 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) var unprefixedpath = this.removePrefix(path)
return storage.exists(unprefixedpath) return this.storage.exists(unprefixedpath)
} }
this.init = function (cb) { init (cb) {
var tree = storage.get(this.structFile) var tree = this.storage.get(this.structFile)
this.tree = tree ? JSON.parse(tree) : {} this.tree = tree ? JSON.parse(tree) : {}
if (cb) cb() if (cb) cb()
} }
this.get = function (path, cb) { get (path, cb) {
var unprefixedpath = this.removePrefix(path) var unprefixedpath = this.removePrefix(path)
var content = storage.get(unprefixedpath) var content = this.storage.get(unprefixedpath)
if (cb) { if (cb) {
cb(null, content) cb(null, content)
} }
return content return content
} }
this.set = function (path, content, cb) { set (path, content, cb) {
var unprefixedpath = this.removePrefix(path) var unprefixedpath = this.removePrefix(path)
updateRefs(unprefixedpath, 'add') this.updateRefs(unprefixedpath, 'add')
var exists = storage.exists(unprefixedpath) var exists = this.storage.exists(unprefixedpath)
if (!storage.set(unprefixedpath, content)) { if (!this.storage.set(unprefixedpath, content)) {
if (cb) cb('error updating ' + path) if (cb) cb('error updating ' + path)
return false return false
} }
if (!exists) { if (!exists) {
event.trigger('fileAdded', [this.type + '/' + unprefixedpath, false]) this.event.trigger('fileAdded', [this.type + '/' + unprefixedpath, false])
} else { } else {
event.trigger('fileChanged', [this.type + '/' + unprefixedpath]) this.event.trigger('fileChanged', [this.type + '/' + unprefixedpath])
} }
if (cb) cb() if (cb) cb()
return true return true
} }
this.addReadOnly = function (path, content) { addReadOnly (path, content) {
return this.set(path, content) return this.set(path, content)
} }
this.isReadOnly = function (path) { isReadOnly (path) {
return false return false
} }
this.remove = function (path) { remove (path) {
var unprefixedpath = this.removePrefix(path) var unprefixedpath = this.removePrefix(path)
updateRefs(unprefixedpath, 'remove') this.updateRefs(unprefixedpath, 'remove')
if (!this._exists(unprefixedpath)) { if (!this._exists(unprefixedpath)) {
return false return false
} }
if (!storage.remove(unprefixedpath)) { if (!this.storage.remove(unprefixedpath)) {
return false return false
} }
event.trigger('fileRemoved', [this.type + '/' + unprefixedpath]) this.event.trigger('fileRemoved', [this.type + '/' + unprefixedpath])
return true return true
} }
this.rename = function (oldPath, newPath, isFolder) { rename (oldPath, newPath, isFolder) {
var unprefixedoldPath = this.removePrefix(oldPath) var unprefixedoldPath = this.removePrefix(oldPath)
var unprefixednewPath = this.removePrefix(newPath) var unprefixednewPath = this.removePrefix(newPath)
updateRefs(unprefixedoldPath, 'remove') this.updateRefs(unprefixedoldPath, 'remove')
updateRefs(unprefixednewPath, 'add') this.updateRefs(unprefixednewPath, 'add')
if (storage.exists(unprefixedoldPath)) { if (this.storage.exists(unprefixedoldPath)) {
if (!storage.rename(unprefixedoldPath, unprefixednewPath)) { if (!this.storage.rename(unprefixedoldPath, unprefixednewPath)) {
return false 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 true
} }
return false return false
} }
this.resolveDirectory = function (path, callback) { resolveDirectory (path, callback) {
var self = this
if (path[0] === '/') path = path.substring(1) if (path[0] === '/') path = path.substring(1)
if (!path) return callback(null, { [self.type]: { } }) if (!path) return callback(null, { [this.type]: {} })
var tree = {} var tree = {}
path = self.removePrefix(path) path = this.removePrefix(path)
var split = path.split('/') // this should be unprefixed path var split = path.split('/') // this should be unprefixed path
var crawlpath = self.tree var crawlpath = this.tree
split.forEach((pathPart, index) => { split.forEach((pathPart, index) => {
if (crawlpath[pathPart]) crawlpath = crawlpath[pathPart] if (crawlpath[pathPart]) crawlpath = crawlpath[pathPart]
}) })
@ -125,7 +137,7 @@ function FilesTree (name, storage) {
callback(null, tree) callback(null, tree)
} }
this.removePrefix = function (path) { removePrefix (path) {
path = path.indexOf(this.type) === 0 ? path.replace(this.type, '') : path path = path.indexOf(this.type) === 0 ? path.replace(this.type, '') : path
if (path[0] === '/') return path.substring(1) if (path[0] === '/') return path.substring(1)
return path 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 yo = require('yo-yo')
var Treeview = require('../ui/TreeView') var Treeview = require('../ui/TreeView')
var modalDialog = require('../ui/modaldialog') var modalDialog = require('../ui/modaldialog')
var modalDialogCustom = require('../ui/modal-dialog-custom')
var EventManager = require('../../lib/events') var EventManager = require('../../lib/events')
var contextMenu = require('../ui/contextMenu') var contextMenu = require('../ui/contextMenu')
var addTooltip = require('../ui/tooltip')
var helper = require('../../lib/helper')
var css = require('./styles/file-explorer-styles') var css = require('./styles/file-explorer-styles')
var globalRegistry = require('../../global/registry') var globalRegistry = require('../../global/registry')
var queryParams = new QueryParams()
let MENU_HANDLE let MENU_HANDLE
function fileExplorer (localRegistry, files) { function fileExplorer (localRegistry, files, menuItems) {
var self = this var self = this
this.events = new EventManager() this.events = new EventManager()
// file provider backend // file provider backend
@ -22,6 +24,35 @@ function fileExplorer (localRegistry, files) {
this.focusElement = null this.focusElement = null
// path currently focused on // path currently focused on
this.focusPath = null 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 = {}
self._components.registry = localRegistry || globalRegistry self._components.registry = localRegistry || globalRegistry
@ -31,6 +62,8 @@ function fileExplorer (localRegistry, files) {
fileManager: self._components.registry.get('filemanager').api 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 // warn if file changed outside of Remix
function remixdDialog () { function remixdDialog () {
return yo`<div>This file has been changed outside of Remix IDE.</div>` 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) self.focusElement = self.treeView.labelAt(self.focusPath)
// TODO: here we update the selected file (it applicable) // 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. // 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)) { if (self.focusElement && !self.focusElement.classList.contains('bg-secondary')) {
self.focusElement.classList.add(css.hasFocus) self.focusElement.classList.add('bg-secondary')
} }
}) })
} }
@ -122,12 +155,19 @@ function fileExplorer (localRegistry, files) {
}, },
formatSelf: function formatSelf (key, data, li) { formatSelf: function formatSelf (key, data, li) {
var isRoot = data.path === self.files.type var isRoot = data.path === self.files.type
return yo`<label class="${data.children ? css.folder : css.file}" return yo`
<div class="${css.items}">
<label
class=${css.label}
data-path="${data.path}" data-path="${data.path}"
style="${isRoot ? 'font-weight:bold;' : ''}" style="${isRoot ? 'font-weight:bold;' : ''}"
onkeydown=${editModeOff} onkeydown=${editModeOff}
onblur=${editModeOff} onblur=${editModeOff}
>${key.split('/').pop()}</label>` >${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 && MENU_HANDLE.hide(null, true)
MENU_HANDLE = contextMenu(event, { MENU_HANDLE = contextMenu(event, {
'Rename': () => { '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 + '"]') var name = label.querySelector('label[data-path="' + key + '"]')
if (name) editModeOn(name) if (name) editModeOn(name)
}, },
'Delete': () => { 'Delete': () => {
if (self.files.readonly) { return addTooltip('cannot delete folder. ' + self.files.type + ' is a read only explorer') } if (self.files.readonly) { return tooltip('cannot delete folder. ' + self.files.type + ' is a read only explorer') }
modalDialogCustom.confirm(null, 'Do you want to delete this folder?', () => { files.remove(key) }, () => {}) 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 && MENU_HANDLE.hide(null, true)
MENU_HANDLE = contextMenu(event, { MENU_HANDLE = contextMenu(event, {
'Rename': () => { '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 + '"]') var name = label.querySelector('label[data-path="' + key + '"]')
if (name) editModeOn(name) if (name) editModeOn(name)
}, },
'Delete': () => { 'Delete': () => {
if (self.files.readonly) { return addTooltip('cannot delete file. ' + self.files.type + ' is a read only explorer') } if (self.files.readonly) { return tooltip('cannot delete file. ' + self.files.type + ' is a read only explorer') }
modalDialogCustom.confirm(null, 'Do you want to delete this file?', () => { files.remove(key) }, () => {}) 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) { 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) { self.treeView.event.register('nodeClick', function (path, childrenContainer) {
if (!childrenContainer) return if (!childrenContainer) return
if (childrenContainer.style.display === 'none') return if (childrenContainer.style.display === 'none') return
self.updatePath(path)
files.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error)
if (!fileTree) return
var newTree = normalize(path, fileTree)
self.treeView.updateNodeFromJSON(path, newTree, true)
})
}) })
function normalize (path, filesList) { // register to main app, trigger when the current file in the editor changed
var prefix = path.split('/')[0] self._deps.fileManager.events.on('currentFileChanged', (newFile) => {
var newList = {} const explorer = self._deps.fileManager.fileProviderOf(newFile)
Object.keys(filesList).forEach(key => { if (self.focusElement && self.focusPath !== newFile) {
newList[prefix + '/' + key] = filesList[key].isDirectory ? {} : { '/content': true } self.focusElement.classList.remove('bg-secondary')
}) self.focusElement = null
return newList 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
}
}
})
// register to main app, trigger when the current file in the editor changed self._deps.fileManager.events.on('noFileSelected', () => {
self._deps.fileManager.event.register('currentFileChanged', (newFile, explorer) => { if (self.focusElement) {
if (self.focusElement && (!explorer || explorer.type !== files.type) && self.focusPath !== newFile) { self.focusElement.classList.remove('bg-secondary')
self.focusElement.classList.remove(css.hasFocus)
self.focusElement = null self.focusElement = null
self.focusPath = null self.focusPath = null
} }
@ -220,7 +251,7 @@ function fileExplorer (localRegistry, files) {
function editModeOn (label) { function editModeOn (label) {
textUnderEdit = label.innerText textUnderEdit = label.innerText
label.setAttribute('contenteditable', true) label.setAttribute('contenteditable', true)
label.classList.add(css.rename) label.classList.add('bg-light')
label.focus() label.focus()
selectElementContents(label) selectElementContents(label)
} }
@ -256,14 +287,23 @@ function fileExplorer (localRegistry, files) {
var isFolder = label.className.indexOf('folder') !== -1 var isFolder = label.className.indexOf('folder') !== -1
var save = textUnderEdit !== label.innerText var save = textUnderEdit !== label.innerText
if (save) { 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.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 () { fileExplorer.prototype.hide = function () {
if (this.container) this.container.style.display = 'none' if (this.container) this.container.style.display = 'none'
} }
@ -277,6 +317,216 @@ fileExplorer.prototype.init = function () {
return this.container 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) { fileExplorer.prototype.ensureRoot = function (cb) {
cb = cb || (() => {}) cb = cb || (() => {})
var self = this var self = this
@ -285,13 +535,23 @@ fileExplorer.prototype.ensureRoot = function (cb) {
self.files.resolveDirectory('/', (error, files) => { self.files.resolveDirectory('/', (error, files) => {
if (error) console.error(error) if (error) console.error(error)
var element = self.treeView.render(files, false) var element = self.treeView.render(files, false)
element.className = css.fileexplorer element.classList.add(css.fileexplorer)
element.events = self.events element.events = self.events
element.api = self.api element.api = self.api
self.container.appendChild(element) self.container.appendChild(element)
self.element = element self.element = element
if (cb) cb() 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 module.exports = fileExplorer

@ -1,67 +1,76 @@
'use strict' 'use strict'
var $ = require('jquery') import yo from 'yo-yo'
var yo = require('yo-yo') const EventEmitter = require('events')
var EventManager = require('../../lib/events')
var globalRegistry = require('../../global/registry') var globalRegistry = require('../../global/registry')
var CompilerImport = require('../compiler/compiler-imports') var CompilerImport = require('../compiler/compiler-imports')
var toaster = require('../ui/tooltip')
import { FileSystemApi } from 'remix-plugin'
/* /*
attach to files event (removed renamed) attach to files event (removed renamed)
trigger: currentFileChanged 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) { constructor (localRegistry) {
this.tabbedFiles = {} super(profile)
this.event = new EventManager() this.openedFiles = {} // list all opened files
this.events = new EventEmitter()
this._components = {} this._components = {}
this._components.compilerImport = new CompilerImport() this._components.compilerImport = new CompilerImport()
this._components.registry = localRegistry || globalRegistry this._components.registry = localRegistry || globalRegistry
} }
init () { init () {
var self = this this._deps = {
self._deps = { editor: this._components.registry.get('editor').api,
editor: self._components.registry.get('editor').api, config: this._components.registry.get('config').api,
config: self._components.registry.get('config').api, browserExplorer: this._components.registry.get('fileproviders/browser').api,
browserExplorer: self._components.registry.get('fileproviders/browser').api, localhostExplorer: this._components.registry.get('fileproviders/localhost').api,
localhostExplorer: self._components.registry.get('fileproviders/localhost').api, gistExplorer: this._components.registry.get('fileproviders/gist').api,
configExplorer: self._components.registry.get('fileproviders/config').api, filesProviders: this._components.registry.get('fileproviders').api
gistExplorer: self._components.registry.get('fileproviders/gist').api, }
filesProviders: self._components.registry.get('fileproviders').api
} 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) })
self._deps.browserExplorer.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) })
self._deps.localhostExplorer.event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) }) this._deps.browserExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) })
self._deps.configExplorer.event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) }) this._deps.localhostExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) })
self._deps.gistExplorer.event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) }) this._deps.gistExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) })
self._deps.browserExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) }) this._deps.localhostExplorer.event.register('errored', (event) => { this.removeTabsOf(this._deps.localhostExplorer) })
self._deps.localhostExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) }) this._deps.localhostExplorer.event.register('closed', (event) => { this.removeTabsOf(this._deps.localhostExplorer) })
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) })
} }
fileRenamedEvent (oldName, newName, isFolder) { fileRenamedEvent (oldName, newName, isFolder) {
var self = this
if (!isFolder) { if (!isFolder) {
self._deps.config.set('currentFile', '') this._deps.config.set('currentFile', '')
self._deps.editor.discard(oldName) this._deps.editor.discard(oldName)
if (this.tabbedFiles[oldName]) { if (this.openedFiles[oldName]) {
delete this.tabbedFiles[oldName] delete this.openedFiles[oldName]
this.tabbedFiles[newName] = newName this.openedFiles[newName] = newName
} }
this.switchFile(newName) this.switchFile(newName)
} else { } else {
var newFocus var newFocus
for (var k in this.tabbedFiles) { for (var k in this.openedFiles) {
if (k.indexOf(oldName + '/') === 0) { if (k.indexOf(oldName + '/') === 0) {
var newAbsolutePath = k.replace(oldName, newName) var newAbsolutePath = k.replace(oldName, newName)
this.tabbedFiles[newAbsolutePath] = newAbsolutePath this.openedFiles[newAbsolutePath] = newAbsolutePath
delete this.tabbedFiles[k] delete this.openedFiles[k]
if (self._deps.config.get('currentFile') === k) { if (this._deps.config.get('currentFile') === k) {
newFocus = newAbsolutePath newFocus = newAbsolutePath
} }
} }
@ -70,7 +79,7 @@ class FileManager {
this.switchFile(newFocus) this.switchFile(newFocus)
} }
} }
this.refreshTabs() this.events.emit('fileRenamed', oldName, newName)
} }
currentFileProvider () { currentFileProvider () {
@ -82,20 +91,87 @@ class FileManager {
} }
currentFile () { currentFile () {
var self = this return this._deps.config.get('currentFile')
return self._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 () { currentPath () {
var self = this var currentFile = this._deps.config.get('currentFile')
var currentFile = self._deps.config.get('currentFile')
var reg = /(.*)(\/).*/ var reg = /(.*)(\/).*/
var path = reg.exec(currentFile) var path = reg.exec(currentFile)
return path ? path[1] : null 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) { removeTabsOf (provider) {
for (var tab in this.tabbedFiles) { for (var tab in this.openedFiles) {
if (this.fileProviderOf(tab).type === provider.type) { if (this.fileProviderOf(tab).type === provider.type) {
this.fileRemovedEvent(tab) this.fileRemovedEvent(tab)
} }
@ -103,78 +179,60 @@ class FileManager {
} }
fileRemovedEvent (path) { fileRemovedEvent (path) {
var self = this if (!this.openedFiles[path]) return
if (!this.tabbedFiles[path]) return if (path === this._deps.config.get('currentFile')) {
if (path === self._deps.config.get('currentFile')) { this._deps.config.set('currentFile', '')
self._deps.config.set('currentFile', '') }
} this._deps.editor.discard(path)
self._deps.editor.discard(path) delete this.openedFiles[path]
delete this.tabbedFiles[path] this.events.emit('fileRemoved', path)
this.refreshTabs()
this.switchFile() this.switchFile()
} }
// Display files that have already been selected switchFile (file) {
refreshTabs (newfile) { const _switchFile = (file) => {
if (newfile) { this.saveCurrentFile()
this.tabbedFiles[newfile] = newfile 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)
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
if (file) return _switchFile(file) if (file) return _switchFile(file)
else { else {
var browserProvider = self._deps.filesProviders['browser'] var browserProvider = this._deps.filesProviders['browser']
browserProvider.resolveDirectory('browser', (error, filesTree) => { browserProvider.resolveDirectory('browser', (error, filesTree) => {
if (error) console.error(error) if (error) console.error(error)
var fileList = Object.keys(filesTree) var fileList = Object.keys(filesTree)
if (fileList.length) { if (fileList.length) {
_switchFile(browserProvider.type + '/' + fileList[0]) _switchFile(browserProvider.type + '/' + fileList[0])
} else { } else {
self.event.trigger('currentFileChanged', []) this._deps.editor.displayEmptyReadOnlySession()
self._deps.editor.displayEmptyReadOnlySession() this.events.emit('noFileSelected')
}
})
}
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)])
} }
}) })
} }
} }
filesFromPath (path, cb) { getFolder (path) {
var provider = this.fileProviderOf(path) // TODO : Change provider with promise
if (provider) { return new Promise((resolve, reject) => {
return provider.resolveDirectory(path, (error, filesTree) => { cb(error, filesTree) }) const provider = this.fileProviderOf(path)
} if (!provider) return reject(`provider for path ${path} not found`)
cb(`provider for path ${path} not found`) provider.resolveDirectory(path, (error, filesTree) => {
if (error) reject(error)
resolve(filesTree)
})
})
} }
fileProviderOf (file) { fileProviderOf (file) {
@ -208,7 +266,6 @@ class FileManager {
} }
syncEditor (path) { syncEditor (path) {
var self = this
var currentFile = this._deps.config.get('currentFile') var currentFile = this._deps.config.get('currentFile')
if (path !== currentFile) return if (path !== currentFile) return
@ -216,7 +273,7 @@ class FileManager {
if (provider) { if (provider) {
provider.get(currentFile, (error, content) => { provider.get(currentFile, (error, content) => {
if (error) console.log(error) if (error) console.log(error)
self._deps.editor.setText(content) this._deps.editor.setText(content)
}) })
} else { } else {
console.log('cannot save ' + currentFile + '. Does not belong to any explorer') 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 csjs = require('csjs-inject')
var styleGuide = require('../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs` var css = csjs`
.label {
margin-top : 4px
}
.fileexplorer { .fileexplorer {
box-sizing : border-box; box-sizing : border-box;
} }
@ -15,13 +16,26 @@ var css = csjs`
cursor : pointer; cursor : pointer;
} }
.file { .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 { .hasFocus {
background-color : ${styles.leftPanel.backgroundColor_FileExplorer};
} }
.rename { .rename {
background-color : ${styles.leftPanel.backgroundColor_Panel};
} }
.remove { .remove {
margin-left : auto; margin-left : auto;

@ -1,23 +1,44 @@
var yo = require('yo-yo') var yo = require('yo-yo')
var EventManager = require('../../lib/events') var EventManager = require('../../lib/events')
var $ = require('jquery')
var Terminal = require('./terminal') var Terminal = require('./terminal')
var Editor = require('../editor/editor') var Editor = require('../editor/editor')
var globalRegistry = require('../../global/registry') var globalRegistry = require('../../global/registry')
var { TabProxy } = require('./tab-proxy.js')
var ContextualListener = require('../editor/contextualListener') var ContextualListener = require('../editor/contextualListener')
var ContextView = require('../editor/contextView') var ContextView = require('../editor/contextView')
var styles = require('./styles/editor-panel-styles')
var cssTabs = styles.cssTabs var csjs = require('csjs-inject')
var css = styles.css
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 { class EditorPanel {
constructor (localRegistry) { constructor (appStore, appManager, mainPanelComponent) {
var self = this var self = this
self._components = {}
self._components.registry = localRegistry || globalRegistry
self.event = new EventManager() 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 () { init () {
var self = this var self = this
@ -28,30 +49,61 @@ class EditorPanel {
udapp: self._components.registry.get('udapp').api, udapp: self._components.registry.get('udapp').api,
pluginManager: self._components.registry.get('pluginmanager').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 = { self.data = {
_FILE_SCROLL_DELTA: 200,
_layout: { _layout: {
top: { top: {
offset: self._deps.config.get('terminal-top-offset') || 500, offset: self._deps.config.get('terminal-top-offset') || 150,
show: true 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 contextualListener = new ContextualListener({editor: self._components.editor, pluginManager: self._deps.pluginManager})
var contextView = new ContextView({contextualListener, editor}) var contextView = new ContextView({contextualListener, editor: self._components.editor})
self._components = { self._components.contextualListener = contextualListener
editor: editor, self._components.contextView = contextView
contextualListener: contextualListener, self._components.terminal = new Terminal({
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, udapp: self._deps.udapp,
compilers: {} appStore: self.appStore,
appManager: self.appManager
}, },
{ {
getPosition: (event) => { getPosition: (event) => {
@ -60,23 +112,16 @@ class EditorPanel {
var height = window.innerHeight var height = window.innerHeight
var newpos = (event.pageY < limitUp) ? limitUp : event.pageY var newpos = (event.pageY < limitUp) ? limitUp : event.pageY
newpos = (newpos < height - limitDown) ? newpos : height - limitDown newpos = (newpos < height - limitDown) ? newpos : height - limitDown
return newpos 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)) self._components.terminal.event.register('resize', delta => self._adjustLayout('top', delta))
if (self._deps.txListener) { if (self._deps.txListener) {
self._components.terminal.event.register('listenOnNetWork', (listenOnNetWork) => { self._components.terminal.event.register('listenOnNetWork', (listenOnNetWork) => {
self._deps.txListener.setListenOnNetwork(listenOnNetWork) self._deps.txListener.setListenOnNetwork(listenOnNetWork)
}) })
} }
if (document && document.head) {
document.head.appendChild(cssTabs)
}
} }
_adjustLayout (direction, delta) { _adjustLayout (direction, delta) {
var limitUp = 0 var limitUp = 0
@ -88,7 +133,7 @@ class EditorPanel {
if (delta === undefined) { if (delta === undefined) {
layout.show = !layout.show layout.show = !layout.show
if (layout.show) delta = layout.offset if (layout.show) delta = layout.offset
else delta = containerHeight else delta = 0
} else { } else {
layout.show = true layout.show = true
self._deps.config.set(`terminal-${direction}-offset`, delta) self._deps.config.set(`terminal-${direction}-offset`, delta)
@ -98,10 +143,11 @@ class EditorPanel {
var tmp = delta - limitDown var tmp = delta - limitDown
delta = tmp > 0 ? tmp : 0 delta = tmp > 0 ? tmp : 0
if (direction === 'top') { if (direction === 'top') {
var height = containerHeight - delta var mainPanelHeight = containerHeight - delta
height = height < 0 ? 0 : height mainPanelHeight = mainPanelHeight < 0 ? 0 : mainPanelHeight
self._view.editor.style.height = `${delta}px` self._view.editor.style.height = `${mainPanelHeight}px`
self._view.terminal.style.height = `${height}px` // - menu bar height 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.editor.resize((document.querySelector('#editorWrap') || {}).checked)
self._components.terminal.scroll2bottom() self._components.terminal.scroll2bottom()
} }
@ -131,14 +177,15 @@ class EditorPanel {
var self = this var self = this
if (self._view.el) return self._view.el if (self._view.el) return self._view.el
self._view.editor = self._components.editor.render() 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.terminal = self._components.terminal.render()
self._view.content = yo` self._view.content = yo`
<div class=${css.content}> <div class=${css.content}>
${self._renderTabsbar()} ${self.tabProxy.renderTabsbar()}
<div class=${css.contextviewcontainer}>
${self._components.contextView.render()}
</div>
${self._view.editor} ${self._view.editor}
${self._view.mainPanel}
${self._components.contextView.render()}
${self._view.terminal} ${self._view.terminal}
</div> </div>
` `
@ -149,6 +196,11 @@ class EditorPanel {
` `
// INIT // INIT
self._adjustLayout('top', self.data._layout.top.offset) 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 return self._view.el
} }
registerCommand (name, command, opts) { registerCommand (name, command, opts) {
@ -158,137 +210,6 @@ class EditorPanel {
updateTerminalFilter (filter) { updateTerminalFilter (filter) {
this._components.terminal.updateJournal(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 module.exports = EditorPanel

@ -1,28 +1,14 @@
/* global FileReader */
var async = require('async')
var $ = require('jquery')
var yo = require('yo-yo') var yo = require('yo-yo')
var CompilerMetadata = require('../files/compiler-metadata') var CompilerMetadata = require('../files/compiler-metadata')
var EventManager = require('../../lib/events') var EventManager = require('../../lib/events')
var Gists = require('gists')
var FileExplorer = require('../files/file-explorer') var FileExplorer = require('../files/file-explorer')
var modalDialog = require('../ui/modaldialog') var { RemixdHandle } = require('../files/remixd-handle.js')
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 globalRegistry = require('../../global/registry') 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 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 canUpload = window.File || window.FileReader || window.FileList || window.Blob
var ghostbar = yo`<div class=${css.ghostbar}></div>`
/* /*
Overview of APIs: Overview of APIs:
@ -41,7 +27,21 @@ var ghostbar = yo`<div class=${css.ghostbar}></div>`
- call fileProvider API - call fileProvider API
*/ */
function filepanel (localRegistry) { const profile = {
name: 'fileExplorers',
displayName: 'File explorers',
methods: [],
events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNjk2IDM4NHE0MCAwIDY4IDI4dDI4IDY4djEyMTZxMCA0MC0yOCA2OHQtNjggMjhoLTk2MHEtNDAgMC02OC0yOHQtMjgtNjh2LTI4OGgtNTQ0cS00MCAwLTY4LTI4dC0yOC02OHYtNjcycTAtNDAgMjAtODh0NDgtNzZsNDA4LTQwOHEyOC0yOCA3Ni00OHQ4OC0yMGg0MTZxNDAgMCA2OCAyOHQyOCA2OHYzMjhxNjgtNDAgMTI4LTQwaDQxNnptLTU0NCAyMTNsLTI5OSAyOTloMjk5di0yOTl6bS02NDAtMzg0bC0yOTkgMjk5aDI5OXYtMjk5em0xOTYgNjQ3bDMxNi0zMTZ2LTQxNmgtMzg0djQxNnEwIDQwLTI4IDY4dC02OCAyOGgtNDE2djY0MGg1MTJ2LTI1NnEwLTQwIDIwLTg4dDQ4LTc2em05NTYgODA0di0xMTUyaC0zODR2NDE2cTAgNDAtMjggNjh0LTY4IDI4aC00MTZ2NjQwaDg5NnoiLz48L3N2Zz4=',
description: ' - ',
kind: 'fileexplorer',
location: 'swapPanel'
}
module.exports = class Filepanel extends BaseApi {
constructor (localRegistry) {
super(profile)
var self = this var self = this
self._components = {} self._components = {}
self._components.registry = localRegistry || globalRegistry self._components.registry = localRegistry || globalRegistry
@ -51,15 +51,19 @@ function filepanel (localRegistry) {
config: self._components.registry.get('config').api, config: self._components.registry.get('config').api,
pluginManager: self._components.registry.get('pluginmanager').api pluginManager: self._components.registry.get('pluginmanager').api
} }
var fileExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['browser']) 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 fileSystemExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['localhost'])
var swarmExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['swarm']) var swarmExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['swarm'])
var githubExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['github']) var githubExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['github'])
var gistExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['gist']) var gistExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['gist'], ['updateGist'])
var configExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['config'])
var httpExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['http']) var httpExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['http'])
var httpsExplorer = new FileExplorer(self._components.registry, self._deps.fileProviders['https']) 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 ---------------------- // ----------------- editor panel ----------------------
self._compilerMetadata = new CompilerMetadata( self._compilerMetadata = new CompilerMetadata(
{ {
@ -71,55 +75,13 @@ function filepanel (localRegistry) {
self._compilerMetadata.syncContractMetadata() self._compilerMetadata.syncContractMetadata()
self.compilerMetadata = () => { return self._compilerMetadata } 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 () { function template () {
return yo` return yo`
<div class=${css.container}> <div class=${css.container}>
<div class=${css.fileexplorer}> <div class="${css.fileexplorer}">
<div class=${css.menu}> <div>
<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=${css.treeview}>${fileExplorer.init()}</div>
<div class="configexplorer ${css.treeview}">${configExplorer.init()}</div>
<div class="filesystemexplorer ${css.treeview}">${fileSystemExplorer.init()}</div> <div class="filesystemexplorer ${css.treeview}">${fileSystemExplorer.init()}</div>
<div class="swarmexplorer ${css.treeview}">${swarmExplorer.init()}</div> <div class="swarmexplorer ${css.treeview}">${swarmExplorer.init()}</div>
<div class="githubexplorer ${css.treeview}">${githubExplorer.init()}</div> <div class="githubexplorer ${css.treeview}">${githubExplorer.init()}</div>
@ -128,7 +90,6 @@ function filepanel (localRegistry) {
<div class="httpsexplorer ${css.treeview}">${httpsExplorer.init()}</div> <div class="httpsexplorer ${css.treeview}">${httpsExplorer.init()}</div>
</div> </div>
</div> </div>
${dragbar}
</div> </div>
` `
} }
@ -137,28 +98,18 @@ function filepanel (localRegistry) {
self.event = event self.event = event
var element = template() var element = template()
fileExplorer.ensureRoot() fileExplorer.ensureRoot()
configExplorer.ensureRoot()
var websocketconn = element.querySelector('.websocketconn')
self._deps.fileProviders['localhost'].event.register('connecting', (event) => { 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) => { 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() fileSystemExplorer.show()
}) })
self._deps.fileProviders['localhost'].event.register('errored', (event) => { 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() fileSystemExplorer.hide()
}) })
self._deps.fileProviders['localhost'].event.register('closed', (event) => { 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() fileSystemExplorer.hide()
}) })
@ -166,10 +117,6 @@ function filepanel (localRegistry) {
self._deps.fileManager.switchFile(path) self._deps.fileManager.switchFile(path)
}) })
configExplorer.events.register('focus', function (path) {
self._deps.fileManager.switchFile(path)
})
fileSystemExplorer.events.register('focus', function (path) { fileSystemExplorer.events.register('focus', function (path) {
self._deps.fileManager.switchFile(path) self._deps.fileManager.switchFile(path)
}) })
@ -195,232 +142,6 @@ function filepanel (localRegistry) {
}) })
self.render = function render () { return element } self.render = function render () { return element }
function uploadFile (event) {
// TODO The file explorer is merely a view on the current state of
// the files module. Please ask the user here if they want to overwrite
// a file and then just use `files.add`. The file explorer will
// pick that up via the 'fileAdded' event from the files module.
;[...this.files].forEach((file) => {
var files = fileExplorer.files
function loadFile () {
var fileReader = new FileReader()
fileReader.onload = function (event) {
if (helper.checkSpecialChars(file.name)) {
modalDialogCustom.alert('Special characters are not allowed')
return
}
var success = files.set(name, event.target.result)
if (!success) modalDialogCustom.alert('Failed to create file ' + name)
else self.event.trigger('focus', [name])
}
fileReader.readAsText(file)
}
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() })
}
})
})
}
// ----------------- 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)])
}
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)
} }
/**
* 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()
}
})
}})
}
}
// ------------------ gist publish --------------
function updateGist () {
var gistId = self._deps.fileProviders['gist'].id
if (!gistId) {
tooltip('no gist content is currently loaded.')
} else {
toGist('gist', gistId)
}
}
function publishToGist (fileProviderName) {
modalDialogCustom.confirm(null, 'Are you very sure you want to publish all your files anonymously as a public gist on github.com?', () => {
toGist(fileProviderName)
})
}
function toGist (fileProviderName, id) {
packageFiles(self._deps.fileProviders[fileProviderName], (error, packaged) => {
if (error) {
console.log(error)
modalDialogCustom.alert('Failed to create gist: ' + error)
} else {
var tokenAccess = self._deps.config.get('settings/gist-access-token')
if (!tokenAccess) {
modalDialogCustom.alert('Remix requires an access token (which includes gists creation permission). Please go to the settings tab for more information.')
} else {
var description = 'Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. \n Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=' + queryParams.get().version + '&optimize=' + queryParams.get().optimize + '&gist='
var gists = new Gists({
token: tokenAccess
})
if (id) {
tooltip('Saving gist (' + id + ') ...')
gists.edit({
description: description,
public: true,
files: packaged,
id: id
}, (error, result) => {
cb(error, result)
})
} else {
tooltip('Creating a new gist ...')
gists.create({
description: description,
public: true,
files: packaged
}, (error, result) => {
cb(error, result)
})
}
}
}
})
}
function cb (error, data) {
if (error) {
modalDialogCustom.alert('Failed to manage gist: ' + error)
} else {
if (data.html_url) {
modalDialogCustom.confirm(null, `The gist is at ${data.html_url}. Would you like to open it in a new window?`, () => {
window.open(data.html_url, '_blank')
})
} else {
modalDialogCustom.alert(data.message + ' ' + data.documentation_url + ' ' + JSON.stringify(data.errors, null, '\t'))
}
}
}
// ------------------ copy files --------------
function copyFiles () {
modalDialogCustom.prompt(null, 'To which other remix-ide instance do you want to copy over all files?', 'https://remix.ethereum.org', (target) => {
doCopy(target)
})
function doCopy (target) {
// package only files from the browser storage.
packageFiles(self._deps.fileProviders['browser'], (error, packaged) => {
if (error) {
console.log(error)
} else {
$('<iframe/>', {
src: target,
style: 'display:none;',
load: function () { this.contentWindow.postMessage(['loadFiles', packaged], '*') }
}).appendTo('body')
}
})
}
}
}
// return all the files, except the temporary/readonly ones..
function packageFiles (filesProvider, callback) {
var ret = {}
filesProvider.resolveDirectory(filesProvider.type, (error, files) => {
if (error) callback(error)
else {
async.eachSeries(Object.keys(files), (path, cb) => {
filesProvider.get(path, (error, content) => {
if (error) cb(error)
else {
ret[path] = { content }
cb()
}
})
}, (error) => {
callback(error, ret)
})
}
})
} }
module.exports = filepanel

@ -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 csjs = require('csjs-inject')
var styleGuide = require('../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs` var css = csjs`
.container { .container {
@ -15,22 +13,8 @@ var css = csjs`
flex-direction : column; flex-direction : column;
position : relative; position : relative;
width : 100%; width : 100%;
} padding-left : 6px;
.menu { padding-top : 6px;
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};
} }
.gist { .gist {
padding : 10px; padding : 10px;
@ -57,49 +41,25 @@ var css = csjs`
cursor : pointer; cursor : pointer;
} }
.connectToLocalhost i:hover { .connectToLocalhost i:hover {
color : ${styles.colors.orange}; color : var(--secondary)
} }
.uploadFile { .uploadFile {
padding : 10px; padding : 10px;
} }
.uploadFile label:hover { .uploadFile label:hover {
color : ${styles.colors.orange}; color : var(--secondary)
} }
.uploadFile label { .uploadFile label {
cursor : pointer; cursor : pointer;
} }
.treeview { .treeview {
background-color : ${styles.colors.general_BackgroundColor};
}
.treeviews {
overflow-y : auto; 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 { .dialog {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.dialogParagraph { .dialogParagraph {
${styles.infoTextBox}
margin-bottom: 2em; margin-bottom: 2em;
word-break: break-word; 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 CommandInterpreterAPI = require('../../lib/cmdInterpreterAPI')
var executionContext = require('../../execution-context') var executionContext = require('../../execution-context')
var Dropdown = require('../ui/dropdown')
var AutoCompletePopup = require('../ui/auto-complete-popup') var AutoCompletePopup = require('../ui/auto-complete-popup')
var Commands = require('../constants/commands')
var csjs = require('csjs-inject') var csjs = require('csjs-inject')
var styleGuide = require('../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = require('./styles/terminal-styles') var css = require('./styles/terminal-styles')
import { BaseApi } from 'remix-plugin'
var packageV = require('../../../package.json')
var KONSOLES = [] var KONSOLES = []
function register (api) { KONSOLES.push(api) } 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) { constructor (opts, api) {
super(profile)
var self = this var self = this
self.event = new EventManager() self.event = new EventManager()
self._api = api self._api = api
@ -42,40 +51,14 @@ class Terminal {
self._view = { el: null, bar: null, input: null, term: null, journal: null, cli: null } self._view = { el: null, bar: null, input: null, term: null, journal: null, cli: null }
self._components = {} self._components = {}
self._components.cmdInterpreter = new CommandInterpreterAPI(this) self._components.cmdInterpreter = new CommandInterpreterAPI(this)
self._components.dropdown = new Dropdown({ self._components.autoCompletePopup = new AutoCompletePopup(self._opts)
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.event.register('handleSelect', function (input) { 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(' ') let textList = self._view.input.innerText.split(' ')
textList.pop() textList.pop()
textList.push(input) textList.push(input)
self._view.input.innerText = `${textList}`.replace(/,/g, ' ') self._view.input.innerText = textList
self._view.input.focus() self._view.input.focus()
yo.update(self._view.autoCompletePopup, self._components.autoCompletePopup.render()) self.putCursor2End(self._view.input)
})
self._components.autoCompletePopup.event.register('updateList', function () {
yo.update(self._view.autoCompletePopup, self._components.autoCompletePopup.render())
}) })
self._commands = {} self._commands = {}
self.commands = {} self.commands = {}
@ -109,64 +92,98 @@ class Terminal {
self._jsSandboxContext = {} self._jsSandboxContext = {}
self._jsSandboxRegistered = {} 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 if (opts.shell) self._shell = opts.shell
register(self) register(self)
} }
focus () {
if (this._view.input) this._view.input.focus()
}
render () { render () {
var self = this var self = this
if (self._view.el) return self._view.el if (self._view.el) return self._view.el
self._view.journal = yo`<div class=${css.journal}></div>` self._view.journal = yo`<div class=${css.journal}></div>`
self._view.input = yo` 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.input.innerText = '\n'
self._view.cli = yo` self._view.cli = yo`
<div class=${css.cli}> <div class="${css.cli}">
<span class=${css.prompt}>${'>'}</span> <span class=${css.prompt}>${'>'}</span>
${self._view.input} ${self._view.input}
</div> </div>
` `
self._view.icon = yo` self._view.icon = yo`
<i onmouseenter=${hover} onmouseleave=${hover} onmousedown=${minimize} <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` self._view.dragbar = yo`
<div onmousedown=${mousedown} class=${css.dragbarHorizontal}></div>` <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.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` self._view.bar = yo`
<div class=${css.bar}> <div class="${css.bar}">
${self._view.dragbar} ${self._view.dragbar}
<div class=${css.menu}> <div class="${css.menu} border-top bg-light">
${self._view.icon} ${self._view.icon}
<div class=${css.clear} onclick=${clear}> <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> onmouseenter=${hover} onmouseleave=${hover}></i>
</div> </div>
${self._view.pendingTxCount} ${self._view.pendingTxCount}
<div class=${css.verticalLine}></div> <div class=${css.verticalLine}></div>
<div class=${css.listen}> <div class="form-check">
<input onchange=${listenOnNetwork} type="checkbox" <input
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you"> 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> </div>
${self._view.dropdown}
<div class=${css.search}> <div class=${css.search}>
<i class="fa fa-search ${css.searchIcon}" aria-hidden="true"></i> <i class="fas fa-search ${css.searchIcon} bg-light" aria-hidden="true"></i>
<input type="text" class=${css.filter} onkeydown=${filter} placeholder="Search transactions"> ${self._view.inputSearch}
</div> </div>
</div> </div>
</div> </div>
` `
self._view.term = yo` 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}> <div class=${css.terminal}>
${self._view.journal} ${self._view.journal}
${self._view.cli} ${self._view.cli}
</div> </div>
</div> </div>
` `
self._view.autoCompletePopup = self._components.autoCompletePopup.render()
self._view.el = yo` self._view.el = yo`
<div class=${css.panel}> <div class="${css.panel}" style="height: 180px;">
${self._view.bar} ${self._view.bar}
${self._view.term} ${self._view.term}
</div> </div>
@ -273,7 +290,7 @@ class Terminal {
if (inserted) { if (inserted) {
text.innerText = '' text.innerText = ''
background.onclick = undefined background.onclick = undefined
self._view.journal.removeChild(placeholder) if (placeholder.parentElement) self._view.journal.removeChild(placeholder)
} }
inserted = false inserted = false
delete self.scroll2bottom delete self.scroll2bottom
@ -340,7 +357,8 @@ class Terminal {
clearTimeout(filtertimeout) clearTimeout(filtertimeout)
} }
filtertimeout = setTimeout(() => { 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) }, 500)
} }
function clear (event) { function clear (event) {
@ -385,7 +403,7 @@ class Terminal {
self._cmdIndex = -1 self._cmdIndex = -1
self._cmdTemp = '' 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> <div>You can use this terminal for: </div>
<ul class=${css2.ul}> <ul class=${css2.ul}>
<li>Checking transactions details and start debugging.</li> <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://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://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><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> </ul>
</li> </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> <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 return self._view.el
function change (event) { 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 (self._view.input.innerText.length === 0) self._view.input.innerText += '\n'
if (event.which === 13) { if (event.which === 13) {
if (event.ctrlKey) { // <ctrl+enter> if (event.ctrlKey) { // <ctrl+enter>
self._view.input.innerText += '\n' self._view.input.innerText += '\n'
putCursor2End(self._view.input) self.putCursor2End(self._view.input)
self.scroll2bottom() self.scroll2bottom()
removeAutoComplete() self._components.autoCompletePopup.removeAutoComplete()
} else { // <enter> } else { // <enter>
self._cmdIndex = -1 self._cmdIndex = -1
self._cmdTemp = '' self._cmdTemp = ''
@ -425,37 +445,30 @@ class Terminal {
self._cmdHistory.unshift(script) self._cmdHistory.unshift(script)
self.commands.script(script) self.commands.script(script)
} }
removeAutoComplete() self._components.autoCompletePopup.removeAutoComplete()
} }
} else if (event.which === 38) { // <arrowUp> } 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 var len = self._cmdHistory.length
if (len === 0) return event.preventDefault() if (len === 0) return event.preventDefault()
if (self._cmdHistory.length - 1 > self._cmdIndex) { if (self._cmdHistory.length - 1 > self._cmdIndex) {
self._cmdIndex++ self._cmdIndex++
} }
self._view.input.innerText = self._cmdHistory[self._cmdIndex] self._view.input.innerText = self._cmdHistory[self._cmdIndex]
putCursor2End(self._view.input) self.putCursor2End(self._view.input)
self.scroll2bottom() self.scroll2bottom()
}
} else if (event.which === 40) { // <arrowDown> } 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) { if (self._cmdIndex > -1) {
self._cmdIndex-- self._cmdIndex--
} }
self._view.input.innerText = self._cmdIndex >= 0 ? self._cmdHistory[self._cmdIndex] : self._cmdTemp self._view.input.innerText = self._cmdIndex >= 0 ? self._cmdHistory[self._cmdIndex] : self._cmdTemp
putCursor2End(self._view.input) self.putCursor2End(self._view.input)
self.scroll2bottom() self.scroll2bottom()
}
} else { } else {
self._cmdTemp = self._view.input.innerText self._cmdTemp = self._view.input.innerText
} }
} }
function putCursor2End (editable) { }
putCursor2End (editable) {
var range = document.createRange() var range = document.createRange()
range.selectNode(editable) range.selectNode(editable)
var child = editable var child = editable
@ -482,46 +495,6 @@ class Terminal {
editable.focus() 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())
}
}
updateJournal (filterEvent) { updateJournal (filterEvent) {
var self = this var self = this
var commands = self.data.activeFilters.commands var commands = self.data.activeFilters.commands
@ -537,8 +510,8 @@ class Terminal {
commands[value] = false commands[value] = false
if (!self._INDEX.commandsMain[value]) return if (!self._INDEX.commandsMain[value]) return
self._INDEX.commandsMain[value].forEach(item => { self._INDEX.commandsMain[value].forEach(item => {
item.root.steps.forEach(item => { self._JOURNAL[item.gidx] = undefined }) item.root.steps.forEach(item => { self._JOURNAL[item.gidx].hide = true })
self._JOURNAL[item.gidx] = undefined self._JOURNAL[item.gidx].hide = true
}) })
} else if (filterEvent.type === 'search') { } else if (filterEvent.type === 'search') {
if (value !== self.data.activeFilters.input) { if (value !== self.data.activeFilters.input) {
@ -557,8 +530,8 @@ class Terminal {
self._JOURNAL.forEach(item => { self._JOURNAL.forEach(item => {
if (item && item.el && !item.hide) df.appendChild(item.el) if (item && item.el && !item.hide) df.appendChild(item.el)
}) })
requestAnimationFrame(function updateDOM () {
self._view.journal.innerHTML = '' self._view.journal.innerHTML = ''
requestAnimationFrame(function updateDOM () {
self._view.journal.appendChild(df) self._view.journal.appendChild(df)
}) })
} }
@ -573,7 +546,7 @@ class Terminal {
self._jobs = [] self._jobs = []
}) })
} }
self._jobs.push(el) if (self.data.activeFilters.commands[item.cmd]) self._jobs.push(el)
} }
scroll2bottom () { scroll2bottom () {
var self = this var self = this
@ -587,7 +560,12 @@ class Terminal {
if (args.length) append(args[0]) 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) { if (mode) {
return function logger (args, scopedCommands, append) { return function logger (args, scopedCommands, append) {
var types = args.map(type) var types = args.map(type)
@ -596,7 +574,7 @@ class Terminal {
if (types[idx] === 'element') val = jsbeautify.html(val) if (types[idx] === 'element') val = jsbeautify.html(val)
return val return val
}) })
append(yo`<span style="color: ${mode};">${values}</span>`) append(yo`<span class="${mode}" >${values}</span>`)
} }
} else { } else {
throw new Error('mode is not supported') throw new Error('mode is not supported')
@ -681,7 +659,6 @@ class Terminal {
function domTerminalFeatures (self, scopedCommands) { function domTerminalFeatures (self, scopedCommands) {
return { return {
compilers: self._opts.compilers,
swarmgw, swarmgw,
ethers, ethers,
remix: self._components.cmdInterpreter, 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 $ = require('jquery')
var remixLib = require('remix-lib') var remixLib = require('remix-lib')
var utils = remixLib.util var utils = remixLib.util
var styleGuide = require('../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = require('./styles/staticAnalysisView-styles') var css = require('./styles/staticAnalysisView-styles')
var globlalRegistry = require('../../global/registry') var globlalRegistry = require('../../global/registry')
@ -34,6 +30,7 @@ function staticAnalysisView (localRegistry) {
self.lastCompilationResult = null self.lastCompilationResult = null
self.lastCompilationSource = null self.lastCompilationSource = null
$('#staticanalysisresult').empty() $('#staticanalysisresult').empty()
if (languageVersion.indexOf('soljson') !== 0) return
self.lastCompilationResult = data self.lastCompilationResult = data
self.lastCompilationSource = source self.lastCompilationSource = source
if (self.view.querySelector('#autorunstaticanalysis').checked) { if (self.view.querySelector('#autorunstaticanalysis').checked) {
@ -46,11 +43,9 @@ staticAnalysisView.prototype.render = function () {
var self = this var self = this
var view = yo` var view = yo`
<div class="${css.analysis}"> <div class="${css.analysis}">
<div id="staticanalysismodules">
${this.modulesView}
</div>
<div class="${css.buttons}"> <div class="${css.buttons}">
<button class="${css.buttonRun}" onclick="${function () { self.run() }}" >Run</button> <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"> <label class="${css.label}" for="autorunstaticanalysis">
<input id="autorunstaticanalysis" <input id="autorunstaticanalysis"
type="checkbox" type="checkbox"
@ -69,7 +64,13 @@ staticAnalysisView.prototype.render = function () {
Check/Uncheck all Check/Uncheck all
</label> </label>
</div> </div>
<div class="${css.result}" "id='staticanalysisresult'></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> </div>
` `
if (!this.view) { if (!this.view) {
@ -97,8 +98,8 @@ staticAnalysisView.prototype.run = function () {
var selected = this.selectedModules() var selected = this.selectedModules()
var warningContainer = $('#staticanalysisresult') var warningContainer = $('#staticanalysisresult')
warningContainer.empty() warningContainer.empty()
if (this.lastCompilationResult) {
var self = this var self = this
if (this.lastCompilationResult && selected.length) {
var warningCount = 0 var warningCount = 0
this.runner.run(this.lastCompilationResult, selected, function (results) { this.runner.run(this.lastCompilationResult, selected, function (results) {
results.map(function (result, i) { results.map(function (result, i) {
@ -119,20 +120,17 @@ staticAnalysisView.prototype.run = function () {
} }
warningCount++ 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>` 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]) self.event.trigger('staticAnaysisWarning', [warningCount])
}) })
} else { } else {
if (selected.length) {
warningContainer.html('No compiled AST available') warningContainer.html('No compiled AST available')
} }
self.event.trigger('staticAnaysisWarning', [-1])
}
} }
staticAnalysisView.prototype.checkModule = function (event) { staticAnalysisView.prototype.checkModule = function (event) {
@ -176,7 +174,7 @@ staticAnalysisView.prototype.renderModules = function () {
</label> </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> <label class="${css.label}"><b>${category[0].categoryDisplayName}</b></label>
${entriesDom} ${entriesDom}
</div>` </div>`

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

@ -3,21 +3,46 @@ var StaticAnalysis = require('../staticanalysis/staticAnalysisView')
var EventManager = require('../../lib/events') var EventManager = require('../../lib/events')
var css = require('./styles/analysis-tab-styles') 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) { constructor (registry) {
super(profile)
this.event = new EventManager() this.event = new EventManager()
this.events = new EventEmitter()
this.registry = registry this.registry = registry
} }
render () { render () {
var staticanalysis = new StaticAnalysis() if (!this.staticanalysis) this.staticanalysis = new StaticAnalysis()
this.registry.put({api: staticanalysis, name: '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 return yo`<div class="${css.analysisTabView}" id="staticanalysisView">${this.staticanalysis.render()}</div>`
this.el = yo`<div class="${css.analysisTabView} "id="staticanalysisView">${staticanalysis.render()}</div>`
return this.el
} }
} }
module.exports = AnalysisTab module.exports = AnalysisTab

@ -1,286 +1,280 @@
/* global Worker */ /* global */
const async = require('async') const EventEmitter = require('events')
const $ = require('jquery') const $ = require('jquery')
const yo = require('yo-yo') const yo = require('yo-yo')
const csjs = require('csjs-inject')
const copy = require('copy-text-to-clipboard') const copy = require('copy-text-to-clipboard')
var minixhr = require('minixhr')
var remixTests = require('remix-tests')
var Compiler = require('remix-solidity').Compiler
var CompilerImport = require('../compiler/compiler-imports')
var QueryParams = require('../../lib/query-params') var QueryParams = require('../../lib/query-params')
var globalRegistry = require('../../global/registry')
const TreeView = require('../ui/TreeView') const TreeView = require('../ui/TreeView')
const modalDialog = require('../ui/modaldialog') const modalDialog = require('../ui/modaldialog')
const copyToClipboard = require('../ui/copy-to-clipboard') const copyToClipboard = require('../ui/copy-to-clipboard')
const modalDialogCustom = require('../ui/modal-dialog-custom') const modalDialogCustom = require('../ui/modal-dialog-custom')
const styleGuide = require('../ui/styles-guide/theme-chooser')
const parseContracts = require('../contract/contractParser') const parseContracts = require('../contract/contractParser')
const publishOnSwarm = require('../contract/publishOnSwarm') const publishOnSwarm = require('../contract/publishOnSwarm')
const addTooltip = require('../ui/tooltip') const addTooltip = require('../ui/tooltip')
var helper = require('../../lib/helper')
const styles = styleGuide.chooser() var css = require('./styles/compile-tab-styles')
module.exports = class CompileTab { const CompileTabLogic = require('./compileTab/compileTab.js')
constructor (localRegistry) { const CompilerContainer = require('./compileTab/compilerContainer.js')
const self = this
self._view = { import { CompilerApi } from 'remix-plugin'
const profile = {
name: 'solidity',
displayName: 'Solidity compiler',
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAACXBIWXMAAASdAAAEnQF8NGuhAAAAB3RJTUUH4wQMDx84DVYryQAAAjBJREFUSMe1102ITmEUB/DfO4aZRuM7otQgqSlCFmIhSRaSJClWs9BY2RAba1tLZTGxmbJWImFsWBhhZONzpJSEaJhhPl6bM/V2ux/vnTtz6untPvc857z/8/E/z61pXnZjHeoFejcwUWSs1qTTFjzHOP7l2PqCAVxuxmAzcgbLsSQH8SQ+YxM6igzOa8LpMvSH4dYI43iK3nDs17AND6oiPt+Qs3qgTso4fjU8r0Z3Fcfd6E0505nYe52olyn0VAn1FaxM2W/HSETgN76l6HREet6URbwPe3LeLw5k73OK7UDZdmrBIJYWROse3hak8gluJ1+0ZhyYwlNsLyCMHjOUvFCfij+Q19vmwjFcy9D5gUdVHDdDmY8xP3HmULDUnCGGswmnL3Oc7sBxsygDUeVDaMvR68fDnKItPSROBNo+/M3QOYwtwWq9s4n6ajBWltyJqAziVQbjlZpO03IzZ8CfDpab7vmJGKP3q14E8mQR7qaAaMdJvKiS4zw5lxG5MVyoWlxZshFHI8RpazP2lgl1DRdjAmVxdR070VVAUIM4Uqa4PuFg6LSlrFVRQJ3hIG2NY1fZUH/Asxy0a+L3a07H9M20nYZjmE8mnK5omNWTWJgCYhTHZup4BAuwPjHDNyT0/iTuYbXo7XdVqvpWA/fWI7dpF4exhufvwWSVmGv66ro10HdlVPpokEkd+/FzNobEQKBY23AuuWpx4xzCxyKDrSXI4nrkPO+DrA2XmjH2H8KUd4MWwdIJAAAAAElFTkSuQmCC',
description: 'Compile solidity contracts',
kind: 'compile',
permission: true,
location: 'swapPanel'
}
// EditorApi:
// - events: ['compilationFinished'],
// - methods: ['getCompilationResult']
class CompileTab extends CompilerApi {
constructor (editor, config, renderer, swarmfileProvider, fileManager, fileProviders, pluginManager) {
super(profile)
this.events = new EventEmitter()
this._view = {
el: null, el: null,
autoCompile: null,
compileButton: null,
warnCompilationSlow: null, warnCompilationSlow: null,
compileIcon: null,
compileContainer: null,
errorContainer: null, errorContainer: null,
errorContainerHead: null, contractEl: null
contractNames: null, }
contractEl: null, this.queryParams = new QueryParams()
config: {
solidity: null
},
optimize: null
}
self._components = {}
self._components.registry = localRegistry || globalRegistry
self._components.queryParams = new QueryParams()
self._components.compilerImport = new CompilerImport(() => { return self._deps.config.get('settings/gist-access-token') })
self._components.compiler = new Compiler((url, cb) => self.importFileCb(url, cb))
// dependencies // dependencies
self._deps = { this.editor = editor
editor: self._components.registry.get('editor').api, this.config = config
config: self._components.registry.get('config').api, this.renderer = renderer
renderer: self._components.registry.get('renderer').api, this.swarmfileProvider = swarmfileProvider
swarmfileProvider: self._components.registry.get('fileproviders/swarm').api, this.fileManager = fileManager
fileManager: self._components.registry.get('filemanager').api, this.fileProviders = fileProviders
fileProviders: self._components.registry.get('fileproviders').api, this.pluginManager = pluginManager
pluginManager: self._components.registry.get('pluginmanager').api
} this.data = {
self.data = { contractsDetails: {}
hideWarnings: self._deps.config.get('hideWarnings') || false,
autoCompile: self._deps.config.get('autoCompile'),
compileTimeout: null,
contractsDetails: {},
maxTime: 1000,
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'
}
self.data.optimize = self._components.queryParams.get().optimize
self.data.optimize = self.data.optimize === 'true'
self._components.queryParams.update({ optimize: self.data.optimize })
self._components.compiler.setOptimize(self.data.optimize)
self._deps.editor.event.register('contentChanged', scheduleCompilation)
self._deps.editor.event.register('sessionSwitched', scheduleCompilation)
function scheduleCompilation () {
if (!self._deps.config.get('autoCompile')) return
if (self.data.compileTimeout) window.clearTimeout(self.data.compileTimeout)
self.data.compileTimeout = window.setTimeout(() => self.runCompiler(), self.data.timeout)
}
self._components.compiler.event.register('compilationDuration', function tabHighlighting (speed) {
if (!self._view.warnCompilationSlow) return
if (speed > self.data.maxTime) {
const msg = `Last compilation took ${speed}ms. We suggest to turn off autocompilation.`
self._view.warnCompilationSlow.setAttribute('title', msg)
self._view.warnCompilationSlow.style.visibility = 'visible'
} else {
self._view.warnCompilationSlow.style.visibility = 'hidden'
} }
this.compileTabLogic = new CompileTabLogic(this.queryParams, this.fileManager, this.editor, this.config, this.fileProviders)
this.compiler = this.compileTabLogic.compiler
this.compileTabLogic.init()
this.compilerContainer = new CompilerContainer(
this.compileTabLogic,
this.editor,
this.config,
this.queryParams
)
}
/************
* EVENTS
*/
listenToEvents () {
let onContentChanged = () => {
this.events.emit('statusChanged', {key: 'edited', title: 'the content has changed, needs recompilation', type: 'info'})
}
this.editor.event.register('contentChanged', onContentChanged)
this.editor.event.register('sessionSwitched', onContentChanged)
this.compiler.event.register('loadingCompiler', () => {
this.events.emit('statusChanged', {key: 'loading', title: 'loading compiler...', type: 'info'})
}) })
self._deps.editor.event.register('contentChanged', function changedFile () {
if (!self._view.compileIcon) return this.compiler.event.register('compilerLoaded', () => {
self._view.compileIcon.classList.add(`${css.bouncingIcon}`) // @TODO: compileView tab this.events.emit('statusChanged', {key: 'none'})
}) })
self._components.compiler.event.register('loadingCompiler', function start () {
if (!self._view.compileIcon) return this.compileTabLogic.event.on('startingCompilation', () => {
self._view.compileIcon.classList.add(`${css.spinningIcon}`) if (this._view.errorContainer) {
self._view.warnCompilationSlow.style.visibility = 'hidden' this._view.errorContainer.innerHTML = ''
self._view.compileIcon.setAttribute('title', 'compiler is loading, please wait a few moments.') }
this.events.emit('statusChanged', {key: 'loading', title: 'compiling...', type: 'info'})
}) })
self._components.compiler.event.register('compilationStarted', function start () {
if (!self._view.compileIcon) return this.fileManager.events.on('currentFileChanged', (name) => {
self._view.errorContainer.innerHTML = '' this.compilerContainer.currentFile = name
self._view.errorContainerHead.innerHTML = '' cleanupErrors()
self._view.compileIcon.classList.remove(`${css.bouncingIcon}`) onContentChanged()
self._view.compileIcon.classList.add(`${css.spinningIcon}`)
self._view.compileIcon.setAttribute('title', 'compiling...')
}) })
self._components.compiler.event.register('compilerLoaded', function loaded () {
if (!self._view.compileIcon) return this.fileManager.events.on('noFileSelected', () => {
self._view.compileIcon.classList.remove(`${css.spinningIcon}`) this.compilerContainer.currentFile = ''
self._view.compileIcon.setAttribute('title', '') cleanupErrors()
}) })
self._components.compiler.event.register('compilationFinished', function finish (success, data, source) {
if (self._view.compileIcon) { const cleanupErrors = () => {
const compileTab = document.querySelector('.compileView') this._view.errorContainer.innerHTML = ''
compileTab.style.color = styles.colors.black this.events.emit('statusChanged', {key: 'none'})
self._view.compileIcon.style.color = styles.colors.black }
self._view.compileIcon.classList.remove(`${css.spinningIcon}`)
self._view.compileIcon.classList.remove(`${css.bouncingIcon}`) this.compiler.event.register('compilationFinished', (success, data, source) => {
self._view.compileIcon.setAttribute('title', 'idle')
}
// reset the contractMetadata list (used by the publish action)
self.data.contractsDetails = {}
// refill the dropdown list
self._view.contractNames.innerHTML = ''
if (success) { if (success) {
// TODO consider using compile tab as a proper module instead of just forwarding event // forwarding the event to the appManager infra
self._deps.pluginManager.receivedDataFrom('sendCompilationResult', 'solidity-compiler', [data.target, source, self.data.selectedVersion, data]) this.events.emit('compilationFinished', source.target, source, 'soljson', data)
self._view.contractNames.removeAttribute('disabled') if (data.errors) {
self._components.compiler.visitContracts(contract => { this.events.emit('statusChanged', {
self.data.contractsDetails[contract.name] = parseContracts(contract.name, contract.object, self._components.compiler.getSource(contract.file)) key: data.errors.length,
var contractName = yo`<option>${contract.name}</option>` title: `compilation finished successful with warning${data.errors.length > 1 ? 's' : ''}`,
self._view.contractNames.appendChild(contractName) type: 'warning'
})
} else this.events.emit('statusChanged', {key: 'succeed', title: 'compilation successful', type: 'success'})
// Store the contracts
this.data.contractsDetails = {}
this.compiler.visitContracts((contract) => {
this.data.contractsDetails[contract.name] = parseContracts(
contract.name,
contract.object,
this.compiler.getSource(contract.file)
)
}) })
} else { } else {
self._view.contractNames.setAttribute('disabled', true) const count = (data.errors ? data.errors.filter(error => error.severity === 'error').length : 0 + data.error ? 1 : 0)
} this.events.emit('statusChanged', {key: count, title: 'compilation failed', type: 'error'})
// hightlight the tab if error }
if (success) document.querySelector('.compileView').style.color = '' // @TODO: compileView tab // Update contract Selection
else document.querySelector('.compileView').style.color = styles.colors.red // @TODO: compileView tab let contractMap = {}
// display warning error if any if (success) this.compiler.visitContracts((contract) => { contractMap[contract.name] = contract })
var error = false let contractSelection = this.contractSelection(Object.keys(contractMap) || [])
yo.update(this._view.contractSelection, contractSelection)
if (data['error']) { if (data['error']) {
error = true this.renderer.error(data['error'].formattedMessage, this._view.errorContainer, {type: data['error'].severity || 'error'})
self._deps.renderer.error(data['error'].formattedMessage, self._view.errorContainer, {type: data['error'].severity || 'error'})
if (data['error'].mode === 'panic') { if (data['error'].mode === 'panic') {
/* return modalDialogCustom.alert(yo`
return modalDialogCustom.alert(yo`<div><i class="fa fa-exclamation-circle ${css.panicError}" aria-hidden="true"></i> <div><i class="fas fa-exclamation-circle ${css.panicError}" aria-hidden="true"></i>
The compiler returned with the following internal error: <br> <b>${data['error'].formattedMessage}.<br> The compiler returned with the following internal error: <br> <b>${data['error'].formattedMessage}.<br>
The compiler might be in a non-sane state, please be careful and do not use further compilation data to deploy to mainnet. The compiler might be in a non-sane state, please be careful and do not use further compilation data to deploy to mainnet.
It is heavily recommended to use another browser not affected by this issue (Firefox is known to not be affected).</b><br> It is heavily recommended to use another browser not affected by this issue (Firefox is known to not be affected).</b><br>
Please join <a href="https://gitter.im/ethereum/remix" target="blank" >remix gitter channel</a> for more information.</div>`) Please join <a href="https://gitter.im/ethereum/remix" target="blank" >remix gitter channel</a> for more information.</div>`)
*/
} }
} }
if (data.errors && data.errors.length) { if (data.errors && data.errors.length) {
error = true data.errors.forEach((err) => {
data.errors.forEach(function (err) { if (this.config.get('hideWarnings')) {
if (self._deps.config.get('hideWarnings')) {
if (err.severity !== 'warning') { if (err.severity !== 'warning') {
self._deps.renderer.error(err.formattedMessage, self._view.errorContainer, {type: err.severity}) this.renderer.error(err.formattedMessage, this._view.errorContainer, {type: err.severity})
} }
} else { } else {
self._deps.renderer.error(err.formattedMessage, self._view.errorContainer, {type: err.severity}) this.renderer.error(err.formattedMessage, this._view.errorContainer, {type: err.severity})
} }
}) })
} }
if (!error && data.contracts) {
self._components.compiler.visitContracts((contract) => {
self._deps.renderer.error(contract.name, self._view.errorContainer, {type: 'success'})
})
}
}) })
// Run the compiler instead of trying to save the website // Run the compiler instead of trying to save the website
$(window).keydown(function (e) { $(window).keydown((e) => {
// ctrl+s or command+s // ctrl+s or command+s
if ((e.metaKey || e.ctrlKey) && e.keyCode === 83) { if ((e.metaKey || e.ctrlKey) && e.keyCode === 83) {
e.preventDefault() e.preventDefault()
self.runCompiler() this.compileTabLogic.runCompiler()
} }
}) })
} }
addWarning (msg, settings) {
const self = this
self._deps.renderer.error(msg, self._view.errorContainerHead, settings)
}
render () {
const self = this
if (self._view.el) return self._view.el
function onchangeLoadVersion (event) {
self.data.selectedVersion = self._view.versionSelector.value
self._updateVersionSelector()
}
function onchangeOptimize (event) { getCompilationResult () {
self.data.optimize = !!self._view.optimize.checked return this.compileTabLogic.compiler.lastCompilationResult
self._components.queryParams.update({ optimize: self.data.optimize })
self._components.compiler.setOptimize(self.data.optimize)
self.runCompiler()
} }
self._components.compiler.event.register('compilerLoaded', (version) => self.setVersionText(version)) /*********
self.fetchAllVersion((allversions, selectedVersion) => { * SUB-COMPONENTS
self.data.allversions = allversions */
self.data.selectedVersion = selectedVersion
if (self._view.versionSelector) self._updateVersionSelector()
})
self._view.optimize = yo`<input onchange=${onchangeOptimize} id="optimize" type="checkbox">` /**
if (self.data.optimize) self._view.optimize.setAttribute('checked', '') * Section to select the compiled contract
* @param {string[]} contractList Names of the compiled contracts
self._view.versionSelector = yo` */
<select onchange=${onchangeLoadVersion} class="${css.select}" id="versionSelector" disabled> contractSelection (contractList = []) {
<option disabled selected>Select new compiler version</option> return contractList.length !== 0
</select>` ? yo`<section class="${css.container} clearfix">
self._view.version = yo`<span id="version"></span>` <!-- Select Compiler Version -->
<header class="navbar navbar-light bg-light input-group mb-3 ${css.compilerArticle}">
self._view.warnCompilationSlow = yo`<i title="Compilation Slow" style="visibility:hidden" class="${css.warnCompilationSlow} fa fa-exclamation-triangle" aria-hidden="true"></i>` <div class="input-group-prepend">
self._view.compileIcon = yo`<i class="fa fa-refresh ${css.icon}" aria-hidden="true"></i>` <label class="border-0 input-group-text" for="compiledContracts">Contract</label>
self._view.compileButton = yo`<div class="${css.compileButton}" onclick=${compile} id="compile" title="Compile source code">${self._view.compileIcon} Start to compile (Ctrl-S)</div>`
self._view.autoCompile = yo`<input class="${css.autocompile}" onchange=${updateAutoCompile} id="autoCompile" type="checkbox" title="Auto compile">`
self._view.hideWarningsBox = yo`<input class="${css.autocompile}" onchange=${hideWarnings} id="hideWarningsBox" type="checkbox" title="Hide warnings">`
if (self.data.autoCompile) self._view.autoCompile.setAttribute('checked', '')
if (self.data.hideWarnings) self._view.hideWarningsBox.setAttribute('checked', '')
self._view.compileContainer = yo`
<div class="${css.compileContainer}">
<div class="${css.info}">
<span>Current version:</span> ${self._view.version}
<div class="${css.crow}">
${self._view.versionSelector}
</div>
<div class="${css.compileButtons}">
<div class=${css.checkboxes}>
<div class="${css.autocompileContainer}">
${self._view.autoCompile}
<label for="autoCompile" class="${css.autocompileText}">Auto compile</label>
</div>
<div class="${css.optimizeContainer}">
<div>${self._view.optimize}</div>
<label for="optimize" class="${css.checkboxText}">Enable Optimization</label>
</div>
<div class=${css.hideWarningsContainer}>
${self._view.hideWarningsBox}
<label for="hideWarningsBox" class="${css.autocompileText}">Hide warnings</label>
</div>
</div>
${self._view.compileButton}
</div>
</div>
</div>`
self._view.errorContainer = yo`<div class='error'></div>`
self._view.errorContainerHead = yo`<div class='error'></div>`
self._view.contractNames = yo`<select class="${css.contractNames}" disabled></select>`
self._view.contractEl = yo`
<div class="${css.container}">
<div class="${css.contractContainer}">
${self._view.contractNames}
<div title="Publish on Swarm" class="${css.publish}" onclick=${publish}>
<i class="${css.copyIcon} fa fa-upload" aria-hidden="true"></i><span>Swarm</span>
</div>
</div> </div>
<div class="${css.contractHelperButtons}"> <select onchange="${e => this.selectContract(e.target.value)}" onload="${e => { this.selectedContract = e.value }}" id="compiledContracts" class="custom-select">
<div title="Display Contract Details" class="${css.details}" onclick=${details}>Details</div> ${contractList.map((name) => yo`<option value="${name}">${name}</option>`)}
<div title="Copy ABI to clipboard" class="${css.copyButton}" onclick=${copyABI}> </select>
<i class="${css.copyIcon} fa fa-clipboard" aria-hidden="true"></i> ABI </header>
<article class="${css.compilerArticle}">
<button class="btn btn-primary btn-block" title="Publish on Swarm" onclick="${() => { this.publish() }}">
<i class="${css.copyIcon} fas fa-upload" aria-hidden="true"></i>
<span>Publish on Swarm</span>
</button>
<button class="btn btn-secondary btn-block" title="Display Contract Details" onclick="${() => { this.details() }}">
Compilation Details
</button>
<!-- Copy to Clipboard -->
<div class="${css.contractHelperButtons} btn-secondary">
<div class="input-group">
<div class="btn-group" role="group" aria-label="Copy to Clipboard">
<button class="btn btn-secondary" title="Copy ABI to clipboard" onclick="${() => { this.copyABI() }}">
<i class="${css.copyIcon} far fa-clipboard" aria-hidden="true"></i>
<span>ABI</span>
</button>
<button class="btn btn-secondary" title="Copy Bytecode to clipboard" onclick="${() => { this.copyBytecode() }}">
<i class="${css.copyIcon} far fa-clipboard" aria-hidden="true"></i>
<span>Bytecode</span>
</button>
</div> </div>
<div title="Copy Bytecode to clipboard" class="${css.copyButton} ${css.bytecodeButton}" onclick=${copyBytecode}>
<i class="${css.copyIcon} fa fa-clipboard" aria-hidden="true"></i> Bytecode
</div> </div>
</div> </div>
</div>` </article>
self._view.el = yo` </section>`
<div class="${css.compileTabView}" id="compileTabView"> : yo`<section class="${css.container} clearfix"><article class="${css.compilerArticle}">
${self._view.compileContainer} <span class="alert alert-warning" role="alert">No Contract Compiled Yet</span>
${self._view.contractEl} </article></section>`
${self._view.errorContainerHead} }
${self._view.errorContainer}
</div>` // TODO : Add success alert when compilation succeed
contractCompiledSuccess () {
return yo`<div></div>`
}
// TODO : Add error alert when compilation failed
contractCompiledError () {
return yo`<div></div>`
}
/************
* METHODS
*/
selectContract (contractName) {
this.selectedContract = contractName
}
publish () {
if (this.selectedContract) {
var contract = this.data.contractsDetails[this.selectedContract]
if (contract.metadata === undefined || contract.metadata.length === 0) {
modalDialogCustom.alert('This contract may be abstract, may not implement an abstract parent\'s methods completely or not invoke an inherited contract\'s constructor correctly.')
} else {
publishOnSwarm(contract, this.fileManager, function (err, uploaded) {
if (err) {
try {
err = JSON.stringify(err)
} catch (e) {}
modalDialogCustom.alert(yo`<span>Failed to publish metadata file to swarm, please check the Swarm gateways is available ( swarm-gateways.net ).<br />
${err}</span>`)
} else {
var result = yo`<div>${uploaded.map((value) => {
return yo`<div><b>${value.filename}</b> : <pre>${value.output.url}</pre></div>`
})}</div>`
modalDialogCustom.alert(yo`<span>Metadata published successfully.<br> <pre>${result}</pre> </span>`)
}
}, (item) => { // triggered each time there's a new verified publish (means hash correspond)
this.swarmfileProvider.addReadOnly(item.hash, item.content)
})
}
}
}
details () {
const help = { const help = {
'Assembly': 'Assembly opcodes describing the contract including corresponding solidity source code', 'Assembly': 'Assembly opcodes describing the contract including corresponding solidity source code',
'Opcodes': 'Assembly opcodes describing the contract', 'Opcodes': 'Assembly opcodes describing the contract',
@ -295,60 +289,21 @@ module.exports = class CompileTab {
'swarmLocation': 'Swarm url where all metadata information can be found (contract needs to be published first)', 'swarmLocation': 'Swarm url where all metadata information can be found (contract needs to be published first)',
'web3Deploy': 'Copy/paste this code to any JavaScript/Web3 console to deploy this contract' 'web3Deploy': 'Copy/paste this code to any JavaScript/Web3 console to deploy this contract'
} }
function updateAutoCompile (event) { self._deps.config.set('autoCompile', self._view.autoCompile.checked) } if (!this.selectedContract) throw new Error('No contract compiled yet')
function compile (event) { self.runCompiler() } const contractProperties = this.data.contractsDetails[this.selectedContract]
function hideWarnings (event) {
self._deps.config.set('hideWarnings', self._view.hideWarningsBox.checked)
compile()
}
function getContractProperty (property) {
const select = self._view.contractNames
if (select.children.length > 0 && select.selectedIndex >= 0) {
const contractName = select.children[select.selectedIndex].innerHTML
const contractProperties = self.data.contractsDetails[contractName]
return contractProperties[property] || null
}
}
function copyContractProperty (property) {
let content = getContractProperty(property)
if (!content) {
addTooltip('No content available for ' + property)
return
}
try {
if (typeof content !== 'string') {
content = JSON.stringify(content, null, '\t')
}
} catch (e) {}
copy(content)
addTooltip('Copied value to clipboard')
}
function copyABI () {
copyContractProperty('abi')
}
function copyBytecode () {
copyContractProperty('bytecode')
}
function details () {
const select = self._view.contractNames
if (select.children.length > 0 && select.selectedIndex >= 0) {
const contractName = select.children[select.selectedIndex].innerHTML
const contractProperties = self.data.contractsDetails[contractName]
const log = yo`<div class="${css.detailsJSON}"></div>` const log = yo`<div class="${css.detailsJSON}"></div>`
Object.keys(contractProperties).map(propertyName => { Object.keys(contractProperties).map(propertyName => {
const copyDetails = yo`<span class="${css.copyDetails}">${copyToClipboard(() => contractProperties[propertyName])}</span>` const copyDetails = yo`<span class="${css.copyDetails}">${copyToClipboard(() => contractProperties[propertyName])}</span>`
const questionMark = yo`<span class="${css.questionMark}"><i title="${help[propertyName]}" class="fa fa-question-circle" aria-hidden="true"></i></span>` const questionMark = yo`<span class="${css.questionMark}"><i title="${help[propertyName]}" class="fas fa-question-circle" aria-hidden="true"></i></span>`
log.appendChild(yo`<div class=${css.log}> log.appendChild(yo`<div class=${css.log}>
<div class="${css.key}">${propertyName} ${copyDetails} ${questionMark}</div> <div class="${css.key}">${propertyName} ${copyDetails} ${questionMark}</div>
${insertValue(contractProperties, propertyName)} ${this.insertValue(contractProperties, propertyName)}
</div>`) </div>`)
}) })
modalDialog(contractName, log, { label: '' }, { label: 'Close' }) modalDialog(this.selectedContract, log, { label: '' }, { label: 'Close' })
} }
}
function insertValue (details, propertyName) { insertValue (details, propertyName) {
var node var node
if (propertyName === 'web3Deploy' || propertyName === 'name' || propertyName === 'Assembly') { if (propertyName === 'web3Deploy' || propertyName === 'name' || propertyName === 'Assembly') {
node = yo`<pre>${details[propertyName]}</pre>` node = yo`<pre>${details[propertyName]}</pre>`
@ -371,7 +326,10 @@ module.exports = class CompileTab {
}) })
if (details[propertyName] !== '') { if (details[propertyName] !== '') {
try { try {
node = yo`<div>${treeView.render(typeof details[propertyName] === 'object' ? details[propertyName] : JSON.parse(details[propertyName]))}</div>` // catch in case the parsing fails. node = yo`
<div>
${treeView.render(typeof details[propertyName] === 'object' ? details[propertyName] : JSON.parse(details[propertyName]))}
</div>` // catch in case the parsing fails.
} catch (e) { } catch (e) {
node = yo`<div>Unable to display "${propertyName}": ${e.message}</div>` node = yo`<div>Unable to display "${propertyName}": ${e.message}</div>`
} }
@ -383,373 +341,56 @@ module.exports = class CompileTab {
} }
return yo`<pre class="${css.value}">${node || ''}</pre>` return yo`<pre class="${css.value}">${node || ''}</pre>`
} }
function publish () {
const selectContractNames = self._view.contractNames getContractProperty (property) {
if (selectContractNames.children.length > 0 && selectContractNames.selectedIndex >= 0) { if (!this.selectedContract) throw new Error('No contract compiled yet')
var contract = self.data.contractsDetails[selectContractNames.children[selectContractNames.selectedIndex].innerHTML] const contractProperties = this.data.contractsDetails[this.selectedContract]
if (contract.metadata === undefined || contract.metadata.length === 0) { return contractProperties[property] || null
modalDialogCustom.alert('This contract may be abstract, may not implement an abstract parent\'s methods completely or not invoke an inherited contract\'s constructor correctly.')
} else {
publishOnSwarm(contract, self._deps.fileManager, function (err, uploaded) {
if (err) {
try {
err = JSON.stringify(err)
} catch (e) {}
modalDialogCustom.alert(yo`<span>Failed to publish metadata file to swarm, please check the Swarm gateways is available ( swarm-gateways.net ).<br />
${err}</span>`)
} else {
var result = yo`<div>${uploaded.map((value) => {
return yo`<div><b>${value.filename}</b> : <pre>${value.output.url}</pre></div>`
})}</div>`
modalDialogCustom.alert(yo`<span>Metadata published successfully.<br> <pre>${result}</pre> </span>`)
}
}, function (item) { // triggered each time there's a new verified publish (means hash correspond)
self._deps.swarmfileProvider.addReadOnly(item.hash, item.content)
})
}
}
}
return self._view.el
}
setVersionText (text) {
const self = this
self.data.version = text
if (self._view.version) self._view.version.innerText = text
}
_updateVersionSelector () {
const self = this
self._view.versionSelector.innerHTML = ''
self._view.versionSelector.appendChild(yo`<option disabled selected>Select new compiler version</option>`)
self.data.allversions.forEach(build => self._view.versionSelector.appendChild(yo`<option value=${build.path}>${build.longVersion}</option>`))
self._view.versionSelector.removeAttribute('disabled')
self._components.queryParams.update({ version: self.data.selectedVersion })
var url
if (self.data.selectedVersion === 'builtin') {
var 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 (self.data.selectedVersion.indexOf('soljson') !== 0 || helper.checkSpecialChars(self.data.selectedVersion)) {
return console.log('loading ' + self.data.selectedVersion + ' not allowed')
}
url = `${self.data.baseurl}/${self.data.selectedVersion}`
}
var 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.
self._components.compiler.loadVersion(true, url)
self.setVersionText('(loading using worker)')
} else {
self._components.compiler.loadVersion(false, url)
self.setVersionText('(loading)')
} }
copyContractProperty (property) {
let content = this.getContractProperty(property)
if (!content) {
addTooltip('No content available for ' + property)
return
} }
fetchAllVersion (callback) {
var self = this
minixhr(`${self.data.baseurl}/list.json`, function (json, event) {
// @TODO: optimise and cache results to improve app loading times
var allversions, selectedVersion
if (event.type !== 'error') {
try { try {
const data = JSON.parse(json) if (typeof content !== 'string') {
allversions = data.builds.slice().reverse() content = JSON.stringify(content, null, '\t')
selectedVersion = self.data.defaultVersion
if (self._components.queryParams.get().version) selectedVersion = self._components.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)
})
}
runCompiler () {
const self = this
self._deps.fileManager.saveCurrentFile()
self._deps.editor.clearAnnotations()
var currentFile = self._deps.config.get('currentFile')
if (currentFile) {
if (/\.sol$/.exec(currentFile)) {
// only compile *.sol file.
var target = currentFile
var sources = {}
var provider = self._deps.fileManager.fileProviderOf(currentFile)
if (provider) {
provider.get(target, (error, content) => {
if (error) {
console.log(error)
} else {
sources[target] = { content }
self._components.compiler.compile(sources, target)
}
})
} else {
console.log('cannot compile ' + currentFile + '. Does not belong to any explorer')
}
}
}
}
importExternal (url, cb) {
const self = this
self._components.compilerImport.import(url,
(loadingMsg) => {
addTooltip(loadingMsg)
},
(error, content, cleanUrl, type, url) => {
if (!error) {
if (self._deps.fileProviders[type]) {
self._deps.fileProviders[type].addReadOnly(cleanUrl, content, url)
}
cb(null, content)
} else {
cb(error)
}
})
}
importFileCb (url, filecb) {
const self = this
if (url.indexOf('/remix_tests.sol') !== -1) {
return filecb(null, remixTests.assertLibCode)
}
var provider = self._deps.fileManager.fileProviderOf(url)
if (provider) {
if (provider.type === 'localhost' && !provider.isConnected()) {
return filecb(`file provider ${provider.type} not available while trying to resolve ${url}`)
} }
provider.exists(url, (error, exist) => { } catch (e) {}
if (error) return filecb(error)
if (exist) { copy(content)
return provider.get(url, filecb) addTooltip('Copied value to clipboard')
} else {
self.importExternal(url, filecb)
}
})
} else if (self._components.compilerImport.isRelativeImport(url)) {
// try to resolve localhost modules (aka truffle imports)
var splitted = /([^/]+)\/(.*)$/g.exec(url)
async.tryEach([
(cb) => { self.importFileCb('localhost/installed_contracts/' + url, cb) },
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { self.importFileCb('localhost/installed_contracts/' + splitted[1] + '/contracts/' + splitted[2], cb) } },
(cb) => { self.importFileCb('localhost/node_modules/' + url, cb) },
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { self.importFileCb('localhost/node_modules/' + splitted[1] + '/contracts/' + splitted[2], cb) } }],
(error, result) => { filecb(error, result) }
)
} else {
self.importExternal(url, filecb)
} }
copyABI () {
this.copyContractProperty('abi')
} }
copyBytecode () {
this.copyContractProperty('bytecode')
} }
const css = csjs` render () {
.title { if (this._view.el) return this._view.el
font-size: 1.1em; this.listenToEvents()
font-weight: bold; this.compilerContainer.activate()
margin-bottom: 1em;
this._view.errorContainer = yo`<div class="${css.errorBlobs}"></div>`
this._view.contractSelection = this.contractSelection()
this._view.compilerContainer = this.compilerContainer.render()
this._view.el = yo`
<div id="compileTabView">
${this._view.compilerContainer}
${this._view.contractSelection}
${this._view.errorContainer}
</div>`
return this._view.el
} }
.panicError {
color: red;
font-size: 20px;
} }
.crow {
display: flex; module.exports = CompileTab
overflow: auto;
clear: both;
padding: .2em;
}
.checkboxText {
font-weight: normal;
}
.crow label {
cursor:pointer;
}
.crowNoFlex {
overflow: auto;
clear: both;
}
.select {
font-weight: bold;
margin: 10px 0px;
${styles.rightPanel.settingsTab.dropdown_SelectCompiler};
}
.info {
${styles.rightPanel.settingsTab.box_SolidityVersionInfo}
margin-bottom: 1em;
word-break: break-word;
}
.compileTabView {
padding: 2%;
}
.contract {
display: block;
margin: 3% 0;
}
.compileContainer {
${styles.rightPanel.compileTab.box_CompileContainer};
margin-bottom: 2%;
}
.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 {
color: ${styles.rightPanel.compileTab.icon_WarnCompilation_Color};
margin-left: 1%;
}
.compileButtons {
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: flex-end;
}
.name {
display: flex;
}
.size {
display: flex;
}
.checkboxes {
display: flex;
width: 100%;
justify-content: space-between;
flex-wrap: wrap;
}
.compileButton {
${styles.rightPanel.compileTab.button_Compile};
width: 100%;
margin: 15px 0 10px 0;
font-size: 12px;
}
.container {
${styles.rightPanel.compileTab.box_CompileContainer};
margin: 0;
margin-bottom: 2%;
}
.contractContainer {
display: flex;
align-items: center;
margin-bottom: 2%;
}
.optimizeContainer {
display: flex;
}
.contractNames {
${styles.rightPanel.compileTab.dropdown_CompileContract};
width:78%;
}
.contractHelperButtons {
display: flex;
cursor: pointer;
text-align: center;
justify-content: flex-end;
margin: 15px 15px 10px 0;
}
.copyButton {
${styles.rightPanel.compileTab.button_Publish};
padding: 0 7px;
min-width: 50px;
width: auto;
margin-left: 5px;
background-color: inherit;
border: inherit;
}
.bytecodeButton {
min-width: 80px;
}
.copyIcon {
margin-right: 5px;
}
.details {
${styles.rightPanel.compileTab.button_Details};
min-width: 70px;
width: 80px;
}
.publish {
display: flex;
align-items: center;
margin-left: 10px;
cursor: pointer;
}
.log {
${styles.rightPanel.compileTab.box_CompileContainer};
display: flex;
flex-direction: column;
margin-bottom: 5%;
overflow: visible;
}
.key {
margin-right: 5px;
color: ${styles.rightPanel.text_Primary};
text-transform: uppercase;
width: 100%;
}
.value {
display: flex;
width: 100%;
margin-top: 1.5%;
}
.questionMark {
margin-left: 2%;
cursor: pointer;
color: ${styles.rightPanel.icon_Color_TogglePanel};
}
.questionMark:hover {
color: ${styles.rightPanel.icon_HoverColor_TogglePanel};
}
.detailsJSON {
padding: 8px 0;
background-color: ${styles.rightPanel.modalDialog_BackgroundColor_Primary};
border: none;
color: ${styles.rightPanel.modalDialog_text_Secondary};
}
.icon {
margin-right: 0.3em;
}
.spinningIcon {
margin-right: .3em;
animation: spin 2s linear infinite;
}
.bouncingIcon {
margin-right: .3em;
animation: bounce 2s infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@-webkit-keyframes bounce {
0% {
margin-bottom: 0;
color: ${styles.colors.transparent};
}
70% {
margin-bottom: 0;
color: ${styles.rightPanel.text_Secondary};
}
100% {
margin-bottom: 0;
color: ${styles.colors.transparent};
}
}
`

@ -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') 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 () { constructor () {
super(profile)
this.el = null 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 Recorder = require('./runTab/model/recorder.js')
var RecorderUI = require('./runTab/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) { constructor (udapp, udappUI, config, fileManager, editor, logCallback, filePanel, pluginManager, compilersArtefacts) {
super(profile)
this.event = new EventManager() this.event = new EventManager()
this.config = config
this.renderInstanceContainer() this.udapp = udapp
this.renderSettings(udapp) this.udappUI = udappUI
this.renderDropdown(udappUI, fileManager, pluginManager, compilersArtefacts, config, editor, udapp, filePanel, logCallback) this.fileManager = fileManager
this.renderRecorder(udapp, udappUI, fileManager, config, logCallback) this.editor = editor
this.renderRecorderCard() this.logCallback = logCallback
this.renderContainer() this.filePanel = filePanel
this.pluginManager = pluginManager
this.compilersArtefacts = compilersArtefacts
} }
renderContainer () { renderContainer () {
this.container = yo`<div class="${css.runTabView}" id="runTabView" ></div>` this.container = yo`<div class="${css.runTabView}" id="runTabView" ></div>`
var el = yo` var el = yo`
<div> <div class="list-group list-group-flush">
${this.settingsUI.render()} ${this.settingsUI.render()}
${this.contractDropdownUI.render()} ${this.contractDropdownUI.render()}
${this.recorderCard.render()} ${this.recorderCard.render()}
@ -37,6 +55,7 @@ class RunTab {
</div> </div>
` `
this.container.appendChild(el) this.container.appendChild(el)
return this.container
} }
renderInstanceContainer () { renderInstanceContainer () {
@ -46,7 +65,7 @@ class RunTab {
<div class=${css.instanceContainerTitle} <div class=${css.instanceContainerTitle}
title="Autogenerated generic user interfaces for interaction with deployed contracts"> title="Autogenerated generic user interfaces for interaction with deployed contracts">
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"> title="Clear instances list and reset recorder" aria-hidden="true">
</i> </i>
</div>` </div>`
@ -114,7 +133,7 @@ class RunTab {
renderRecorderCard () { renderRecorderCard () {
const collapsedView = yo` const collapsedView = yo`
<div class=${css.recorderCollapsedView}> <div class=${css.recorderCollapsedView}>
<div class=${css.recorderCount}>${this.recorderCount}</div> <div class="${css.recorderCount} badge badge-pill badge-primary">${this.recorderCount}</div>
</div>` </div>`
const expandedView = yo` const expandedView = yo`
@ -144,8 +163,18 @@ class RunTab {
} }
render () { 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 module.exports = RunTab

@ -18,6 +18,7 @@ class ContractDropdownUI {
listenToEvents () { listenToEvents () {
this.dropdownLogic.event.register('newlyCompiled', (success, data, source, compiler, compilerFullName) => { 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]}`) var contractNames = document.querySelector(`.${css.contractNames.classNames[0]}`)
contractNames.innerHTML = '' contractNames.innerHTML = ''
if (success) { if (success) {
@ -43,11 +44,11 @@ class ContractDropdownUI {
} }
render () { 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>` 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="fa fa-info ${css.infoDeployAction}" aria-hidden="true" title="*.sol files allows deploying and accessing contracts. *.abi files only allows accessing contracts."></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.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}" disabled></select>` this.selectContractNames = yo`<select class="${css.contractNames} custom-select" disabled></select>`
this.createPanel = yo`<div class="${css.button}"></div>` this.createPanel = yo`<div class="${css.button}"></div>`
this.orLabel = yo`<div class="${css.orLabel}">or</div>` this.orLabel = yo`<div class="${css.orLabel}">or</div>`
@ -60,18 +61,19 @@ class ContractDropdownUI {
${this.createPanel} ${this.createPanel}
${this.orLabel} ${this.orLabel}
<div class="${css.button} ${css.atAddressSect}"> <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} ${this.atAddressButtonInput}
</div> </div>
</div> </div>
</div> </div>
` `
this.selectContractNames.addEventListener('change', this.setInputParamsPlaceHolder.bind(this)) this.selectContractNames.addEventListener('change', this.setInputParamsPlaceHolder.bind(this))
this.setInputParamsPlaceHolder()
return el return el
} }
changeCurrentFile (currentFile) { changeCurrentFile (currentFile) {
if (!document.querySelector(`.${css.contractNames}`)) return
document.querySelector(`.${css.contractNames}`).classList.remove(css.contractNamesError) document.querySelector(`.${css.contractNames}`).classList.remove(css.contractNamesError)
var contractNames = document.querySelector(`.${css.contractNames.classNames[0]}`) var contractNames = document.querySelector(`.${css.contractNames.classNames[0]}`)
contractNames.innerHTML = '' contractNames.innerHTML = ''
@ -139,7 +141,7 @@ class ContractDropdownUI {
} }
var promptCb = (okCb, cancelCb) => { 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) => { var statusCb = (msg) => {
@ -153,7 +155,7 @@ class ContractDropdownUI {
return this.logCallback(error) return this.logCallback(error)
} }
this.event.trigger('newContractInstanceAdded', [contractObject, address, this.selectContractNames.value]) this.event.trigger('newContractInstanceAdded', [contractObject, address, contractObject.name])
} }
if (selectedContract.isOverSizeLimit()) { if (selectedContract.isOverSizeLimit()) {
@ -163,7 +165,7 @@ class ContractDropdownUI {
{ {
label: 'Force Send', label: 'Force Send',
fn: () => { 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', label: 'Cancel',
fn: () => { 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 () { loadFromAddress () {

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

@ -23,9 +23,9 @@ class RecorderUI {
.recorder {} .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` 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"> onclick=${this.triggerRecordButton.bind(this)} title="Save Transactions" aria-hidden="true">
</i>` </i>`
@ -56,7 +56,7 @@ class RecorderUI {
} }
var promptCb = (okCb, cancelCb) => { 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) => { var alertCb = (msg) => {
@ -76,7 +76,7 @@ class RecorderUI {
triggerRecordButton () { triggerRecordButton () {
this.recorder.saveScenario( this.recorder.saveScenario(
(path, cb) => { (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) => { (error) => {
if (error) return modalDialogCustom.alert(error) if (error) return modalDialogCustom.alert(error)

@ -1,18 +1,20 @@
var $ = require('jquery') const $ = require('jquery')
var yo = require('yo-yo') const yo = require('yo-yo')
var remixLib = require('remix-lib') const remixLib = require('remix-lib')
var EventManager = remixLib.EventManager const EventManager = remixLib.EventManager
var css = require('../styles/run-tab-styles') const css = require('../styles/run-tab-styles')
var copyToClipboard = require('../../ui/copy-to-clipboard') const copyToClipboard = require('../../ui/copy-to-clipboard')
var modalDialogCustom = require('../../ui/modal-dialog-custom') const modalDialogCustom = require('../../ui/modal-dialog-custom')
var addTooltip = require('../../ui/tooltip') const addTooltip = require('../../ui/tooltip')
var helper = require('../../../lib/helper.js') const helper = require('../../../lib/helper.js')
const globalRegistry = require('../../../global/registry')
class SettingsUI { class SettingsUI {
constructor (settings) { constructor (settings) {
this.settings = settings this.settings = settings
this.event = new EventManager() this.event = new EventManager()
this._components = {}
this.settings.event.register('transactionExecuted', (error, from, to, data, lookupOnly, txResult) => { this.settings.event.register('transactionExecuted', (error, from, to, data, lookupOnly, txResult) => {
if (error) return if (error) return
@ -20,6 +22,11 @@ class SettingsUI {
this.updateAccountBalances() this.updateAccountBalances()
}) })
this._components.registry = globalRegistry
this._deps = {
networkModule: this._components.registry.get('network').api
}
setInterval(() => { setInterval(() => {
this.updateAccountBalances() this.updateAccountBalances()
}, 10 * 1000) }, 10 * 1000)
@ -40,7 +47,7 @@ class SettingsUI {
} }
render () { render () {
this.netUI = yo`<span class=${css.network}></span>` this.netUI = yo`<span class="${css.network} badge badge-secondary"></span>`
var environmentEl = yo` var environmentEl = yo`
<div class="${css.crow}"> <div class="${css.crow}">
@ -48,8 +55,7 @@ class SettingsUI {
Environment Environment
</div> </div>
<div class=${css.environment}> <div class=${css.environment}>
${this.netUI} <select id="selectExEnvOptions" onchange=${() => { this.updateNetwork() }} class="form-control ${css.select}">
<select id="selectExEnvOptions" onchange=${() => { this.updateNetwork() }} class="${css.select}">
<option id="vm-mode" <option id="vm-mode"
title="Execution environment does not connect to any node, everything is local and in memory only." title="Execution environment does not connect to any node, everything is local and in memory only."
value="vm" checked name="executionContext"> JavaScript VM value="vm" checked name="executionContext"> JavaScript VM
@ -64,48 +70,59 @@ class SettingsUI {
value="web3" name="executionContext"> Web3 Provider value="web3" name="executionContext"> Web3 Provider
</option> </option>
</select> </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>
</div> </div>
` `
const networkEl = yo`
var accountEl = 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.crow}">
<div class="${css.col1_1}"> <div class="${css.col1_1}">
Account 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>
<div class=${css.account}> <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)} ${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>
</div> </div>
` `
var gasPriceEl = yo` const gasPriceEl = yo`
<div class="${css.crow}"> <div class="${css.crow}">
<div class="${css.col1_1}">Gas limit</div> <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> </div>
` `
var valueEl = yo` const valueEl = yo`
<div class="${css.crow}"> <div class="${css.crow}">
<div class="${css.col1_1}">Value</div> <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"> <div class="${css.gasValueContainer}">
<select name="unit" class="${css.col2_2}" id="unit"> <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="wei">wei</option>
<option data-unit="gwei">gwei</option> <option data-unit="gwei">gwei</option>
<option data-unit="finney">finney</option> <option data-unit="finney">finney</option>
<option data-unit="ether">ether</option> <option data-unit="ether">ether</option>
</select> </select>
</div> </div>
</div>
` `
var el = yo` const el = yo`
<div class="${css.settings}"> <div class="${css.settings}">
${environmentEl} ${environmentEl}
${networkEl}
${accountEl} ${accountEl}
${gasPriceEl} ${gasPriceEl}
${valueEl} ${valueEl}
@ -134,7 +151,10 @@ class SettingsUI {
this.settings.event.register('addProvider', (network) => { this.settings.event.register('addProvider', (network) => {
selectExEnv.appendChild(yo`<option selectExEnv.appendChild(yo`<option
title="Manually added environment: ${network.url}" title="Manually added environment: ${network.url}"
value="${network.name}" name="executionContext"> ${network.name} value="${network.name}"
name="executionContext"
>
${network.name}
</option>`) </option>`)
addTooltip(`${network.name} [${network.url}] added`) addTooltip(`${network.name} [${network.url}] added`)
}) })
@ -150,8 +170,8 @@ class SettingsUI {
selectExEnv.addEventListener('change', (event) => { selectExEnv.addEventListener('change', (event) => {
let context = selectExEnv.options[selectExEnv.selectedIndex].value let context = selectExEnv.options[selectExEnv.selectedIndex].value
this.settings.changeExecutionContext(context, () => { this.settings.changeExecutionContext(context, () => {
modalDialogCustom.confirm(null, 'Are you sure you want to connect to an ethereum node?', () => { modalDialogCustom.confirm('External node request', 'Are you sure you want to connect to an ethereum node?', () => {
modalDialogCustom.prompt(null, 'Web3 Provider Endpoint', 'http://localhost:8545', (target) => { modalDialogCustom.prompt('External node request', 'Web3 Provider Endpoint', 'http://localhost:8545', (target) => {
this.settings.setProviderFromEndpoint(target, context, (alertMsg) => { this.settings.setProviderFromEndpoint(target, context, (alertMsg) => {
if (alertMsg) { if (alertMsg) {
modalDialogCustom.alert(alertMsg) modalDialogCustom.alert(alertMsg)
@ -229,7 +249,8 @@ class SettingsUI {
this.netUI.innerHTML = 'can\'t detect network ' this.netUI.innerHTML = 'can\'t detect network '
return 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 yo = require('yo-yo')
var csjs = require('csjs-inject') const globalRegistry = require('../../global/registry')
var remixLib = require('remix-lib')
const defaultPlugins = require('../plugin/plugins')
var globalRegistry = require('../../global/registry')
var tooltip = require('../ui/tooltip') var tooltip = require('../ui/tooltip')
var copyToClipboard = require('../ui/copy-to-clipboard') 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 EventManager = require('../../lib/events')
var css = require('./styles/settings-tab-styles')
import { BaseApi } from 'remix-plugin'
module.exports = class SettingsTab { const profile = {
constructor (localRegistry) { name: 'settings',
const self = this displayName: 'Settings',
self._components = {} methods: [],
self._components.registry = localRegistry || globalRegistry events: [],
// dependencies icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xMTUyIDg5NnEwLTEwNi03NS0xODF0LTE4MS03NS0xODEgNzUtNzUgMTgxIDc1IDE4MSAxODEgNzUgMTgxLTc1IDc1LTE4MXptNTEyLTEwOXYyMjJxMCAxMi04IDIzdC0yMCAxM2wtMTg1IDI4cS0xOSA1NC0zOSA5MSAzNSA1MCAxMDcgMTM4IDEwIDEyIDEwIDI1dC05IDIzcS0yNyAzNy05OSAxMDh0LTk0IDcxcS0xMiAwLTI2LTlsLTEzOC0xMDhxLTQ0IDIzLTkxIDM4LTE2IDEzNi0yOSAxODYtNyAyOC0zNiAyOGgtMjIycS0xNCAwLTI0LjUtOC41dC0xMS41LTIxLjVsLTI4LTE4NHEtNDktMTYtOTAtMzdsLTE0MSAxMDdxLTEwIDktMjUgOS0xNCAwLTI1LTExLTEyNi0xMTQtMTY1LTE2OC03LTEwLTctMjMgMC0xMiA4LTIzIDE1LTIxIDUxLTY2LjV0NTQtNzAuNXEtMjctNTAtNDEtOTlsLTE4My0yN3EtMTMtMi0yMS0xMi41dC04LTIzLjV2LTIyMnEwLTEyIDgtMjN0MTktMTNsMTg2LTI4cTE0LTQ2IDM5LTkyLTQwLTU3LTEwNy0xMzgtMTAtMTItMTAtMjQgMC0xMCA5LTIzIDI2LTM2IDk4LjUtMTA3LjV0OTQuNS03MS41cTEzIDAgMjYgMTBsMTM4IDEwN3E0NC0yMyA5MS0zOCAxNi0xMzYgMjktMTg2IDctMjggMzYtMjhoMjIycTE0IDAgMjQuNSA4LjV0MTEuNSAyMS41bDI4IDE4NHE0OSAxNiA5MCAzN2wxNDItMTA3cTktOSAyNC05IDEzIDAgMjUgMTAgMTI5IDExOSAxNjUgMTcwIDcgOCA3IDIyIDAgMTItOCAyMy0xNSAyMS01MSA2Ni41dC01NCA3MC41cTI2IDUwIDQxIDk4bDE4MyAyOHExMyAyIDIxIDEyLjV0OCAyMy41eiIvPjwvc3ZnPg==',
self._deps = { description: 'Remix-IDE settings',
config: self._components.registry.get('config').api, kind: 'settings',
editorPanel: self._components.registry.get('editorpanel').api, location: 'swapPanel'
editor: self._components.registry.get('editor').api
} }
self._view = { /* eslint-disable */
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
}
this._view = { /* eslint-disable */
el: null, el: null,
optionVM: null, personal: null, warnPersonalMode: null, generateContractMetadata: null, optionVM: null, personal: null, warnPersonalMode: null, generateContractMetadata: null,
pluginInput: null, versionSelector: null, version: null,
theme: { dark: null, light: null, clean: null },
plugins: {},
config: { config: {
general: null, themes: null, general: null, themes: null
plugin: null
} }
} /* eslint-enable */ } /* eslint-enable */
self.data = {} this.event = new EventManager()
self.event = new EventManager() }
self._components.themeStorage = new Storage('style:')
self.data.currentTheme = self._components.themeStorage.get('theme') || 'light' 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 () { render () {
const self = this const self = this
if (self._view.el) return self._view.el if (self._view.el) return self._view.el
// Gist settings // Gist settings
var gistAccessToken = yo`<input id="gistaccesstoken" type="password">` var gistAccessToken = yo`<input id="gistaccesstoken" type="password" class="form-control mb-2 ${css.inline}" placeholder="Token">`
var token = self._deps.config.get('settings/gist-access-token') var token = this.config.get('settings/gist-access-token')
if (token) gistAccessToken.value = token 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 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 id="removegisttoken" onclick=${() => { gistAccessToken.value = ''; self._deps.config.set('settings/gist-access-token', ''); tooltip('Access token removed') }} value="Remove" 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">`
self._view.gistToken = yo`<div class="${css.checkboxText}">${gistAccessToken}${copyToClipboard(() => self._deps.config.get('settings/gist-access-token'))}${gistAddToken}${gistRemoveToken}</div>` 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">`
self._view.optionVM = yo`<input onchange=${onchangeOption} id="alwaysUseVM" type="checkbox">` if (this.config.get('settings/always-use-vm')) this._view.optionVM.setAttribute('checked', '')
if (self._deps.config.get('settings/always-use-vm')) self._view.optionVM.setAttribute('checked', '') this._view.personal = yo`<input onchange=${onchangePersonal} id="personal" type="checkbox" class="align-middle form-check-input">`
self._view.personal = yo`<input onchange=${onchangePersonal} id="personal" type="checkbox">` if (this.config.get('settings/personal-mode')) this._view.personal.setAttribute('checked', '')
if (self._deps.config.get('settings/personal-mode')) self._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. 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. 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, ...). 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. 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(' ') 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>` this._view.warnPersonalMode = yo`<i title=${warnText} class="${css.icon} fas fa-exclamation-triangle text-warning" aria-hidden="true"></i>`
self._view.generateContractMetadata = yo`<input onchange=${onchangeGenerateContractMetadata} id="generatecontractmetadata" type="checkbox">` this._view.generateContractMetadata = yo`<input onchange=${onchangeGenerateContractMetadata} id="generatecontractmetadata" type="checkbox" class="form-check-input">`
if (self._deps.config.get('settings/generate-contract-metadata')) self._view.generateContractMetadata.setAttribute('checked', '')
self._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">` if (this.config.get('settings/generate-contract-metadata')) this._view.generateContractMetadata.setAttribute('checked', '')
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')
self._view.config.general = yo` this._view.pluginInput = yo`<textarea rows="4" cols="70" id="plugininput" type="text" class="${css.pluginTextArea}" ></textarea>`
<div class="${css.info}">
<div class=${css.title}>General settings</div> this._view.themes = this._deps.themeModule.getThemes()
<div class="${css.crow}"> this._view.themesCheckBoxes = this.createThemeCheckies()
<div>${self._view.generateContractMetadata}</div> this._view.config.homePage = yo`
<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> <div class="${css.info} card">
<div class="${css.crow}"> <div class="card-body">
<div>${self._view.optionVM}</div> <h6 class="${css.title} card-title">Have a question?</h6>
<span class="${css.checkboxText}">Always use Ethereum VM at Load</span> <button class="btn btn-primary sm-1" onclick="${() => { window.open('https://gitter.im/ethereum/remix') }}">Gitter Channel</button>
</div> </div>
<div class="${css.crow}"> </div>`
<div><input id="editorWrap" type="checkbox" onchange=${function () { self._deps.editor.resize(this.checked) }}></div> this._view.config.general = yo`
<span class="${css.checkboxText}">Text Wrap</span> <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>
<div class="${css.crow}"> <div class="form-check ${css.frow}">
<div>${self._view.personal}></div> <div>${this._view.optionVM}</div>
<span class="${css.checkboxText}">Enable Personal Mode ${self._view.warnPersonalMode}></span> <label class="form-check-label align-middle" for="alwaysUseVM">Always use Ethereum VM at Load</label>
</div> </div>
<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>
` <div class="form-check ${css.frow}">
self._view.gistToken = yo` <div>${this._view.personal}></div>
<div class="${css.info}"> <label class="form-check-label align-middle" for="personal">Enable Personal Mode ${this._view.warnPersonalMode}></label>
<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>
<div class="${css.crow}">
${self._view.theme.light}
<label for="themeLight">Light Theme</label>
</div> </div>
<div class="${css.crow}">
${self._view.theme.dark}
<label for="themeDark">Dark Theme</label>
</div> </div>
<div class="${css.crow}"> `
${self._view.theme.clean} this._view.gistToken = yo`
<label for="themeClean">Clean Theme</label> <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>
</div>` </div>`
self._view.config.plugins = yo`<div></div>` this._view.config.themes = yo`
self._view.config.plugin = yo` <div class="${css.info} card">
<div class="${css.info}"> <div class="card-body">
<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> <h6 class="${css.title} card-title">Themes</h6>
<div class="${css.crowNoFlex}"> ${this._view.themesCheckBoxes}
<div>Load plugin from JSON description: </div>
${self._view.pluginInput}
<input onclick=${onloadPluginJson} type="button" value="Load" class="${css.initPlugin}">
${self._view.config.plugins}
</div> </div>
</div>` </div>`
self._view.el = yo` this._view.el = yo`
<div class="${css.settingsTabView}" id="settingsView"> <div class="${css.settingsTabView}" id="settingsView">
${self._view.config.general} ${this._view.config.homePage}
${self._view.config.plugin} ${this._view.config.general}
${self._view.gistToken} ${this._view.gistToken}
${self._view.config.themes} ${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>` </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) { 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) { function onchangeOption (event) {
self._deps.config.set('settings/always-use-vm', !self._deps.config.get('settings/always-use-vm')) self.config.set('settings/always-use-vm', !self.config.get('settings/always-use-vm'))
}
function onswitch2darkTheme (event) {
styleGuide.switchTheme('dark')
window.location.reload()
}
function onswitch2lightTheme (event) {
styleGuide.switchTheme('light')
window.location.reload()
}
function onswitch2cleanTheme (event) {
styleGuide.switchTheme('clean')
window.location.reload()
} }
function onchangePersonal (event) { 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;
}
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 { this._deps.themeModule.switchTheme()
cursor: pointer; return this._view.el
} }
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 { .analysisTabView {
padding: 2%; padding: 2%;
padding-bottom: 3em; 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 csjs = require('csjs-inject')
var styles = require('../../ui/styles-guide/theme-chooser').chooser()
const css = csjs` const css = csjs`
.debuggerTabView { .debuggerTabView {
@ -7,7 +6,6 @@ const css = csjs`
} }
.debugger { .debugger {
margin-bottom: 1%; margin-bottom: 1%;
${styles.rightPanel.debuggerTab.box_Debugger}
} }
` `

@ -1,6 +1,4 @@
var csjs = require('csjs-inject') var csjs = require('csjs-inject')
var styleGuide = require('../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs` var css = csjs`
.runTabView { .runTabView {
@ -14,28 +12,25 @@ var css = csjs`
font-size: 12px; font-size: 12px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding-left: 15px;
} }
.settings { .settings {
${styles.rightPanel.runTab.box_RunTab}
margin-bottom: 2%; margin-bottom: 2%;
padding: 10px 15px 15px 15px; padding: 10px 0px 15px 15px;
} }
.recorderCount { .recorderCount {
border: 1px solid ${styles.rightPanel.runTab.icon_HoverColor}; /* margin-right: 30px; */
border-radius: 50%; /* min-width: 13px; */
margin-right: 30px; /* display: flex; */
min-width: 13px; /* justify-content: center; */
height: 13px; /* align-items: center; */
display: flex; /* font-size: 10px; */
justify-content: center;
align-items: center;
font-size: 10px;
} }
.crow { .crow {
margin-top: .5em; margin-top: .5em;
display: flex; display: flex;
align-items: center; align-items: center;
width: 500px; /*width: 500px;*/
} }
.col1 { .col1 {
width: 30%; width: 30%;
@ -44,7 +39,6 @@ var css = csjs`
} }
.col1_1 { .col1_1 {
font-size: 12px; font-size: 12px;
width: 15%;
min-width: 75px; min-width: 75px;
float: left; float: left;
align-self: center; align-self: center;
@ -53,43 +47,36 @@ var css = csjs`
display: flex; display: flex;
align-items: center; align-items: center;
position: relative; position: relative;
width: 259px; width: 100%;
padding-right: 25px;
} }
.account { .account {
display: flex; display: flex;
align-items: center; align-items: center;
width: 266px; width: 90%;
} }
.col2 { .col2 {
${styles.rightPanel.runTab.input_RunTab}
border-radius: 3px; border-radius: 3px;
} }
.col2_1 { .col2_1 {
${styles.rightPanel.runTab.input_RunTab}
width: 164px; width: 164px;
min-width: 164px; min-width: 164px;
} }
.col2_2 { .col2_2 {
${styles.rightPanel.runTab.dropdown_RunTab}
width: 82px;
min-width: 82px;
} }
.select { .select {
${styles.rightPanel.runTab.dropdown_RunTab}
font-weight: normal; font-weight: normal;
width: 250px; width: 100%;
} }
.instanceContainer { .instanceContainer {
${styles.rightPanel.runTab.box_Instance}
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-bottom: 2%; margin-bottom: 2%;
border: none; border: none;
text-align: center; text-align: center;
padding: 10px 0px 15px 15px; padding: 10px 0px 15px 0px;
} }
.pendingTxsContainer { .pendingTxsContainer {
${styles.rightPanel.runTab.box_Instance}
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-top: 2%; margin-top: 2%;
@ -97,8 +84,8 @@ var css = csjs`
text-align: center; text-align: center;
} }
.container { .container {
${styles.rightPanel.runTab.box_RunTab} margin-bottom: 4%;
margin-bottom: 2%; padding-left: 15px;
} }
.recorderCollapsedView, .recorderCollapsedView,
.recorderExpandedView { .recorderExpandedView {
@ -109,12 +96,10 @@ var css = csjs`
margin: 0 15px 15px 0; margin: 0 15px 15px 0;
} }
.contractNames { .contractNames {
${styles.rightPanel.runTab.dropdown_RunTab}
width: 100%; width: 100%;
border: 1px solid border: 1px solid
} }
.contractNamesError { .contractNamesError {
border: 1px solid ${styles.appProperties.errorText_Color}
} }
.subcontainer { .subcontainer {
display: flex; display: flex;
@ -127,18 +112,16 @@ var css = csjs`
margin-top: 13px; margin-top: 13px;
} }
.transaction { .transaction {
${styles.rightPanel.runTab.button_transaction}
} }
.atAddress { .atAddress {
margin: 0; margin: 0;
min-width: 100px; min-width: 100px;
width: 100px; width: 100px;
font-size: 10px; /* font-size: 10px; */
word-break: inherit; word-break: inherit;
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
border-right: 0; border-right: 0;
${styles.rightPanel.runTab.button_atAddress}
} }
.atAddressSect { .atAddressSect {
margin-top: 6px; margin-top: 6px;
@ -147,20 +130,20 @@ var css = csjs`
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
} }
.ataddressinput {
padding: .25rem;
}
.create { .create {
${styles.rightPanel.runTab.button_Create}
} }
.input { .input {
${styles.rightPanel.runTab.input_RunTab};
font-size: 10px; font-size: 10px;
} }
.noInstancesText { .noInstancesText {
${styles.rightPanel.runTab.box_Instance}
font-style: italic; font-style: italic;
text-align: left; text-align: left;
padding-left: 15px;
} }
.pendingTxsText { .pendingTxsText {
${styles.rightPanel.runTab.borderBox_Instance}
font-style: italic; font-style: italic;
display: flex; display: flex;
justify-content: space-evenly; justify-content: space-evenly;
@ -173,15 +156,15 @@ var css = csjs`
align-items: center; align-items: center;
} }
.transact { .transact {
color: ${styles.colors.lightRed}; color: var(--warning);
margin-right: .3em; margin-right: .3em;
} }
.payable { .payable {
color: ${styles.colors.red}; color: var(--warning);
margin-right: .3em; margin-right: .3em;
} }
.call { .call {
color: ${styles.colors.lightBlue}; color: var(--info);
margin-right: .3em; margin-right: .3em;
} }
.pendingContainer { .pendingContainer {
@ -199,31 +182,23 @@ var css = csjs`
cursor: pointer; cursor: pointer;
font-size: 12px; font-size: 12px;
cursor: pointer; cursor: pointer;
color: ${styles.rightPanel.runTab.icon_Color};
margin-left: 5px; margin-left: 5px;
} }
.icon:hover { .icon:hover {
font-size: 12px; font-size: 12px;
color: ${styles.rightPanel.runTab.icon_HoverColor}; color: var(--warning);
} }
.errorIcon { .errorIcon {
color: ${styles.appProperties.errorText_Color}; color: var(--warning);
margin-left: 15px; margin-left: 15px;
} }
.failDesc { .failDesc {
color: ${styles.appProperties.errorText_Color}; color: var(--warning);
padding-left: 10px; padding-left: 10px;
display: inline; display: inline;
} }
.network { .network {
display: flex; margin-left: 8px;
justify-content: flex-end;
align-items: center;
position: absolute;
color: grey;
width: 100%;
height: 100%;
padding-right: 28px;
pointer-events: none; pointer-events: none;
} }
.networkItem { .networkItem {
@ -235,7 +210,6 @@ var css = csjs`
.transactionActions { .transactionActions {
display: flex; display: flex;
justify-content: space-evenly; justify-content: space-evenly;
${styles.rightPanel.runTab.box_Info_RunTab};
width: 145px; width: 145px;
} }
.orLabel { .orLabel {
@ -244,6 +218,24 @@ var css = csjs`
.infoDeployAction { .infoDeployAction {
margin-left: 5px; margin-left: 5px;
font-size: 13px; 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 csjs = require('csjs-inject')
var styleGuide = require('../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs` const css = csjs`
.settingsTabView { .settingsTabView {
padding: 2%; padding: 2%;
display: flex;
} }
.info { .info {
${styles.rightPanel.settingsTab.box_SolidityVersionInfo} margin-bottom: .6rem;
margin-bottom: 1em;
word-break: break-word; word-break: break-word;
font-size: .8rem;
}
.info h7 {
margin-bottom: .5rem;
} }
.title { .title {
font-size: 1.1em; // font-size: 1.1em;
font-weight: bold; // font-weight: bold;
margin-bottom: 1em; // margin-bottom: 1em;
}
.frow {
margin-bottom: .5rem;
} }
.crow { .crow {
display: flex; // display: flex;
overflow: auto; // overflow: auto;
clear: both; // clear: both;
padding: .2em; // padding: .2em;
} }
.checkboxText { .checkboxText {
font-weight: normal; font-weight: normal;
@ -38,11 +41,6 @@ var css = csjs`
padding: .5em; padding: .5em;
font-weight: bold; font-weight: bold;
} }
.select {
font-weight: bold;
margin-top: 1em;
${styles.rightPanel.settingsTab.dropdown_SelectCompiler}
}
.heading { .heading {
margin-bottom: 0; margin-bottom: 0;
} }
@ -53,6 +51,7 @@ var css = csjs`
input { input {
margin-right: 5px; margin-right: 5px;
cursor: pointer; cursor: pointer;
width: inherit;
} }
input[type=radio] { input[type=radio] {
margin-top: 2px; margin-top: 2px;
@ -60,23 +59,35 @@ var css = csjs`
.pluginTextArea { .pluginTextArea {
font-family: unset; font-family: unset;
} }
.pluginLoad {
vertical-align: top; .removePlugin {
} cursor: pointer;
i.warnIt {
color: ${styles.appProperties.warningText_Color};
} }
.icon { .icon {
margin-right: .5em; margin-right: .5em;
} }
.remixdinstallation {
padding: 3px;
border-radius: 2px;
margin-left: 5px;
}
.savegisttoken { .savegisttoken {
margin-left: 5px; margin-left: 5px;
} }
.aPlugin {
display: inline-block;
padding-left: 10px;
padding-top: 4px;
padding-bottom: 6px;
max-width: 100px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
vertical-align: middle;
}
.removePlugin{
padding-left: 7px;
padding-right: 7px;
margin-left: 10px;
}
.inline {
display: inline;
width: 50%;
} }
` `

@ -1,5 +1,4 @@
const csjs = require('csjs-inject') const csjs = require('csjs-inject')
const styles = require('../../ui/styles-guide/theme-chooser').chooser()
const css = csjs` const css = csjs`
.supportTabView { .supportTabView {
@ -12,7 +11,6 @@ const css = csjs`
overflow-y: auto; overflow-y: auto;
} }
.chat { .chat {
${styles.rightPanel.supportTab.box_IframeContainer}
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@ -49,7 +47,6 @@ const css = csjs`
border: none; border: none;
} }
.infoBox { .infoBox {
${styles.rightPanel.supportTab.box_SupportInfo}
} }
.remixdinstallation { .remixdinstallation {
padding: 3px; padding: 3px;
@ -57,7 +54,6 @@ const css = csjs`
margin-left: 5px; margin-left: 5px;
} }
.info { .info {
${styles.rightPanel.settingsTab.box_SolidityVersionInfo};
margin-top: 1em; margin-top: 1em;
word-break: break-word; word-break: break-word;
} }

@ -1,25 +1,25 @@
var csjs = require('csjs-inject') var csjs = require('csjs-inject')
var styleGuide = require('../../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs` var css = csjs`
.testTabView {} .testTabView {}
.infoBox { .infoBox {
${styles.rightPanel.testTab.box_listTests};
margin: 2%; margin: 2%;
} }
.tests {} .tests {}
.testList { .testList {
${styles.rightPanel.testTab.box_listTests};
line-height: 2em; line-height: 2em;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin: 2%; margin: 2%;
max-height: 300px;
overflow-y: auto;
} }
.container { .container {
${styles.rightPanel.testTab.box_listTests};
margin: 2%; margin: 2%;
padding-bottom: 5%; padding-bottom: 5%;
max-height: 300px;
overflow-y: auto;
} }
.outputTitle { .outputTitle {
font-weight: bold; font-weight: bold;
@ -29,7 +29,6 @@ var css = csjs`
font-weight: bold; font-weight: bold;
} }
.testPass { .testPass {
background-color: ${styles.rightPanel.testTab.color_testPass};
} }
.testLog { .testLog {
margin-bottom: 1%; margin-bottom: 1%;
@ -37,22 +36,17 @@ var css = csjs`
padding: 1% 1% 1% 5%; padding: 1% 1% 1% 5%;
} }
.testFailure { .testFailure {
background-color: ${styles.rightPanel.testTab.color_testFail};
} }
.testFailureSummary { .testFailureSummary {
color: ${styles.appProperties.errorText_Color};
} }
.buttons { .buttons {
${styles.rightPanel.testTab.box_listTests};
margin: 2%; margin: 2%;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.runButton { .runButton {
${styles.rightPanel.testTab.button_runTests};
} }
.generateTestFile { .generateTestFile {
${styles.rightPanel.testTab.button_generateTestFile};
min-width: 100px min-width: 100px
} }
.title { .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,149 +1,82 @@
var yo = require('yo-yo') var yo = require('yo-yo')
var async = require('async') var async = require('async')
var helper = require('../../lib/helper.js')
var tooltip = require('../ui/tooltip') 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 css = require('./styles/test-tab-styles')
var remixTests = require('remix-tests') var remixTests = require('remix-tests')
import { BaseApi } from 'remix-plugin'
module.exports = class TestTab { const TestTabLogic = require('./testTab/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>`
}
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) { const profile = {
// total stats for the test name: 'solidityUnitTesting',
// result.passingNum displayName: 'Solidity unit testing',
// result.failureNum methods: [],
// result.timePassed events: [],
cb() 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'
} }
var updateFinalResult = function (_err, result, filename) { module.exports = class TestTab extends BaseApi {
testsSummary.hidden = false constructor (fileManager, filePanel, compileTab) {
if (_err) { super(profile)
testsSummary.appendChild(yo`<div class=${css.testFailureSummary} >${_err.message}</div>`) this.compileTab = compileTab
return this._view = { el: null }
} this.compileTab = compileTab
testsSummary.appendChild(yo`<div class=${css.summaryTitle}> ${filename} </div>`) this.fileManager = fileManager
if (result.totalPassing > 0) { this.filePanel = filePanel
testsSummary.appendChild(yo`<div>${result.totalPassing} passing (${result.totalTime}s)</div>`) this.testTabLogic = new TestTabLogic(fileManager)
testsSummary.appendChild(yo`<br>`) this.data = {}
} this.testList = yo`<div class=${css.testList}></div>`
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) { activate () {
self._deps.fileManager.fileProviderOf(testFilePath).get(testFilePath, (error, content) => { this.listenToEvents()
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) })
}
})
} }
function getTests (self, cb) { deactivate () {
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)
}
})
} }
self._deps.filePanel.event.register('newTestFileCreated', file => { listenToEvents () {
var testList = self.view.querySelector("[class^='testList']") this.filePanel.event.register('newTestFileCreated', file => {
var test = yo`<label class="singleTestLabel"><input class="singleTest" onchange=${(e) => toggleCheckbox(e.target.checked, file)} type="checkbox" checked="true">${file}</label>` 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) testList.appendChild(test)
self.data.allTests.push(file) this.data.allTests.push(file)
self.data.selectedTests.push(file) this.data.selectedTests.push(file)
}) })
self._deps.fileManager.event.register('currentFileChanged', (file, provider) => { this.fileManager.events.on('currentFileChanged', (file, provider) => {
getTests(self, (error, tests) => { this.testTabLogic.getTests((error, tests) => {
if (error) return tooltip(error) if (error) return tooltip(error)
self.data.allTests = tests this.data.allTests = tests
self.data.selectedTests = [...self.data.allTests] this.data.selectedTests = [...this.data.allTests]
if (!tests.length) {
yo.update(self.testList, yo`<div class=${css.testList}>No test file available</div>`) const testsMessage = (tests.length ? this.listTests() : 'No test file available')
} else { yo.update(this.testList, yo`<div class=${css.testList}>${testsMessage}</div>`)
yo.update(self.testList, yo`<div class=${css.testList}>${listTests()}</div>`)
} if (!this.testsOutput || !this.testsSummary) return
testsOutput.hidden = true
testsSummary.hidden = true this.testsOutput.hidden = true
testsOutput.innerHTML = '' this.testsSummary.hidden = true
testsSummary.innerHTML = '' this.testsOutput.innerHTML = ''
this.testsSummary.innerHTML = ''
}) })
}) })
}
// self._events.filePanel.register('fileRenamed', (oldName, newName, isFolder) => { listTests () {
// debugger 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>`)
// 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)
// })
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>`)
} }
function toggleCheckbox (eChecked, test) { toggleCheckbox (eChecked, test) {
if (!self.data.selectedTests) { if (!this.data.selectedTests) {
self.data.selectedTests = self._view.el.querySelectorAll('.singleTest:checked') this.data.selectedTests = this._view.el.querySelectorAll('.singleTest:checked')
} }
let selectedTests = self.data.selectedTests let selectedTests = this.data.selectedTests
selectedTests = eChecked ? [...selectedTests, test] : selectedTests.filter(el => el !== test) selectedTests = eChecked ? [...selectedTests, test] : selectedTests.filter(el => el !== test)
self.data.selectedTests = selectedTests this.data.selectedTests = selectedTests
let checkAll = self._view.el.querySelector('[id="checkAllTests"]') let checkAll = this._view.el.querySelector('[id="checkAllTests"]')
if (eChecked) { if (eChecked) {
checkAll.checked = true checkAll.checked = true
} else if (!selectedTests.length) { } else if (!selectedTests.length) {
@ -151,43 +84,89 @@ module.exports = class TestTab {
} }
} }
function checkAll (event) { checkAll (event) {
let checkBoxes = self._view.el.querySelectorAll('.singleTest') let checkBoxes = this._view.el.querySelectorAll('.singleTest')
const checkboxesLabels = self._view.el.querySelectorAll('.singleTestLabel') const checkboxesLabels = this._view.el.querySelectorAll('.singleTestLabel')
// checks/unchecks all // checks/unchecks all
for (let i = 0; i < checkBoxes.length; i++) { for (let i = 0; i < checkBoxes.length; i++) {
checkBoxes[i].checked = event.target.checked checkBoxes[i].checked = event.target.checked
toggleCheckbox(event.target.checked, checkboxesLabels[i].innerText) this.toggleCheckbox(event.target.checked, checkboxesLabels[i].innerText)
} }
} }
var runTests = function () { testCallback (result) {
testsOutput.innerHTML = '' this.testsOutput.hidden = false
testsSummary.innerHTML = '' if (result.type === 'contract') {
var tests = self.data.selectedTests this.testsOutput.appendChild(yo`<div class=${css.outputTitle}>${result.filename} (${result.value})</div>`)
async.eachOfSeries(tests, (value, key, callback) => { runTest(value, callback) }) } 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 () { updateFinalResult (_err, result, filename) {
var fileManager = self._deps.fileManager this.testsSummary.hidden = false
var path = fileManager.currentPath() if (_err) {
var fileProvider = fileManager.fileProviderOf(path) this.testsSummary.appendChild(yo`<div class="${css.testFailureSummary} text-danger" >${_err.message}</div>`)
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, testContractSample)) {
modalDialogCustom.alert('Failed to create test file ' + newFile)
} else {
fileManager.switchFile(newFile)
} }
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` var el = yo`
<div class="${css.testTabView}" id="testView"> <div class="${css.testTabView} card" id="testView">
<div class="${css.infoBox}"> <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). Test your smart contract by creating a foo_test.sol file (open ballot_test.sol to see the example).
<br/> <br/>
You will find more informations in the <a href="https://remix.readthedocs.io/en/latest/unittesting_tab.html">documentation</a> 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/> <br/>
For more details, see For more details, see
How to test smart contracts guide in our documentation. 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>
<div class="${css.tests}"> <div class="${css.tests}">
${self.testList}
<div class="${css.buttons}"> <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"> <label class="${css.label}" for="checkAllTests">
<input id="checkAllTests" <input id="checkAllTests"
type="checkbox" type="checkbox"
onclick="${function (event) { checkAll(event) }}" onclick="${(event) => { this.checkAll(event) }}"
checked="true" checked="true"
> >
Check/Uncheck all Check/Uncheck all
</label> </label>
</div> </div>
${testsOutput} ${this.testList}
${testsSummary} <hr>
<div class="${css.buttons}" ><h6>Results:${this.loading}</h6></div>
${this.testsOutput}
${this.testsSummary}
</div> </div>
</div> </div>
` `
if (!self._view.el) self._view.el = el if (!this._view.el) this._view.el = el
return 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; flex-shrink: 0;
} }
.label_tv { .label_tv {
display: flex;
align-items: center; align-items: center;
} }
` `
@ -78,7 +77,7 @@ class TreeView {
formatData (key, data, children, expand, keyPath) { formatData (key, data, children, expand, keyPath) {
var self = this var self = this
var li = yo`<li key=${keyPath} class=${css.li_tv}></li>` 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` var label = yo`
<div key=${keyPath} class=${css.label_tv}> <div key=${keyPath} class=${css.label_tv}>
${caret} ${caret}
@ -88,7 +87,7 @@ class TreeView {
if (data.children) { if (data.children) {
var list = yo`<ul key=${keyPath} class=${css.ul_tv}>${children}</ul>` var list = yo`<ul key=${keyPath} class=${css.ul_tv}>${children}</ul>`
list.style.display = 'none' 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 () { label.onclick = function () {
self.expand(keyPath) self.expand(keyPath)
} }
@ -121,7 +120,7 @@ class TreeView {
var node = this.nodeAt(path) var node = this.nodeAt(path)
if (node) { if (node) {
node.style.display = node.style.display === 'none' ? 'block' : 'none' 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]) this.event.trigger('nodeClick', [path, node])
} }
} }

@ -1,12 +1,12 @@
var yo = require('yo-yo') var yo = require('yo-yo')
var remixLib = require('remix-lib') var remixLib = require('remix-lib')
var EventManager = remixLib.EventManager var EventManager = remixLib.EventManager
var Commands = require('../constants/commands')
var modal = require('./modaldialog.js') var modal = require('./modaldialog.js')
// -------------- styling ---------------------- // -------------- styling ----------------------
var css = require('./styles/auto-complete-popup-styles') var css = require('./styles/auto-complete-popup-styles')
var cssModal = require('./styles/modaldialog-styles')
/* USAGE: /* USAGE:
@ -22,114 +22,204 @@ class AutoCompletePopup {
constructor (opts = {}) { constructor (opts = {}) {
var self = this var self = this
self.event = new EventManager() self.event = new EventManager()
self.isOpen = false
self.opts = opts
self.data = { self.data = {
_options: opts.options || [] _options: []
}
self._components = {
modal: null
} }
self._view = {} self._view = {}
self._startingElement = 0 self._startingElement = 0
self._elementsToShow = 3 self._elementsToShow = 4
self._removePopUp = this.resetCSSValuesModalContainer self._selectedElement = 0
} this.extraCommands = []
this.render()
resetCSSValuesModalContainer () { this.extendAutocompletion()
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
} }
render () { render () {
var self = this var self = this
var header = yo`<div class="${css.text}">Remix Commands</div>`
self._view.autoComplete = yo` self._view.autoComplete = yo`
<div class="${css.popup}"> <div class="${css.popup}">
<div> <div>
${self.data._options.map((item, index) => { ${self.data._options.map((item, index) => {
return yo` return yo`
<div class="${css.listHandlerHide}"> <div class="${css.autoCompleteItem} ${css.listHandlerHide} item ${self._selectedElement === index ? 'bg-secondary' : ''}">
<a value=${index}> <div value=${index} onclick=${(event) => { self.handleSelect(event.srcElement.innerText) }}>
<div onclick=${handleSelect}> ${getKeyOf(item)}
${Object.keys(item)[0]}
</div> </div>
</a>
<div> <div>
${Object.values(item)[0]} ${getValueOf(item)}
</div> </div>
<hr/>
</div> </div>
` `
})} })}
</div> </div>
<div class="${css.listHandlerHide}"> <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 class="${css.pageNumberAlignment}">Page ${(self._startingElement / self._elementsToShow) + 1} of ${Math.ceil(self.data._options.length / self._elementsToShow)}</div>
</div> </div>
</div> </div>
` `
function setUpPopUp () { function setUpPopUp () {
handleOpenPopup() handleOpenPopup()
handleNagivationButtons()
handleListSize() handleListSize()
} }
function handleOpenPopup () { function handleOpenPopup () {
if (self.data._options.length > 1) { if (self.data._options.length > 0) {
self._view.autoComplete.style.display = 'block' self._view.autoComplete.style.display = 'block'
modal(header.innerText, self._view.autoComplete, {label: null}, self._components.modal = modal('', self._view.autoComplete, {label: null}, {label: null}, null, { class: css.modalContent, hideClose: true })
{
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
} }
} }
function handleListSize () { function handleListSize () {
if (self.data._options.length >= self._startingElement) { if (self.data._options.length >= self._startingElement) {
for (let i = self._startingElement; i < (self._elementsToShow + self._startingElement); i++) { for (let i = self._startingElement; i < (self._elementsToShow + self._startingElement); i++) {
if (self._view.autoComplete.children[0].children[i]) { let el = self._view.autoComplete.querySelectorAll('.item')[i]
self._view.autoComplete.children[0].children[i].className = css.listHandlerShow if (el) {
el.classList.remove(css.listHandlerHide)
el.classList.add(css.listHandlerShow)
} }
} }
} }
} }
function handleListIteration (event) { setUpPopUp()
if (event.srcElement.value === 'true' || event.which === 40) {
if ((self._startingElement + self._elementsToShow) < self.data._options.length) { return self._view
self._startingElement += self._elementsToShow
} }
} else {
if (self._startingElement > 0) { handleSelect (text) {
self._startingElement -= self._elementsToShow 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)
} }
})
} }
self.event.trigger('updateList') })
this.extraCommands.forEach(item => {
const command = getKeyOf(item)
if (command.includes(autoCompleteInput.trim())) {
this.data._options.push(item)
} }
})
function editCSSValuesModalContainer () { if (this.data._options.length === 1 && event.which === 9) {
var modalContent = document.querySelector(`.${cssModal.modalContent}`) // if only one option and tab is pressed, we resolve it
let newModalContent = modalContent ? document.querySelector(`.${cssModal.modalContent}`) : document.querySelector(`.${css.modalContent}`) event.preventDefault()
newModalContent.className = css.modalContent textList.pop()
textList.push(getKeyOf(this.data._options[0]))
this.handleSelect(`${textList}`.replace(/,/g, ' '))
this.removeAutoComplete()
return
}
yo.update(this._view, this.render())
return true
}
return false
}
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)
})
})
}
} }
setUpPopUp() function getKeyOf (item) {
return self._view return Object.keys(item)[0]
} }
function getValueOf (item) {
return Object.values(item)[0]
} }
module.exports = AutoCompletePopup module.exports = AutoCompletePopup

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

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

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

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

@ -41,15 +41,23 @@ class Dropdown {
self._view.selected = yo` self._view.selected = yo`
<div class=${css.selectbox}> <div class=${css.selectbox}>
<span class=${css.selected}> [${self.data.selected.length}] ${self.data.selected.join(', ')}</span> <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> </div>
` `
self._view.el = yo` 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} ${self._view.selected}
<div class=${css.options} style="display: none;"> <div class="${css.options} bg-light" style="display: none;}">
${self.data._options.map(label => { ${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) { if (self.data.selected.indexOf(label) !== -1) {
input.checked = true input.checked = true
self.event.trigger('select', [label]) self.event.trigger('select', [label])
@ -58,7 +66,7 @@ class Dropdown {
return yo` return yo`
<div class=${css.option}> <div class=${css.option}>
${input} ${input}
<label>${label}</label> <label class="text-dark" for="${label}">${label}</label>
</div> </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) { function prompt (title, text, hidden, inputValue, ok, cancel, focus) {
if (!inputValue) inputValue = '' if (!inputValue) inputValue = ''
var type = hidden ? 'password' : 'text' 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>`, modal(title, yo`<div>${text}<div>${input}</div></div>`,
{ {
fn: () => { if (typeof ok === 'function') ok(document.getElementById('prompt_text').value) } fn: () => { if (typeof ok === 'function') ok(document.getElementById('prompt_text').value) }

@ -1,61 +1,94 @@
var yo = require('yo-yo') var yo = require('yo-yo')
var css = require('./styles/modaldialog-styles') var css = require('./styles/modaldialog-styles')
module.exports = (title, content, ok, cancel, focusSelector) => { module.exports = (title, content, ok, cancel, focusSelector, opts) => {
var container = document.querySelector(`.${css.modal}`) let agreed = true
let footerIsActive = true
opts = opts || {}
var container = document.querySelector(`.modal`)
if (!container) { if (!container) {
document.querySelector('body').appendChild(html()) document.querySelector('body').appendChild(html(opts))
container = document.querySelector(`.${css.modal}`) container = document.querySelector(`.modal`)
} }
var closeDiv = document.getElementById('modal-close') var closeDiv = document.getElementById('modal-close')
if (opts.hideClose) closeDiv.style.display = 'none'
var okDiv = document.getElementById('modal-footer-ok') var okDiv = document.getElementById('modal-footer-ok')
okDiv.innerHTML = (ok && ok.label !== undefined) ? ok.label : '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') var cancelDiv = document.getElementById('modal-footer-cancel')
cancelDiv.innerHTML = (cancel && cancel.label !== undefined) ? cancel.label : '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 modal = document.querySelector(`.modal-body`)
var modalTitle = document.querySelector(`.${css.modalHeader} h2`) var modalTitle = document.querySelector(`.modal-header h6`)
modalTitle.innerHTML = '' modalTitle.innerHTML = ''
if (title) modalTitle.innerHTML = title if (title) modalTitle.innerText = title
modal.innerHTML = '' modal.innerHTML = ''
if (content) modal.appendChild(content) if (content) modal.appendChild(content)
setFocusOn('ok')
show() 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 () { function okListener () {
removeEventListener() removeEventListener()
hide() hide()
if (ok && ok.fn) ok.fn() if (ok && ok.fn && agreed) ok.fn()
} }
function cancelListener () { function cancelListener () {
removeEventListener() removeEventListener()
hide() hide()
if (cancel && cancel.fn) cancel.fn() if (cancel && cancel.fn) cancel.fn()
if (container) {
container.class = `modal`
container = null
}
} }
function modalKeyEvent (e) { function modalKeyEvent (e) {
if (e.keyCode === 27) { if (e.keyCode === 27) { // Esc
cancelListener() cancelListener()
} else if (e.keyCode === 13) { } else if (e.keyCode === 13) { // Enter
e.preventDefault() e.preventDefault()
okListener() 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 () { function hide () {
container.style.display = 'none' if (container) container.style.display = 'none'
} }
function show () { function show () {
if (!container) return
container.style.display = 'block' container.style.display = 'block'
if (focusSelector) { if (focusSelector) {
const focusTarget = document.querySelector(`.${css.modal} ${focusSelector}`) const focusTarget = document.querySelector(`.modal ${focusSelector}`)
if (focusTarget) { if (focusTarget) {
focusTarget.focus() focusTarget.focus()
if (typeof focusTarget.setSelectionRange === 'function') { if (typeof focusTarget.setSelectionRange === 'function') {
@ -70,27 +103,43 @@ module.exports = (title, content, ok, cancel, focusSelector) => {
cancelDiv.removeEventListener('click', cancelListener) cancelDiv.removeEventListener('click', cancelListener)
closeDiv.removeEventListener('click', cancelListener) closeDiv.removeEventListener('click', cancelListener)
document.removeEventListener('keydown', modalKeyEvent) document.removeEventListener('keydown', modalKeyEvent)
if (document.getElementById('modal-background')) {
document.getElementById('modal-background').removeEventListener('click', cancelListener) document.getElementById('modal-background').removeEventListener('click', cancelListener)
} }
}
okDiv.addEventListener('click', okListener) okDiv.addEventListener('click', okListener)
cancelDiv.addEventListener('click', cancelListener) cancelDiv.addEventListener('click', cancelListener)
closeDiv.addEventListener('click', cancelListener) closeDiv.addEventListener('click', cancelListener)
document.addEventListener('keydown', modalKeyEvent) 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 () { function html (opts) {
return yo`<div id="modal-dialog" class="${css.modal}"> return yo`
<div id="modal-background" class="${css['modalBackground']}"> </div> <div id="modal-dialog" class="modal" tabindex="-1" role="dialog">
<div class="${css['modalContent']}"> <div id="modal-background" class="modal-dialog" role="document">
<div class="${css['modalHeader']}"> <div class="modal-content ${css.modalContent} ${opts.class}">
<h2></h2> <div class="modal-header">
<i id="modal-close" title="Close" class="fa fa-times ${css['modalClose']}" aria-hidden="true"></i> <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>
<div class="${css['modalBody']}"> - <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 class="${css['modalFooter']}">
<span id="modal-footer-ok" class=${css['modalFooterOk']}>OK</span><span id="modal-footer-cancel" class=${css['modalFooterCancel']}>Cancel</span>
</div> </div>
</div> </div>
</div>` </div>`

@ -16,7 +16,6 @@ function Renderer (localRegistry) {
self._components.registry = localRegistry || globlalRegistry self._components.registry = localRegistry || globlalRegistry
// dependencies // dependencies
self._deps = { self._deps = {
editor: self._components.registry.get('editor').api,
fileManager: self._components.registry.get('filemanager').api, fileManager: self._components.registry.get('filemanager').api,
config: self._components.registry.get('config').api config: self._components.registry.get('config').api
} }
@ -27,13 +26,15 @@ function Renderer (localRegistry) {
Renderer.prototype._error = function (file, error) { Renderer.prototype._error = function (file, error) {
const self = this const self = this
const editor = self._components.registry.get('editor').api
if (file === self._deps.config.get('currentFile')) { if (file === self._deps.config.get('currentFile')) {
self._deps.editor.addAnnotation(error) editor.addAnnotation(error)
} }
} }
Renderer.prototype._errorClick = function (errFile, errLine, errCol) { Renderer.prototype._errorClick = function (errFile, errLine, errCol) {
const self = this const self = this
const editor = self._components.registry.get('editor').api
if (errFile !== self._deps.config.get('currentFile')) { if (errFile !== self._deps.config.get('currentFile')) {
// TODO: refactor with this._components.contextView.jumpTo // TODO: refactor with this._components.contextView.jumpTo
var provider = self._deps.fileManager.fileProviderOf(errFile) var provider = self._deps.fileManager.fileProviderOf(errFile)
@ -41,11 +42,11 @@ Renderer.prototype._errorClick = function (errFile, errLine, errCol) {
provider.exists(errFile, (error, exist) => { provider.exists(errFile, (error, exist) => {
if (error) return console.log(error) if (error) return console.log(error)
self._deps.fileManager.switchFile(errFile) self._deps.fileManager.switchFile(errFile)
self._deps.editor.gotoLine(errLine, errCol) editor.gotoLine(errLine, errCol)
}) })
} }
} else { } 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 $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) $(container).append($error)
$error.click((ev) => { $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