Merge branch 'master' into ci-build

pull/12/head
Aniket 4 years ago committed by GitHub
commit d7ccf193fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      .env
  2. 3
      apps/remix-ide/.babelrc
  3. 38
      apps/remix-ide/.circleci/config.yml
  4. 1
      apps/remix-ide/.dockerignore
  5. 6
      apps/remix-ide/.env
  6. 4
      apps/remix-ide/.gitignore
  7. 6
      apps/remix-ide/Dockerfile
  8. 28
      apps/remix-ide/Dockerfile.dev
  9. 37
      apps/remix-ide/README.md
  10. BIN
      apps/remix-ide/assets/img/deployAndRun.webp
  11. BIN
      apps/remix-ide/assets/img/fileManager.webp
  12. BIN
      apps/remix-ide/assets/img/ipfs.webp
  13. BIN
      apps/remix-ide/assets/img/localPlugin.webp
  14. BIN
      apps/remix-ide/assets/img/pluginManager.webp
  15. BIN
      apps/remix-ide/assets/img/remixLogo.webp
  16. BIN
      apps/remix-ide/assets/img/settings.webp
  17. BIN
      apps/remix-ide/assets/img/solidity.webp
  18. BIN
      apps/remix-ide/assets/img/staticAnalysis.webp
  19. BIN
      apps/remix-ide/assets/img/swarm.webp
  20. BIN
      apps/remix-ide/assets/img/unitTesting.webp
  21. 181
      apps/remix-ide/assets/js/editor/darkTheme.js
  22. 15
      apps/remix-ide/build.yaml
  23. 6
      apps/remix-ide/ci/browser_tests.sh
  24. 4
      apps/remix-ide/ci/browser_tests_chrome.sh
  25. 4
      apps/remix-ide/ci/browser_tests_firefox.sh
  26. 16
      apps/remix-ide/ci/build_and_publish_docker_images.sh
  27. 18
      apps/remix-ide/docker-compose.yaml
  28. 2
      apps/remix-ide/docs/plugin_manager.md
  29. 12
      apps/remix-ide/docs/run.md
  30. 2
      apps/remix-ide/embark/index.js
  31. 40
      apps/remix-ide/nginx.conf
  32. 17984
      apps/remix-ide/package-lock.json
  33. 28
      apps/remix-ide/package.json
  34. 53
      apps/remix-ide/src/app.js
  35. 4
      apps/remix-ide/src/app/compiler/compiler-abstract.js
  36. 28
      apps/remix-ide/src/app/compiler/compiler-artefacts.js
  37. 6
      apps/remix-ide/src/app/compiler/compiler-helpers.js
  38. 6
      apps/remix-ide/src/app/compiler/compiler-sourceVerifier-fetchAndCompile.js
  39. 54
      apps/remix-ide/src/app/compiler/compiler-utils.js
  40. 2
      apps/remix-ide/src/app/components/local-plugin.js
  41. 2
      apps/remix-ide/src/app/components/plugin-manager-component.js
  42. 17
      apps/remix-ide/src/app/editor/editor.js
  43. 4
      apps/remix-ide/src/app/editor/example-contracts.js
  44. 43
      apps/remix-ide/src/app/files/file-explorer.js
  45. 51
      apps/remix-ide/src/app/files/fileManager.js
  46. 67
      apps/remix-ide/src/app/files/fileProvider.js
  47. 245
      apps/remix-ide/src/app/files/remixDProvider.js
  48. 54
      apps/remix-ide/src/app/files/remixd-handle.js
  49. 6
      apps/remix-ide/src/app/panels/file-panel.js
  50. 11
      apps/remix-ide/src/app/panels/main-view.js
  51. 35
      apps/remix-ide/src/app/panels/styles/terminal-styles.js
  52. 13
      apps/remix-ide/src/app/panels/terminal.js
  53. 2
      apps/remix-ide/src/app/tabs/analysis-tab.js
  54. 63
      apps/remix-ide/src/app/tabs/compile-tab.js
  55. 2
      apps/remix-ide/src/app/tabs/compileTab/compileTab.js
  56. 107
      apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js
  57. 14
      apps/remix-ide/src/app/tabs/debugger-tab.js
  58. 95
      apps/remix-ide/src/app/tabs/debugger/debuggerUI.js
  59. 4
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/ButtonNavigator.js
  60. 2
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/Slider.js
  61. 9
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/StepManager.js
  62. 16
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/TxBrowser.js
  63. 48
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/VmDebugger.js
  64. 8
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/styles/basicStyles.js
  65. 13
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/vmDebugger/CodeListView.js
  66. 43
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/vmDebugger/DropdownPanel.js
  67. 17
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/vmDebugger/FunctionPanel.js
  68. 3
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/vmDebugger/SolidityState.js
  69. 2
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/vmDebugger/StepDetail.js
  70. 2
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/vmDebugger/utils/SolidityTypeFormatter.js
  71. 3
      apps/remix-ide/src/app/tabs/runTab/model/dropdownlogic.js
  72. 4
      apps/remix-ide/src/app/tabs/settings-tab.js
  73. 2
      apps/remix-ide/src/app/tabs/staticanalysis/staticAnalysisView.js
  74. 91
      apps/remix-ide/src/app/tabs/test-tab.js
  75. 53
      apps/remix-ide/src/app/tabs/testTab/testTab.js
  76. 10
      apps/remix-ide/src/app/tabs/theme-module.js
  77. 2
      apps/remix-ide/src/app/udapp/make-udapp.js
  78. 2
      apps/remix-ide/src/app/udapp/run-tab.js
  79. 32
      apps/remix-ide/src/app/ui/TreeView.js
  80. 4
      apps/remix-ide/src/app/ui/landing-page/landing-page.js
  81. 10
      apps/remix-ide/src/framingService.js
  82. 1
      apps/remix-ide/src/lib/helper.js
  83. 16
      apps/remix-ide/src/lib/offsetToLineColumnConverter.js
  84. 3
      apps/remix-ide/test-browser/commands/clickLaunchIcon.js
  85. 27
      apps/remix-ide/test-browser/commands/getInstalledPlugins.js
  86. 3
      apps/remix-ide/test-browser/commands/goToVMTraceStep.js
  87. 28
      apps/remix-ide/test-browser/commands/noWorkerErrorFor.js
  88. 2
      apps/remix-ide/test-browser/commands/verifyCallReturnValue.js
  89. 14
      apps/remix-ide/test-browser/commands/verifyContracts.js
  90. 27
      apps/remix-ide/test-browser/helpers/init.js
  91. 100
      apps/remix-ide/test-browser/tests/compiler_api.test.js
  92. 18
      apps/remix-ide/test-browser/tests/debugger.test.js
  93. 2
      apps/remix-ide/test-browser/tests/defaultLayout.test.js
  94. 2
      apps/remix-ide/test-browser/tests/editor.test.js
  95. 6
      apps/remix-ide/test-browser/tests/fileExplorer.test.js
  96. 190
      apps/remix-ide/test-browser/tests/fileManager_api.test.js
  97. 21
      apps/remix-ide/test-browser/tests/generalSettings.test.js
  98. 2
      apps/remix-ide/test-browser/tests/gist.test.js
  99. 4
      apps/remix-ide/test-browser/tests/libraryDeployment.test.js
  100. 12
      apps/remix-ide/test-browser/tests/pluginManager.test.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,3 +1,3 @@
gist_token = <token>
account_passphrase = <passphrase>
account_password = <password>
gist_token=<token>
account_passphrase=<passphrase>
account_password=<password>

@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env"]
}

@ -7,7 +7,7 @@ jobs:
remix-ide-chrome:
docker:
# specify the version you desire here
- image: circleci/node:10.18.0-browsers
- image: circleci/node:9.11.2-browsers
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
@ -41,7 +41,7 @@ jobs:
remix-ide-firefox:
docker:
# specify the version you desire here
- image: circleci/node:10.18.0-browsers
- image: circleci/node:9.11.2-browsers
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
@ -81,7 +81,7 @@ jobs:
remix-ide-run-deploy:
docker:
# specify the version you desire here
- image: circleci/node:10.18.0-browsers
- image: circleci/node:9.11.2-browsers
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
@ -113,7 +113,7 @@ jobs:
deploy-remix-live:
docker:
# specify the version you desire here
- image: circleci/node:10.18.0-browsers
- image: circleci/node:9.11.2-browsers
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
@ -139,10 +139,33 @@ jobs:
- store_artifacts:
path: ./reports/screenshots
publish-docker:
docker:
# specify the version you desire here
- image: circleci/node:10.19.0-buster
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
resource_class: xlarge
# - image: circleci/mongo:3.4.4
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "assets background.js build icon.png index.html manifest.json README.md soljson.js package.json"
working_directory: ~/remix-ide
steps:
- checkout
- setup_remote_docker
- run: npm install
- run: npm run build
- run: ./ci/build_and_publish_docker_images.sh
deploy-remix-alpha:
docker:
# specify the version you desire here
- image: circleci/node:10.18.0-browsers
- image: circleci/node:9.11.2-browsers
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
@ -175,6 +198,11 @@ workflows:
- remix-ide-chrome
- remix-ide-firefox
- remix-ide-run-deploy
- publish-docker:
requires:
- remix-ide-chrome
- remix-ide-firefox
- remix-ide-run-deploy
- deploy-remix-live:
requires:
- remix-ide-chrome

@ -1,3 +1,3 @@
gist_token = <token>
account_passphrase = <passphrase>
account_password = <password>
gist_token=<token>
account_passphrase=<passphrase>
account_password=<password>

@ -1,3 +1,6 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
.idea
.vscode
build
@ -12,3 +15,4 @@ remix
contracts
TODO
.tern-port
temp_publish_docker

@ -0,0 +1,6 @@
FROM nginx:alpine
WORKDIR /
COPY ./temp_publish_docker/ /usr/share/nginx/html/
EXPOSE 80

@ -0,0 +1,28 @@
# This dockerfile is to build each branch seperately (for dev purpouses)
FROM node:10
# Create Remix user, don't use root!
# RUN yes | adduser --disabled-password remix && mkdir /app
# USER remix
# #Now do remix stuff
# USER remix
WORKDIR /home/remix
COPY ./ ./
RUN npm ci
RUN npm run build
FROM nginx:alpine
WORKDIR /
COPY --from=0 /home/remix/build/ /usr/share/nginx/html/build/
COPY --from=0 /home/remix/index.html /usr/share/nginx/html/index.html
COPY --from=0 /home/remix/nginx.conf /etc/nginx/nginx.conf
COPY --from=0 /home/remix/assets/ /usr/share/nginx/html/assets/
COPY --from=0 /home/remix/icon.png /usr/share/nginx/html/icon.png
COPY --from=0 /home/remix/background.js /usr/share/nginx/html/background.js
COPY --from=0 /home/remix/soljson.js /usr/share/nginx/html/soljson.js
COPY --from=0 /home/remix/package.json /usr/share/nginx/html/package.json
EXPOSE 80

@ -45,6 +45,43 @@ npm run setupremix # only if you plan to link remix and remix-ide repositories
npm start
```
## Docker:
Prerequisites:
* Docker (https://docs.docker.com/desktop/)
* Docker-compose (https://docs.docker.com/compose/install/)
### Run with docker
If you want to run latest changes that are merged into master branch then run:
```
docker pull remixproject/remix-ide:latest
docker run -p 8080:80 remixproject/remix-ide:latest
```
If you want to run latest remix-live release run.
```
docker pull remixproject/remix-ide:remix_live
docker run -p 8080:80 remixproject/remix-ide:remix_live
```
### Run with docker-compose:
To run locally without building you only need docker-compose.yaml file and you can run:
```
docker-compose pull
docker-compose up -d
```
Then go to http://localhost:8080 and you can use you Remix instance.
To fetch docker-compose file without cloning this repo run:
```
curl https://raw.githubusercontent.com/ethereum/remix-ide/master/docker-compose.yaml > docker-compose.yaml
```
## DEVELOPING:
Run `npm start` and open `http://127.0.0.1:4200` in your browser.

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

@ -0,0 +1,181 @@
/* eslint-disable */
ace.define("ace/theme/remixDark",["require","exports","module","ace/lib/dom"], function(acequire, exports, module) {
exports.isDark = true;
exports.cssClass = "ace-remixDark";
exports.cssText = ".ace-remixDark .ace_gutter {\
background: #2a2c3f;\
color: #8789a1;\
border-right: 1px solid #282828;\
}\
.ace-remixDark .ace_gutter-cell.ace_warning {\
background-image: none;\
background: #FC0;\
border-left: none;\
padding-left: 0;\
color: #000;\
}\
.ace-remixDark .ace_gutter-cell.ace_error {\
background-position: -6px center;\
background-image: none;\
background: #F10;\
border-left: none;\
padding-left: 0;\
color: #000;\
}\
.ace-remixDark .ace_print-margin {\
border-left: 1px solid #555;\
right: 0;\
background: #1D1D1D;\
}\
.ace-remixDark {\
background-color: #222336;\
color: #a2a3bd;\
}\
.ace-remixDark .ace_cursor {\
border-left: 2px solid #FFFFFF;\
}\
.ace-remixDark .ace_cursor.ace_overwrite {\
border-left: 0px;\
border-bottom: 1px solid #FFFFFF;\
}\
.ace-remixDark .ace_marker-layer .ace_selection {\
background: #494836;\
}\
.ace-remixDark .ace_marker-layer .ace_step {\
background: rgb(198, 219, 174);\
}\
.ace-remixDark .ace_marker-layer .ace_bracket {\
margin: -1px 0 0 -1px;\
border: 1px solid #FCE94F;\
}\
.ace-remixDark .ace_marker-layer .ace_active-line {\
background: #363950;\
}\
.ace-remixDark .ace_gutter-active-line {\
background-color: #363950;\
}\
.ace-remixDark .ace_invisible {\
color: #404040;\
}\
.ace-remixDark .ace_rparen {\
color: #d4d7ed;\
}\
.ace-remixDark .ace_lparen {\
color: #d4d7ed;\
}\
.ace-remixDark .ace_keyword {\
color:#ffa76d;\
}\
.ace-remixDark .ace_keyword.ace_operator {\
color:#eceeff;\
}\
.ace-remixDark .ace_constant {\
color:#1EDAFB;\
}\
.ace-remixDark .ace_constant.ace_language {\
color:#FDC251;\
}\
.ace-remixDark .ace_constant.ace_library {\
color:#8DFF0A;\
}\
.ace-remixDark .ace_constant.ace_numeric {\
color:#eceeff;\
}\
.ace-remixDark .ace_invalid {\
color:#FFFFFF;\
background-color:#990000;\
}\
.ace-remixDark .ace_invalid.ace_deprecated {\
color:#FFFFFF;\
background-color:#990000;\
}\
.ace-remixDark .ace_support {\
color: #999;\
}\
.ace-remixDark .ace_support.ace_function {\
color:#3fe2a7;\
}\
.ace-remixDark .ace_function {\
color:#3fe2a7;\
}\
.ace-remixDark .ace_string {\
color:#eceeff;\
}\
.ace-remixDark .ace_comment {\
color:#a7a7a7;\
font-style:italic;\
padding-bottom: 0px;\
}\
.ace-remixDark .ace_type {\
color:#75ceef;\
}]\
.ace-remixDark .ace_visibility (\
color:#f7d777;\
)\
.ace-remixDark .ace_identifier {\
color:#bec1dd;\
}\
.ace-remixDark .ace_modifier {\
color:#efff2f;\
}\
.ace-remixDark .ace-boolean {\
color:#ff86ac;\
}\
.ace-remixDark .ace_statemutability {\
color:#FFCC00;\
}\
.ace-remixDark .ace_variable {\
color:#e0bb83;\
}\
.ace-remixDark .ace_meta.ace_tag {\
color:#BE53E6;\
}\
.ace-remixDark .ace_entity.ace_other.ace_attribute-name {\
color:#4aa8fd;\
}\
.ace-remixDark .ace_markup.ace_underline {\
text-decoration: underline;\
}\
.ace-remixDark .ace_fold-widget {\
text-align: center;\
}\
.ace-remixDark .ace_fold-widget:hover {\
color: #777;\
}\
.ace-remixDark .ace_fold-widget.ace_start,\
.ace-remixDark .ace_fold-widget.ace_end,\
.ace-remixDark .ace_fold-widget.ace_closed{\
background: none;\
border: none;\
box-shadow: none;\
}\
.ace-remixDark .ace_fold-widget.ace_start:after {\
content: '▾'\
}\
.ace-remixDark .ace_fold-widget.ace_end:after {\
content: '▴'\
}\
.ace-remixDark .ace_fold-widget.ace_closed:after {\
content: '‣'\
}\
.ace-remixDark .ace_indent-guide {\
border-right:1px dotted #333;\
margin-right:-1px;\
}\
.ace-remixDark .ace_fold { \
background: #222; \
border-radius: 3px; \
color: #7AF; \
border: none; \
}\
.ace-remixDark .ace_fold:hover {\
background: #CCC; \
color: #000;\
}\
";
var dom = acequire("../lib/dom");
dom.importCssString(exports.cssText, exports.cssClass);
});

@ -0,0 +1,15 @@
version: "3.7"
x-project-base:
&project-base
restart: always
networks:
- remixide
networks:
remixide:
services:
remixide:
build:
context: .
dockerfile: Dockerfile

@ -21,8 +21,10 @@ setupRemixd
sleep 5
npm run nightwatch_parallel || TEST_EXITCODE=1
npm run nightwatch_local_runAndDeploy || TEST_EXITCODE=1
TESTFILES=$(circleci tests glob "./test-browser/tests/**/*.test.js" | circleci tests split --split-by=timings)
for TESTFILE in $TESTFILES; do
./node_modules/.bin/nightwatch --config nightwatch.js --env chrome $TESTFILE || TEST_EXITCODE=1
done
echo "$TEST_EXITCODE"
if [ "$TEST_EXITCODE" -eq 1 ]

@ -22,7 +22,9 @@ setupRemixd
sleep 5
TESTFILES=$(circleci tests glob "./apps/remix-ide/test-browser/tests/**/*.test.js" | circleci tests split --split-by=timings)
npm run nightwatch_local_chrome $TESTFILES
for TESTFILE in $TESTFILES; do
./node_modules/.bin/nightwatch --config nightwatch.js --env chrome $TESTFILE || TEST_EXITCODE=1
done
echo "$TEST_EXITCODE"
if [ "$TEST_EXITCODE" -eq 1 ]

@ -22,7 +22,9 @@ setupRemixd
sleep 5
TESTFILES=$(circleci tests glob "./apps/remix-ide/test-browser/tests/**/*.test.js" | circleci tests split --split-by=timings)
npm run nightwatch_local_firefox $TESTFILES
for TESTFILE in $TESTFILES; do
./node_modules/.bin/nightwatch --config nightwatch.js --env firefox $TESTFILE || TEST_EXITCODE=1
done
echo "$TEST_EXITCODE"
if [ "$TEST_EXITCODE" -eq 1 ]

@ -0,0 +1,16 @@
#!/bin/bash
set -e
export TAG="$CIRCLE_BRANCH"
if [ "$CIRCLE_BRANCH" == "master" ]; then
export TAG="latest";
fi
rm -rf temp_publish_docker
mkdir temp_publish_docker
cp -r $FILES_TO_PACKAGE temp_publish_docker
docker login --username $DOCKER_USER --password $DOCKER_PASS
docker-compose -f docker-compose.yaml -f build.yaml build
docker push remixproject/remix-ide:$TAG

@ -0,0 +1,18 @@
version: "3.7"
x-project-base:
&project-base
restart: always
networks:
- remixide
networks:
remixide:
services:
remixide:
<<: *project-base
image: remixproject/remix-ide:$TAG
container_name: remixide-${TAG}
ports:
- 8080:80
- 65520:65520

@ -4,7 +4,7 @@ Plugin Manager
## Everything is a PLUGIN in Remix
In order to integrate new tools made by us and by ...you into Remix, we've now made everything a plugin.
This architcture will also allow Remix or just parts of Remix to be integrated into other projects (your's for example).
This architecture will also allow Remix or just parts of Remix to be integrated into other projects (your's for example).
This means that you only load the functionality you need.

@ -108,16 +108,16 @@ Using the Recorder
The Recorder is a tool used to save a bunch of transactions in a JSON file and
rerun them later either in the same environment or in another.
Saving to the JSON file ( by default its called senario.json) allows one to easily check the transaction list, tweak input parameters, change linked library, etc...
Saving to the JSON file ( by default its called scenario.json) allows one to easily check the transaction list, tweak input parameters, change linked library, etc...
There are many use cases for the recorder.
For instance:
- After having coded and tested contracts in a constrained
environment (like the JavaScript VM), you could then change the environment and redeploy it to a more realistic environment like a test net with an **injected web3** or to a Geth node. By using the generated **senario.json** file, you will be using all the same settings that you used in the Javascript VM. And this mean that you won't need to click the interface 100 times or whatever to get the state that you achieved originally. So the recorder could be a tool to protect your sanity.
environment (like the JavaScript VM), you could then change the environment and redeploy it to a more realistic environment like a test net with an **injected web3** or to a Geth node. By using the generated **scenario.json** file, you will be using all the same settings that you used in the Javascript VM. And this mean that you won't need to click the interface 100 times or whatever to get the state that you achieved originally. So the recorder could be a tool to protect your sanity.
You can also change the settings in the senario.json file to customize the playback.
You can also change the settings in the scenario.json file to customize the playback.
- Deploying contract does often require more than creating one
transaction and so the recorder will automate this deployment.
@ -127,10 +127,10 @@ For instance:
![](images/a-runtab-recorder.png)
### senario.json
To create this file in the recorder, you first of course need to have run some transactions. In the image above - it has a `0` next to **Transactions Recorded**. So this isn't the right moment to save transactions because - well because there aren't any. Each time you make a transaction, that number will increment. Then when you are ready, click the floppy disk icon and the senario.json file will be created.
### scenario.json
To create this file in the recorder, you first of course need to have run some transactions. In the image above - it has a `0` next to **Transactions Recorded**. So this isn't the right moment to save transactions because - well because there aren't any. Each time you make a transaction, that number will increment. Then when you are ready, click the floppy disk icon and the scenario.json file will be created.
The JSON file below is an example of the senario.json file.
The JSON file below is an example of the scenario.json file.
In it, 3 transactions are executed:

@ -13,7 +13,7 @@ const DEFAULT_OPTIONS = {
module.exports = (embark) => {
// plugin options
const readOnly = embark.pluginConfig.readOnly || false
const {protocol, host, port} = merge.recursive(DEFAULT_OPTIONS, embark.pluginConfig.remixIde)
const { protocol, host, port } = merge.recursive(DEFAULT_OPTIONS, embark.pluginConfig.remixIde)
// globals
const remixIdeUrl = `${protocol}://${host}` + `${port ? `:${port}` : ''}`

@ -0,0 +1,40 @@
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 300;
gzip_disable "msie6";
include /etc/nginx/conf.d/*.conf;
server {
listen 80 default_server;
listen [::]:80 default_server;
root /usr/share/nginx/html;
index index.html index.htm;
server_name _;
location / {
try_files $uri $uri/ /index.html;
}
}
}

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{
"name": "remix-ide",
"version": "v0.10.1",
"version": "v0.10.2",
"description": "Extendable Web IDE for Ethereum",
"devDependencies": {
"@babel/core": "^7.4.5",
@ -49,20 +49,20 @@
"minixhr": "^3.2.2",
"mkdirp": "^0.5.1",
"nanohtml": "^1.6.3",
"nightwatch": "^1.3.5",
"nightwatch": "^0.9.20",
"notify-error": "^1.2.0",
"npm-link-local": "^1.1.0",
"npm-merge-driver": "^2.3.5",
"npm-run-all": "^4.0.2",
"onchange": "^3.2.1",
"remix-analyzer": "0.5.2",
"remix-debug": "0.4.4",
"remix-lib": "0.4.29",
"remix-simulator": "0.1.9-beta.5",
"remix-solidity": "0.3.30",
"remix-analyzer": "0.5.3",
"remix-debug": "0.4.5",
"remix-lib": "0.4.30",
"remix-simulator": "0.1.9-beta.6",
"remix-solidity": "0.3.31",
"remix-tabs": "1.0.48",
"remix-tests": "0.1.33",
"remixd": "0.1.8-alpha.10",
"remix-tests": "0.1.34",
"remixd": "0.2.3-alpha.1",
"request": "^2.83.0",
"rimraf": "^2.6.1",
"selenium-standalone": "^6.17.0",
@ -79,9 +79,8 @@
"yo-yoify": "^3.7.3"
},
"dependencies": {
"@remixproject/engine": "^0.2.3",
"@remixproject/engine": "^0.2.5",
"http-server": "^0.11.1",
"remixd": "0.1.8-alpha.10",
"standard": "^8.5.0"
},
"repository": {
@ -166,6 +165,7 @@
"nightwatch_local_firefox": "nightwatch --config nightwatch.js --env firefox",
"nightwatch_local_chrome": "nightwatch --config nightwatch.js --env chrome",
"nightwatch_local_ballot": "nightwatch ./test-browser/tests/ballot.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_usingWorker": "nightwatch ./test-browser/tests/usingWebWorker.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_libraryDeployment": "nightwatch ./test-browser/tests/libraryDeployment.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_solidityImport": "nightwatch ./test-browser/tests/solidityImport.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_recorder": "nightwatch ./test-browser/tests/recorder.test.js --config nightwatch.js --env chrome ",
@ -185,7 +185,9 @@
"nightwatch_local_fileExplorer": "nightwatch ./test-browser/tests/fileExplorer.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_debugger": "nightwatch ./test-browser/tests/debugger.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_editor": "nightwatch ./test-browser/tests/editor.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_compiler": "nightwatch ./test-browser/tests/compiler_api.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_txListener": "nightwatch ./test-browser/tests/txListener.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_fileManager": "nightwatch ./test-browser/tests/fileManager_api.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_runAndDeploy": "nightwatch ./test-browser/tests/runAndDeploy.js --config nightwatch.js --env chrome-runAndDeploy ",
"onchange": "onchange build/app.js -- npm-run-all lint",
"prepublish": "mkdirp build; npm-run-all -ls downloadsolc_root build",
@ -201,4 +203,4 @@
"reinstall": "rm ./node-modules/ -rf; rm package-lock.json; rm ./build/ -rf; npm install; npm run build",
"ganache-cli": "npx ganache-cli"
}
}
}

@ -5,7 +5,6 @@ var csjs = require('csjs-inject')
var yo = require('yo-yo')
var remixLib = require('@remix-project/remix-lib')
var registry = require('./global/registry')
var Remixd = require('./lib/remixd')
var loadFileFromParent = require('./loadFilesFromParent')
var { OffsetToLineColumnConverter } = require('./lib/offsetToLineColumnConverter')
var QueryParams = require('./lib/query-params')
@ -122,6 +121,7 @@ var css = csjs`
class App {
constructor (api = {}, events = {}, opts = {}) {
var self = this
self.appManager = new RemixAppManager({})
self._components = {}
self._view = {}
self._view.splashScreen = yo`
@ -135,7 +135,7 @@ class App {
document.body.appendChild(self._view.splashScreen)
// setup storage
var configStorage = new Storage('config-v0.8:')
const configStorage = new Storage('config-v0.8:')
// load app config
const config = new Config(configStorage)
@ -145,14 +145,7 @@ class App {
self._components.filesProviders = {}
self._components.filesProviders['browser'] = new FileProvider('browser')
registry.put({api: self._components.filesProviders['browser'], name: 'fileproviders/browser'})
var remixd = new Remixd(65520)
registry.put({api: remixd, name: 'remixd'})
remixd.event.register('system', (message) => {
if (message.error) toolTip(message.error)
})
self._components.filesProviders['localhost'] = new RemixDProvider(remixd)
self._components.filesProviders['localhost'] = new RemixDProvider(self.appManager)
registry.put({api: self._components.filesProviders['localhost'], name: 'fileproviders/localhost'})
registry.put({api: self._components.filesProviders, name: 'fileproviders'})
@ -235,9 +228,15 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
}
// APP_MANAGER
const appManager = new RemixAppManager({})
const workspace = appManager.pluginLoader.get()
const appManager = self.appManager
const pluginLoader = appManager.pluginLoader
const workspace = pluginLoader.get()
const engine = new Engine(appManager)
engine.setPluginOption = ({ name, kind }) => {
if (kind === 'provider') return {queueTimeout: 60000 * 4}
if (name === 'LearnEth') return {queueTimeout: 60000}
return {queueTimeout: 10000}
}
await engine.onload()
// SERVICES
@ -258,7 +257,7 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
editor.event.register('requiringToSaveCurrentfile', () => fileManager.saveCurrentFile())
// ----------------- fileManager servive ----------------------------
const fileManager = new FileManager(editor)
const fileManager = new FileManager(editor, appManager)
registry.put({api: fileManager, name: 'filemanager'})
const blockchain = new Blockchain(registry.get('config').api)
@ -397,22 +396,38 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
await appManager.activatePlugin(['contentImport', 'theme', 'editor', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter'])
await appManager.activatePlugin(['mainPanel', 'menuicons'])
await appManager.activatePlugin(['home', 'sidePanel', 'hiddenPanel', 'pluginManager', 'fileExplorers', 'settings', 'contextualListener', 'scriptRunner', 'terminal', 'fetchAndCompile'])
await appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await appManager.activatePlugin(['home', 'hiddenPanel', 'pluginManager', 'fileExplorers', 'settings', 'contextualListener', 'scriptRunner', 'terminal', 'fetchAndCompile'])
const queryParams = new QueryParams()
const params = queryParams.get()
// Set workspace after initial activation
if (Array.isArray(workspace)) await appManager.activatePlugin(workspace)
if (Array.isArray(workspace)) {
try {
await appManager.activatePlugin(workspace)
} catch (e) {
console.error(e)
}
} else {
// activate solidity plugin
appManager.ensureActivated('solidity')
appManager.ensureActivated('udapp')
}
// Load and start the service who manager layout and frame
const framingService = new FramingService(sidePanel, menuicons, mainview, this._components.resizeFeature)
framingService.start()
framingService.start(params)
// If plugins are loaded from the URL params, we focus on the last one.
if (pluginLoader.current === 'queryParams' && Array.isArray(workspace) && workspace.length > 0) menuicons.select(workspace[workspace.length - 1])
// get the file list from the parent iframe
loadFileFromParent(fileManager)
// get the file from gist
const gistHandler = new GistHandler()
const queryParams = new QueryParams()
const loadedFromGist = gistHandler.loadFromGist(queryParams.get(), fileManager)
const loadedFromGist = gistHandler.loadFromGist(params, fileManager)
if (!loadedFromGist) {
// insert example contracts if there are no files to show
self._components.filesProviders['browser'].resolveDirectory('/', (error, filesList) => {
@ -428,4 +443,6 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
if (isElectron()) {
appManager.activatePlugin('remixd')
}
if (params.embed) framingService.embed()
}

@ -32,6 +32,10 @@ module.exports = class CompilerAbstract {
getSourceName (fileIndex) {
if (this.data && this.data.sources) {
return Object.keys(this.data.sources)[fileIndex]
} else if (Object.keys(this.source.sources).length === 1) {
// if we don't have ast, we return the only one filename present.
const sourcesArray = Object.keys(this.source.sources)
return sourcesArray[0]
}
return null
}

@ -14,24 +14,52 @@ module.exports = class CompilerArtefacts extends Plugin {
constructor () {
super(profile)
this.compilersArtefacts = {}
this.compilersArtefactsPerFile = {}
}
clear () {
this.compilersArtefacts = {}
this.compilersArtefactsPerFile = {}
}
onActivation () {
const saveCompilationPerFileResult = (file, source, languageVersion, data) => {
this.compilersArtefactsPerFile[file] = new CompilerAbstract(languageVersion, data, source)
}
this.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => {
this.compilersArtefacts['__last'] = new CompilerAbstract(languageVersion, data, source)
saveCompilationPerFileResult(file, source, languageVersion, data)
})
this.on('vyper', 'compilationFinished', (file, source, languageVersion, data) => {
this.compilersArtefacts['__last'] = new CompilerAbstract(languageVersion, data, source)
saveCompilationPerFileResult(file, source, languageVersion, data)
})
this.on('lexon', 'compilationFinished', (file, source, languageVersion, data) => {
this.compilersArtefacts['__last'] = new CompilerAbstract(languageVersion, data, source)
saveCompilationPerFileResult(file, source, languageVersion, data)
})
this.on('yulp', 'compilationFinished', (file, source, languageVersion, data) => {
this.compilersArtefacts['__last'] = new CompilerAbstract(languageVersion, data, source)
saveCompilationPerFileResult(file, source, languageVersion, data)
})
}
getAllContractDatas () {
const contractsData = {}
Object.keys(this.compilersArtefactsPerFile).map((targetFile) => {
const contracts = this.compilersArtefactsPerFile[targetFile].getContracts()
Object.keys(contracts).map((file) => { contractsData[file] = contracts[file] })
})
// making sure we save last compilation result in there
if (this.compilersArtefacts['__last']) {
const contracts = this.compilersArtefacts['__last'].getContracts()
Object.keys(contracts).map((file) => { contractsData[file] = contracts[file] })
}
return contractsData
}
addResolvedContract (address, compilerData) {

@ -1,5 +1,5 @@
'use strict'
import { canUseWorker } from './compiler-utils'
import { canUseWorker, urlFromVersion } from './compiler-utils'
import { Compiler } from '@remix-project/remix-solidity'
import CompilerAbstract from './compiler-abstract'
@ -9,9 +9,9 @@ export const compile = async (compilationTargets, settings) => {
const compiler = new Compiler(() => {})
compiler.set('evmVersion', settings.evmVersion)
compiler.set('optimize', settings.optimize)
compiler.loadVersion(canUseWorker(settings.version), settings.compilerUrl)
compiler.set('language', settings.language)
compiler.loadVersion(canUseWorker(settings.version), urlFromVersion(settings.version))
compiler.event.register('compilationFinished', (success, compilationData, source) => {
if (!success) return reject(compilationData)
resolve(new CompilerAbstract(settings.version, compilationData, source))
})
compiler.event.register('compilerLoaded', _ => compiler.compile(compilationTargets, ''))

@ -1,7 +1,6 @@
const ethutil = require('ethereumjs-util')
import * as packageJson from '../../../package.json'
import { Plugin } from '@remixproject/engine'
import { urlFromVersion } from './compiler-utils'
import { compile } from './compiler-helpers'
import globalRegistry from '../../global/registry'
@ -106,10 +105,9 @@ export default class FetchAndCompile extends Plugin {
// compile
const settings = {
version: data.metadata.compiler.version,
languageName: data.metadata.language,
language: data.metadata.language,
evmVersion: data.metadata.settings.evmVersion,
optimize: data.metadata.settings.optimizer.enabled,
compilerUrl: urlFromVersion(data.metadata.compiler.version)
optimize: data.metadata.settings.optimizer.enabled
}
try {
setTimeout(_ => this.emit('compiling', settings), 0)

@ -1,48 +1,44 @@
const semver = require('semver')
const minixhr = require('minixhr')
/* global Worker */
export const baseUrl = 'https://solc-bin.ethereum.org/bin'
export const baseURLBin = 'https://solc-bin.ethereum.org/bin'
export const baseURLWasm = 'https://solc-bin.ethereum.org/wasm'
export const pathToURL = {}
/**
* Retrieves the URL of the given compiler version
* @param version is the version of compiler with or without 'soljson-v' prefix and .js postfix
*/
export function urlFromVersion (version) {
return `${baseUrl}/soljson-v${version}.js`
if (!version.startsWith('soljson-v')) version = 'soljson-v' + version
if (!version.endsWith('.js')) version = version + '.js'
return `${pathToURL[version]}/${version}`
}
/**
* Checks if the worker can be used to load a compiler.
* checks a compiler whitelist, browser support and OS.
*/
export function canUseWorker (selectedVersion) {
// Following restrictions should be deleted when Solidity will release fixed versions of compilers.
// See https://github.com/ethereum/remix-ide/issues/2461
const isChrome = !!window.chrome
const os = retrieveOS()
// define a whitelist for Linux
const linuxWL = ['0.4.26', '0.5.3', '0.5.4', '0.5.5']
const version = semver.coerce(selectedVersion)
// defining whitelist for chrome
let isFromWhiteList = false
switch (os) {
case 'Windows':
isFromWhiteList = semver.gt(version, '0.5.2') || version === '0.4.26'
break
case 'Linux':
isFromWhiteList = semver.gt(version, '0.5.13') || linuxWL.includes(version)
break
default :
isFromWhiteList = true
}
return browserSupportWorker() && (!isChrome || (isChrome && isFromWhiteList))
const isNightly = selectedVersion.includes('nightly')
return browserSupportWorker() && (
semver.gt(version, '0.6.3') ||
semver.gt(version, '0.3.6') && !isNightly
)
}
function browserSupportWorker () {
return document.location.protocol !== 'file:' && Worker !== undefined
}
function retrieveOS () {
let osName = 'Unknown OS'
if (navigator.platform.indexOf('Win') !== -1) {
osName = 'Windows'
} else if (navigator.platform.indexOf('Linux') !== -1) {
osName = 'Linux'
}
return osName
// returns a promise for minixhr
export function promisedMiniXhr (url) {
return new Promise((resolve, reject) => {
minixhr(url, (json, event) => {
resolve({ json, event })
})
})
}

@ -37,7 +37,7 @@ module.exports = class LocalPlugin {
*/
create () {
const profile = {
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xMjYyIDEwNzVxLTM3IDEyMS0xMzggMTk1dC0yMjggNzQtMjI4LTc0LTEzOC0xOTVxLTgtMjUgNC00OC41dDM4LTMxLjVxMjUtOCA0OC41IDR0MzEuNSAzOHEyNSA4MCA5Mi41IDEyOS41dDE1MS41IDQ5LjUgMTUxLjUtNDkuNSA5Mi41LTEyOS41cTgtMjYgMzItMzh0NDktNCAzNyAzMS41IDQgNDguNXptLTQ5NC00MzVxMCA1My0zNy41IDkwLjV0LTkwLjUgMzcuNS05MC41LTM3LjUtMzcuNS05MC41IDM3LjUtOTAuNSA5MC41LTM3LjUgOTAuNSAzNy41IDM3LjUgOTAuNXptNTEyIDBxMCA1My0zNy41IDkwLjV0LTkwLjUgMzcuNS05MC41LTM3LjUtMzcuNS05MC41IDM3LjUtOTAuNSA5MC41LTM3LjUgOTAuNSAzNy41IDM3LjUgOTAuNXptMjU2IDI1NnEwLTEzMC01MS0yNDguNXQtMTM2LjUtMjA0LTIwNC0xMzYuNS0yNDguNS01MS0yNDguNSA1MS0yMDQgMTM2LjUtMTM2LjUgMjA0LTUxIDI0OC41IDUxIDI0OC41IDEzNi41IDIwNCAyMDQgMTM2LjUgMjQ4LjUgNTEgMjQ4LjUtNTEgMjA0LTEzNi41IDEzNi41LTIwNCA1MS0yNDguNXptMTI4IDBxMCAyMDktMTAzIDM4NS41dC0yNzkuNSAyNzkuNS0zODUuNSAxMDMtMzg1LjUtMTAzLTI3OS41LTI3OS41LTEwMy0zODUuNSAxMDMtMzg1LjUgMjc5LjUtMjc5LjUgMzg1LjUtMTAzIDM4NS41IDEwMyAyNzkuNSAyNzkuNSAxMDMgMzg1LjV6Ii8+PC9zdmc+',
icon: 'assets/img/localPlugin.webp',
methods: [],
location: 'sidePanel',
type: 'iframe',

@ -62,7 +62,7 @@ const profile = {
displayName: 'Plugin manager',
methods: [],
events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNzU1IDQ1M3EzNyAzOCAzNyA5MC41dC0zNyA5MC41bC00MDEgNDAwIDE1MCAxNTAtMTYwIDE2MHEtMTYzIDE2My0zODkuNSAxODYuNXQtNDExLjUtMTAwLjVsLTM2MiAzNjJoLTE4MXYtMTgxbDM2Mi0zNjJxLTEyNC0xODUtMTAwLjUtNDExLjV0MTg2LjUtMzg5LjVsMTYwLTE2MCAxNTAgMTUwIDQwMC00MDFxMzgtMzcgOTEtMzd0OTAgMzcgMzcgOTAuNS0zNyA5MC41bC00MDAgNDAxIDIzNCAyMzQgNDAxLTQwMHEzOC0zNyA5MS0zN3Q5MCAzN3oiLz48L3N2Zz4=',
icon: 'assets/img/pluginManager.webp',
description: 'Start/stop services, modules and plugins',
kind: 'settings',
location: 'sidePanel',

@ -20,8 +20,9 @@ require('brace/mode/javascript')
require('brace/mode/python')
require('brace/mode/json')
require('brace/mode/rust')
require('brace/theme/chaos')
require('brace/theme/chrome')
require('brace/theme/chrome') // for all light themes
require('brace/theme/chaos') // for all dark themes
require('../../../assets/js/editor/darkTheme') // a custom one for remix 'Dark' theme
const css = csjs`
.ace-editor {
@ -62,10 +63,11 @@ class Editor extends Plugin {
this._themes = {
'light': 'chrome',
'dark': 'chaos'
'dark': 'chaos',
'remixDark': 'remixDark'
}
themeModule.events.on('themeChanged', (theme) => {
this.setTheme(theme.quality)
this.setTheme(theme.name === 'Dark' ? 'remixDark' : theme.quality)
})
// Init
@ -324,6 +326,13 @@ class Editor extends Plugin {
* @param {string} content Content of the document or update.
*/
open (path, content) {
/*
we have the following cases:
- URL prepended with "localhost"
- URL prepended with "browser"
- URL not prepended with the file explorer. We assume (as it is in the whole app, that this is a "browser" URL
*/
if (!path.startsWith('localhost') && !path.startsWith('browser')) path = `browser/${path}`
if (!this.sessions[path]) {
const session = this._createSession(content, this._getMode(path))
this.sessions[path] = session

@ -217,7 +217,7 @@ contract Ballot {
var ballotTest = `pragma solidity >=0.4.22 <0.7.0;
import "remix_tests.sol"; // this import is automatically injected by Remix.
import "./3_Ballot.sol";
import "../3_Ballot.sol";
contract BallotTest {
@ -245,5 +245,5 @@ module.exports = {
storage: { name: '1_Storage.sol', content: storage },
owner: { name: '2_Owner.sol', content: owner },
ballot: { name: '3_Ballot.sol', content: ballot },
ballot_test: { name: '4_Ballot_test.sol', content: ballotTest }
ballot_test: { name: 'tests/4_Ballot_test.sol', content: ballotTest }
}

@ -102,10 +102,10 @@ function fileExplorer (localRegistry, files, menuItems) {
function fileAdded (filepath) {
self.ensureRoot(() => {
var folderpath = filepath.split('/').slice(0, -1).join('/')
var currentTree = self.treeView.nodeAt(folderpath)
if (currentTree && self.treeView.isExpanded(folderpath)) {
const folderpath = filepath.split('/').slice(0, -1).join('/')
const currentTree = self.treeView.nodeAt(folderpath)
if (!self.treeView.isExpanded(folderpath)) self.treeView.expand(folderpath)
if (currentTree) {
self.files.resolveDirectory(folderpath, (error, fileTree) => {
if (error) console.error(error)
if (!fileTree) return
@ -136,15 +136,20 @@ function fileExplorer (localRegistry, files, menuItems) {
if (!fileTree) return
fileTree = normalize(folderpath, fileTree)
self.treeView.updateNodeFromJSON(folderpath, fileTree, true)
if (!self.treeView.isExpanded(folderpath)) self.treeView.expand(folderpath)
})
})
}
function fileRemoved (filepath) {
var label = self.treeView.labelAt(filepath)
const label = self.treeView.labelAt(filepath)
filepath = filepath.split('/').slice(0, -1).join('/')
if (label && label.parentElement) {
label.parentElement.removeChild(label)
}
self.updatePath(filepath)
}
function fileRenamed (oldName, newName, isFolder) {
@ -240,10 +245,6 @@ function fileExplorer (localRegistry, files, menuItems) {
if (!removeFolder) {
tooltip(`failed to remove ${key}. Make sure the directory is empty before removing it.`)
} else {
const { type } = fileManager.currentFileProvider()
self.updatePath(type)
}
}, () => {})
}
@ -286,9 +287,6 @@ function fileExplorer (localRegistry, files, menuItems) {
if (!removeFile) {
tooltip(`Failed to remove file ${key}.`)
} else {
const { type } = fileManager.currentFileProvider()
self.updatePath(type)
}
},
() => {}
@ -438,12 +436,12 @@ fileExplorer.prototype.uploadFile = function (event) {
let files = this.files
function loadFile () {
var fileReader = new FileReader()
fileReader.onload = function (event) {
fileReader.onload = async function (event) {
if (helper.checkSpecialChars(file.name)) {
modalDialogCustom.alert('Special characters are not allowed')
return
}
var success = files.set(name, event.target.result)
var success = await files.set(name, event.target.result)
if (!success) {
modalDialogCustom.alert('Failed to create file ' + name)
} else {
@ -638,7 +636,7 @@ fileExplorer.prototype.renderMenuItems = function () {
<label
id=${action}
data-id="fileExplorerUploadFile${action}"
class="${icon} ${css.newFile}"
class="${icon} mb-0 ${css.newFile}"
title="${title}"
>
<input id="fileUpload" data-id="fileExplorerFileUpload" type="file" onchange=${(event) => {
@ -649,13 +647,14 @@ fileExplorer.prototype.renderMenuItems = function () {
`
} else {
return yo`
<span
id=${action}
data-id="fileExplorerNewFile${action}"
onclick=${(event) => { event.stopPropagation(); this[ action ]() }}
class="newFile ${icon} ${css.newFile}"
title=${title}>
</span>
<span
id=${action}
data-id="fileExplorerNewFile${action}"
onclick=${(event) => { event.stopPropagation(); this[ action ]() }}
class="newFile ${icon} ${css.newFile}"
title=${title}
>
</span>
`
}
})

@ -20,10 +20,10 @@ 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=',
icon: 'assets/img/fileManager.webp',
permission: true,
version: packageJson.version,
methods: ['file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'rename', 'readdir', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile'],
methods: ['file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'rename', 'mkdir', 'readdir', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile'],
kind: 'file-system'
}
const errorMsg = {
@ -38,7 +38,7 @@ const createError = (err) => {
}
class FileManager extends Plugin {
constructor (editor) {
constructor (editor, appManager) {
super(profile)
this.openedFiles = {} // list all opened files
this.events = new EventEmitter()
@ -46,6 +46,7 @@ class FileManager extends Plugin {
this._components = {}
this._components.compilerImport = new CompilerImport()
this._components.registry = globalRegistry
this.appManager = appManager
this.init()
}
@ -194,10 +195,24 @@ class FileManager extends Plugin {
* @returns {void}
*/
async rename (oldPath, newPath) {
await this.__handleExists(oldPath, `Cannot rename ${oldPath}`)
await this._handleExists(oldPath, `Cannot rename ${oldPath}`)
const isFile = await this.isFile(oldPath)
const newPathExists = await this.exists(newPath)
const provider = this.fileProviderOf(oldPath)
this.fileRenamedEvent(oldPath, newPath, !isFile)
if (isFile) {
if (newPathExists) {
modalDialogCustom.alert('File already exists.')
return
}
return provider.rename(oldPath, newPath, false)
} else {
if (newPathExists) {
modalDialogCustom.alert('Folder already exists.')
return
}
return provider.rename(oldPath, newPath, true)
}
}
/**
@ -479,6 +494,32 @@ class FileManager extends Plugin {
return this._deps.filesProviders['browser']
}
// returns the list of directories inside path
dirList (path) {
const dirPaths = []
const collectList = (path) => {
return new Promise((resolve, reject) => {
this.readdir(path).then((ls) => {
const promises = Object.keys(ls).map((item, index) => {
const root = (path.indexOf('/') === -1) ? path : path.substr(0, path.indexOf('/'))
const curPath = `${root}/${item}` // adding 'browser' or 'localhost'
if (ls[item].isDirectory && !dirPaths.includes(curPath)) {
dirPaths.push(curPath)
resolve(dirPaths)
}
return new Promise((resolve, reject) => { resolve() })
})
Promise.all(promises).then(() => { resolve(dirPaths) })
})
})
}
return collectList(path)
}
isRemixDActive () {
return this.appManager.isActive('remixd')
}
saveCurrentFile () {
var currentFile = this._deps.config.get('currentFile')
if (currentFile && this.editor.current()) {

@ -93,9 +93,13 @@ class FileProvider {
cb = cb || function () {}
var unprefixedpath = this.removePrefix(path)
var exists = window.remixFileSystem.existsSync(unprefixedpath)
if (exists && window.remixFileSystem.readFileSync(unprefixedpath, 'utf8') === content) return true
if (exists && window.remixFileSystem.readFileSync(unprefixedpath, 'utf8') === content) {
cb()
return true
}
if (!exists && unprefixedpath.indexOf('/') !== -1) {
this.createDir(path)
// the last element is the filename and we should remove it
this.createDir(path.substr(0, path.lastIndexOf('/')))
}
try {
window.remixFileSystem.writeFileSync(unprefixedpath, content)
@ -113,9 +117,8 @@ class FileProvider {
}
createDir (path, cb) {
var unprefixedpath = this.removePrefix(path)
const unprefixedpath = this.removePrefix(path)
const paths = unprefixedpath.split('/')
paths.pop() // last element should the filename
if (paths.length && paths[0] === '') paths.shift()
let currentCheck = ''
paths.forEach((value) => {
@ -154,35 +157,38 @@ class FileProvider {
* @param {*} path is the folder to be removed
*/
remove (path) {
path = this.removePrefix(path)
if (window.remixFileSystem.existsSync(path)) {
const stat = window.remixFileSystem.statSync(path)
try {
if (!stat.isDirectory()) {
return this.removeFile(path)
} else {
const items = window.remixFileSystem.readdirSync(path)
if (items.length !== 0) {
items.forEach((item, index) => {
const curPath = `${path}/${item}`
if (window.remixFileSystem.statSync(curPath).isDirectory()) { // delete folder
this.remove(curPath)
} else { // delete file
this.removeFile(curPath)
}
})
if (window.remixFileSystem.readdirSync(path).length === 0) window.remixFileSystem.rmdirSync(path, console.log)
return new Promise((resolve, reject) => {
path = this.removePrefix(path)
if (window.remixFileSystem.existsSync(path)) {
const stat = window.remixFileSystem.statSync(path)
try {
if (!stat.isDirectory()) {
resolve(this.removeFile(path))
} else {
// folder is empty
window.remixFileSystem.rmdirSync(path, console.log)
const items = window.remixFileSystem.readdirSync(path)
if (items.length !== 0) {
items.forEach((item, index) => {
const curPath = `${path}/${item}`
if (window.remixFileSystem.statSync(curPath).isDirectory()) { // delete folder
this.remove(curPath)
} else { // delete file
this.removeFile(curPath)
}
})
if (window.remixFileSystem.readdirSync(path).length === 0) window.remixFileSystem.rmdirSync(path, console.log)
} else {
// folder is empty
window.remixFileSystem.rmdirSync(path, console.log)
}
this.event.trigger('fileRemoved', [this._normalizePath(path)])
}
} catch (e) {
console.log(e)
return resolve(false)
}
} catch (e) {
console.log(e)
return false
}
}
return true
return resolve(true)
})
}
removeFile (path) {
@ -216,10 +222,11 @@ class FileProvider {
window.remixFileSystem.readdir(path, (error, files) => {
var ret = {}
if (files) {
files.forEach(element => {
const absPath = (path === '/' ? '' : path) + '/' + element
ret[absPath.indexOf('/') === 0 ? absPath.replace('/', '') : absPath] = { isDirectory: window.remixFileSystem.statSync(absPath).isDirectory() }
ret[absPath.indexOf('/') === 0 ? absPath.substr(1, absPath.length) : absPath] = { isDirectory: window.remixFileSystem.statSync(absPath).isDirectory() }
// ^ ret does not accept path starting with '/'
})
}

@ -1,12 +1,10 @@
'use strict'
var EventManager = require('../../lib/events')
var pathtool = require('path')
module.exports = class RemixDProvider {
constructor (remixd) {
constructor (appManager) {
this.event = new EventManager()
this._remixd = remixd
this.remixd = remixapi(remixd, this)
this._appManager = appManager
this.type = 'localhost'
this.error = { 'EEXIST': 'File already exists' }
this._isReady = false
@ -14,39 +12,34 @@ module.exports = class RemixDProvider {
this._readOnlyMode = false
this.filesContent = {}
this.files = {}
}
_registerEvent () {
var remixdEvents = ['connecting', 'connected', 'errored', 'closed']
remixdEvents.forEach((value) => {
remixd.event.register(value, (event) => {
this._appManager.on('remixd', value, (event) => {
this.event.trigger(value, [event])
})
})
remixd.event.register('notified', (data) => {
if (data.scope === 'sharedfolder') {
if (data.name === 'created') {
this.init(() => {
this.event.trigger('fileAdded', [this.type + '/' + data.value.path, data.value.isReadOnly, data.value.isFolder])
})
} else if (data.name === 'removed') {
this.init(() => {
this.event.trigger('fileRemoved', [this.type + '/' + data.value.path])
})
} else if (data.name === 'changed') {
this._remixd.call('sharedfolder', 'get', {path: data.value}, (error, content) => {
if (error) {
console.log(error)
} else {
var path = this.type + '/' + data.value
this.filesContent[path] = content
this.event.trigger('fileExternallyChanged', [path, content])
}
})
} else if (data.name === 'rootFolderChanged') {
// new path has been set, we should reset
this.event.trigger('folderAdded', [this.type + '/'])
}
}
this._appManager.on('remixd', 'folderAdded', (path) => {
this.event.trigger('folderAdded', [this.addPrefix(path)])
})
this._appManager.on('remixd', 'fileAdded', (path) => {
this.event.trigger('fileAdded', [this.addPrefix(path)])
})
this._appManager.on('remixd', 'fileChanged', (path) => {
this.event.trigger('fileChanged', [this.addPrefix(path)])
})
this._appManager.on('remixd', 'fileRemoved', (path) => {
this.event.trigger('fileRemoved', [this.addPrefix(path)])
})
this._appManager.on('remixd', 'fileRenamed', (oldPath, newPath) => {
this.event.trigger('fileRemoved', [this.addPrefix(oldPath), this.addPrefix(newPath)])
})
}
@ -55,38 +48,35 @@ module.exports = class RemixDProvider {
}
close (cb) {
this.remixd.exit()
this._isReady = false
cb()
}
init (cb) {
this._remixd.ensureSocket((error) => {
if (error) return cb(error)
this._isReady = !error
this._remixd.call('sharedfolder', 'folderIsReadOnly', {}, (error, result) => {
this._readOnlyMode = result
cb(error)
})
if (this._isReady) return cb && cb()
this._appManager.call('remixd', 'folderIsReadOnly', {})
.then((result) => {
this._isReady = true
this._readOnlyMode = result
this._registerEvent()
cb && cb()
}).catch((error) => {
cb && cb(error)
})
}
// @TODO: refactor all `this._remixd.call(....)` uses into `this.remixd[api](...)`
// where `api = ...`:
// this.remixd.read(path, (error, content) => {})
// this.remixd.write(path, content, (error, result) => {})
// this.remixd.rename(path1, path2, (error, result) => {})
// this.remixd.remove(path, (error, result) => {})
// this.remixd.dir(path, (error, filesList) => {})
//
// this.remixd.exists(path, (error, isValid) => {})
async exists (path, cb) {
exists (path, cb) {
if (!this._isReady) return cb && cb('provider not ready')
const unprefixedpath = this.removePrefix(path)
const callId = await this._remixd.call('sharedfolder', 'exists', {path: unprefixedpath})
const result = await this._remixd.receiveResponse(callId)
return cb(null, result)
return this._appManager.call('remixd', 'exists', { path: unprefixedpath })
.then((result) => {
if (cb) return cb(null, result)
return result
}).catch((error) => {
if (cb) return cb(error)
throw new Error(error)
})
}
getNormalizedName (path) {
@ -98,67 +88,75 @@ module.exports = class RemixDProvider {
}
get (path, cb) {
if (!this._isReady) return cb && cb('provider not ready')
var unprefixedpath = this.removePrefix(path)
this._remixd.call('sharedfolder', 'get', {path: unprefixedpath}, (error, file) => {
if (!error) {
this.filesContent[path] = file.content
if (file.readonly) { this._readOnlyFiles[path] = 1 }
cb(error, file.content)
} else {
// display the last known content.
// TODO should perhaps better warn the user that the file is not synced.
cb(null, this.filesContent[path])
}
this._appManager.call('remixd', 'get', { path: unprefixedpath })
.then((file) => {
this.filesContent[path] = file.content
if (file.readonly) { this._readOnlyFiles[path] = 1 }
cb(null, file.content)
}).catch((error) => {
if (error) console.log(error)
// display the last known content.
// TODO should perhaps better warn the user that the file is not synced.
return cb(null, this.filesContent[path])
})
}
set (path, content, cb) {
var unprefixedpath = this.removePrefix(path)
this._remixd.call('sharedfolder', 'set', {path: unprefixedpath, content: content}, (error, result) => {
if (cb) return cb(error, result)
var path = this.type + '/' + unprefixedpath
this.event.trigger('fileChanged', [path])
async set (path, content, cb) {
if (!this._isReady) return cb && cb('provider not ready')
const unprefixedpath = this.removePrefix(path)
return this._appManager.call('remixd', 'set', { path: unprefixedpath, content: content }).then(async (result) => {
if (cb) return cb(null, result)
}).catch((error) => {
if (cb) return cb(error)
throw new Error(error)
})
return true
}
isReadOnly (path) {
return this._readOnlyMode || this._readOnlyFiles[path] === 1
}
async remove (path) {
var unprefixedpath = this.removePrefix(path)
const callId = await this._remixd.call('sharedfolder', 'remove', {path: unprefixedpath}, (error, result) => {
if (error) console.log(error)
var path = this.type + '/' + unprefixedpath
delete this.filesContent[path]
this.init(() => {
this.event.trigger('fileRemoved', [path])
remove (path) {
return new Promise((resolve, reject) => {
if (!this._isReady) return reject('provider not ready')
const unprefixedpath = this.removePrefix(path)
this._appManager.call('remixd', 'remove', { path: unprefixedpath })
.then(result => {
const path = this.type + '/' + unprefixedpath
delete this.filesContent[path]
resolve(true)
this.init()
}).catch(error => {
if (error) console.log(error)
resolve(false)
})
})
return await this._remixd.receiveResponse(callId)
}
rename (oldPath, newPath, isFolder) {
var unprefixedoldPath = this.removePrefix(oldPath)
var unprefixednewPath = this.removePrefix(newPath)
this._remixd.call('sharedfolder', 'rename', {oldPath: unprefixedoldPath, newPath: unprefixednewPath}, (error, result) => {
if (error) {
console.log(error)
if (this.error[error.code]) error = this.error[error.code]
this.event.trigger('fileRenamedError', [this.error[error.code]])
} else {
var newPath = this.type + '/' + unprefixednewPath
var oldPath = this.type + '/' + unprefixedoldPath
this.filesContent[newPath] = this.filesContent[oldPath]
delete this.filesContent[oldPath]
this.init(() => {
this.event.trigger('fileRenamed', [oldPath, newPath, isFolder])
})
}
const unprefixedoldPath = this.removePrefix(oldPath)
const unprefixednewPath = this.removePrefix(newPath)
if (!this._isReady) return new Promise((resolve, reject) => reject('provider not ready'))
return this._appManager.call('remixd', 'rename', { oldPath: unprefixedoldPath, newPath: unprefixednewPath })
.then(result => {
const newPath = this.type + '/' + unprefixednewPath
const oldPath = this.type + '/' + unprefixedoldPath
this.filesContent[newPath] = this.filesContent[oldPath]
delete this.filesContent[oldPath]
this.init(() => {
this.event.trigger('fileRenamed', [oldPath, newPath, isFolder])
})
return result
}).catch(error => {
console.log(error)
if (this.error[error.code]) error = this.error[error.code]
this.event.trigger('fileRenamedError', [this.error[error.code]])
})
return true
}
isExternalFolder (path) {
@ -171,56 +169,33 @@ module.exports = class RemixDProvider {
return path
}
addPrefix (path) {
if (path.indexOf(this.type + '/') === 0) return path
if (path[0] === '/') return 'localhost' + path
return 'localhost/' + path
}
resolveDirectory (path, callback) {
var self = this
if (path[0] === '/') path = path.substring(1)
if (!path) return callback(null, { [self.type]: { } })
path = self.removePrefix(path)
self.remixd.dir(path, callback)
const unprefixedpath = this.removePrefix(path)
if (!this._isReady) return callback && callback('provider not ready')
this._appManager.call('remixd', 'resolveDirectory', { path: unprefixedpath }).then((result) => {
callback(null, result)
}).catch(callback)
}
async isDirectory (path) {
const unprefixedpath = this.removePrefix(path)
const callId = await this._remixd.call('sharedfolder', 'isDirectory', {path: unprefixedpath})
return await this._remixd.receiveResponse(callId)
if (!this._isReady) throw new Error('provider not ready')
return await this._appManager.call('remixd', 'isDirectory', {path: unprefixedpath})
}
async isFile (path) {
const unprefixedpath = this.removePrefix(path)
const callId = await this._remixd.call('sharedfolder', 'isFile', {path: unprefixedpath})
return await this._remixd.receiveResponse(callId)
if (!this._isReady) throw new Error('provider not ready')
return await this._appManager.call('remixd', 'isFile', { path: unprefixedpath })
}
}
function remixapi (remixd, self) {
const read = (path, callback) => {
path = '' + (path || '')
path = pathtool.join('./', path)
remixd.call('sharedfolder', 'get', { path }, (error, content) => callback(error, content))
}
const write = (path, content, callback) => {
path = '' + (path || '')
path = pathtool.join('./', path)
remixd.call('sharedfolder', 'set', { path, content }, (error, result) => callback(error, result))
}
const rename = (path, newpath, callback) => {
path = '' + (path || '')
path = pathtool.join('./', path)
remixd.call('sharedfolder', 'rename', { oldPath: path, newPath: newpath }, (error, result) => callback(error, result))
}
const remove = (path, callback) => {
path = '' + (path || '')
path = pathtool.join('./', path)
remixd.call('sharedfolder', 'remove', { path }, (error, result) => callback(error, result))
}
const dir = (path, callback) => {
path = '' + (path || '')
path = pathtool.join('./', path)
remixd.call('sharedfolder', 'resolveDirectory', { path }, (error, filesList) => callback(error, filesList))
}
const exit = () => { remixd.close() }
const api = { read, write, rename, remove, dir, exit, event: remixd.event }
return api
}

@ -1,5 +1,5 @@
import isElectron from 'is-electron'
import { Plugin } from '@remixproject/engine'
import { WebsocketPlugin } from '@remixproject/engine'
import * as packageJson from '../../../package.json'
var yo = require('yo-yo')
var modalDialog = require('../ui/modaldialog')
@ -20,14 +20,16 @@ var css = csjs`
const profile = {
name: 'remixd',
methods: [],
displayName: 'RemixD',
url: 'ws://127.0.0.1:65520',
methods: ['folderIsReadOnly', 'resolveDirectory', 'get', 'exists', 'isFile', 'set', 'rename', 'remove', 'isDirectory', 'list'],
events: [],
description: 'Using Remixd daemon, allow to access file system',
kind: 'other',
version: packageJson.version
}
export class RemixdHandle extends Plugin {
export class RemixdHandle extends WebsocketPlugin {
constructor (fileSystemExplorer, locahostProvider, appManager) {
super(profile)
this.fileSystemExplorer = fileSystemExplorer
@ -36,16 +38,19 @@ export class RemixdHandle extends Plugin {
}
deactivate () {
this.fileSystemExplorer.hide()
if (super.socket) super.deactivate()
this.locahostProvider.close((error) => {
if (error) console.log(error)
})
}
activate () {
this.fileSystemExplorer.show()
this.connectToLocalhost()
}
canceled () {
async canceled () {
this.appManager.ensureDeactivated('remixd')
}
@ -53,9 +58,9 @@ export class RemixdHandle extends Plugin {
* 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
* @param {String} txHash - hash of the transaction
*/
connectToLocalhost () {
async connectToLocalhost () {
let connection = (error) => {
if (error) {
console.log(error)
@ -65,13 +70,22 @@ export class RemixdHandle extends Plugin {
)
this.canceled()
} else {
this.fileSystemExplorer.ensureRoot()
const intervalId = setInterval(() => {
if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed
clearInterval(intervalId)
console.log(error)
modalDialogCustom.alert(
'Connection to remixd terminated' +
'Please make sure remixd is still running in the background.'
)
this.canceled()
}
}, 3000)
this.locahostProvider.init(_ => this.fileSystemExplorer.ensureRoot())
}
}
if (this.locahostProvider.isConnected()) {
this.locahostProvider.close((error) => {
if (error) console.log(error)
})
this.deactivate()
} else if (!isElectron()) {
// warn the user only if he/she is in the browser context
modalDialog(
@ -79,7 +93,18 @@ export class RemixdHandle extends Plugin {
remixdDialog(),
{ label: 'Connect',
fn: () => {
this.locahostProvider.init((error) => connection(error))
try {
super.activate()
setTimeout(() => {
if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed
connection(new Error('Connection with daemon failed.'))
} else {
connection()
}
}, 3000)
} catch (error) {
connection(error)
}
}
},
{ label: 'Cancel',
@ -89,7 +114,12 @@ export class RemixdHandle extends Plugin {
}
)
} else {
this.locahostProvider.init((error) => connection(error))
try {
super.activate()
setTimeout(() => { connection() }, 2000)
} catch (error) {
connection(error)
}
}
}
}

@ -32,7 +32,7 @@ const profile = {
displayName: 'File explorers',
methods: [],
events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNjk2IDM4NHE0MCAwIDY4IDI4dDI4IDY4djEyMTZxMCA0MC0yOCA2OHQtNjggMjhoLTk2MHEtNDAgMC02OC0yOHQtMjgtNjh2LTI4OGgtNTQ0cS00MCAwLTY4LTI4dC0yOC02OHYtNjcycTAtNDAgMjAtODh0NDgtNzZsNDA4LTQwOHEyOC0yOCA3Ni00OHQ4OC0yMGg0MTZxNDAgMCA2OCAyOHQyOCA2OHYzMjhxNjgtNDAgMTI4LTQwaDQxNnptLTU0NCAyMTNsLTI5OSAyOTloMjk5di0yOTl6bS02NDAtMzg0bC0yOTkgMjk5aDI5OXYtMjk5em0xOTYgNjQ3bDMxNi0zMTZ2LTQxNmgtMzg0djQxNnEwIDQwLTI4IDY4dC02OCAyOGgtNDE2djY0MGg1MTJ2LTI1NnEwLTQwIDIwLTg4dDQ4LTc2em05NTYgODA0di0xMTUyaC0zODR2NDE2cTAgNDAtMjggNjh0LTY4IDI4aC00MTZ2NjQwaDg5NnoiLz48L3N2Zz4=',
icon: 'assets/img/fileManager.webp',
description: ' - ',
kind: 'fileexplorer',
location: 'sidePanel',
@ -64,8 +64,8 @@ module.exports = class Filepanel extends ViewPlugin {
const explorers = yo`
<div>
<div class=${css.treeview} data-id="filePanelFileExplorerTree">${fileExplorer.init()}</div>
<div class="filesystemexplorer ${css.treeview}">${fileSystemExplorer.init()}</div>
<div class="pl-2 ${css.treeview}" data-id="filePanelFileExplorerTree">${fileExplorer.init()}</div>
<div class="pl-2 filesystemexplorer ${css.treeview}">${fileSystemExplorer.init()}</div>
</div>
`

@ -80,7 +80,7 @@ export class MainView {
self.data = {
_layout: {
top: {
offset: self._deps.config.get('terminal-top-offset') || 150,
offset: self._terminalTopOffset(),
show: true
}
}
@ -97,6 +97,9 @@ export class MainView {
})
}
}
_terminalTopOffset () {
return this._deps.config.get('terminal-top-offset') || 150
}
_adjustLayout (direction, delta) {
var limitUp = 0
var limitDown = 32
@ -126,6 +129,12 @@ export class MainView {
self._components.terminal.scroll2bottom()
}
}
minimizeTerminal () {
this._adjustLayout('top')
}
showTerminal (offset) {
this._adjustLayout('top', offset || this._terminalTopOffset())
}
getTerminal () {
return this._components.terminal
}

File diff suppressed because one or more lines are too long

@ -147,11 +147,11 @@ class Terminal extends Plugin {
self._view.icon = yo`
<i onmouseenter=${hover} onmouseleave=${hover} onmousedown=${minimize}
class="btn btn-secondary btn-sm align-items-center ${css.toggleTerminal} fas fa-angle-double-down" data-id="terminalToggleIcon"></i>`
class="mx-2 ${css.toggleTerminal} fas fa-angle-double-down" data-id="terminalToggleIcon"></i>`
self._view.dragbar = yo`
<div onmousedown=${mousedown} class=${css.dragbarHorizontal}></div>`
self._view.pendingTxCount = yo`<div class=${css.pendingTx} title='Pending Transactions'>0</div>`
self._view.pendingTxCount = yo`<div class="mx-2" title='Pending Transactions'>0</div>`
self._view.inputSearch = yo`<input
spellcheck="false"
type="text"
@ -166,21 +166,22 @@ class Terminal extends Plugin {
${self._view.dragbar}
<div class="${css.menu} border-top border-dark bg-light" data-id="terminalToggleMenu">
${self._view.icon}
<div class=${css.clear} id="clearConsole" data-id="terminalClearConsole" onclick=${clear}>
<div class="mx-2" id="clearConsole" data-id="terminalClearConsole" onclick=${clear}>
<i class="fas fa-ban" aria-hidden="true" title="Clear console"
onmouseenter=${hover} onmouseleave=${hover}></i>
</div>
${self._view.pendingTxCount}
<div class=${css.verticalLine}></div>
<div class="form-check">
<div class="mx-2 d-flex pb-1 align-items-center ${css.listenOnNetwork} custom-control custom-checkbox">
<input
class="pb-0 form-check-input custom-control-input"
id="listenNetworkCheck"
onchange=${listenOnNetwork}
type="checkbox" class="form-check-input "
type="checkbox"
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you"
>
<label
class="${css.listenOnNetworkLabel} form-check-label"
class="pt-1 form-check-label custom-control-label text-nowrap""
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you"
for="listenNetworkCheck"
>

@ -11,7 +11,7 @@ const profile = {
displayName: 'Solidity static analysis',
methods: [],
events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMjA0OCIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMjA0OCAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0yMDQ4IDE1MzZ2MTI4aC0yMDQ4di0xNTM2aDEyOHYxNDA4aDE5MjB6bS0xMjgtMTI0OHY0MzVxMCAyMS0xOS41IDI5LjV0LTM1LjUtNy41bC0xMjEtMTIxLTYzMyA2MzNxLTEwIDEwLTIzIDEwdC0yMy0xMGwtMjMzLTIzMy00MTYgNDE2LTE5Mi0xOTIgNTg1LTU4NXExMC0xMCAyMy0xMHQyMyAxMGwyMzMgMjMzIDQ2NC00NjQtMTIxLTEyMXEtMTYtMTYtNy41LTM1LjV0MjkuNS0xOS41aDQzNXExNCAwIDIzIDl0OSAyM3oiLz48L3N2Zz4=',
icon: 'assets/img/staticAnalysis.webp',
description: 'Checks the contract code for security vulnerabilities and bad practices.',
kind: 'analysis',
location: 'sidePanel',

File diff suppressed because one or more lines are too long

@ -68,7 +68,7 @@ class CompileTab {
const sources = { [target]: { content } }
this.event.emit('startingCompilation')
// setTimeout fix the animation on chrome... (animation triggered by 'staringCompilation')
setTimeout(() => this.compiler.compile(sources, target), 100)
setTimeout(() => { this.compiler.compile(sources, target); resolve() }, 100)
})
})
}

@ -1,12 +1,11 @@
const yo = require('yo-yo')
const minixhr = require('minixhr')
const helper = require('../../../lib/helper')
const addTooltip = require('../../ui/tooltip')
const semver = require('semver')
const modalDialogCustom = require('../../ui/modal-dialog-custom')
const css = require('../styles/compile-tab-styles')
import { canUseWorker } from '../../compiler/compiler-utils'
import { canUseWorker, baseURLBin, baseURLWasm, urlFromVersion, pathToURL, promisedMiniXhr } from '../../compiler/compiler-utils'
class CompilerContainer {
@ -24,8 +23,7 @@ class CompilerContainer {
timeout: 300,
allversions: null,
selectedVersion: null,
defaultVersion: 'soljson-v0.6.6+commit.6c089d02.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'
defaultVersion: 'soljson-v0.6.6+commit.6c089d02.js' // this default version is defined: in makeMockCompiler (for browser test) and in package.json (downloadsolc_root) for the builtin compiler
}
}
@ -320,17 +318,21 @@ class CompilerContainer {
this.compileIfAutoCompileOn()
}
/*
The following functions are handlers for internal events.
*/
onchangeOptimize () {
this.compileTabLogic.setOptimize(!!this._view.optimize.checked)
this.compileIfAutoCompileOn()
}
onchangeLanguage (event) {
this.compileTabLogic.setLanguage(event.target.value)
onchangeLanguage () {
this.compileTabLogic.setLanguage(this._view.languageSelector.value)
this.compileIfAutoCompileOn()
}
onchangeEvmVersion (_) {
onchangeEvmVersion () {
let s = this._view.evmVersionSelector
let v = s.value
if (v === 'default') {
@ -340,12 +342,44 @@ class CompilerContainer {
this.compileIfAutoCompileOn()
}
onchangeLoadVersion (event) {
onchangeLoadVersion () {
this.data.selectedVersion = this._view.versionSelector.value
this._updateVersionSelector()
this._updateLanguageSelector()
}
/*
The following functions map with the above event handlers.
They are an external API for modifying the compiler configuration.
*/
setConfiguration (settings) {
this.setLanguage(settings.language)
this.setEvmVersion(settings.evmVersion)
this.setOptimize(settings.optimize)
this.setVersion(settings.version)
}
setOptimize (enabled) {
this._view.optimize.checked = enabled
this.onchangeOptimize()
}
setLanguage (lang) {
this._view.languageSelector.value = lang
this.onchangeLanguage()
}
setEvmVersion (version) {
this._view.evmVersionSelector.value = version || 'default'
this.onchangeEvmVersion()
}
setVersion (version) {
this._view.versionSelector.value = `soljson-v${version}.js`
this.onchangeLoadVersion()
}
_shouldBeAdded (version) {
return !version.includes('nightly') ||
(version.includes('nightly') && this._view.includeNightlies.checked)
@ -383,13 +417,13 @@ class CompilerContainer {
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}`
url = `${urlFromVersion(this.data.selectedVersion)}`
}
// 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.
if (canUseWorker(this.data.selectedVersion)) {
if (this.data.selectedVersion !== 'builtin' && canUseWorker(this.data.selectedVersion)) {
this.compileTabLogic.compiler.loadVersion(true, url)
this.setVersionText('(loading using worker)')
} else {
@ -413,27 +447,42 @@ class CompilerContainer {
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 #2461
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'
// fetching both normal and wasm builds and creating a [version, baseUrl] map
async fetchAllVersion (callback) {
let allVersions, selectedVersion, allVersionsWasm
// fetch normal builds
const binRes = await promisedMiniXhr(`${baseURLBin}/list.json`)
// fetch wasm builds
const wasmRes = await promisedMiniXhr(`${baseURLWasm}/list.json`)
if (binRes.event.type === 'error' && wasmRes.event.type === 'error') {
allVersions = [{ path: 'builtin', longVersion: 'latest local version' }]
selectedVersion = 'builtin'
callback(allVersions, selectedVersion)
}
try {
allVersions = JSON.parse(binRes.json).builds.slice().reverse()
selectedVersion = this.data.defaultVersion
if (this.queryParams.get().version) selectedVersion = this.queryParams.get().version
if (wasmRes.event.type !== 'error') {
allVersionsWasm = JSON.parse(wasmRes.json).builds.slice().reverse()
}
callback(allversions, selectedVersion)
})
} 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. Error: ' + e)
}
// replace in allVersions those compiler builds which exist in allVersionsWasm with new once
if (allVersionsWasm && allVersions) {
allVersions.forEach((compiler, index) => {
const wasmIndex = allVersionsWasm.findIndex(wasmCompiler => { return wasmCompiler.longVersion === compiler.longVersion })
if (wasmIndex !== -1) {
allVersions[index] = allVersionsWasm[wasmIndex]
pathToURL[compiler.path] = baseURLWasm
} else {
pathToURL[compiler.path] = baseURLBin
}
})
}
callback(allVersions, selectedVersion)
}
scheduleCompilation () {
if (!this.config.get('autoCompile')) return
if (this.data.compileTimeout) window.clearTimeout(this.data.compileTimeout)

@ -11,7 +11,7 @@ const profile = {
displayName: 'Debugger',
methods: ['debug', 'getTrace'],
events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNjk2IDk2MHEwIDI2LTE5IDQ1dC00NSAxOWgtMjI0cTAgMTcxLTY3IDI5MGwyMDggMjA5cTE5IDE5IDE5IDQ1dC0xOSA0NXEtMTggMTktNDUgMTl0LTQ1LTE5bC0xOTgtMTk3cS01IDUtMTUgMTN0LTQyIDI4LjUtNjUgMzYuNS04MiAyOS05NyAxM3YtODk2aC0xMjh2ODk2cS01MSAwLTEwMS41LTEzLjV0LTg3LTMzLTY2LTM5LTQzLjUtMzIuNWwtMTUtMTQtMTgzIDIwN3EtMjAgMjEtNDggMjEtMjQgMC00My0xNi0xOS0xOC0yMC41LTQ0LjV0MTUuNS00Ni41bDIwMi0yMjdxLTU4LTExNC01OC0yNzRoLTIyNHEtMjYgMC00NS0xOXQtMTktNDUgMTktNDUgNDUtMTloMjI0di0yOTRsLTE3My0xNzNxLTE5LTE5LTE5LTQ1dDE5LTQ1IDQ1LTE5IDQ1IDE5bDE3MyAxNzNoODQ0bDE3My0xNzNxMTktMTkgNDUtMTl0NDUgMTkgMTkgNDUtMTkgNDVsLTE3MyAxNzN2Mjk0aDIyNHEyNiAwIDQ1IDE5dDE5IDQ1em0tNDgwLTU3NmgtNjQwcTAtMTMzIDkzLjUtMjI2LjV0MjI2LjUtOTMuNSAyMjYuNSA5My41IDkzLjUgMjI2LjV6Ii8+PC9zdmc+',
icon: 'assets/img/debuggerLogo.webp',
description: 'Debug transactions',
kind: 'debugging',
location: 'sidePanel',
@ -56,19 +56,25 @@ class DebuggerTab extends ViewPlugin {
})
this.debuggerUI = new DebuggerUI(
this,
this.el.querySelector('#debugger'),
this.blockchain,
(address, receipt) => {
const target = (address && remixLib.helpers.trace.isContractCreation(address)) ? receipt.contractAddress : address
return this.call('fetchAndCompile', 'resolve', target || receipt.contractAddress || receipt.to, '.debug', this.blockchain.web3())
})
}
)
this.call('manager', 'activatePlugin', 'source-verification')
this.call('manager', 'activatePlugin', 'source-verification').catch(e => console.log(e.message))
// this.call('manager', 'activatePlugin', 'udapp')
return this.el
}
deactivate () {
this.debuggerUI.deleteHighlights()
super.deactivate()
}
debug (hash) {
if (this.debuggerUI) this.debuggerUI.debug(hash)
}

@ -22,17 +22,12 @@ var css = csjs`
.statusMessage {
margin-left: 15px;
}
.innerShift {
padding: 2px;
margin-left: 10px;
}
`
class DebuggerUI {
constructor (container, blockchain, fetchContractAndCompile) {
this.registry = globalRegistry
this.blockchain = blockchain
constructor (debuggerModule, component, fetchContractAndCompile) {
this.debuggerModule = debuggerModule
this.fetchContractAndCompile = fetchContractAndCompile
this.event = new EventManager()
@ -48,62 +43,62 @@ class DebuggerUI {
this.view
container.appendChild(this.render())
component.appendChild(this.render())
this.setEditor()
}
setEditor () {
const self = this
this.editor = this.registry.get('editor').api
this.editor = globalRegistry.get('editor').api
self.editor.event.register('breakpointCleared', (fileName, row) => {
if (self.debugger) self.debugger.breakPointManager.remove({fileName: fileName, row: row})
this.editor.event.register('breakpointCleared', (fileName, row) => {
if (this.debugger) this.debugger.breakPointManager.remove({fileName: fileName, row: row})
})
self.editor.event.register('breakpointAdded', (fileName, row) => {
if (self.debugger) self.debugger.breakPointManager.add({fileName: fileName, row: row})
this.editor.event.register('breakpointAdded', (fileName, row) => {
if (this.debugger) this.debugger.breakPointManager.add({fileName: fileName, row: row})
})
self.editor.event.register('contentChanged', function () {
if (self.debugger) self.debugger.unload()
this.editor.event.register('contentChanged', () => {
if (this.debugger) this.debugger.unload()
})
}
listenToEvents () {
const self = this
if (!self.debugger) return
if (!this.debugger) return
this.debugger.event.register('debuggerStatus', function (isActive) {
self.sourceHighlighter.currentSourceLocation(null)
self.isActive = isActive
this.debugger.event.register('debuggerStatus', async (isActive) => {
await this.debuggerModule.call('editor', 'discardHighlight')
this.isActive = isActive
})
this.debugger.event.register('newSourceLocation', async function (lineColumnPos, rawLocation) {
const contracts = await self.fetchContractAndCompile(
self.currentReceipt.contractAddress || self.currentReceipt.to,
self.currentReceipt)
this.debugger.event.register('newSourceLocation', async (lineColumnPos, rawLocation) => {
const contracts = await this.fetchContractAndCompile(
this.currentReceipt.contractAddress || this.currentReceipt.to,
this.currentReceipt)
if (contracts) {
const path = contracts.getSourceName(rawLocation.file)
if (path) self.sourceHighlighter.currentSourceLocationFromfileName(lineColumnPos, path)
if (path) {
await this.debuggerModule.call('editor', 'discardHighlight')
await this.debuggerModule.call('editor', 'highlight', lineColumnPos, path)
}
}
})
this.debugger.event.register('debuggerUnloaded', self.unLoad.bind(this))
this.debugger.event.register('debuggerUnloaded', () => this.unLoad())
}
startTxBrowser () {
const self = this
let txBrowser = new TxBrowser()
this.txBrowser = txBrowser
txBrowser.event.register('requestDebug', function (blockNumber, txNumber, tx) {
if (self.debugger) self.debugger.unload()
self.startDebugging(blockNumber, txNumber, tx)
txBrowser.event.register('requestDebug', (blockNumber, txNumber, tx) => {
if (this.debugger) this.debugger.unload()
this.startDebugging(blockNumber, txNumber, tx)
})
txBrowser.event.register('unloadRequested', this, function (blockNumber, txIndex, tx) {
if (self.debugger) self.debugger.unload()
txBrowser.event.register('unloadRequested', this, (blockNumber, txIndex, tx) => {
if (this.debugger) this.debugger.unload()
})
}
@ -113,13 +108,13 @@ class DebuggerUI {
getDebugWeb3 () {
return new Promise((resolve, reject) => {
this.blockchain.detectNetwork((error, network) => {
this.debuggerModule.blockchain.detectNetwork((error, network) => {
let web3
if (error || !network) {
web3 = init.web3DebugNode(this.blockchain.web3())
web3 = init.web3DebugNode(this.debuggerModule.blockchain.web3())
} else {
const webDebugNode = init.web3DebugNode(network.name)
web3 = !webDebugNode ? this.blockchain.web3() : webDebugNode
web3 = !webDebugNode ? this.debuggerModule.blockchain.web3() : webDebugNode
}
init.extendWeb3(web3)
resolve(web3)
@ -134,7 +129,7 @@ class DebuggerUI {
this.currentReceipt = await web3.eth.getTransactionReceipt(txNumber)
this.debugger = new Debugger({
web3,
offsetToLineColumnConverter: this.registry.get('offsettolinecolumnconverter').api,
offsetToLineColumnConverter: globalRegistry.get('offsettolinecolumnconverter').api,
compilationResult: async (address) => {
try {
return await this.fetchContractAndCompile(address, this.currentReceipt)
@ -164,7 +159,7 @@ class DebuggerUI {
this.currentReceipt = await web3.eth.getTransactionReceipt(hash)
const debug = new Debugger({
web3,
offsetToLineColumnConverter: this.registry.get('offsettolinecolumnconverter').api,
offsetToLineColumnConverter: globalRegistry.get('offsettolinecolumnconverter').api,
compilationResult: async (address) => {
try {
return await this.fetchContractAndCompile(address, this.currentReceipt)
@ -186,26 +181,28 @@ class DebuggerUI {
}
render () {
this.debuggerPanelsView = yo`<div class="${css.innerShift}"></div>`
this.debuggerHeadPanelsView = yo`<div class="${css.innerShift}"></div>`
this.stepManagerView = yo`<div class="${css.innerShift}"></div>`
this.debuggerPanelsView = yo`<div class="px-2"></div>`
this.debuggerHeadPanelsView = yo`<div class="px-2"></div>`
this.stepManagerView = yo`<div class="px-2"></div>`
var view = yo`<div>
<div class="${css.innerShift}">
var view = yo`
<div>
<div class="px-2">
${this.txBrowser.render()}
${this.debuggerHeadPanelsView}
${this.stepManagerView}
${this.debuggerHeadPanelsView}
</div>
<div class="${css.statusMessage}" >${this.statusMessage}</div>
<div class="${css.statusMessage}">${this.statusMessage}</div>
${this.debuggerPanelsView}
</div>`
</div>
`
if (!this.view) {
this.view = view
}
return view
}
unLoad () {
async unLoad () {
yo.update(this.debuggerHeadPanelsView, yo`<div></div>`)
yo.update(this.debuggerPanelsView, yo`<div></div>`)
yo.update(this.stepManagerView, yo`<div></div>`)
@ -218,6 +215,10 @@ class DebuggerUI {
this.event.trigger('traceUnloaded')
}
async deleteHighlights () {
await this.debuggerModule.call('editor', 'discardHighlight')
}
renderDebugger () {
yo.update(this.debuggerHeadPanelsView, this.vmDebugger.renderHead())
yo.update(this.debuggerPanelsView, this.vmDebugger.render())

@ -45,14 +45,14 @@ function ButtonNavigator () {
ButtonNavigator.prototype.render = function () {
var self = this
var view = yo`<div class="${css.buttons}">
<div class="${css.stepButtons} btn-group p-1">
<div class="${css.stepButtons} btn-group py-1">
<button id='overback' class='btn btn-primary btn-sm ${css.navigator} ${css.stepButton} fas fa-reply' title='Step over back' onclick=${function () { self.event.trigger('stepOverBack') }} disabled=${this.overBackDisabled} ></button>
<button id='intoback' data-id="buttonNavigatorIntoBack" class='btn btn-primary btn-sm ${css.navigator} ${css.stepButton} fas fa-level-up-alt' title='Step back' onclick=${function () { self.event.trigger('stepIntoBack') }} disabled=${this.intoBackDisabled} ></button>
<button id='intoforward' data-id="buttonNavigatorIntoForward" class='btn btn-primary btn-sm ${css.navigator} ${css.stepButton} fas fa-level-down-alt' title='Step into' onclick=${function () { self.event.trigger('stepIntoForward') }} disabled=${this.intoForwardDisabled} ></button>
<button id='overforward' class='btn btn-primary btn-sm ${css.navigator} ${css.stepButton} fas fa-share' title='Step over forward'onclick=${function () { self.event.trigger('stepOverForward') }} disabled=${this.overForwardDisabled} ></button>
</div>
<div class="${css.jumpButtons} btn-group p-1">
<div class="${css.jumpButtons} btn-group py-1">
<button class='btn btn-primary btn-sm ${css.navigator} ${css.jumpButton} fas fa-step-backward' id='jumppreviousbreakpoint' data-id="buttonNavigatorJumpPreviousBreakpoint" title='Jump to the previous breakpoint' onclick=${function () { self.event.trigger('jumpPreviousBreakpoint') }} disabled=${this.jumpPreviousBreakpointDisabled} ></button>
<button class='btn btn-primary btn-sm ${css.navigator} ${css.jumpButton} fas fa-eject' id='jumpout' title='Jump out' onclick=${function () { self.event.trigger('jumpOut') }} disabled=${this.jumpOutDisabled} ></button>
<button class='btn btn-primary btn-sm ${css.navigator} ${css.jumpButton} fas fa-step-forward' id='jumpnextbreakpoint' data-id="buttonNavigatorJumpNextBreakpoint" title='Jump to the next breakpoint' onclick=${function () { self.event.trigger('jumpNextBreakpoint') }} disabled=${this.jumpNextBreakpointDisabled} ></button>

@ -43,7 +43,7 @@ class Slider {
render () {
var self = this
var view = yo`<div>
<input id='slider' data-id="slider" style='width: 100%' type='range' min=0 max=${this.max} value=0
<input id='slider' data-id="slider" class='w-100 my-0' type='range' min=0 max=${this.max} value=0
onchange=${function () { self.onChange() }} oninput=${function () { self.onChange() }} disabled=${this.disabled} />
</div>`
if (!this.view) {

@ -49,10 +49,11 @@ StepManager.prototype.remove = function () {
}
StepManager.prototype.render = function () {
return yo`<div>
${this.slider.render()}
${this.buttonNavigator.render()}
</div>`
return yo`
<div class="py-1">
${this.slider.render()}
${this.buttonNavigator.render()}
</div>`
}
module.exports = StepManager

@ -11,11 +11,6 @@ var css = csjs`
display: flex;
flex-direction: column;
}
.txinputs {
width: 100%;
display: flex;
justify-content: center;
}
.txinput {
width: inherit;
font-size: small;
@ -23,16 +18,9 @@ var css = csjs`
overflow: hidden;
text-overflow: ellipsis;
}
.txbuttons {
width: 100%;
display: flex;
justify-content: center;
}
.txbutton {
width: inherit;
}
.txbuttonstart {
}
.txbutton:hover {
}
.vmargin {
@ -110,10 +98,10 @@ TxBrowser.prototype.render = function () {
var view = yo`
<div class="${css.container}">
<div class="${css.txContainer}">
<div class="${css.txinputs} p-1 input-group">
<div class=" py-1 d-flex justify-content-center w-100 input-group">
${this.state.txNumberInput}
</div>
<div class="${css.txbuttons} btn-group p-1">
<div class="d-flex justify-content-center w-100 btn-group py-1">
${txButton}
</div>
</div>

@ -1,11 +1,11 @@
'use strict'
var csjs = require('csjs-inject')
var yo = require('yo-yo')
var CodeListView = require('./vmDebugger/CodeListView')
var CalldataPanel = require('./vmDebugger/CalldataPanel')
var MemoryPanel = require('./vmDebugger/MemoryPanel')
var CallstackPanel = require('./vmDebugger/CallstackPanel')
var FunctionPanel = require('./vmDebugger/FunctionPanel')
var StackPanel = require('./vmDebugger/StackPanel')
var StoragePanel = require('./vmDebugger/StoragePanel')
var StepDetail = require('./vmDebugger/StepDetail')
@ -14,18 +14,6 @@ var SolidityLocals = require('./vmDebugger/SolidityLocals')
var FullStoragesChangesPanel = require('./vmDebugger/FullStoragesChanges')
var DropdownPanel = require('./vmDebugger/DropdownPanel')
var css = csjs`
.asmCode {
width: 100%;
}
.stepDetail {
width: 100%;
}
.vmheadView {
margin-top:10px;
}
`
function VmDebugger (vmDebuggerLogic) {
var self = this
this.view
@ -48,6 +36,16 @@ function VmDebugger (vmDebuggerLogic) {
this.stackPanel = new StackPanel()
this.vmDebuggerLogic.event.register('traceManagerStackUpdate', this.stackPanel.update.bind(this.stackPanel))
this.functionPanel = new FunctionPanel()
this.vmDebuggerLogic.event.register('functionsStackUpdate', (stack) => {
if (stack === null) return
let functions = []
for (let func of stack) {
functions.push(func.functionDefinition.attributes.name + '(' + func.inputs.join(', ') + ')')
}
this.functionPanel.update(functions)
})
this.storagePanel = new StoragePanel()
this.vmDebuggerLogic.event.register('traceManagerStorageUpdate', this.storagePanel.update.bind(this.storagePanel))
@ -107,27 +105,39 @@ function VmDebugger (vmDebuggerLogic) {
self.asmCode.basicPanel.show()
self.stackPanel.basicPanel.show()
self.functionPanel.basicPanel.show()
self.storagePanel.basicPanel.show()
self.memoryPanel.basicPanel.show()
self.stepDetail.basicPanel.show()
self.calldataPanel.basicPanel.show()
self.callstackPanel.basicPanel.show()
})
this.vmDebuggerLogic.event.register('newCallTree', () => {
if (!self.view) return
self.functionPanel.basicPanel.show()
self.solidityLocals.basicPanel.show()
self.solidityState.basicPanel.show()
self.solidityPanel.hidden = false
})
this.vmDebuggerLogic.start()
}
VmDebugger.prototype.renderHead = function () {
this.solidityPanel = yo`
<div class="w-100" hidden>
${this.functionPanel.render()}
${this.solidityLocals.render()}
${this.solidityState.render()}
</div>
`
const headView = yo`
<div id="vmheadView" class="${css.vmheadView} container">
<div class="row" >
<div class="${css.asmCode} column">${this.asmCode.render()}</div>
<div class="${css.stepDetail} column">${this.stepDetail.render()}</div>
<div id="vmheadView" class="mt-1 px-0">
<div class="d-flex flex-column">
${this.solidityPanel}
<div class="w-100">${this.asmCode.render()}</div>
<div class="w-100">${this.stepDetail.render()}</div>
</div>
</div>
`
@ -144,10 +154,8 @@ VmDebugger.prototype.remove = function () {
VmDebugger.prototype.render = function () {
const view = yo`
<div id="vmdebugger" class="pl-2">
<div id="vmdebugger" class="px-2">
<div>
${this.solidityLocals.render()}
${this.solidityState.render()}
${this.stackPanel.render()}
${this.memoryPanel.render()}
${this.storagePanel.render()}

@ -9,14 +9,6 @@ module.exports = {
font: {
'font-family': 'arial,sans-serif'
},
innerShift: {
'padding': '2px',
'margin-left': '10px'
},
container: {
'margin': '10px',
'padding': '5px'
},
statusMessage: {
'margin-left': '15px'
},

@ -8,7 +8,7 @@ var csjs = require('csjs-inject')
var css = csjs`
.instructions {
overflow-y: scroll;
max-height: 150px;
max-height: 130px;
}
`
function CodeListView () {
@ -41,12 +41,9 @@ CodeListView.prototype.indexChanged = function (index) {
}
let codeView = this.view.querySelector('#asmitems')
this.itemSelected = codeView.children[index]
this.itemSelected.style.setProperty('background-color', 'var(--info)')
this.itemSelected.style.setProperty('color', 'var(--light)')
this.itemSelected.style.setProperty('border-color', 'var(--primary)')
this.itemSelected.style.setProperty('border-style', 'solid')
this.itemSelected.setAttribute('selected', 'selected')
if (this.itemSelected.firstChild) {
this.itemSelected.firstChild.setAttribute('style', 'margin-left: 2px')
}
codeView.scrollTop = this.itemSelected.offsetTop - parseInt(codeView.offsetTop)
}
@ -67,9 +64,9 @@ CodeListView.prototype.changed = function (code, address, index) {
CodeListView.prototype.renderAssemblyItems = function () {
if (this.code) {
var codeView = this.code.map(function (item, i) {
return yo`<div key=${i} value=${i}><span>${item}</span></div>`
return yo`<div class="px-1" key=${i} value=${i}><span>${item}</span></div>`
})
return yo`<div class=${css.instructions} id='asmitems' ref='itemsList'>
return yo`<div class="pl-2 my-1 small ${css.instructions}" id='asmitems' ref='itemsList'>
${codeView}
</div>`
}

@ -1,12 +1,12 @@
'use strict'
var yo = require('yo-yo')
const copy = require('copy-text-to-clipboard')
var EventManager = require('../../../../../lib/events')
var TreeView = require('../../../../ui/TreeView') // TODO setup a direct reference to the UI components
const yo = require('yo-yo')
const copyToClipboard = require('../../../../ui/copy-to-clipboard')
const EventManager = require('../../../../../lib/events')
const TreeView = require('../../../../ui/TreeView') // TODO setup a direct reference to the UI components
var csjs = require('csjs-inject')
const csjs = require('csjs-inject')
var css = csjs`
const css = csjs`
.title {
display: flex;
align-items: center;
@ -24,8 +24,6 @@ var css = csjs`
.eyeButton {
margin: 3px;
}
.eyeButton:hover {
}
.dropdownpanel {
width: 100%;
word-break: break-word;
@ -87,7 +85,7 @@ DropdownPanel.prototype.update = function (_data, _header) {
this.view.querySelector('.dropdownpanel .dropdowncontent').style.display = 'block'
this.view.querySelector('.dropdownpanel .dropdownrawcontent').innerText = JSON.stringify(_data, null, '\t')
if (!this.displayContentOnly) {
this.view.querySelector('.title div.btn').style.display = 'block'
this.view.querySelector('.title i.fa-copy').style.display = 'block'
this.view.querySelector('.title span').innerText = _header || ' '
}
this.message('')
@ -101,6 +99,11 @@ DropdownPanel.prototype.setContent = function (node) {
yo.update(this.view, this.render(null, node))
}
DropdownPanel.prototype.copyClipboard = function () {
const content = this.view.querySelector('.dropdownpanel .dropdownrawcontent')
return content.innerText ? content.innerText : content.textContent
}
DropdownPanel.prototype.render = function (overridestyle, node) {
var content = yo`<div>Empty</div>`
if (this.json) {
@ -108,20 +111,21 @@ DropdownPanel.prototype.render = function (overridestyle, node) {
}
overridestyle === undefined ? {} : overridestyle
var self = this
var title = !self.displayContentOnly ? yo`<div class="${css.title} title">
<div class="${css.icon} fas fa-caret-right" onclick=${function () { self.toggle() }} ></div>
<div class="${css.name}" onclick=${function () { self.toggle() }} >${this.name}</div><span class="${css.nameDetail}" onclick=${function () { self.toggle() }} ></span>
<div onclick=${function () { self.copyClipboard() }} title='raw' class="${css.eyeButton} btn far fa-clipboard"></div>
</div>` : yo`<div></div>`
var contentNode = yo`<div class='dropdownpanel ${css.dropdownpanel}' style='display:none'>
var contentNode = yo`
<div class='dropdownpanel ${css.dropdownpanel}' style='display:none'>
<i class="${css.refresh} fas fa-sync" aria-hidden="true"></i>
<div class='dropdowncontent'>${node || content}</div>
<div class='dropdownrawcontent' style='display:none'></div>
<div class='message' style='display:none'></div>
</div>`
var title = !self.displayContentOnly ? yo`<div class="${css.title} py-0 px-1 title">
<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>
${copyToClipboard(() => this.copyClipboard())}
</div>` : yo`<div></div>`
var view = yo`
<div class="border rounded p-1 m-1 bg-light">
<div class="border rounded px-1 mt-1 bg-light">
<style>
@-moz-keyframes spin {
to { -moz-transform: rotate(359deg); }
@ -143,11 +147,6 @@ DropdownPanel.prototype.render = function (overridestyle, node) {
return view
}
DropdownPanel.prototype.copyClipboard = function () {
var content = this.view.querySelector('.dropdownpanel .dropdownrawcontent')
if (content) copy(content.innerText ? content.innerText : content.textContent)
}
DropdownPanel.prototype.toggle = function () {
var el = this.view.querySelector('.dropdownpanel')
var caret = this.view.querySelector('.title').firstElementChild

@ -0,0 +1,17 @@
'use strict'
var DropdownPanel = require('./DropdownPanel')
var yo = require('yo-yo')
function FunctionPanel () {
this.basicPanel = new DropdownPanel('Function Stack', {json: true, displayContentOnly: false})
}
FunctionPanel.prototype.update = function (calldata) {
this.basicPanel.update(calldata)
}
FunctionPanel.prototype.render = function () {
return yo`<div id="FunctionPanel">${this.basicPanel.render()}</div>`
}
module.exports = FunctionPanel

@ -26,7 +26,8 @@ SolidityState.prototype.setUpdating = function () {
SolidityState.prototype.render = function () {
if (this.view) return
this.view = yo`<div id='soliditystate' >
this.view = yo`
<div id='soliditystate' >
${this.basicPanel.render()}
</div>`
return this.view

@ -2,7 +2,7 @@ var yo = require('yo-yo')
var DropdownPanel = require('./DropdownPanel')
function StepDetail () {
this.basicPanel = new DropdownPanel('Step detail', {json: true, displayContentOnly: true})
this.basicPanel = new DropdownPanel('Step details', {json: true, displayContentOnly: false})
this.detail = { 'vm trace step': '-', 'execution step': '-', 'add memory': '', 'gas': '', 'remaining gas': '-', 'loaded address': '-' }
}

@ -12,7 +12,7 @@ function formatSelf (key, data) {
if (data.type === 'string') {
data.self = JSON.stringify(data.self)
}
return yo `<label style='${keyStyle};white-space:pre-wrap;'> ${' ' + key}:<label style=${style}>${' ' + data.self}</label><label style='font-style:italic'> ${data.isProperty || !data.type ? '' : ' ' + data.type}</label></label>`
return yo `<label class="mb-0" style='${keyStyle};white-space:pre-wrap;'> ${' ' + key}:<label class="mb-0" style=${style}>${' ' + data.self}</label><label style='font-style:italic'> ${data.isProperty || !data.type ? '' : ' ' + data.type}</label></label>`
}
function extractData (item, parent, key) {

@ -34,6 +34,9 @@ class DropdownLogic {
this.runView.on('lexon', 'compilationFinished', (file, source, languageVersion, data) =>
broadcastCompilationResult(file, source, languageVersion, data)
)
this.runView.on('yulp', 'compilationFinished', (file, source, languageVersion, data) =>
broadcastCompilationResult(file, source, languageVersion, data)
)
}
loadContractFromAddress (address, confirmCb, cb) {

@ -12,7 +12,7 @@ const profile = {
displayName: 'Settings',
methods: ['getGithubAccessToken'],
events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xMTUyIDg5NnEwLTEwNi03NS0xODF0LTE4MS03NS0xODEgNzUtNzUgMTgxIDc1IDE4MSAxODEgNzUgMTgxLTc1IDc1LTE4MXptNTEyLTEwOXYyMjJxMCAxMi04IDIzdC0yMCAxM2wtMTg1IDI4cS0xOSA1NC0zOSA5MSAzNSA1MCAxMDcgMTM4IDEwIDEyIDEwIDI1dC05IDIzcS0yNyAzNy05OSAxMDh0LTk0IDcxcS0xMiAwLTI2LTlsLTEzOC0xMDhxLTQ0IDIzLTkxIDM4LTE2IDEzNi0yOSAxODYtNyAyOC0zNiAyOGgtMjIycS0xNCAwLTI0LjUtOC41dC0xMS41LTIxLjVsLTI4LTE4NHEtNDktMTYtOTAtMzdsLTE0MSAxMDdxLTEwIDktMjUgOS0xNCAwLTI1LTExLTEyNi0xMTQtMTY1LTE2OC03LTEwLTctMjMgMC0xMiA4LTIzIDE1LTIxIDUxLTY2LjV0NTQtNzAuNXEtMjctNTAtNDEtOTlsLTE4My0yN3EtMTMtMi0yMS0xMi41dC04LTIzLjV2LTIyMnEwLTEyIDgtMjN0MTktMTNsMTg2LTI4cTE0LTQ2IDM5LTkyLTQwLTU3LTEwNy0xMzgtMTAtMTItMTAtMjQgMC0xMCA5LTIzIDI2LTM2IDk4LjUtMTA3LjV0OTQuNS03MS41cTEzIDAgMjYgMTBsMTM4IDEwN3E0NC0yMyA5MS0zOCAxNi0xMzYgMjktMTg2IDctMjggMzYtMjhoMjIycTE0IDAgMjQuNSA4LjV0MTEuNSAyMS41bDI4IDE4NHE0OSAxNiA5MCAzN2wxNDItMTA3cTktOSAyNC05IDEzIDAgMjUgMTAgMTI5IDExOSAxNjUgMTcwIDcgOCA3IDIyIDAgMTItOCAyMy0xNSAyMS01MSA2Ni41dC01NCA3MC41cTI2IDUwIDQxIDk4bDE4MyAyOHExMyAyIDIxIDEyLjV0OCAyMy41eiIvPjwvc3ZnPg==',
icon: 'assets/img/settings.webp',
description: 'Remix-IDE settings',
kind: 'settings',
location: 'sidePanel',
@ -69,7 +69,7 @@ module.exports = class SettingsTab extends ViewPlugin {
// Gist settings
const token = this.config.get('settings/gist-access-token')
const gistAccessToken = yo`<input id="gistaccesstoken" data-id="settingsTabGistAccessToken" type="password" class="border form-control-sm mb-2 ${css.inline}" placeholder="Token">`
const gistAccessToken = yo`<input id="gistaccesstoken" data-id="settingsTabGistAccessToken" type="password" class="form-control border form-control-sm mb-2 ${css.inline}" placeholder="Token">`
if (token) gistAccessToken.value = token
const removeToken = () => { self.config.set('settings/gist-access-token', ''); gistAccessToken.value = ''; tooltip('Access token removed') }
const saveToken = () => { this.config.set('settings/gist-access-token', gistAccessToken.value); tooltip('Access token saved') }

@ -169,7 +169,7 @@ staticAnalysisView.prototype.run = function () {
<span class="d-flex flex-column">
<span class="h6 font-weight-bold">${result.name}</span>
${item.warning}
${item.more ? yo`<span><a href="${item.more}" target="blank">more</a></span>` : yo`<span></span>`}
${item.more ? yo`<span><a href="${item.more}" target="_blank">more</a></span>` : yo`<span></span>`}
<span class="" title="Position in ${fileName}">Pos: ${locationString}</span>
</span>`
self._components.renderer.error(

@ -4,7 +4,7 @@ var tooltip = require('../ui/tooltip')
var css = require('./styles/test-tab-styles')
var remixTests = require('@remix-project/remix-tests')
import { ViewPlugin } from '@remixproject/engine'
import { canUseWorker, baseUrl } from '../compiler/compiler-utils'
import { canUseWorker, urlFromVersion } from '../compiler/compiler-utils'
const TestTabLogic = require('./testTab/testTab')
@ -13,7 +13,7 @@ const profile = {
displayName: 'Solidity unit testing',
methods: ['testFromPath', 'testFromSource'],
events: [],
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4wUDEhQZ0zbrmQAAAfNJREFUWMPF17lrFVEUx/EPaIKovfAScSndjUtULFQSYhHF0r/Dwsa/RywUiTaWgvaChWsiKkSMZte4o7G5A49x7r0zLy/PA6eZOef3PXebuYfu2xCmcQ9b9NgOYw6rwR9ia6/gR7HQBi/8PjavN/w4FivghV9bT/gwlhLwHzjTVPQ8rqAvE3ciA/+O8abwy/gVBG4lijiJ5czIL64FXvhNbCzFnaoBv9AUPo7fEcEb2BDiTuNTAv4NYxX6u/EIM7GZuZoQXcX1sJk+J2K+YrRCexfetsX9xKVyUB9uZ4r4k3j3BSMR+JvIMv2zQfsxkSkiBj9XAd8ZgRf+vmop+nGnAXwlcs534HUm93FsQ9YtIjby7XiVyZ3BntSpyBWxgrMR+FQG/gF76xzNftxtMO1rgo+G5AdBqLBN4d9eCCyHD1En8Oi0j4UPSBE4hcFSERN4Fz7BZRvEZKcjHynBC5/EQI1lGqgJ3xcTmE4kvswUMRBiUvCPKTg8zQi8QKsirxXe5eD7c1N4ALMZoeelIlrhWSpnNmjXsoM1iihmYhueZGIXcKTp7/hQ6UZb5c+Cp2LmglZHVqeIlC+G2/GarNMiFnGsWzfdpkV0Fd7e5czXgC+FvmDdWq35/wVvbzbnI/DhXvV9Q6W+r6fw9hZsKnjX4H8B0Aamri7CrBsAAAAASUVORK5CYII=',
icon: 'assets/img/unitTesting.webp',
description: 'Fast tool to generate unit tests for your contracts',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/unittesting.html'
@ -33,11 +33,13 @@ module.exports = class TestTab extends ViewPlugin {
this.runningTestsNumber = 0
this.readyTestsNumber = 0
this.areTestsRunning = false
this.defaultPath = 'browser/tests'
appManager.event.on('activate', (name) => {
if (name === 'solidity') this.updateRunAction(fileManager.currentFile())
if (name === 'solidity') this.updateRunAction()
})
appManager.event.on('deactivate', (name) => {
if (name === 'solidity') this.updateRunAction(fileManager.currentFile())
if (name === 'solidity') this.updateRunAction()
})
}
@ -56,9 +58,6 @@ module.exports = class TestTab extends ViewPlugin {
})
this.fileManager.events.on('noFileSelected', () => {
this.updateGenerateFileAction()
this.updateRunAction()
this.updateTestFileList()
})
this.fileManager.events.on('currentFileChanged', (file, provider) => this.updateForNewCurrent(file))
@ -86,6 +85,7 @@ module.exports = class TestTab extends ViewPlugin {
}
listTests () {
if (!this.data.allTests) return []
return this.data.allTests.map(
testFile => this.createSingleTest(testFile)
)
@ -301,7 +301,7 @@ module.exports = class TestTab extends ViewPlugin {
let runningTest = {}
runningTest[path] = { content }
const {currentVersion, evmVersion, optimize} = this.compileTab.getCurrentCompilerConfig()
const currentCompilerUrl = baseUrl + '/' + currentVersion
const currentCompilerUrl = urlFromVersion(currentVersion)
const compilerConfig = {
currentCompilerUrl,
evmVersion,
@ -327,7 +327,7 @@ module.exports = class TestTab extends ViewPlugin {
const runningTest = {}
runningTest[testFilePath] = { content }
const {currentVersion, evmVersion, optimize} = this.compileTab.getCurrentCompilerConfig()
const currentCompilerUrl = baseUrl + '/' + currentVersion
const currentCompilerUrl = urlFromVersion(currentVersion)
const compilerConfig = {
currentCompilerUrl,
evmVersion,
@ -351,6 +351,13 @@ module.exports = class TestTab extends ViewPlugin {
})
}
updateCurrentPath (e) {
const newValue = e.target.value === '' ? this.defaultPath : e.target.value
this.testTabLogic.setCurrentPath(newValue)
this.updateRunAction()
this.updateForNewCurrent()
}
runTests () {
this.areTestsRunning = true
this.hasBeenStopped = false
@ -386,21 +393,16 @@ module.exports = class TestTab extends ViewPlugin {
}
updateGenerateFileAction (currentFile) {
let el = yo`<button
class="btn border w-50"
data-id="testTabGenerateTestFile"
title="Generate sample test file."
onclick="${this.testTabLogic.generateTestFile.bind(this.testTabLogic)}"
>
Generate
</button>`
if (
!currentFile ||
(currentFile && currentFile.split('.').pop().toLowerCase() !== 'sol')
) {
el.setAttribute('disabled', 'disabled')
el.setAttribute('title', 'No solidity file selected')
}
let el = yo`
<button
class="btn border w-50"
data-id="testTabGenerateTestFile"
title="Generate sample test file."
onclick="${this.testTabLogic.generateTestFile.bind(this.testTabLogic)}"
>
Generate
</button>
`
if (!this.generateFileActionElement) {
this.generateFileActionElement = el
} else {
@ -411,13 +413,13 @@ module.exports = class TestTab extends ViewPlugin {
updateRunAction (currentFile) {
let el = yo`
<button id="runTestsTabRunAction" title="Run tests" data-id="testTabRunTestsTabRunAction" class="w-50 btn btn-primary" onclick="${() => this.runTests()}">
<button id="runTestsTabRunAction" title="Run tests" data-id="testTabRunTestsTabRunAction" class="w-50 btn btn-primary" onclick="${() => this.runTests()}">
<span class="fas fa-play ml-2"></span>
<label class="${css.labelOnBtn} btn btn-primary p-1 ml-2 m-0">Run</label>
</button>
`
const isSolidityActive = this.appManager.actives.includes('solidity')
if (!currentFile || !isSolidityActive || (currentFile && currentFile.split('.').pop().toLowerCase() !== 'sol')) {
const isSolidityActive = this.appManager.isActive('solidity')
if (!isSolidityActive || !this.listTests().length) {
el.setAttribute('disabled', 'disabled')
if (!currentFile || (currentFile && currentFile.split('.').pop().toLowerCase() !== 'sol')) {
el.setAttribute('title', 'No solidity file selected')
@ -451,6 +453,7 @@ module.exports = class TestTab extends ViewPlugin {
} else {
yo.update(this.testFilesListElement, el)
}
this.updateRunAction()
return this.testFilesListElement
}
@ -482,11 +485,40 @@ module.exports = class TestTab extends ViewPlugin {
return yo`<span class='text-info h6'>Progress: ${ready} finished (of ${this.runningTestsNumber})</span>`
}
updateDirList () {
for (var o of this.uiPathList.querySelectorAll('option')) o.remove()
this.uiPathList.appendChild(yo`<option>browser</option>`)
if (this.testTabLogic.isRemixDActive()) this.uiPathList.appendChild(yo`<option>localhost</option>`)
if (!this._view.el) return
this.testTabLogic.dirList(this.inputPath.value).then((options) => {
options.forEach((path) => this.uiPathList.appendChild(yo`<option>${path}</option>`))
})
}
render () {
this.onActivationInternal()
this.testsOutput = yo`<div class="mx-3 mb-2 pb-4 border-top border-primary" hidden='true' id="solidityUnittestsOutput" data-id="testTabSolidityUnitTestsOutput"></a>`
this.testsExecutionStopped = yo`<label class="text-warning h6" data-id="testTabTestsExecutionStopped">The test execution has been stopped</label>`
this.testsExecutionStoppedError = yo`<label class="text-danger h6" data-id="testTabTestsExecutionStoppedError">The test execution has been stopped because of error(s) in your test file</label>`
this.uiPathList = yo`<datalist id="utPathList"></datalist>`
this.inputPath = yo`<input
placeholder=${this.defaultPath}
list="utPathList"
class="custom-select"
id="utPath"
data-id="uiPathInput"
name="utPath"
style="background-image: var(--primary);"
onkeydown=${(e) => { if (e.keyCode === 191) this.updateDirList() }}
onchange=${(e) => this.updateCurrentPath(e)}/>`
const availablePaths = yo`
<div>
${this.inputPath}
${this.uiPathList}
</div>
`
this.updateDirList()
this.testsExecutionStopped.hidden = true
this.testsExecutionStoppedError.hidden = true
this.resultStatistics = this.createResultLabel()
@ -495,7 +527,9 @@ module.exports = class TestTab extends ViewPlugin {
<div class="${css.testTabView} px-2" id="testView">
<div class="${css.infoBox}">
<p class="text-lg"> Test your smart contract in Solidity.</p>
<p> Click on "Generate" to generate a sample test file.</p>
<p> Select directory to load and generate test files.</p>
<label>Test directory:</label>
${availablePaths}
</div>
<div class="${css.tests}">
<div class="d-flex p-2">
@ -518,6 +552,7 @@ module.exports = class TestTab extends ViewPlugin {
</div>
`
this._view.el = el
this.testTabLogic.setCurrentPath(this.defaultPath)
this.updateForNewCurrent(this.fileManager.currentFile())
return el
}

@ -1,52 +1,71 @@
const helper = require('../../../lib/helper.js')
const modalDialogCustom = require('../../ui/modal-dialog-custom')
const remixPath = require('path')
class TestTabLogic {
constructor (fileManager) {
this.fileManager = fileManager
this.currentPath = 'browser/tests'
}
setCurrentPath (path) {
if (path.indexOf('/') === 0) return
this.currentPath = path
const fileProvider = this.fileManager.fileProviderOf(path.split('/')[0])
fileProvider.exists(path, (e, res) => { if (!res) fileProvider.createDir(path) })
}
generateTestFile () {
const path = this.fileManager.currentPath()
const fileName = this.fileManager.currentFile()
const fileProvider = this.fileManager.fileProviderOf(path)
let fileName = this.fileManager.currentFile()
const hasCurrent = !!fileName && this.fileManager.currentFile().split('.').pop().toLowerCase() === 'sol'
if (!hasCurrent) fileName = this.currentPath + '/newFile.sol'
const fileProvider = this.fileManager.fileProviderOf(this.currentPath)
if (!fileProvider) return
helper.createNonClashingNameWithPrefix(fileName, fileProvider, '_test', (error, newFile) => {
const splittedFileName = fileName.split('/')
let fileNameToImport = (!hasCurrent) ? fileName : this.currentPath + '/' + splittedFileName[splittedFileName.length - 1]
helper.createNonClashingNameWithPrefix(fileNameToImport, fileProvider, '_test', (error, newFile) => {
if (error) return modalDialogCustom.alert('Failed to create file. ' + newFile + ' ' + error)
const splittedFileName = fileName.split('/')
// This is fine for now because test file is created on same path where file to be tested is.
// This should be updated to pass complete path, if test file comes from different directory/path
const fileNameToImport = splittedFileName[splittedFileName.length - 1]
if (!fileProvider.set(newFile, this.generateTestContractSample(fileNameToImport))) return modalDialogCustom.alert('Failed to create test file ' + newFile)
if (!fileProvider.set(newFile, this.generateTestContractSample(hasCurrent, fileName))) return modalDialogCustom.alert('Failed to create test file ' + newFile)
this.fileManager.open(newFile)
this.fileManager.syncEditor(newFile)
})
}
dirList (path) {
return this.fileManager.dirList(path)
}
isRemixDActive () {
return this.fileManager.isRemixDActive()
}
async getTests (cb) {
const path = this.fileManager.currentPath()
if (!path) return cb(null, [])
const provider = this.fileManager.fileProviderOf(path)
if (!this.currentPath) return cb(null, [])
const provider = this.fileManager.fileProviderOf(this.currentPath)
if (!provider) return cb(null, [])
const tests = []
let files
try {
files = await this.fileManager.readdir(path)
files = await this.fileManager.readdir(this.currentPath)
} catch (e) {
cb(e.message)
}
for (var file in files) {
if (/.(_test.sol)$/.exec(file)) tests.push(provider.type + '/' + file)
}
cb(null, tests, path)
cb(null, tests, this.currentPath)
}
// @todo(#2758): If currently selected file is compiled and compilation result is available,
// 'contractName' should be <compiledContractName> + '_testSuite'
generateTestContractSample (fileToImport, contractName = 'testSuite') {
generateTestContractSample (hasCurrent, fileToImport, contractName = 'testSuite') {
let relative = remixPath.relative(this.currentPath, remixPath.dirname(fileToImport))
if (relative === '') relative = '.'
const comment = hasCurrent ? `import "${relative}/${remixPath.basename(fileToImport)}";` : '// Import here the file to test.'
return `pragma solidity >=0.4.22 <0.7.0;
import "remix_tests.sol"; // this import is automatically injected by Remix.
import "./${fileToImport}";
${comment}
// File name has to end with '_test.sol', this file can contain more than one testSuite contracts
contract ${contractName} {
@ -65,7 +84,7 @@ contract ${contractName} {
Assert.notEqual(uint(2), uint(3), "2 should not be equal to 3");
}
function checkSuccess2() public view returns (bool) {
function checkSuccess2() public pure returns (bool) {
// Use the return value (true or false) to test the contract
return true;
}

@ -1,11 +1,12 @@
import { Plugin } from '@remixproject/engine'
import { EventEmitter } from 'events'
import QueryParams from '../../lib/query-params'
import * as packageJson from '../../../package.json'
import yo from 'yo-yo'
const themes = [
{name: 'Dark', quality: 'dark', url: 'https://res.cloudinary.com/dvtmp0niu/raw/upload/v1584965247/remix-dark_tmkdla.css'},
{name: 'Light', quality: 'light', url: 'https://res.cloudinary.com/dvtmp0niu/raw/upload/v1584966540/remix-light_t0c780.css'},
{name: 'Light', quality: 'light', url: 'https://res.cloudinary.com/remix/raw/upload/v1594059208/remix-themes/remix-light_csxus2.css'},
{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'},
@ -17,7 +18,6 @@ const themes = [
{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'}
]
@ -38,7 +38,9 @@ export class ThemeModule extends Plugin {
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') : 'Dark'
const theme = (new QueryParams()).get().theme
this.active = theme || this._deps.config.get('settings/theme') || 'Dark'
this.forced = theme !== undefined
}
/** Return the active theme */
@ -76,7 +78,7 @@ export class ThemeModule extends Plugin {
}
const next = themeName || this.active // Name
const nextTheme = this.themes[next] // Theme
this._deps.config.set('settings/theme', next)
if (!this.forced) 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

@ -32,7 +32,7 @@ export function makeUdapp (blockchain, compilersArtefacts, logHtmlCallback) {
const txlistener = blockchain.getTxListener({
api: {
contracts: function () {
if (compilersArtefacts['__last']) return compilersArtefacts['__last'].getContracts()
if (compilersArtefacts['__last']) return compilersArtefacts.getAllContractDatas()
return null
},
resolveReceipt: function (tx, cb) {

@ -20,7 +20,7 @@ const UniversalDAppUI = require('../ui/universal-dapp-ui')
const profile = {
name: 'udapp',
displayName: 'Deploy & run transactions',
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzFfY29weSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4Ig0KCSB5PSIwcHgiIHdpZHRoPSI3NDIuNTQ1cHgiIGhlaWdodD0iNjc2Ljg4NnB4IiB2aWV3Qm94PSIwIC0wLjIwNCA3NDIuNTQ1IDY3Ni44ODYiDQoJIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAtMC4yMDQgNzQyLjU0NSA2NzYuODg2IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnPg0KCTxwb2x5Z29uIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBwb2ludHM9IjI5NS45MTEsMC43MTEgNDg4LjkxMSwzMDQuMTg2IDQ4OC45MTEsMzk3LjE4MSAyOTMuOTExLDY3Ni41NTYgDQoJCTc0MS43ODYsMzQ5Ljk0MyAJIi8+DQoJPHBvbHlnb24gc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50cz0iNDE3LjA4Myw0MDYuNTg5IDIwOS43OTEsNTE5LjQ5NCAxLjg0Niw0MDYuMjM0IDIwOS43OTEsNjc1Ljg2MyAJIi8+DQoJPHBvbHlnb24gc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50cz0iNDE3LjA4MywzMTguNzA3IDIwOS43OTEsMC43MTEgMS44NDYsMzE4LjQyOCAyMDkuNzkxLDQzMS42ODkgCSIvPg0KPC9nPg0KPC9zdmc+DQo=',
icon: 'assets/img/deployAndRun.webp',
description: 'execute and save transactions',
kind: 'udapp',
location: 'sidePanel',

@ -9,7 +9,6 @@ var css = csjs`
-webkit-margin-start: 0px;
-webkit-margin-end: 0px;
-webkit-padding-start: 0px;
margin-left: 10px;
}
.ul_tv {
list-style-type: none;
@ -22,13 +21,19 @@ var css = csjs`
.caret_tv {
width: 10px;
flex-shrink: 0;
padding-right: 5px;
}
.label_tv {
align-items: center;
.label_item {
word-break: break-all;
}
.label_tv>span {
.label_key {
min-width: 15%;
max-width: 80%;
word-break: break-word;
}
.label_value {
min-width: 10%;
}
`
var EventManager = require('../../lib/events')
@ -75,22 +80,22 @@ class TreeView {
var children = Object.keys(json).map((innerkey) => {
return this.renderObject(json[innerkey], json, innerkey, expand, innerkey)
})
return yo`<ul key=${key} data-id="treeViewUl${key}" class="${css.ul_tv}">${children}</ul>`
return yo`<ul key=${key} data-id="treeViewUl${key}" class="${css.ul_tv} ml-0 px-2">${children}</ul>`
}
formatData (key, data, children, expand, keyPath) {
var self = this
var li = yo`<li key=${keyPath} data-id="treeViewLi${keyPath}" class=${css.li_tv}></li>`
var caret = yo`<div class="fas fa-caret-right caret ${css.caret_tv}"></div>`
var caret = yo`<div class="px-1 fas fa-caret-right caret ${css.caret_tv}"></div>`
var label = yo`
<div key=${keyPath} data-id="treeViewDiv${keyPath}" class=${css.label_tv}>
<div key=${keyPath} data-id="treeViewDiv${keyPath}" class="d-flex flex-row align-items-center">
${caret}
<span>${self.formatSelf(key, data, li)}</span>
<span class="w-100">${self.formatSelf(key, data, li)}</span>
</div>`
const expanded = self.expandPath.includes(keyPath)
li.appendChild(label)
if (data.children) {
var list = yo`<ul key=${keyPath} data-id="treeViewUlList${keyPath}" class=${css.ul_tv}>${children}</ul>`
var list = yo`<ul key=${keyPath} data-id="treeViewUlList${keyPath}" class="pl-2 ${css.ul_tv}">${children}</ul>`
list.style.display = expanded ? 'block' : 'none'
caret.className = list.style.display === 'none' ? `fas fa-caret-right caret ${css.caret_tv}` : `fas fa-caret-down caret ${css.caret_tv}`
caret.setAttribute('data-id', `treeViewToggle${keyPath}`)
@ -99,7 +104,7 @@ class TreeView {
if (self.isExpanded(keyPath)) {
if (!self.expandPath.includes(keyPath)) self.expandPath.push(keyPath)
} else {
self.expandPath = self.expandPath.filter(path => path !== keyPath)
self.expandPath = self.expandPath.filter(path => !path.startsWith(keyPath))
}
}
label.oncontextmenu = function (event) {
@ -164,7 +169,12 @@ class TreeView {
}
formatSelfDefault (key, data) {
return yo`<label>${key}: ${data.self}</label>`
return yo`
<div class="d-flex mb-1 flex-row ${css.label_item}">
<label class="small font-weight-bold pr-1 ${css.label_key}">${key}:</label>
<label class="m-0 ${css.label_value}">${data.self}</label>
</div>
`
}
extractDataDefault (item, parent, key) {

File diff suppressed because one or more lines are too long

@ -8,7 +8,7 @@ export class FramingService {
this.resizeFeature = resizeFeature
}
start () {
start (params) {
this.sidePanel.events.on('toggle', () => {
this.resizeFeature.panel.clientWidth !== 0 ? this.resizeFeature.hidePanel() : this.resizeFeature.showPanel()
})
@ -33,5 +33,13 @@ export class FramingService {
e.preventDefault()
}
})
if (params.minimizeterminal) this.mainView.minimizeTerminal()
if (params.minimizesidepanel) this.resizeFeature.hidePanel()
}
embed () {
this.mainView.minimizeTerminal()
this.resizeFeature.hidePanel()
}
}

@ -84,4 +84,3 @@ function find (args, query) {
})
return isMatch
}

@ -19,11 +19,17 @@ export class OffsetToLineColumnConverter extends Plugin {
offsetToLineColumn (rawLocation, file, sources, asts) {
if (!this.lineBreakPositionsByContent[file]) {
for (var filename in asts) {
var source = asts[filename]
if (source.id === file) {
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[filename].content)
break
const sourcesArray = Object.keys(sources)
if (!asts && file === 0 && sourcesArray.length === 1) {
// if we don't have ast, we process the only one available content
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[sourcesArray[0]].content)
} else {
for (var filename in asts) {
const source = asts[filename]
if (source.id === file) {
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[filename].content)
break
}
}
}
}

@ -2,7 +2,8 @@ const EventEmitter = require('events')
class ClickLaunchIcon extends EventEmitter {
command (icon) {
this.api.waitForElementVisible('#icon-panel div[plugin="' + icon + '"]').click('#icon-panel div[plugin="' + icon + '"]').perform(() => {
this.api.waitForElementVisible('#icon-panel div[plugin="' + icon + '"]').click('#icon-panel div[plugin="' + icon + '"]').perform((done) => {
done()
this.emit('complete')
})
return this

@ -3,26 +3,23 @@ const EventEmitter = require('events')
class GetInstalledPlugins extends EventEmitter {
command (cb) {
const browser = this.api
const plugins = []
browser.click('*[data-id="remixIdeIconPanel"]')
.waitForElementPresent('[plugin]:not([plugin=""])')
.elements('css selector', '[plugin]:not([plugin=""])', (res) => {
res.value.forEach(function (jsonWebElement) {
const jsonWebElementId = jsonWebElement.ELEMENT || jsonWebElement[Object.keys(jsonWebElement)[0]]
browser.elementIdAttribute(jsonWebElementId, 'plugin', (jsonElement) => {
const attribute = jsonElement.value
browser.waitForElementPresent('[plugin]:not([plugin=""]')
.perform((done) => {
browser.execute(() => {
const pluginNames = []
const plugins = document.querySelectorAll('[plugin]:not([plugin=""]')
plugins.push(attribute)
plugins.forEach(plugin => {
pluginNames.push(plugin.getAttribute('plugin'))
})
return pluginNames
}, [], (result) => {
done()
cb(result.value)
this.emit('complete')
})
})
.perform((done) => {
done()
cb(plugins)
this.emit('complete')
})
return this
}
}

@ -17,10 +17,9 @@ function goToVMtraceStep (browser, step, incr, done) {
browser.execute(function (step) {
return document.querySelector('#stepdetail').innerHTML
}, [step], function (result) {
if (result.value.indexOf('vm trace step: ' + step) !== -1) {
if (result.value.indexOf('vm trace step:') !== -1 && result.value.indexOf(step) !== -1) {
done()
} else if (incr > 1000) {
console.log(result)
browser.assert.fail('goToVMtraceStep fails', 'info about error', '')
done()
} else {

@ -0,0 +1,28 @@
const EventEmitter = require('events')
class NoWorkerErrorFor extends EventEmitter {
command (version) {
this.api.perform((done) => {
noWorkerErrorFor(this.api, version, () => {
done()
this.emit('complete')
})
})
return this
}
}
function noWorkerErrorFor (browser, version, callback) {
browser
.setSolidityCompilerVersion(version)
.click('*[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('*[data-id="compilationFinishedWith_' + version + '"]', 10000)
.notContainsText('*[data-id="compiledErrors"]', 'worker error:undefined')
.notContainsText('*[data-id="compiledErrors"]', 'Uncaught RangeError: Maximum call stack size exceeded')
.notContainsText('*[data-id="compiledErrors"]', 'RangeError: Maximum call stack size exceeded')
.perform(() => {
callback()
})
}
module.exports = NoWorkerErrorFor

@ -24,7 +24,7 @@ function verifyCallReturnValue (browser, address, checks, done) {
}, [address], function (result) {
console.log('verifyCallReturnValue', result)
for (var k in checks) {
browser.assert.equal(result.value[k], checks[k])
browser.assert.equal(result.value[k].trim(), checks[k].trim())
}
done()
})

@ -1,7 +1,7 @@
const EventEmitter = require('events')
class VerifyContracts extends EventEmitter {
command (compiledContractNames, opts = { wait: 1000 }) {
command (compiledContractNames, opts = { wait: 1000, version: null }) {
this.api.perform((done) => {
verifyContracts(this.api, compiledContractNames, opts, () => {
done()
@ -17,6 +17,18 @@ function getCompiledContracts (browser, opts, callback) {
.clickLaunchIcon('solidity')
.pause(opts.wait)
.waitForElementPresent('*[data-id="compiledContracts"] option')
.perform((done) => {
if (opts.version) {
browser
.click('*[data-id="compilation-details"]')
.waitForElementVisible('*[data-id="treeViewDivcompiler"]')
.pause(2000)
.click('*[data-id="treeViewDivcompiler"]')
.waitForElementVisible('*[data-id="treeViewLicompiler/version"]')
.assert.containsText('*[data-id="treeViewLicompiler/version"]', `version:\n${opts.version}`)
.perform(done)
} else done()
})
.execute(function () {
var contracts = document.querySelectorAll('*[data-id="compiledContracts"] option')
if (!contracts) {

@ -11,28 +11,27 @@ module.exports = function (browser, callback, url, preloadPlugins = true) {
initModules(browser, () => {
browser.clickLaunchIcon('solidity')
.pause(2000)
.click('*[for="autoCompile"]')
.perform(function () {
callback()
.execute(() => {
document.getElementById('autoCompile').click()
})
})
} else callback()
}
})
})
.perform(() => {
callback()
})
}
function initModules (browser, callback) {
browser.pause(5000)
.click('#icon-panel div[plugin="pluginManager"]')
.scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_solidity"] button')
.pause(5000)
.scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_udapp"] button')
.scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_solidityStaticAnalysis"] button')
.scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_debugger"] button')
.scrollAndClick('#icon-panel div[plugin="fileExplorers"]')
.click('[data-id="verticalIconsKindpluginManager"]')
.scrollAndClick('[data-id="pluginManagerComponentActivateButtonsolidityStaticAnalysis"]')
.scrollAndClick('[data-id="pluginManagerComponentActivateButtondebugger"]')
.scrollAndClick('[data-id="verticalIconsKindfileExplorers"]')
.clickLaunchIcon('settings')
.setValue('#gistaccesstoken', process.env.gist_token)
.click('#savegisttoken')
.click('#settingsView #Flatly') // e2e tests were initially developed with Flatly. Some tests are failing with the default one (Dark), because the dark theme put uppercase everywhere.
.setValue('[data-id="settingsTabGistAccessToken"]', process.env.gist_token)
.click('[data-id="settingsTabSaveGistToken"]')
.click('[data-id="settingsTabThemeFlatly"]') // e2e tests were initially developed with Flatly. Some tests are failing with the default one (Dark), because the dark theme put uppercase everywhere.
.perform(() => { callback() })
}

@ -0,0 +1,100 @@
'use strict'
var examples = require('../../src/app/editor/example-contracts')
var init = require('../helpers/init')
var sauce = require('./sauce')
var sources = [
{'browser/Untitled.sol': {content: examples.ballot.content}}
]
module.exports = {
before: function (browser, done) {
init(browser, done)
},
'@sources': function () {
return sources
},
'Should compile using "compileWithParamaters" API': function (browser) {
browser
.addFile('test_jsCompile.js', { content: jsCompile })
.executeScript('remix.exeCurrent()')
.pause(5000)
.journalChildIncludes('"languageversion": "0.6.8+commit.0bbfe453"')
},
'Should update the compiler configuration with "setCompilerConfig" API': function (browser) {
browser
.addFile('test_updateConfiguration.js', { content: updateConfiguration })
.executeScript('remix.exeCurrent()')
.pause(5000)
.addFile('test_updateConfiguration.sol', { content: simpleContract })
.verifyContracts(['StorageTestUpdateConfiguration'], {wait: 5000, version: '0.6.8+commit.0bbfe453'})
.end()
},
tearDown: sauce
}
const simpleContract = `pragma solidity >=0.4.22 <0.7.0;
/**
* @title Storage
* @dev Store & retreive value in a variable
*/
contract StorageTestUpdateConfiguration {
uint256 number;
/**
* @dev Store value in variable
* @param num value to store
*/
function store(uint256 num) public {
number = num;
}
/**
* @dev Return value
* @return value of 'number'
*/
function retreive() public view returns (uint256){
return number;
}
}
`
const jsCompile = `(async () => {
try {
const contract = {
"storage.sol": {content : \`${simpleContract}\` }
}
console.log('compile')
const params = {
optimize: false,
evmVersion: null,
language: 'Solidity',
version: '0.6.8+commit.0bbfe453'
}
const result = await remix.call('solidity', 'compileWithParameters', contract, params)
console.log('result ', result)
} catch (e) {
console.log(e.message)
}
})()`
const updateConfiguration = `(async () => {
try {
const params = {
optimize: false,
evmVersion: null,
language: 'Solidity',
version: '0.6.8+commit.0bbfe453'
}
await remix.call('solidity', 'setCompilerConfig', params)
} catch (e) {
console.log(e.message)
}
})()`

@ -43,7 +43,7 @@ module.exports = {
.setValue('*[data-id="slider"]', 50)
.pause(2000)
.assert.containsText('*[data-id="solidityLocals"]', 'no locals')
.assert.containsText('*[data-id="stepdetail"]', 'vm trace step: 92')
.assert.containsText('*[data-id="stepdetail"]', 'vm trace step:\n92')
},
'Should step back and forward transaction': function (browser) {
@ -51,12 +51,12 @@ module.exports = {
.waitForElementPresent('*[data-id="buttonNavigatorIntoBack"]')
.scrollAndClick('*[data-id="buttonNavigatorIntoBack"]')
.pause(2000)
.assert.containsText('*[data-id="stepdetail"]', 'vm trace step: 91')
.assert.containsText('*[data-id="stepdetail"]', 'execution step: 91')
.assert.containsText('*[data-id="stepdetail"]', 'vm trace step:\n91')
.assert.containsText('*[data-id="stepdetail"]', 'execution step:\n91')
.click('*[data-id="buttonNavigatorIntoForward"]')
.pause(2000)
.assert.containsText('*[data-id="stepdetail"]', 'vm trace step: 92')
.assert.containsText('*[data-id="stepdetail"]', 'execution step: 92')
.assert.containsText('*[data-id="stepdetail"]', 'vm trace step:\n92')
.assert.containsText('*[data-id="stepdetail"]', 'execution step:\n92')
},
'Should jump through breakpoints': function (browser) {
@ -66,12 +66,12 @@ module.exports = {
.waitForElementVisible('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]')
.click('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]')
.pause(2000)
.assert.containsText('*[data-id="stepdetail"]', 'vm trace step: 0')
.assert.containsText('*[data-id="stepdetail"]', 'execution step: 0')
.assert.containsText('*[data-id="stepdetail"]', 'vm trace step:\n0')
.assert.containsText('*[data-id="stepdetail"]', 'execution step:\n0')
.click('*[data-id="buttonNavigatorJumpNextBreakpoint"]')
.pause(2000)
.assert.containsText('*[data-id="stepdetail"]', 'vm trace step: 140')
.assert.containsText('*[data-id="stepdetail"]', 'execution step: 140')
.assert.containsText('*[data-id="stepdetail"]', 'vm trace step:\n184')
.assert.containsText('*[data-id="stepdetail"]', 'execution step:\n184')
.end()
},

@ -18,7 +18,7 @@ module.exports = {
browser.waitForElementVisible('div[data-id="remixIdeSidePanel"]')
.assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORERS')
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.waitForElementVisible('li[key="browser/4_Ballot_test.sol"]')
.waitForElementVisible('li[key="browser/3_Ballot.sol"]')
},
'Loads Main View': function (browser) {

@ -66,10 +66,12 @@ module.exports = {
.click('*[data-id="settingsTabThemeDark"]')
.pause(2000)
.waitForElementVisible('*[data-id="editorInput"]')
/* @todo(#2863) ch for class and not colors
.checkElementStyle('.ace_keyword', 'color', aceThemes.dark.keyword)
.checkElementStyle('.ace_comment.ace_doc', 'color', aceThemes.dark.comment)
.checkElementStyle('.ace_function', 'color', aceThemes.dark.function)
.checkElementStyle('.ace_variable', 'color', aceThemes.dark.variable)
*/
},
'Should highlight source code': function (browser) {

@ -22,8 +22,7 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogContainer"]')
.setValue('*[data-id="modalDialogCustomPromptText"]', '5_New_contract.sol')
.modalFooterOKClick()
.pause(2000)
.waitForElementVisible('*[data-id="treeViewLibrowser/5_New_contract.sol"]')
.waitForElementVisible('*[data-id="treeViewLibrowser/5_New_contract.sol"]', 7000)
},
'Should rename `5_New_contract.sol` to 5_Renamed_Contract.sol': function (browser) {
@ -82,8 +81,7 @@ module.exports = {
.click('*[data-id="fileExplorerNewFilepublishToGist"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]')
.modalFooterOKClick()
.pause(10000)
.waitForElementVisible('*[data-id="modalDialogContainer"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]', 7000)
.modalFooterOKClick()
.pause(2000)
.perform((done) => {

@ -0,0 +1,190 @@
'use strict'
var init = require('../helpers/init')
var sauce = require('./sauce')
module.exports = {
before: function (browser, done) {
init(browser, done)
},
'Should execute `file` api from file manager external api': function (browser) {
browser
.addFile('file.js', { content: executeFile })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.journalLastChildIncludes('browser/file.js')
},
'Should execute `exists` api from file manager external api': function (browser) {
browser
.addFile('exists.js', { content: executeExists })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.journalChildIncludes('browser/exists.js true')
.journalChildIncludes('browser/non-exists.js false')
},
'Should execute `open` api from file manager external api': function (browser) {
browser
.addFile('open.js', { content: executeOpen })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.journalLastChildIncludes('browser/3_Ballot.sol')
},
'Should execute `writeFile` api from file manager external api': function (browser) {
browser
.addFile('writeFile.js', { content: executeWriteFile })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.openFile('browser/new_contract.sol')
.assert.containsText('[data-id="editorInput"]', 'pragma solidity ^0.6.0')
},
'Should execute `readFile` api from file manager external api': function (browser) {
browser
.addFile('readFile.js', { content: executeReadFile })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.journalLastChildIncludes('pragma solidity ^0.6.0')
},
'Should execute `copyFile` api from file manager external api': function (browser) {
browser
.addFile('copyFile.js', { content: executeCopyFile })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.journalLastChildIncludes('pragma solidity >=0.4.22 <0.7.0;')
},
'Should execute `rename` api from file manager external api': function (browser) {
browser
.addFile('renameFile.js', { content: executeRename })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.waitForElementPresent('[data-id="treeViewLibrowser/old_contract.sol"]')
},
'Should execute `mkdir` api from file manager external api': function (browser) {
browser
.addFile('mkdirFile.js', { content: executeMkdir })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.waitForElementPresent('[data-id="treeViewLibrowser/Test_Folder"]')
},
'Should execute `readdir` api from file manager external api': function (browser) {
browser
.addFile('readdirFile.js', { content: executeReaddir })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.journalLastChildIncludes('Test_Folder isDirectory true')
},
'Should execute `remove` api from file manager external api': function (browser) {
browser
.addFile('removeFile.js', { content: executeRemove })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.waitForElementNotPresent('[data-id="treeViewLibrowser/old_contract.sol"]')
.end()
},
tearDown: sauce
}
const executeFile = `
const run = async () => {
const result = await remix.call('fileManager', 'file')
console.log(result)
}
run()
`
const executeExists = `
const run = async () => {
const result1 = await remix.call('fileManager', 'exists', 'browser/exists.js')
const result2 = await remix.call('fileManager', 'exists', 'browser/non-exists.js')
console.log('browser/exists.js ' + result1)
console.log('browser/non-exists.js ' + result2)
}
run()
`
const executeOpen = `
const run = async () => {
await remix.call('fileManager', 'open', 'browser/3_Ballot.sol')
const result = await remix.call('fileManager', 'file')
console.log(result)
}
run()
`
const executeWriteFile = `
const run = async () => {
await remix.call('fileManager', 'writeFile', 'browser/new_contract.sol', 'pragma solidity ^0.6.0')
}
run()
`
const executeReadFile = `
const run = async () => {
const result = await remix.call('fileManager', 'readFile', 'browser/new_contract.sol')
console.log(result)
}
run()
`
const executeCopyFile = `
const run = async () => {
await remix.call('fileManager', 'copyFile', 'browser/3_Ballot.sol', 'browser/new_contract.sol')
const result = await remix.call('fileManager', 'readFile', 'browser/new_contract.sol')
console.log(result)
}
run()
`
const executeRename = `
const run = async () => {
await remix.call('fileManager', 'rename', 'browser/new_contract.sol', 'browser/old_contract.sol')
}
run()
`
const executeMkdir = `
const run = async () => {
await remix.call('fileManager', 'mkdir', 'browser/Test_Folder/')
}
run()
`
const executeReaddir = `
const run = async () => {
const result = await remix.call('fileManager', 'readdir', 'browser/')
console.log('Test_Folder isDirectory ', result["Test_Folder"].isDirectory)
}
run()
`
const executeRemove = `
const run = async () => {
await remix.call('fileManager', 'remove', 'browser/old_contract.sol')
}
run()
`

@ -37,7 +37,6 @@ module.exports = {
.click('*[data-id="compilerContainerCompileBtn"]')
.pause(3000)
.click('*[data-id="verticalIconsKindfileExplorers"]')
.openFile('browser/artifacts')
.openFile('browser/artifacts/Ballot.json')
},
@ -209,18 +208,6 @@ module.exports = {
.checkElementStyle(':root', '--danger', remixIdeThemes.darkly.danger)
},
'Should load Slate theme': function (browser) {
browser.waitForElementVisible('*[data-id="verticalIconsKindsettings"]', 5000)
.click('*[data-id="settingsTabThemeSlate"]')
.pause(2000)
.checkElementStyle(':root', '--primary', remixIdeThemes.slate.primary)
.checkElementStyle(':root', '--secondary', remixIdeThemes.slate.secondary)
.checkElementStyle(':root', '--success', remixIdeThemes.slate.success)
.checkElementStyle(':root', '--info', remixIdeThemes.slate.info)
.checkElementStyle(':root', '--warning', remixIdeThemes.slate.warning)
.checkElementStyle(':root', '--danger', remixIdeThemes.slate.danger)
},
'Should load Superhero theme': function (browser) {
browser.waitForElementVisible('*[data-id="verticalIconsKindsettings"]', 5000)
.click('*[data-id="settingsTabThemeSuperhero"]')
@ -334,14 +321,6 @@ var remixIdeThemes = {
warning: '#F39C12',
danger: '#E74C3C'
},
slate: {
primary: '#3A3F44',
secondary: '#7A8288',
success: '#62c462',
info: '#5bc0de',
warning: '#f89406',
danger: '#ee5f5b'
},
superhero: {
primary: '#DF691A',
secondary: '#4E5D6C',

@ -45,7 +45,6 @@ module.exports = {
.modalFooterCancelClick()
.executeScript(`remix.loadgist('${gistid}')`)
.perform((done) => { if (runtimeBrowser === 'chrome') { browser.openFile('browser/gists') } done() })
.openFile(`browser/gists/${gistid}`)
.openFile(`browser/gists/${gistid}/1_Storage.sol`)
.perform(done)
}
@ -86,7 +85,6 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogCustomPromptText"]')
.setValue('*[data-id="modalDialogCustomPromptText"]', testData.validGistId)
.modalFooterOKClick()
.openFile(`browser/gists/${testData.validGistId}`)
.openFile(`browser/gists/${testData.validGistId}/ApplicationRegistry`)
.waitForElementVisible(`div[title='browser/gists/${testData.validGistId}/ApplicationRegistry']`)
.assert.containsText(`div[title='browser/gists/${testData.validGistId}/ApplicationRegistry'] > span`, 'ApplicationRegistry')

@ -26,7 +26,7 @@ module.exports = {
.waitForElementPresent('.instance:nth-of-type(2)')
.click('.instance:nth-of-type(2) > div > button')
.perform((done) => {
browser.testConstantFunction(addressRef, 'get - call', '', '0: uint256: 45').perform(() => {
browser.testConstantFunction(addressRef, 'get - call', '', '0:\nuint256: 45').perform(() => {
done()
})
})
@ -96,7 +96,7 @@ function checkDeployShouldSucceed (browser, address, callback) {
.click('.instance:nth-of-type(3) > div > button')
.perform(() => {
browser
.testConstantFunction(addressRef, 'get - call', '', '0: uint256: 45')
.testConstantFunction(addressRef, 'get - call', '', '0:\nuint256: 45')
.perform(() => { callback() })
})
}

@ -27,8 +27,8 @@ module.exports = {
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtondebugger"]')
.clearValue('*[data-id="pluginManagerComponentSearchInput"]')
.click('*[data-id="pluginManagerComponentSearchInput"]')
.keys('Deploy & run transactions')
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtonudapp"]')
.keys('Vyper')
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtonvyper"]')
.clearValue('*[data-id="pluginManagerComponentSearchInput"]')
.click('*[data-id="pluginManagerComponentSearchInput"]')
.keys('ZoKrates')
@ -43,8 +43,8 @@ module.exports = {
.click('*[data-id="pluginManagerComponentPluginManager"]')
.scrollAndClick('*[data-id="pluginManagerComponentActivateButtondebugger"]')
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtondebugger"]')
.scrollAndClick('*[data-id="pluginManagerComponentActivateButtonudapp"]')
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonudapp"]')
.scrollAndClick('*[data-id="pluginManagerComponentActivateButtonvyper"]')
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonvyper"]')
.scrollAndClick('*[data-id="pluginManagerComponentActivateButtonZoKrates"]')
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonZoKrates"]')
},
@ -54,8 +54,8 @@ module.exports = {
.click('*[data-id="pluginManagerComponentPluginManager"]')
.scrollAndClick('*[data-id="pluginManagerComponentDeactivateButtondebugger"]')
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtondebugger"]')
.scrollAndClick('*[data-id="pluginManagerComponentDeactivateButtonudapp"]')
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtonudapp"]')
.scrollAndClick('*[data-id="pluginManagerComponentDeactivateButtonvyper"]')
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtonvyper"]')
},
/*

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

Loading…
Cancel
Save