Merge branch 'master' of https://github.com/ethereum/remix-project into editorcontext

pull/5370/head
filip mertens 2 years ago
commit 2d4be91941
  1. 6
      .circleci/config.yml
  2. 31
      README.md
  3. 16
      apps/remix-ide-e2e/src/commands/createContract.ts
  4. 18
      apps/remix-ide-e2e/src/commands/currentWorkspaceIs.ts
  5. 18
      apps/remix-ide-e2e/src/commands/switchEnvironment.ts
  6. 18
      apps/remix-ide-e2e/src/commands/switchWorkspace.ts
  7. 10
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  8. 2
      apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts
  9. 18
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  10. 4
      apps/remix-ide-e2e/src/tests/generalSettings.test.ts
  11. 8
      apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts
  12. 4
      apps/remix-ide-e2e/src/tests/migrateFileSystem.test.ts
  13. 12
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  14. 8
      apps/remix-ide-e2e/src/tests/providers.test.ts
  15. 16
      apps/remix-ide-e2e/src/tests/recorder.test.ts
  16. 2
      apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
  17. 2
      apps/remix-ide-e2e/src/tests/signingMessage.test.ts
  18. 2
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  19. 12
      apps/remix-ide-e2e/src/tests/specialFunctions.test.ts
  20. 8
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  21. 6
      apps/remix-ide-e2e/src/tests/transactionExecution.test.ts
  22. 4
      apps/remix-ide-e2e/src/tests/url.test.ts
  23. 14
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  24. 6
      apps/remix-ide-e2e/src/types/index.d.ts
  25. 2
      apps/remix-ide/ci/deploy_from_travis_remix-alpha.sh
  26. 2
      apps/remix-ide/ci/deploy_from_travis_remix-beta.sh
  27. 2
      apps/remix-ide/ci/deploy_from_travis_remix-live.sh
  28. 15
      apps/remix-ide/src/404.html
  29. 6
      apps/remix-ide/src/app.js
  30. 13
      apps/remix-ide/src/app/files/dgitProvider.js
  31. 9
      apps/remix-ide/src/app/files/fileManager.ts
  32. 3
      apps/remix-ide/src/app/panels/file-panel.js
  33. 8
      apps/remix-ide/src/app/tabs/abstract-provider.tsx
  34. 2
      apps/remix-ide/src/app/tabs/external-http-provider.tsx
  35. 21
      apps/remix-ide/src/app/tabs/injected-arbitrum-one-provider.tsx
  36. 21
      apps/remix-ide/src/app/tabs/injected-optimism-provider.tsx
  37. 75
      apps/remix-ide/src/app/tabs/injected-provider.tsx
  38. 34
      apps/remix-ide/src/app/udapp/run-tab.js
  39. 53
      apps/remix-ide/src/blockchain/blockchain.js
  40. 25
      apps/remix-ide/src/blockchain/execution-context.js
  41. 2
      apps/remix-ide/src/remixAppManager.js
  42. 14
      libs/remix-core-plugin/src/lib/constants/uups.ts
  43. 60
      libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts
  44. 215
      libs/remix-core-plugin/src/types/contract.ts
  45. 3
      libs/remix-ui/helper/src/index.ts
  46. 42
      libs/remix-ui/helper/src/lib/components/custom-dropdown.tsx
  47. 18
      libs/remix-ui/helper/src/lib/helper-components.tsx
  48. 16
      libs/remix-ui/helper/src/lib/remix-ui-helper.ts
  49. 48
      libs/remix-ui/panel/src/lib/plugins/panel-header.tsx
  50. 1
      libs/remix-ui/panel/src/lib/plugins/panel.css
  51. 3
      libs/remix-ui/plugin-manager/src/lib/components/ActivePluginCard.tsx
  52. 3
      libs/remix-ui/plugin-manager/src/lib/components/InactivePluginCard.tsx
  53. 6
      libs/remix-ui/plugin-manager/src/lib/components/LocalPluginForm.tsx
  54. 9
      libs/remix-ui/run-tab/src/lib/actions/deploy.ts
  55. 13
      libs/remix-ui/run-tab/src/lib/actions/events.ts
  56. 17
      libs/remix-ui/run-tab/src/lib/actions/payload.ts
  57. 2
      libs/remix-ui/run-tab/src/lib/components/account.tsx
  58. 20
      libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx
  59. 337
      libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx
  60. 62
      libs/remix-ui/run-tab/src/lib/components/environment.tsx
  61. 2
      libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx
  62. 3
      libs/remix-ui/run-tab/src/lib/constants/index.ts
  63. 3
      libs/remix-ui/run-tab/src/lib/css/run-tab.css
  64. 39
      libs/remix-ui/run-tab/src/lib/reducers/runTab.ts
  65. 14
      libs/remix-ui/run-tab/src/lib/types/index.ts
  66. 81
      libs/remix-ui/settings/src/lib/github-settings.tsx
  67. 21
      libs/remix-ui/settings/src/lib/remix-ui-settings.tsx
  68. 4
      libs/remix-ui/settings/src/lib/settingsAction.ts
  69. 12
      libs/remix-ui/settings/src/types/index.ts
  70. 12
      libs/remix-ui/tooltip-popup/.babelrc
  71. 18
      libs/remix-ui/tooltip-popup/.eslintrc.json
  72. 7
      libs/remix-ui/tooltip-popup/README.md
  73. 1
      libs/remix-ui/tooltip-popup/src/index.ts
  74. 0
      libs/remix-ui/tooltip-popup/src/lib/tooltip-popup.module.css
  75. 27
      libs/remix-ui/tooltip-popup/src/lib/tooltip-popup.tsx
  76. 6
      libs/remix-ui/tooltip-popup/src/types/index.ts
  77. 20
      libs/remix-ui/tooltip-popup/tsconfig.json
  78. 13
      libs/remix-ui/tooltip-popup/tsconfig.lib.json
  79. 55
      libs/remix-ui/workspace/src/lib/actions/index.ts
  80. 23
      libs/remix-ui/workspace/src/lib/actions/payload.ts
  81. 71
      libs/remix-ui/workspace/src/lib/actions/workspace.ts
  82. 5
      libs/remix-ui/workspace/src/lib/contexts/index.ts
  83. 38
      libs/remix-ui/workspace/src/lib/css/remix-ui-workspace.css
  84. 11
      libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
  85. 73
      libs/remix-ui/workspace/src/lib/reducers/workspace.ts
  86. 279
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  87. 2
      libs/remixd/package.json
  88. 3
      nx.json
  89. 16
      package.json
  90. 3
      tsconfig.base.json
  91. 17
      workspace.json
  92. 84
      yarn.lock

@ -323,7 +323,7 @@ jobs:
environment: environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI" - COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js" - FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/404.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
working_directory: ~/remix-project working_directory: ~/remix-project
steps: steps:
@ -352,7 +352,7 @@ jobs:
environment: environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI" - COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js" - FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/404.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
working_directory: ~/remix-project working_directory: ~/remix-project
steps: steps:
@ -380,7 +380,7 @@ jobs:
environment: environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI" - COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js" - FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/404.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
working_directory: ~/remix-project working_directory: ~/remix-project
steps: steps:

@ -1,13 +1,24 @@
[![CircleCI](https://circleci.com/gh/ethereum/remix-project.svg?style=svg)](https://circleci.com/gh/ethereum/remix-project) <p align="center">
[![Documentation Status](https://readthedocs.org/projects/docs/badge/?version=latest)](https://remix-ide.readthedocs.io/en/latest/index.html) <img src="./apps/remix-ide/src/assets/img/icon.png" alt="Remix Logo" width="200"/>
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/ethereum/remix-project/blob/master/CONTRIBUTING.md) </p>
![GitHub contributors](https://img.shields.io/github/contributors/ethereum/remix-project) <h3 align="center">Remix Project</h3>
[![Awesome Remix](https://img.shields.io/badge/Awesome--Remix-resources-green)](https://github.com/ethereum/awesome-remix)
<div align="center">
[![CircleCI](https://img.shields.io/circleci/build/github/ethereum/remix-project?logo=circleci)](https://circleci.com/gh/ethereum/remix-project)
[![Documentation Status](https://readthedocs.org/projects/remix-ide/badge/?version=latest)](https://remix-ide.readthedocs.io/en/latest/index.html)
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat&logo=github)](https://github.com/ethereum/remix-project/blob/master/CONTRIBUTING.md)
[![GitHub contributors](https://img.shields.io/github/contributors/ethereum/remix-project?style=flat&logo=github)](https://github.com/ethereum/remix-project/blob/master/CONTRIBUTING.md)
[![Awesome Remix](https://img.shields.io/badge/Awesome--Remix-resources-green?logo=awesomelists)](https://github.com/ethereum/awesome-remix)
![GitHub](https://img.shields.io/github/license/ethereum/remix-project) ![GitHub](https://img.shields.io/github/license/ethereum/remix-project)
[![Join the chat at https://gitter.im/ethereum/remix](https://badges.gitter.im/ethereum/remix.svg)](https://gitter.im/ethereum/remix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Gitter Chat](https://img.shields.io/badge/Gitter%20-chat-brightgreen?style=plastic&logo=gitter)](https://gitter.im/ethereum/remix)
[![Twitter Follow](https://img.shields.io/twitter/follow/ethereumremix?style=social)](https://twitter.com/ethereumremix) [![Twitter Follow](https://img.shields.io/twitter/follow/ethereumremix?style=flat&logo=twitter&color=green)](https://twitter.com/ethereumremix)
</div>
## Remix Project
# Remix Project
**Remix Project** is a rich toolset including Remix IDE, a comprehensive smart contract development tool. The Remix Project also includes Remix Plugin Engine and Remix Libraries which are low-level tools for wider use. **Remix Project** is a rich toolset including Remix IDE, a comprehensive smart contract development tool. The Remix Project also includes Remix Plugin Engine and Remix Libraries which are low-level tools for wider use.
## Remix IDE ## Remix IDE
@ -43,7 +54,7 @@ Note: It contains the latest supported version of Solidity available at the time
"npm": "^6.14.15" "npm": "^6.14.15"
} }
``` ```
* Install [Nx CLI](https://nx.dev/react/cli/overview) globally to enable running **nx executable commands**. * Install [Nx CLI](https://nx.dev/using-nx/nx-cli) globally to enable running **nx executable commands**.
```bash ```bash
yarn global add @nrwl/cli yarn global add @nrwl/cli
``` ```
@ -116,7 +127,7 @@ curl https://raw.githubusercontent.com/ethereum/remix-project/master/docker-comp
### Troubleshooting ### Troubleshooting
If you have trouble building the project, make sure that you have the correct version of `node`, `npm` and `nvm`. Also ensure [Nx CLI](https://nx.dev/react/cli/overview) is installed globally. If you have trouble building the project, make sure that you have the correct version of `node`, `npm` and `nvm`. Also ensure [Nx CLI](https://nx.dev/using-nx/nx-cli) is installed globally.
Run: Run:

@ -2,7 +2,7 @@ import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events' import EventEmitter from 'events'
class CreateContract extends EventEmitter { class CreateContract extends EventEmitter {
command (this: NightwatchBrowser, inputParams: string[]): NightwatchBrowser { command (this: NightwatchBrowser, inputParams: string): NightwatchBrowser {
this.api.perform((done) => { this.api.perform((done) => {
createContract(this.api, inputParams, () => { createContract(this.api, inputParams, () => {
done() done()
@ -13,19 +13,11 @@ class CreateContract extends EventEmitter {
} }
} }
function createContract (browser: NightwatchBrowser, inputParams: string[], callback: VoidFunction) { function createContract (browser: NightwatchBrowser, inputParams: string, callback: VoidFunction) {
if (inputParams.length === 1) { if (inputParams) {
browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams[0], function () { browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams, function () {
browser.click('.udapp_contractActionsContainerSingle > button').pause(500).perform(function () { callback() }) browser.click('.udapp_contractActionsContainerSingle > button').pause(500).perform(function () { callback() })
}) })
} else if (inputParams.length > 1) {
browser.perform((done) => {
for (let i = 0; i < inputParams.length; i++) {
browser.setValue(`div.udapp_multiArg:nth-child(${i + 1}) > input`, inputParams[i])
}
done()
})
.click('div.udapp_multiArg > button').pause(500).perform(function () { callback() })
} else { } else {
browser browser
.click('.udapp_contractActionsContainerSingle > button') .click('.udapp_contractActionsContainerSingle > button')

@ -3,15 +3,15 @@ import EventEmitter from 'events'
class CurrentWorkspaceIs extends EventEmitter { class CurrentWorkspaceIs extends EventEmitter {
command (this: NightwatchBrowser, name: string): NightwatchBrowser { command (this: NightwatchBrowser, name: string): NightwatchBrowser {
this.api const browser = this.api
.execute(function () {
const el = document.querySelector('select[data-id="workspacesSelect"]') as HTMLSelectElement browser.getText('[data-id="workspacesSelect"]', function (result) {
return el.value browser.assert.equal(result.value, name)
}, [], (result) => { })
console.log(result) .perform((done) => {
this.api.assert.equal(result.value, name) done()
this.emit('complete') this.emit('complete')
}) })
return this return this
} }
} }

@ -0,0 +1,18 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class switchEnvironment extends EventEmitter {
command (this: NightwatchBrowser, provider: string): NightwatchBrowser {
this.api.waitForElementVisible('[data-id="settingsSelectEnvOptions"]')
.click('[data-id="settingsSelectEnvOptions"] button')
.waitForElementVisible(`[data-id="dropdown-item-${provider}"]`)
.click(`[data-id="dropdown-item-${provider}"]`)
.perform((done) => {
done()
this.emit('complete')
})
return this
}
}
module.exports = switchEnvironment

@ -0,0 +1,18 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class switchWorkspace extends EventEmitter {
command (this: NightwatchBrowser, workspaceName: string): NightwatchBrowser {
this.api.waitForElementVisible('[data-id="workspacesSelect"]')
.click('[data-id="workspacesSelect"]')
.waitForElementVisible(`[data-id="dropdown-item-${workspaceName}"]`)
.click(`[data-id="dropdown-item-${workspaceName}"]`)
.perform((done) => {
done()
this.emit('complete')
})
return this
}
}
module.exports = switchWorkspace

@ -83,7 +83,7 @@ module.exports = {
browser browser
.openFile('Untitled.sol') .openFile('Untitled.sol')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') .switchEnvironment('External Http Provider')
.waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]') .waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]')
.execute(function () { .execute(function () {
const modal = document.querySelector('[data-id="basic-http-provider-modal-footer-ok-react"]') as any const modal = document.querySelector('[data-id="basic-http-provider-modal-footer-ok-react"]') as any
@ -91,13 +91,7 @@ module.exports = {
modal.click() modal.click()
}) })
.pause(5000) .pause(5000)
.execute(function () { .waitForElementContainsText('#selectExEnvOptions button', 'External Http Provider')
const env: any = document.getElementById('selectExEnvOptions')
return env.value
}, [], function (result) {
browser.assert.ok(result.value === 'External Http Provider', 'Web3 Provider not selected')
})
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.pause(2000) .pause(2000)

@ -78,7 +78,7 @@ module.exports = {
browser browser
.openFile('Untitled.sol') .openFile('Untitled.sol')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') .switchEnvironment('External Http Provider')
.waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]') .waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]')
.execute(function () { .execute(function () {
const modal = document.querySelector('[data-id="basic-http-provider-modal-footer-ok-react"]') as any const modal = document.querySelector('[data-id="basic-http-provider-modal-footer-ok-react"]') as any

@ -86,7 +86,7 @@ module.exports = {
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.waitForElementPresent('*[title="Deploy - transact (not payable)"]', 35000) .waitForElementPresent('*[title="Deploy - transact (not payable)"]', 35000)
.selectContract('ERC20') .selectContract('ERC20')
.createContract(["tokenName", "symbol"]) .createContract('"tokenName", "symbol"')
.debugTransaction(0) .debugTransaction(0)
.pause(2000) .pause(2000)
.waitForElementVisible('#stepdetail') .waitForElementVisible('#stepdetail')
@ -115,7 +115,7 @@ module.exports = {
.testContracts('withABIEncoderV2.sol', sources[2]['withABIEncoderV2.sol'], ['test']) .testContracts('withABIEncoderV2.sol', sources[2]['withABIEncoderV2.sol'], ['test'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectContract('test') .selectContract('test')
.createContract([]) .createContract('')
.clearConsole() .clearConsole()
.clickInstance(0) .clickInstance(0)
.clickFunction('test1 - transact (not payable)', { types: 'bytes userData', values: '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000015b38da6a701c568545dcfcb03fcb875f56beddc4' }) .clickFunction('test1 - transact (not payable)', { types: 'bytes userData', values: '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000015b38da6a701c568545dcfcb03fcb875f56beddc4' })
@ -148,7 +148,7 @@ module.exports = {
.testContracts('locals.sol', sources[3]['locals.sol'], ['testLocals']) .testContracts('locals.sol', sources[3]['locals.sol'], ['testLocals'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.waitForElementPresent('*[title="Deploy - transact (not payable)"]', 40000) .waitForElementPresent('*[title="Deploy - transact (not payable)"]', 40000)
.createContract([]) .createContract('')
.pause(2000) .pause(2000)
.clearConsole() .clearConsole()
.clickInstance(0) .clickInstance(0)
@ -173,7 +173,7 @@ module.exports = {
.pause(2000) .pause(2000)
.testContracts('withGeneratedSources.sol', sources[4]['withGeneratedSources.sol'], ['A']) .testContracts('withGeneratedSources.sol', sources[4]['withGeneratedSources.sol'], ['A'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.createContract([]) .createContract('')
.clearConsole() .clearConsole()
.clickInstance(0) .clickInstance(0)
.clickFunction('f - transact (not payable)', { types: 'uint256[] ', values: '[]' }) .clickFunction('f - transact (not payable)', { types: 'uint256[] ', values: '[]' })
@ -214,10 +214,10 @@ module.exports = {
.setSolidityCompilerVersion('soljson-v0.8.7+commit.e28d00a7.js') .setSolidityCompilerVersion('soljson-v0.8.7+commit.e28d00a7.js')
.addFile('useDebugNodes.sol', sources[5]['useDebugNodes.sol']) // compile contract .addFile('useDebugNodes.sol', sources[5]['useDebugNodes.sol']) // compile contract
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') // select web3 provider with debug nodes URL .switchEnvironment('External Http Provider') // select web3 provider with debug nodes URL
.clearValue('*[data-id="modalDialogCustomPromp"]') .clearValue('*[data-id="modalDialogCustomPromptText"]')
.setValue('*[data-id="modalDialogCustomPromp"]', 'https://remix-rinkeby.ethdevops.io') .setValue('*[data-id="modalDialogCustomPromptText"]', 'https://remix-rinkeby.ethdevops.io')
.modalFooterOKClick('basic-http-provider') .modalFooterOKClick()
.waitForElementPresent('*[title="Deploy - transact (not payable)"]', 65000) // wait for the compilation to succeed .waitForElementPresent('*[title="Deploy - transact (not payable)"]', 65000) // wait for the compilation to succeed
.clickLaunchIcon('debugger') .clickLaunchIcon('debugger')
.clearValue('*[data-id="debuggerTransactionInput"]') .clearValue('*[data-id="debuggerTransactionInput"]')
@ -235,7 +235,7 @@ module.exports = {
.testContracts('reverted.sol', sources[6]['reverted.sol'], ['A', 'B', 'C']) .testContracts('reverted.sol', sources[6]['reverted.sol'], ['A', 'B', 'C'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectContract('A') .selectContract('A')
.createContract([]) .createContract('')
.pause(500) .pause(500)
.clickInstance(0) .clickInstance(0)
.clickFunction('callA - transact (not payable)') .clickFunction('callA - transact (not payable)')

@ -41,7 +41,7 @@ module.exports = {
.setValue('*[data-id="settingsTabGistAccessToken"]', '**********') .setValue('*[data-id="settingsTabGistAccessToken"]', '**********')
.click('*[data-id="settingsTabSaveGistToken"]') .click('*[data-id="settingsTabSaveGistToken"]')
.waitForElementVisible('*[data-shared="tooltipPopup"]', 5000) .waitForElementVisible('*[data-shared="tooltipPopup"]', 5000)
.assert.containsText('*[data-shared="tooltipPopup"]', 'Access token has been saved') .assert.containsText('*[data-shared="tooltipPopup"]', 'GitHub credentials updated')
.pause(3000) .pause(3000)
}, },
@ -59,7 +59,7 @@ module.exports = {
.pause(1000) .pause(1000)
.click('*[data-id="settingsTabRemoveGistToken"]') .click('*[data-id="settingsTabRemoveGistToken"]')
.waitForElementVisible('*[data-shared="tooltipPopup"]', 5000) .waitForElementVisible('*[data-shared="tooltipPopup"]', 5000)
.assert.containsText('*[data-shared="tooltipPopup"]', 'Access token removed') .assert.containsText('*[data-shared="tooltipPopup"]', 'GitHub credentials removed')
.assert.containsText('*[data-id="settingsTabGistAccessToken"]', '') .assert.containsText('*[data-id="settingsTabGistAccessToken"]', '')
}, },

@ -22,7 +22,7 @@ module.exports = {
browser.verifyContracts(['test']) browser.verifyContracts(['test'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectContract('test') .selectContract('test')
.createContract([]) .createContract('')
.getAddressAtPosition(0, (address) => { .getAddressAtPosition(0, (address) => {
console.log('testAutoDeployLib ' + address) console.log('testAutoDeployLib ' + address)
addressRef = address addressRef = address
@ -46,7 +46,7 @@ module.exports = {
.verifyContracts(['test']) .verifyContracts(['test'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectContract('lib') // deploy lib .selectContract('lib') // deploy lib
.createContract([]) .createContract('')
.perform((done) => { .perform((done) => {
browser.getAddressAtPosition(0, (address) => { browser.getAddressAtPosition(0, (address) => {
console.log(address) console.log(address)
@ -74,7 +74,7 @@ function checkDeployShouldFail (browser: NightwatchBrowser, callback: VoidFuncti
.openFile('Untitled5.sol') .openFile('Untitled5.sol')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectContract('test') // deploy lib .selectContract('test') // deploy lib
.createContract([]) .createContract('')
.pause(2000) .pause(2000)
.getText('div[class^="terminal"]', (value) => { .getText('div[class^="terminal"]', (value) => {
console.log('value: ', value) console.log('value: ', value)
@ -98,7 +98,7 @@ function checkDeployShouldSucceed (browser: NightwatchBrowser, address: string,
.openFile('Untitled5.sol') .openFile('Untitled5.sol')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectContract('test') // deploy lib .selectContract('test') // deploy lib
.createContract([]) .createContract('')
.getAddressAtPosition(1, (address) => { .getAddressAtPosition(1, (address) => {
addressRef = address addressRef = address
}) })

@ -81,7 +81,7 @@ module.exports = {
// these are test data entries // these are test data entries
'Should have a workspace_test #group1 #group3 #group5 #group7': function (browser: NightwatchBrowser) { 'Should have a workspace_test #group1 #group3 #group5 #group7': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000) browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000)
.click('*[data-id="workspacesSelect"] option[value="workspace_test"]') .switchWorkspace('workspace_test')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest_contracts"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemtest_contracts"]')
}, },
'Should have a sol file with test data #group1 #group3 #group5 #group7': function (browser: NightwatchBrowser) { 'Should have a sol file with test data #group1 #group3 #group5 #group7': function (browser: NightwatchBrowser) {
@ -103,7 +103,7 @@ module.exports = {
}, },
'Should have a empty workspace #group1 #group3 #group5 #group7': function (browser: NightwatchBrowser) { 'Should have a empty workspace #group1 #group3 #group5 #group7': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000) browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000)
.click('*[data-id="workspacesSelect"] option[value="emptyspace"]') .switchWorkspace('emptyspace')
}, },
// end of test data entries // end of test data entries
'Should load with all storage blocked #group4': function (browser: NightwatchBrowser) { 'Should load with all storage blocked #group4': function (browser: NightwatchBrowser) {

@ -188,7 +188,7 @@ module.exports = {
.frameParent() .frameParent()
.useCss() .useCss()
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.waitForElementContainsText('#selectExEnvOptions option:checked', 'Remix VM (Berlin)') .waitForElementContainsText('#selectExEnvOptions button', 'Remix VM (Berlin)')
.clickLaunchIcon('localPlugin') .clickLaunchIcon('localPlugin')
.useXpath() .useXpath()
// @ts-ignore // @ts-ignore
@ -298,25 +298,25 @@ module.exports = {
}, null, null) }, null, null)
}, },
'Should get all workspaces #group2': async function (browser: NightwatchBrowser) { 'Should get all workspaces #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', ['default_workspace', 'emptyworkspace', 'testspace'], null, null) await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"default_workspace",isGitRepo:false}, {name:"emptyworkspace",isGitRepo:false}, {name:"testspace",isGitRepo:false}], null, null)
}, },
'Should have set workspace event #group2': async function (browser: NightwatchBrowser) { 'Should have set workspace event #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, { event: 'setWorkspace', args: [{ name: 'newspace', isLocalhost: false }] }, 'newspace') await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, { event: 'setWorkspace', args: [{ name: 'newspace', isLocalhost: false }] }, 'newspace')
}, },
'Should have event when switching workspace #group2': async function (browser: NightwatchBrowser) { 'Should have event when switching workspace #group2': async function (browser: NightwatchBrowser) {
// @ts-ignore // @ts-ignore
browser.frameParent().useCss().clickLaunchIcon('filePanel').click('*[data-id="workspacesSelect"] option[value="default_workspace"]').useXpath().click('//*[@data-id="verticalIconsKindlocalPlugin"]').frame(0, async () => { browser.frameParent().useCss().clickLaunchIcon('filePanel').switchWorkspace('default_workspace').useXpath().click('//*[@data-id="verticalIconsKindlocalPlugin"]').frame(0, async () => {
await clickAndCheckLog(browser, null, null, { event: 'setWorkspace', args: [{ name: 'default_workspace', isLocalhost: false }] }, null) await clickAndCheckLog(browser, null, null, { event: 'setWorkspace', args: [{ name: 'default_workspace', isLocalhost: false }] }, null)
}) })
}, },
'Should rename workspace #group2': async function (browser: NightwatchBrowser) { 'Should rename workspace #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:renameWorkspace', null, null, ['default_workspace', 'renamed']) await clickAndCheckLog(browser, 'filePanel:renameWorkspace', null, null, ['default_workspace', 'renamed'])
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', ['emptyworkspace', 'testspace', 'newspace', 'renamed'], null, null) await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"emptyworkspace",isGitRepo:false},{name:"testspace",isGitRepo:false},{name:"newspace",isGitRepo:false},{name:"renamed",isGitRepo:false}], null, null)
}, },
'Should delete workspace #group2': async function (browser: NightwatchBrowser) { 'Should delete workspace #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:deleteWorkspace', null, null, ['testspace']) await clickAndCheckLog(browser, 'filePanel:deleteWorkspace', null, null, ['testspace'])
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', ['emptyworkspace', 'newspace', 'renamed'], null, null) await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"emptyworkspace",isGitRepo:false},{name:"newspace",isGitRepo:false},{name:"renamed",isGitRepo:false}], null, null)
}, },
// DGIT // DGIT
'Should have changes on new workspace #group3': async function (browser: NightwatchBrowser) { 'Should have changes on new workspace #group3': async function (browser: NightwatchBrowser) {
@ -391,7 +391,7 @@ module.exports = {
.useCss() .useCss()
.clickLaunchIcon('pluginManager') .clickLaunchIcon('pluginManager')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="Hardhat Provider"]') .switchEnvironment('Hardhat Provider')
.modalFooterOKClick('hardhat-provider') .modalFooterOKClick('hardhat-provider')
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom') // e.g Custom (1337) network .waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom') // e.g Custom (1337) network
.clickLaunchIcon('localPlugin') .clickLaunchIcon('localPlugin')

@ -10,7 +10,7 @@ module.exports = {
'Should switch to ganache provider, set a custom URL and fail to connect': function (browser: NightwatchBrowser) { 'Should switch to ganache provider, set a custom URL and fail to connect': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000) browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="Ganache Provider"]') .switchEnvironment('Ganache Provider')
.waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]') .waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]')
.execute(() => { .execute(() => {
(document.querySelector('*[data-id="ganache-providerModalDialogModalBody-react"] input') as any).focus() (document.querySelector('*[data-id="ganache-providerModalDialogModalBody-react"] input') as any).focus()
@ -25,7 +25,7 @@ module.exports = {
}, },
'Should switch to ganache provider, use the default ganache URL and succeed to connect': function (browser: NightwatchBrowser) { 'Should switch to ganache provider, use the default ganache URL and succeed to connect': function (browser: NightwatchBrowser) {
browser.click('*[data-id="Ganache Provider"]') browser.switchEnvironment('Ganache Provider')
.waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]') .waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]')
.modalFooterOKClick('ganache-provider') .modalFooterOKClick('ganache-provider')
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (') .waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (')
@ -33,7 +33,7 @@ module.exports = {
'Should switch to foundry provider, set a custom URL and fail to connect': function (browser: NightwatchBrowser) { 'Should switch to foundry provider, set a custom URL and fail to connect': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000) browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.click('*[data-id="Foundry Provider"]') .switchEnvironment('Foundry Provider')
.waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]') .waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]')
.execute(() => { .execute(() => {
(document.querySelector('*[data-id="foundry-providerModalDialogModalBody-react"] input') as any).focus() (document.querySelector('*[data-id="foundry-providerModalDialogModalBody-react"] input') as any).focus()
@ -48,7 +48,7 @@ module.exports = {
}, },
'Should switch to foundry provider, use the default foundry URL and succeed to connect': function (browser: NightwatchBrowser) { 'Should switch to foundry provider, use the default foundry URL and succeed to connect': function (browser: NightwatchBrowser) {
browser.click('*[data-id="Foundry Provider"]') browser.switchEnvironment('Foundry Provider')
.waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]') .waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]')
.modalFooterOKClick('foundry-provider') .modalFooterOKClick('foundry-provider')
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (') .waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (')

@ -3,6 +3,7 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init' import init from '../helpers/init'
module.exports = { module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) { before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done) init(browser, done)
}, },
@ -11,7 +12,7 @@ module.exports = {
return sources return sources
}, },
'Run Scenario': function (browser: NightwatchBrowser) { 'Run Scenario #group1': function (browser: NightwatchBrowser) {
let addressRef let addressRef
browser.addFile('scenario.json', { content: records }) browser.addFile('scenario.json', { content: records })
.pause(5000) .pause(5000)
@ -36,10 +37,10 @@ module.exports = {
.click('*[data-id="deployAndRunClearInstances"]') .click('*[data-id="deployAndRunClearInstances"]')
}, },
'Save scenario': function (browser: NightwatchBrowser) { 'Save scenario #group1': function (browser: NightwatchBrowser) {
browser.testContracts('testRecorder.sol', sources[0]['testRecorder.sol'], ['testRecorder']) browser.testContracts('testRecorder.sol', sources[0]['testRecorder.sol'], ['testRecorder'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.createContract(['12']) .createContract('12')
.clickInstance(0) .clickInstance(0)
.clickFunction('set - transact (not payable)', { types: 'uint256 _p', values: '34' }) .clickFunction('set - transact (not payable)', { types: 'uint256 _p', values: '34' })
.click('.savetransaction') .click('.savetransaction')
@ -64,7 +65,7 @@ module.exports = {
}) })
}, },
'Record more than one contract': function (browser: NightwatchBrowser) { 'Record more than one contract #group1': function (browser: NightwatchBrowser) {
// deploy 2 contracts (2 different ABIs), save the record, reexecute and test one of the function. // deploy 2 contracts (2 different ABIs), save the record, reexecute and test one of the function.
browser browser
.click('*[data-id="deployAndRunClearInstances"]') .click('*[data-id="deployAndRunClearInstances"]')
@ -72,11 +73,11 @@ module.exports = {
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectContract('t1est') .selectContract('t1est')
.pause(1000) .pause(1000)
.createContract([]) .createContract('')
.clickInstance(0) .clickInstance(0)
.selectContract('t2est') .selectContract('t2est')
.pause(1000) .pause(1000)
.createContract([]) .createContract('')
.click('.savetransaction') .click('.savetransaction')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]') .waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.execute(function () { .execute(function () {
@ -84,6 +85,7 @@ module.exports = {
modalOk.click() modalOk.click()
}) })
.pause(1000)
.click('*[data-id="deployAndRunClearInstances"]') // clear udapp .click('*[data-id="deployAndRunClearInstances"]') // clear udapp
.click('*[data-id="terminalClearConsole"]') // clear terminal .click('*[data-id="terminalClearConsole"]') // clear terminal
.click('[data-id="runtransaction"]') .click('[data-id="runtransaction"]')
@ -98,7 +100,7 @@ module.exports = {
}, },
'Run with live "mode"': function (browser: NightwatchBrowser) { 'Run with live "mode" #group1': function (browser: NightwatchBrowser) {
let addressRef: string let addressRef: string
browser.addFile('scenario_live_mode.json', { content: JSON.stringify(liveModeScenario, null, '\t') }) browser.addFile('scenario_live_mode.json', { content: JSON.stringify(liveModeScenario, null, '\t') })
.addFile('scenario_live_mode_storage.sol', { content: testStorageForLiveMode }) .addFile('scenario_live_mode_storage.sol', { content: testStorageForLiveMode })

@ -32,7 +32,7 @@ module.exports = {
'Should sign message using account key #group2': function (browser: NightwatchBrowser) { 'Should sign message using account key #group2': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="settingsRemixRunSignMsg"]') browser.waitForElementVisible('*[data-id="settingsRemixRunSignMsg"]')
.click('select[id="selectExEnvOptions"] option[value="vm-berlin"]') .switchEnvironment('vm-berlin')
.pause(2000) .pause(2000)
.click('*[data-id="settingsRemixRunSignMsg"]') .click('*[data-id="settingsRemixRunSignMsg"]')
.pause(2000) .pause(2000)

@ -33,7 +33,7 @@ module.exports = {
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.pause(5000) .pause(5000)
.selectContract('ECVerify') .selectContract('ECVerify')
.createContract([]) .createContract('')
.clickInstance(0) .clickInstance(0)
.perform((done) => { .perform((done) => {
browser.getAddressAtPosition(0, (address) => { browser.getAddressAtPosition(0, (address) => {

@ -181,7 +181,7 @@ module.exports = {
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_new' }) .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_new' })
.waitForElementVisible('*[data-id="fileSystem-modal-footer-ok-react"]') .waitForElementVisible('*[data-id="fileSystem-modal-footer-ok-react"]')
.execute(function () { (document.querySelector('[data-id="fileSystem-modal-footer-ok-react"]') as HTMLElement).click() }) .execute(function () { (document.querySelector('[data-id="fileSystem-modal-footer-ok-react"]') as HTMLElement).click() })
.waitForElementPresent('*[data-id="workspacesSelect"] option[value="workspace_new"]') .currentWorkspaceIs('workspace_new')
.waitForElementVisible('li[data-id="treeViewLitreeViewItem.deps/remix-tests/remix_tests.sol"]') .waitForElementVisible('li[data-id="treeViewLitreeViewItem.deps/remix-tests/remix_tests.sol"]')
.waitForElementVisible('li[data-id="treeViewLitreeViewItem.deps/remix-tests/remix_accounts.sol"]') .waitForElementVisible('li[data-id="treeViewLitreeViewItem.deps/remix-tests/remix_accounts.sol"]')
.openFile('.deps/remix-tests/remix_tests.sol') .openFile('.deps/remix-tests/remix_tests.sol')

@ -24,7 +24,7 @@ module.exports = {
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.selectContract('CheckSpecials') .selectContract('CheckSpecials')
.createContract([]) // deploy .createContract('') // deploy
.clickInstance(0) .clickInstance(0)
.perform((done) => { .perform((done) => {
browser.getAddressAtPosition(0, (address) => { browser.getAddressAtPosition(0, (address) => {
@ -92,7 +92,7 @@ module.exports = {
.testContracts('receiveOnly.sol', sources[1]['receiveOnly.sol'], ['CheckSpecials']) .testContracts('receiveOnly.sol', sources[1]['receiveOnly.sol'], ['CheckSpecials'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectContract('CheckSpecials') .selectContract('CheckSpecials')
.createContract([]) .createContract('')
.clickInstance(0) .clickInstance(0)
.perform((done) => { .perform((done) => {
browser.getAddressAtPosition(0, (address) => { browser.getAddressAtPosition(0, (address) => {
@ -122,7 +122,7 @@ module.exports = {
.testContracts('fallbackOnlyPayable.sol', sources[2]['fallbackOnlyPayable.sol'], ['CheckSpecials']) .testContracts('fallbackOnlyPayable.sol', sources[2]['fallbackOnlyPayable.sol'], ['CheckSpecials'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectContract('CheckSpecials') .selectContract('CheckSpecials')
.createContract([]) .createContract('')
.clickInstance(0) .clickInstance(0)
.perform((done) => { .perform((done) => {
browser.getAddressAtPosition(0, (address) => { browser.getAddressAtPosition(0, (address) => {
@ -153,7 +153,7 @@ module.exports = {
.testContracts('fallbackOnlyNotPayable.sol', sources[3]['fallbackOnlyNotPayable.sol'], ['CheckSpecials']) .testContracts('fallbackOnlyNotPayable.sol', sources[3]['fallbackOnlyNotPayable.sol'], ['CheckSpecials'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectContract('CheckSpecials') .selectContract('CheckSpecials')
.createContract([]) .createContract('')
.clickInstance(0) .clickInstance(0)
.perform((done) => { .perform((done) => {
browser.getAddressAtPosition(0, (address) => { browser.getAddressAtPosition(0, (address) => {
@ -174,7 +174,7 @@ module.exports = {
.clearValue('#value') .clearValue('#value')
.setValue('#value', '0') .setValue('#value', '0')
.pause(2000) .pause(2000)
.createContract([]) .createContract('')
.pause(1000) .pause(1000)
.clickInstance(0).pause(1000) .clickInstance(0).pause(1000)
.perform((done) => { .perform((done) => {
@ -208,7 +208,7 @@ module.exports = {
.waitForElementVisible('#value') .waitForElementVisible('#value')
.clearValue('#value') .clearValue('#value')
.setValue('#value', '0').pause(2000) .setValue('#value', '0').pause(2000)
.createContract([]) .createContract('')
.clickInstance(0) .clickInstance(0)
.pause(1000) .pause(1000)
.perform((done) => { .perform((done) => {

@ -46,11 +46,11 @@ module.exports = {
.waitForElementContainsText('*[data-id="terminalJournal"]', '["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db","0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB","0x617F2E2fD72FD9D5503197092aC168c91465E7f2","0x17F6AD8Ef982297579C203069C1DbfFE4348c372","0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678","0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7","0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C","0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC","0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c","0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C","0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB","0x583031D1113aD414F02576BD6afaBfb302140225","0xdD870fA1b7C4700F2BD7f44238821C26f7392148"]') .waitForElementContainsText('*[data-id="terminalJournal"]', '["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db","0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB","0x617F2E2fD72FD9D5503197092aC168c91465E7f2","0x17F6AD8Ef982297579C203069C1DbfFE4348c372","0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678","0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7","0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C","0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC","0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c","0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C","0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB","0x583031D1113aD414F02576BD6afaBfb302140225","0xdD870fA1b7C4700F2BD7f44238821C26f7392148"]')
}, },
'Call web3.eth.getAccounts() using Web3 Provider #group5': function (browser: NightwatchBrowser) { 'Call web3.eth.getAccounts() using External Http Provider #group5': function (browser: NightwatchBrowser) {
browser browser
.click('*[data-id="terminalClearConsole"]') // clear the terminal .click('*[data-id="terminalClearConsole"]') // clear the terminal
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') .switchEnvironment('External Http Provider')
.modalFooterOKClick('basic-http-provider') .modalFooterOKClick('basic-http-provider')
.ExecuteScriptInTerminal('web3.eth.getAccounts()') .ExecuteScriptInTerminal('web3.eth.getAccounts()')
.waitForElementContainsText('*[data-id="terminalJournal"]', '["', 60000) // we check if an array is present, don't need to check for the content .waitForElementContainsText('*[data-id="terminalJournal"]', '["', 60000) // we check if an array is present, don't need to check for the content
@ -95,7 +95,7 @@ module.exports = {
browser browser
.clickLaunchIcon('settings') .clickLaunchIcon('settings')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="settingsVMLondonMode"]') .switchEnvironment('vm-london')
.click('*[data-id="terminalClearConsole"]') // clear the terminal .click('*[data-id="terminalClearConsole"]') // clear the terminal
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.click('*[data-id="treeViewDivtreeViewItem"]') // make sure we create the file at the root folder .click('*[data-id="treeViewDivtreeViewItem"]') // make sure we create the file at the root folder
@ -172,7 +172,7 @@ module.exports = {
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="deployAndRunClearInstances"]') .click('*[data-id="deployAndRunClearInstances"]')
.selectContract('OwnerTest') .selectContract('OwnerTest')
.createContract([]) .createContract('')
.pause(1000) .pause(1000)
.journalChildIncludes('constructor', { shouldHaveOnlyOneOccurence: true }) .journalChildIncludes('constructor', { shouldHaveOnlyOneOccurence: true })
.pause(5000) .pause(5000)

@ -161,7 +161,7 @@ module.exports = {
browser browser
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.clearTransactions() .clearTransactions()
.click('*[data-id="settingsVMLondonMode"]') // switch to London fork .switchEnvironment('vm-london') // switch to London fork
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('.udapp_contractActionsContainerSingle > button') .click('.udapp_contractActionsContainerSingle > button')
.clickInstance(0) .clickInstance(0)
@ -202,10 +202,10 @@ module.exports = {
.addFile('Storage.sol', sources[6]['Storage.sol']) .addFile('Storage.sol', sources[6]['Storage.sol'])
.addFile('Owner.sol', sources[6]['Owner.sol']) .addFile('Owner.sol', sources[6]['Owner.sol'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.createContract(['42', '24']) .createContract('42, 24')
.openFile('Storage.sol') .openFile('Storage.sol')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.createContract(['102']) // this creation will fail if the component hasn't been properly reset. .createContract('102') // this creation will fail if the component hasn't been properly reset.
.clickInstance(1) .clickInstance(1)
.clickFunction('store - transact (not payable)', { types: 'uint256 num', values: '24' }) .clickFunction('store - transact (not payable)', { types: 'uint256 num', values: '24' })
.testFunction('last', // we check if the contract is actually reachable. .testFunction('last', // we check if the contract is actually reachable.

@ -29,6 +29,7 @@ module.exports = {
.click('[for="autoCompile"]') // we set it too false again .click('[for="autoCompile"]') // we set it too false again
.click('[for="autoCompile"]') // back to True in the local storage .click('[for="autoCompile"]') // back to True in the local storage
.assert.containsText('*[data-id="compilerContainerCompileBtn"]', 'contract-76747f6e19.sol') .assert.containsText('*[data-id="compilerContainerCompileBtn"]', 'contract-76747f6e19.sol')
.clickLaunchIcon('filePanel')
.currentWorkspaceIs('code-sample') .currentWorkspaceIs('code-sample')
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content && content.indexOf( browser.assert.ok(content && content.indexOf(
@ -57,6 +58,7 @@ module.exports = {
.url('http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js&url=https://github.com/ethereum/remix-project/blob/master/apps/remix-ide/contracts/app/solidity/mode.sol&code=cHJhZ21hIHNvbGlkaXR5ID49MC42LjAgPDAuNy4wOwoKaW1wb3J0ICJodHRwczovL2dpdGh1Yi5jb20vT3BlblplcHBlbGluL29wZW56ZXBwZWxpbi1jb250cmFjdHMvYmxvYi9tYXN0ZXIvY29udHJhY3RzL2FjY2Vzcy9Pd25hYmxlLnNvbCI7Cgpjb250cmFjdCBHZXRQYWlkIGlzIE93bmFibGUgewogIGZ1bmN0aW9uIHdpdGhkcmF3KCkgZXh0ZXJuYWwgb25seU93bmVyIHsKICB9Cn0') .url('http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js&url=https://github.com/ethereum/remix-project/blob/master/apps/remix-ide/contracts/app/solidity/mode.sol&code=cHJhZ21hIHNvbGlkaXR5ID49MC42LjAgPDAuNy4wOwoKaW1wb3J0ICJodHRwczovL2dpdGh1Yi5jb20vT3BlblplcHBlbGluL29wZW56ZXBwZWxpbi1jb250cmFjdHMvYmxvYi9tYXN0ZXIvY29udHJhY3RzL2FjY2Vzcy9Pd25hYmxlLnNvbCI7Cgpjb250cmFjdCBHZXRQYWlkIGlzIE93bmFibGUgewogIGZ1bmN0aW9uIHdpdGhkcmF3KCkgZXh0ZXJuYWwgb25seU93bmVyIHsKICB9Cn0')
.refresh() // we do one reload for making sure we already have the default workspace .refresh() // we do one reload for making sure we already have the default workspace
.pause(5000) .pause(5000)
.clickLaunchIcon('filePanel')
.currentWorkspaceIs('code-sample') .currentWorkspaceIs('code-sample')
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content && content.indexOf( browser.assert.ok(content && content.indexOf(
@ -113,7 +115,7 @@ module.exports = {
.url('http://127.0.0.1:8080/#optimize=false&runs=200&evmVersion=null&version=soljson-v0.6.12+commit.27d51765.js&url=https://raw.githubusercontent.com/EthVM/evm-source-verification/main/contracts/1/0x011e5846975c6463a8c6337eecf3cbf64e328884/input.json') .url('http://127.0.0.1:8080/#optimize=false&runs=200&evmVersion=null&version=soljson-v0.6.12+commit.27d51765.js&url=https://raw.githubusercontent.com/EthVM/evm-source-verification/main/contracts/1/0x011e5846975c6463a8c6337eecf3cbf64e328884/input.json')
.refresh() .refresh()
.pause(5000) .pause(5000)
.waitForElementPresent('*[data-id="workspacesSelect"] option[value="code-sample"]') .switchWorkspace('code-sample')
.openFile('@openzeppelin') .openFile('@openzeppelin')
.openFile('@openzeppelin/contracts') .openFile('@openzeppelin/contracts')
.openFile('@openzeppelin/contracts/access') .openFile('@openzeppelin/contracts/access')

@ -235,7 +235,7 @@ module.exports = {
.pause(2000) .pause(2000)
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]') .waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]')
.pause(2000) .pause(2000)
.click('*[data-id="workspacesSelect"] option[value="workspace_name"]') .switchWorkspace('workspace_name')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
}, },
@ -249,23 +249,23 @@ module.exports = {
.setValue('*[data-id="modalDialogCustomPromptTextRename"]', 'workspace_name_renamed') .setValue('*[data-id="modalDialogCustomPromptTextRename"]', 'workspace_name_renamed')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.waitForElementPresent('*[data-id="workspacesSelect"] option[value="workspace_name_1"]') .switchWorkspace('workspace_name_1')
.click('*[data-id="workspacesSelect"] option[value="workspace_name_1"]')
.pause(2000) .pause(2000)
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]') .waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]')
.waitForElementPresent('*[data-id="workspacesSelect"] option[value="workspace_name_renamed"]') .switchWorkspace('workspace_name_renamed')
.click('*[data-id="workspacesSelect"] option[value="workspace_name_renamed"]')
.pause(2000) .pause(2000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.sol"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.sol"]')
}, },
'Should delete a workspace #group1': function (browser: NightwatchBrowser) { 'Should delete a workspace #group1': function (browser: NightwatchBrowser) {
browser browser
.click('*[data-id="workspacesSelect"] option[value="workspace_name_1"]') .switchWorkspace('workspace_name_1')
.click('*[data-id="workspaceDelete"]') // delete workspace_name_1 .click('*[data-id="workspaceDelete"]') // delete workspace_name_1
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.waitForElementNotPresent('*[data-id="workspacesSelect"] option[value="workspace_name_1"]') .waitForElementVisible('[data-id="workspacesSelect"]')
.click('[data-id="workspacesSelect"]')
.waitForElementNotPresent(`[data-id="dropdown-item-workspace_name_1"]`)
.end() .end()
}, },

@ -34,7 +34,7 @@ declare module 'nightwatch' {
getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser, getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser,
modalFooterCancelClick(id?: string): NightwatchBrowser, modalFooterCancelClick(id?: string): NightwatchBrowser,
selectContract(contractName: string): NightwatchBrowser, selectContract(contractName: string): NightwatchBrowser,
createContract(inputParams: string[]): NightwatchBrowser, createContract(inputParams: string): NightwatchBrowser,
getAddressAtPosition(index: number, cb: (pos: string) => void): NightwatchBrowser, getAddressAtPosition(index: number, cb: (pos: string) => void): NightwatchBrowser,
testConstantFunction(address: string, fnFullName: string, expectedInput: NightwatchTestConstantFunctionExpectedInput | null, expectedOutput: string): NightwatchBrowser, testConstantFunction(address: string, fnFullName: string, expectedInput: NightwatchTestConstantFunctionExpectedInput | null, expectedOutput: string): NightwatchBrowser,
getEditorValue(callback: (content: string) => void): NightwatchBrowser, getEditorValue(callback: (content: string) => void): NightwatchBrowser,
@ -62,7 +62,9 @@ declare module 'nightwatch' {
clearConsole (this: NightwatchBrowser): NightwatchBrowser clearConsole (this: NightwatchBrowser): NightwatchBrowser
clearTransactions (this: NightwatchBrowser): NightwatchBrowser clearTransactions (this: NightwatchBrowser): NightwatchBrowser
getBrowserLogs (this: NightwatchBrowser): NightwatchBrowser getBrowserLogs (this: NightwatchBrowser): NightwatchBrowser
currentSelectedFileIs (name: string): NightwatchBrowser currentSelectedFileIs (name: string): NightwatchBrowser,
switchWorkspace: (workspaceName: string) => NightwatchBrowser
switchEnvironment: (provider: string) => NightwatchBrowser
} }
export interface NightwatchBrowser { export interface NightwatchBrowser {

@ -14,7 +14,7 @@ echo "To use an offline copy, download \`remix-$SHA.zip\`." >> README.md
cp -r $FILES_TO_PACKAGE "./" cp -r $FILES_TO_PACKAGE "./"
rm -rf dist rm -rf dist
ls ls
FILES_TO_DEPLOY="assets index.html main*.js polyfills*.js favicon.ico vendors~app*.js app*.js raw-loader*.js" FILES_TO_DEPLOY="assets index.html 404.html main*.js polyfills*.js favicon.ico vendors~app*.js app*.js raw-loader*.js"
# ZIP the whole directory # ZIP the whole directory
zip -r remix-$SHA.zip $FILES_TO_DEPLOY zip -r remix-$SHA.zip $FILES_TO_DEPLOY
# -f is needed because "build" is part of .gitignore # -f is needed because "build" is part of .gitignore

@ -14,7 +14,7 @@ echo "To use an offline copy, download \`remix-$SHA.zip\`." >> README.md
cp -r $FILES_TO_PACKAGE "./" cp -r $FILES_TO_PACKAGE "./"
rm -rf dist rm -rf dist
ls ls
FILES_TO_DEPLOY="assets index.html main*.js polyfills*.js favicon.ico vendors~app*.js app*.js raw-loader*.js" FILES_TO_DEPLOY="assets index.html 404.html main*.js polyfills*.js favicon.ico vendors~app*.js app*.js raw-loader*.js"
# ZIP the whole directory # ZIP the whole directory
zip -r remix-$SHA.zip $FILES_TO_DEPLOY zip -r remix-$SHA.zip $FILES_TO_DEPLOY
# -f is needed because "build" is part of .gitignore # -f is needed because "build" is part of .gitignore

@ -14,7 +14,7 @@ echo "To use an offline copy, download \`remix-$SHA.zip\`." >> README.md
cp -r $FILES_TO_PACKAGE "./" cp -r $FILES_TO_PACKAGE "./"
rm -rf dist rm -rf dist
ls ls
FILES_TO_DEPLOY="assets index.html main*.js polyfills*.js vendors~app*.js app*.js raw-loader*.js" FILES_TO_DEPLOY="assets index.html 404.html main*.js polyfills*.js vendors~app*.js app*.js raw-loader*.js"
# ZIP the whole directory # ZIP the whole directory
zip -r remix-$SHA.zip $FILES_TO_DEPLOY zip -r remix-$SHA.zip $FILES_TO_DEPLOY
# -f is needed because "build" is part of .gitignore # -f is needed because "build" is part of .gitignore

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body></body>
<script>
let route = window.location.pathname
if (route.startsWith('/address/0x') && route.length === 51) {
window.location.href = '/#address=' + route.replace('/address/', '')
} else if (route.endsWith('.sol')) {
window.location.href = '/#url=https://github.com' + route
} else window.location.href = '/'
</script>
</html>

@ -31,6 +31,8 @@ import { HardhatProvider } from './app/tabs/hardhat-provider'
import { GanacheProvider } from './app/tabs/ganache-provider' import { GanacheProvider } from './app/tabs/ganache-provider'
import { FoundryProvider } from './app/tabs/foundry-provider' import { FoundryProvider } from './app/tabs/foundry-provider'
import { ExternalHttpProvider } from './app/tabs/external-http-provider' import { ExternalHttpProvider } from './app/tabs/external-http-provider'
import { Injected0ptimismProvider } from './app/tabs/injected-optimism-provider'
import { InjectedArbitrumOneProvider } from './app/tabs/injected-arbitrum-one-provider'
const isElectron = require('is-electron') const isElectron = require('is-electron')
@ -183,6 +185,8 @@ class AppComponent {
const ganacheProvider = new GanacheProvider(blockchain) const ganacheProvider = new GanacheProvider(blockchain)
const foundryProvider = new FoundryProvider(blockchain) const foundryProvider = new FoundryProvider(blockchain)
const externalHttpProvider = new ExternalHttpProvider(blockchain) const externalHttpProvider = new ExternalHttpProvider(blockchain)
const injected0ptimismProvider = new Injected0ptimismProvider(blockchain)
const injectedArbitrumOneProvider = new InjectedArbitrumOneProvider(blockchain)
// ----------------- convert offset to line/column service ----------- // ----------------- convert offset to line/column service -----------
const offsetToLineColumnConverter = new OffsetToLineColumnConverter() const offsetToLineColumnConverter = new OffsetToLineColumnConverter()
Registry.getInstance().put({ Registry.getInstance().put({
@ -244,6 +248,8 @@ class AppComponent {
ganacheProvider, ganacheProvider,
foundryProvider, foundryProvider,
externalHttpProvider, externalHttpProvider,
injected0ptimismProvider,
injectedArbitrumOneProvider,
this.walkthroughService, this.walkthroughService,
search search
]) ])

@ -233,12 +233,11 @@ class DGitProvider extends Plugin {
return this.calculateLocalStorage() return this.calculateLocalStorage()
} }
async clone (input) { async clone (input, workspaceName, workspaceExists = false) {
const permission = await this.askUserPermission('clone', 'Import multiple files into your workspaces.') const permission = await this.askUserPermission('clone', 'Import multiple files into your workspaces.')
if (!permission) return false if (!permission) return false
if (this.calculateLocalStorage() > 10000) throw new Error('The local storage of the browser is full.') if (this.calculateLocalStorage() > 10000) throw new Error('The local storage of the browser is full.')
await this.call('filePanel', 'createWorkspace', `workspace_${Date.now()}`, true) if (!workspaceExists) await this.call('filePanel', 'createWorkspace', workspaceName || `workspace_${Date.now()}`, true)
const cmd = { const cmd = {
url: input.url, url: input.url,
singleBranch: input.singleBranch, singleBranch: input.singleBranch,
@ -249,9 +248,11 @@ class DGitProvider extends Plugin {
} }
const result = await git.clone(cmd) const result = await git.clone(cmd)
setTimeout(async () => { if (!workspaceExists) {
await this.call('fileManager', 'refresh') setTimeout(async () => {
}, 1000) await this.call('fileManager', 'refresh')
}, 1000)
}
return result return result
} }

@ -19,7 +19,7 @@ const profile = {
icon: 'assets/img/fileManager.webp', icon: 'assets/img/fileManager.webp',
permission: true, permission: true,
version: packageJson.version, version: packageJson.version,
methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'saveCurrentFile', 'setBatchFiles'], methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'saveCurrentFile', 'setBatchFiles', 'isGitRepo'],
kind: 'file-system' kind: 'file-system'
} }
const errorMsg = { const errorMsg = {
@ -810,6 +810,13 @@ class FileManager extends Plugin {
return provider.workspace return provider.workspace
} }
} }
async isGitRepo (directory: string): Promise<boolean> {
const path = directory + '/.git'
const exists = await this.exists(path)
return exists
}
} }
module.exports = FileManager module.exports = FileManager

@ -152,6 +152,9 @@ module.exports = class Filepanel extends ViewPlugin {
const workspaceProvider = this.fileProviders.workspace const workspaceProvider = this.fileProviders.workspace
this.currentWorkspaceMetadata = { name: workspace.name, isLocalhost: workspace.isLocalhost, absolutePath: `${workspaceProvider.workspacesPath}/${workspace.name}` } this.currentWorkspaceMetadata = { name: workspace.name, isLocalhost: workspace.isLocalhost, absolutePath: `${workspaceProvider.workspacesPath}/${workspace.name}` }
if (workspace.name !== " - connect to localhost - ") {
localStorage.setItem('currentWorkspace', workspace.name)
}
this.emit('setWorkspace', workspace) this.emit('setWorkspace', workspace)
} }

@ -3,21 +3,21 @@ import { AppModal, AlertModal, ModalTypes } from '@remix-ui/app'
import { Blockchain } from '../../blockchain/blockchain' import { Blockchain } from '../../blockchain/blockchain'
import { ethers } from 'ethers' import { ethers } from 'ethers'
type JsonDataRequest = { export type JsonDataRequest = {
id: number, id: number,
jsonrpc: string // version jsonrpc: string // version
method: string, method: string,
params: Array<any>, params: Array<any>,
} }
type JsonDataResult = { export type JsonDataResult = {
id: number, id: number,
jsonrpc: string // version jsonrpc: string // version
result: any result: any
} }
type RejectRequest = (error: Error) => void export type RejectRequest = (error: Error) => void
type SuccessRequest = (data: JsonDataResult) => void export type SuccessRequest = (data: JsonDataResult) => void
export abstract class AbstractProvider extends Plugin { export abstract class AbstractProvider extends Plugin {
provider: ethers.providers.JsonRpcProvider provider: ethers.providers.JsonRpcProvider

@ -30,7 +30,7 @@ export class ExternalHttpProvider extends AbstractProvider {
<br /> <br />
<b>WARNING:</b> It is not safe to use the --http.corsdomain flag with a wildcard: <b>--http.corsdomain *</b> <b>WARNING:</b> It is not safe to use the --http.corsdomain flag with a wildcard: <b>--http.corsdomain *</b>
<br /> <br />
<br />For more info: <a href="https://remix-ide.readthedocs.io/en/latest/run.html#more-about-web3-provider" target="_blank" rel="noreferrer">Remix Docs on Web3 Provider</a> <br />For more info: <a href="https://remix-ide.readthedocs.io/en/latest/run.html#more-about-web3-provider" target="_blank" rel="noreferrer">Remix Docs on External HTTP Provider</a>
<br /> <br />
<br /> <br />
External HTTP Provider Endpoint External HTTP Provider Endpoint

@ -0,0 +1,21 @@
import * as packageJson from '../../../../../package.json'
import { InjectedProvider } from './injected-provider'
const profile = {
name: 'injected-arbitrum-one-provider',
displayName: 'Injected Arbitrum One Provider',
kind: 'provider',
description: 'injected Arbitrum One Provider',
methods: ['sendAsync'],
version: packageJson.version
}
export class InjectedArbitrumOneProvider extends InjectedProvider {
constructor () {
super(profile)
this.chainName = 'Arbitrum One'
this.chainId = '0xa4b1'
this.rpcUrls = ['https://arb1.arbitrum.io/rpc']
}
}

@ -0,0 +1,21 @@
import * as packageJson from '../../../../../package.json'
import { InjectedProvider } from './injected-provider'
const profile = {
name: 'injected-optimism-provider',
displayName: 'Injected Optimism Provider',
kind: 'provider',
description: 'injected Optimism Provider',
methods: ['sendAsync'],
version: packageJson.version
}
export class Injected0ptimismProvider extends InjectedProvider {
constructor () {
super(profile)
this.chainName = 'Optimism'
this.chainId = '0xa'
this.rpcUrls = ['https://mainnet.optimism.io']
}
}

@ -0,0 +1,75 @@
import { Plugin } from '@remixproject/engine'
import { JsonDataRequest, RejectRequest, SuccessRequest } from './abstract-provider'
import { ethers } from 'ethers'
import Web3 from 'web3'
export class InjectedProvider extends Plugin {
provider: any
chainName: string
chainId: string
rpcUrls: Array<string>
constructor (profile) {
super(profile)
if ((window as any).ethereum) {
this.provider = new Web3((window as any).ethereum)
}
}
sendAsync (data: JsonDataRequest): Promise<any> {
return new Promise((resolve, reject) => {
this.sendAsyncInternal(data, resolve, reject)
})
}
private async sendAsyncInternal (data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise<void> {
// Check the case where current environment is VM on UI and it still sends RPC requests
// This will be displayed on UI tooltip as 'cannot get account list: Environment Updated !!'
if (!this.provider) {
this.call('notification', 'toast', 'No injected provider (e.g Metamask) has been found.')
return reject(new Error('no injected provider found.'))
}
try {
if ((window as any) && typeof (window as any).ethereum.enable === 'function') (window as any).ethereum.enable()
if (!await (window as any).ethereum._metamask.isUnlocked()) this.call('notification', 'toast', 'Please make sure the injected provider is unlocked (e.g Metamask).')
await addL2Network(this.chainName, this.chainId, this.rpcUrls)
const resultData = await this.provider.currentProvider.send(data.method, data.params)
resolve({ jsonrpc: '2.0', result: resultData.result, id: data.id })
} catch (error) {
reject(error)
}
}
}
export const addL2Network = async (chainName: string, chainId: string, rpcUrls: Array<string>) => {
try {
await (window as any).ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: chainId }],
});
} catch (switchError) {
// This error code indicates that the chain has not been added to MetaMask.
if (switchError.code === 4902) {
try {
await (window as any).ethereum.request({
method: 'wallet_addEthereumChain',
params: [
{
chainId: chainId,
chainName: chainName,
rpcUrls: rpcUrls,
},
],
});
await (window as any).ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: chainId }],
});
} catch (addError) {
// handle "add" error
}
}
// handle other "switch" errors
}
}

@ -102,6 +102,7 @@ export class RunTab extends ViewPlugin {
await this.call('blockchain', 'addProvider', { await this.call('blockchain', 'addProvider', {
name: 'Hardhat Provider', name: 'Hardhat Provider',
isInjected: false,
provider: { provider: {
async sendAsync (payload, callback) { async sendAsync (payload, callback) {
try { try {
@ -116,6 +117,7 @@ export class RunTab extends ViewPlugin {
await this.call('blockchain', 'addProvider', { await this.call('blockchain', 'addProvider', {
name: 'Ganache Provider', name: 'Ganache Provider',
isInjected: false,
provider: { provider: {
async sendAsync (payload, callback) { async sendAsync (payload, callback) {
try { try {
@ -130,6 +132,7 @@ export class RunTab extends ViewPlugin {
await this.call('blockchain', 'addProvider', { await this.call('blockchain', 'addProvider', {
name: 'Foundry Provider', name: 'Foundry Provider',
isInjected: false,
provider: { provider: {
async sendAsync (payload, callback) { async sendAsync (payload, callback) {
try { try {
@ -144,6 +147,7 @@ export class RunTab extends ViewPlugin {
await this.call('blockchain', 'addProvider', { await this.call('blockchain', 'addProvider', {
name: 'Wallet Connect', name: 'Wallet Connect',
isInjected: false,
provider: { provider: {
async sendAsync (payload, callback) { async sendAsync (payload, callback) {
try { try {
@ -169,6 +173,36 @@ export class RunTab extends ViewPlugin {
} }
} }
}) })
await this.call('blockchain', 'addProvider', {
name: 'Optimism Provider',
isInjected: true,
provider: {
async sendAsync (payload, callback) {
try {
const result = await udapp.call('injected-optimism-provider', 'sendAsync', payload)
callback(null, result)
} catch (e) {
callback(e)
}
}
}
})
await this.call('blockchain', 'addProvider', {
name: 'Arbitrum One Provider',
isInjected: true,
provider: {
async sendAsync (payload, callback) {
try {
const result = await udapp.call('injected-arbitrum-one-provider', 'sendAsync', payload)
callback(null, result)
} catch (e) {
callback(e)
}
}
}
})
} }
writeFile (fileName, content) { writeFile (fileName, content) {

@ -11,8 +11,7 @@ import InjectedProvider from './providers/injected.js'
import NodeProvider from './providers/node.js' import NodeProvider from './providers/node.js'
import { execution, EventManager, helpers } from '@remix-project/remix-lib' import { execution, EventManager, helpers } from '@remix-project/remix-lib'
import { etherScanLink } from './helper' import { etherScanLink } from './helper'
import { logBuilder, confirmProxyMsg } from "@remix-ui/helper" import { logBuilder, cancelUpgradeMsg, cancelProxyMsg } from "@remix-ui/helper"
import { cancelProxyMsg } from '@remix-ui/helper'
const { txFormat, txExecution, typeConversion, txListener: Txlistener, TxRunner, TxRunnerWeb3, txHelper } = execution const { txFormat, txExecution, typeConversion, txListener: Txlistener, TxRunner, TxRunnerWeb3, txHelper } = execution
const { txResultHelper: resultToRemixTx } = helpers const { txResultHelper: resultToRemixTx } = helpers
const packageJson = require('../../../../package.json') const packageJson = require('../../../../package.json')
@ -150,9 +149,11 @@ export class Blockchain extends Plugin {
cancelLabel: 'Cancel', cancelLabel: 'Cancel',
okFn: () => { okFn: () => {
this.runProxyTx(proxyData, implementationContractObject) this.runProxyTx(proxyData, implementationContractObject)
_paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'modal ok confirmation'])
}, },
cancelFn: () => { cancelFn: () => {
this.call('notification', 'toast', cancelProxyMsg()) this.call('notification', 'toast', cancelProxyMsg())
_paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'cancel proxy deployment'])
}, },
hideFn: () => null hideFn: () => null
} }
@ -161,7 +162,9 @@ export class Blockchain extends Plugin {
async runProxyTx (proxyData, implementationContractObject) { async runProxyTx (proxyData, implementationContractObject) {
const args = { useCall: false, data: proxyData } const args = { useCall: false, data: proxyData }
let networkInfo
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => { const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
networkInfo = network
// continue using original authorization given by user // continue using original authorization given by user
continueTxExecution(null) continueTxExecution(null)
} }
@ -171,14 +174,60 @@ export class Blockchain extends Plugin {
if (error) { if (error) {
const log = logBuilder(error) const log = logBuilder(error)
_paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'Proxy deployment failed: ' + error])
return this.call('terminal', 'logHtml', log) return this.call('terminal', 'logHtml', log)
} }
if (networkInfo.name === 'VM') this.config.set('vm/proxy', address)
else this.config.set(`${networkInfo.name}/${networkInfo.currentFork}/${networkInfo.id}/proxy`, address)
_paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'Proxy deployment successful'])
return this.call('udapp', 'resolveContractAndAddInstance', implementationContractObject, address) return this.call('udapp', 'resolveContractAndAddInstance', implementationContractObject, address)
} }
this.runTx(args, confirmationCb, continueCb, promptCb, finalCb) this.runTx(args, confirmationCb, continueCb, promptCb, finalCb)
} }
async upgradeProxy(proxyAddress, newImplAddress, data, newImplementationContractObject) {
const upgradeModal = {
id: 'confirmProxyDeployment',
title: 'ERC1967',
message: `Confirm you want to upgrade your contract to new implementation ${newImplAddress}.`,
modalType: 'modal',
okLabel: 'OK',
cancelLabel: 'Cancel',
okFn: () => {
this.runUpgradeTx(proxyAddress, data, newImplementationContractObject)
_paq.push(['trackEvent', 'blockchain', 'Upgrade With Proxy', 'proxy upgrade confirmation click'])
},
cancelFn: () => {
this.call('notification', 'toast', cancelUpgradeMsg())
_paq.push(['trackEvent', 'blockchain', 'Upgrade With Proxy', 'proxy upgrade cancel click'])
},
hideFn: () => null
}
this.call('notification', 'modal', upgradeModal)
}
async runUpgradeTx (proxyAddress, data, newImplementationContractObject) {
const args = { useCall: false, data, to: proxyAddress }
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
// continue using original authorization given by user
continueTxExecution(null)
}
const continueCb = (error, continueTxExecution, cancelCb) => { continueTxExecution() }
const promptCb = (okCb, cancelCb) => { okCb() }
const finalCb = (error, txResult, address, returnValue) => {
if (error) {
const log = logBuilder(error)
_paq.push(['trackEvent', 'blockchain', 'Upgrade With Proxy', 'Upgrade failed'])
return this.call('terminal', 'logHtml', log)
}
_paq.push(['trackEvent', 'blockchain', 'Upgrade With Proxy', 'Upgrade Successful'])
return this.call('udapp', 'resolveContractAndAddInstance', newImplementationContractObject, proxyAddress)
}
this.runTx(args, confirmationCb, continueCb, promptCb, finalCb)
}
async getEncodedFunctionHex (args, funABI) { async getEncodedFunctionHex (args, funABI) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
txFormat.encodeFunctionCall(args, funABI, (error, data) => { txFormat.encodeFunctionCall(args, funABI, (error, data) => {

@ -152,9 +152,12 @@ export class ExecutionContext {
if (context === 'injected') { if (context === 'injected') {
if (injectedProvider === undefined) { if (injectedProvider === undefined) {
infoCb('No injected Web3 provider found. Make sure your provider (e.g. MetaMask) is active and running (when recently activated you may have to reload the page).') infoCb('No injected provider found. Make sure your provider (e.g. MetaMask) is active and running (when recently activated you may have to reload the page).')
return cb() return cb()
} else { } else {
if (injectedProvider && injectedProvider._metamask && injectedProvider._metamask.isUnlocked) {
if (!await injectedProvider._metamask.isUnlocked()) this.call('notification', 'toast', 'Please make sure the injected provider is unlocked (e.g Metamask).')
}
this.askPermission() this.askPermission()
this.executionContext = context this.executionContext = context
web3.setProvider(injectedProvider) web3.setProvider(injectedProvider)
@ -166,11 +169,21 @@ export class ExecutionContext {
if (this.customNetWorks[context]) { if (this.customNetWorks[context]) {
var network = this.customNetWorks[context] var network = this.customNetWorks[context]
this.setProviderFromEndpoint(network.provider, { context: network.name }, (error) => { if (!this.customNetWorks[context].isInjected) {
if (error) infoCb(error) this.setProviderFromEndpoint(network.provider, { context: network.name }, (error) => {
cb() if (error) infoCb(error)
}) cb()
} })
} else {
// injected
this.askPermission()
this.executionContext = context
web3.setProvider(network.provider)
await this._updateChainContext()
this.event.trigger('contextChanged', [context])
return cb()
}
}
} }
currentblockGasLimit () { currentblockGasLimit () {

@ -19,7 +19,7 @@ const sensitiveCalls = {
} }
export function isNative(name) { export function isNative(name) {
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification', 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider'] const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification', 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider', 'injected-arbitrum-one-provider']
return nativePlugins.includes(name) || requiredModules.includes(name) return nativePlugins.includes(name) || requiredModules.includes(name)
} }

@ -88,4 +88,18 @@ export const UUPSfunAbi = {
type: "constructor", type: "constructor",
outputs: [], outputs: [],
stateMutability: "payable" stateMutability: "payable"
}
export const UUPSupgradeAbi = {
"inputs": [
{
"internalType": "address",
"name": "newImplementation",
"type": "address"
}
],
"name": "upgradeTo",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
} }

@ -1,12 +1,12 @@
import { Plugin } from '@remixproject/engine'; import { Plugin } from '@remixproject/engine'
import { ContractABI, ContractAST, DeployOption } from '../types/contract'; import { ContractABI, ContractAST, ContractSources, DeployOptions } from '../types/contract'
import { UUPS, UUPSABI, UUPSBytecode, UUPSfunAbi } from './constants/uups'; import { UUPS, UUPSABI, UUPSBytecode, UUPSfunAbi, UUPSupgradeAbi } from './constants/uups'
const proxyProfile = { const proxyProfile = {
name: 'openzeppelin-proxy', name: 'openzeppelin-proxy',
displayName: 'openzeppelin-proxy', displayName: 'openzeppelin-proxy',
description: 'openzeppelin-proxy', description: 'openzeppelin-proxy',
methods: ['isConcerned', 'execute', 'getDeployOptions'] methods: ['isConcerned', 'executeUUPSProxy', 'executeUUPSContractUpgrade', 'getProxyOptions', 'getUpgradeOptions']
}; };
export class OpenZeppelinProxy extends Plugin { export class OpenZeppelinProxy extends Plugin {
blockchain: any blockchain: any
@ -28,26 +28,37 @@ export class OpenZeppelinProxy extends Plugin {
return false return false
} }
async getDeployOptions (contracts: ContractABI): Promise<{ [name: string]: DeployOption }> { async getProxyOptions (data: ContractSources, file: string): Promise<{ [name: string]: DeployOptions }> {
const contracts = data.contracts[file]
const ast = data.sources[file].ast
const inputs = {} const inputs = {}
if (this.kind === 'UUPS') { if (this.kind === 'UUPS') {
Object.keys(contracts).map(name => { Object.keys(contracts).map(name => {
const abi = contracts[name].abi if (ast) {
const initializeInput = abi.find(node => node.name === 'initialize') const UUPSSymbol = ast.exportedSymbols['UUPSUpgradeable'] ? ast.exportedSymbols['UUPSUpgradeable'][0] : null
if (initializeInput) { ast.absolutePath === file && ast.nodes.map((node) => {
inputs[name] = { if (node.name === name && node.linearizedBaseContracts.includes(UUPSSymbol)) {
inputs: initializeInput, const abi = contracts[name].abi
initializeInputs: this.blockchain.getInputs(initializeInput) const initializeInput = abi.find(node => node.name === 'initialize')
}
inputs[name] = {
options: [{ title: 'Deploy with Proxy', active: false }, { title: 'Upgrade with Proxy', active: false }],
initializeOptions: {
inputs: initializeInput,
initializeInputs: initializeInput ? this.blockchain.getInputs(initializeInput) : null
}
}
}
})
} }
}) })
} }
return inputs return inputs
} }
async execute(implAddress: string, args: string | string [] = '', initializeABI, implementationContractObject): Promise<void> { async executeUUPSProxy(implAddress: string, args: string | string [] = '', initializeABI, implementationContractObject): Promise<void> {
// deploy the proxy, or use an existing one // deploy the proxy, or use an existing one
if (!initializeABI) throw new Error('Cannot deploy proxy: Missing initialize ABI') if (!initializeABI) throw new Error('Cannot deploy proxy: Missing initialize ABI')
args = args === '' ? [] : args args = args === '' ? [] : args
@ -56,6 +67,13 @@ export class OpenZeppelinProxy extends Plugin {
if (this.kind === 'UUPS') this.deployUUPSProxy(implAddress, _data, implementationContractObject) if (this.kind === 'UUPS') this.deployUUPSProxy(implAddress, _data, implementationContractObject)
} }
async executeUUPSContractUpgrade (proxyAddress: string, newImplAddress: string, newImplementationContractObject): Promise<void> {
if (!newImplAddress) throw new Error('Cannot upgrade: Missing implementation address')
if (!proxyAddress) throw new Error('Cannot upgrade: Missing proxy address')
if (this.kind === 'UUPS') this.upgradeUUPSProxy(proxyAddress, newImplAddress, newImplementationContractObject)
}
async deployUUPSProxy (implAddress: string, _data: string, implementationContractObject): Promise<void> { async deployUUPSProxy (implAddress: string, _data: string, implementationContractObject): Promise<void> {
const args = [implAddress, _data] const args = [implAddress, _data]
const constructorData = await this.blockchain.getEncodedParams(args, UUPSfunAbi) const constructorData = await this.blockchain.getEncodedParams(args, UUPSfunAbi)
@ -74,4 +92,20 @@ export class OpenZeppelinProxy extends Plugin {
implementationContractObject.name = proxyName implementationContractObject.name = proxyName
this.blockchain.deployProxy(data, implementationContractObject) this.blockchain.deployProxy(data, implementationContractObject)
} }
async upgradeUUPSProxy (proxyAddress: string, newImplAddress: string, newImplementationContractObject): Promise<void> {
const fnData = await this.blockchain.getEncodedFunctionHex([newImplAddress], UUPSupgradeAbi)
const proxyName = 'ERC1967Proxy'
const data = {
contractABI: UUPSABI,
contractName: proxyName,
funAbi: UUPSupgradeAbi,
funArgs: [newImplAddress],
linkReferences: {},
dataHex: fnData.replace('0x', '')
}
// re-use implementation contract's ABI for UI display in udapp and change name to proxy name.
newImplementationContractObject.name = proxyName
this.blockchain.upgradeProxy(proxyAddress, newImplAddress, data, newImplementationContractObject)
}
} }

@ -54,101 +54,132 @@ export interface ContractAST {
}[] }[]
} }
export interface ContractABI { export type ContractABI = {
[key: string]: { inputs: [];
abi: ({ stateMutability: string;
inputs: never[]; type: string;
stateMutability: string; anonymous?: undefined;
type: string; name?: string;
anonymous?: undefined; outputs?: undefined;
name?: undefined; } | {
outputs?: undefined; anonymous: boolean;
} | { inputs: {
anonymous: boolean; indexed: boolean;
inputs: { internalType: string;
indexed: boolean; name: string;
internalType: string; type: string;
name: string; }[];
type: string; name: string;
}[]; type: string;
name: string; stateMutability?: undefined;
type: string; outputs?: undefined;
stateMutability?: undefined; } | {
outputs?: undefined; inputs: {
} | { internalType: string;
inputs: { name: string;
internalType: string; type: string;
name: string; }[];
type: string; name: string;
}[]; outputs: {
name: string; internalType: string;
outputs: { name: string;
internalType: string; type: string;
name: string; }[];
type: string; stateMutability: string;
}[]; type: string;
stateMutability: string; anonymous?: undefined;
type: string;
anonymous?: undefined;
})[];
devdoc: {
kind: string;
methods: {
[key: string]: {
[key: string]: string
}
};
version: number;
};
evm: any
metadata: string;
storageLayout: {
storage: {
astId: number;
contract: string;
label: string;
offset: number;
slot: string;
type: string;
}[];
types: {
[key: string]: {
base: string;
encoding: string;
label: string;
numberOfBytes: string;
members?: {
astId: number;
contract: string;
label: string;
offset: number;
slot: string;
type: string;
}[];
};
};
};
userdoc: {
kind: string;
methods: any;
version: number;
};
};
} }
export type DeployMode = 'Deploy with Proxy' | 'Upgrade with Proxy'
export type DeployOption = { export type DeployOption = {
initializeInputs: string, initializeInputs: string,
inputs: {
inputs: { inputs: {
inputs: { internalType?: string,
internalType?: string, name: string,
name: string, type: string
type: string }[],
}[], name: "initialize",
name: "initialize", outputs?: any[],
outputs?: any[], stateMutability: string,
stateMutability: string, type: string,
type: string, payable?: boolean,
payable?: boolean, constant?: any
constant?: any }
}
export interface DeployOptions {
initializeOptions: DeployOption,
options: { title: DeployMode, active: boolean }[]
}
export interface ContractSources {
contracts: {
[path: string]: {
[contractName: string]: {
abi: ContractABI[],
devdoc: {
kind: string
methods: {
[key: string]: {
[key: string]: string
}
};
version: number
}
evm: any
metadata: string
storageLayout: {
storage: {
astId: number
contract: string
label: string
offset: number
slot: string
type: string
}[]
types: {
[key: string]: {
base: string
encoding: string
label: string
numberOfBytes: string
members?: {
astId: number
contract: string
label: string
offset: number
slot: string
type: string
}[]
}
}
}
userdoc: {
kind: string
methods: any
version: number
}
}
}
},
error: {
component: string,
errorCode: string,
formattedMessage: string,
message: string,
severity: string,
sourceLocation: {
end: number,
file: string,
start: number
},
type: string
}[],
sources: {
[path: string]: {
ast: ContractAST,
id: number
}
} }
} }

@ -1,3 +1,4 @@
export * from './lib/remix-ui-helper' export * from './lib/remix-ui-helper'
export * from './lib/helper-components' export * from './lib/helper-components'
export * from './lib/components/PluginViewWrapper' export * from './lib/components/PluginViewWrapper'
export * from './lib/components/custom-dropdown'

@ -0,0 +1,42 @@
// The forwardRef is important!!
import React, { Ref } from "react"
// Dropdown needs access to the DOM node in order to position the Menu
export const CustomToggle = React.forwardRef(({ children, onClick, icon, className = '' }: { children: React.ReactNode, onClick: (e) => void, icon: string, className: string }, ref: Ref<HTMLButtonElement>) => (
<button
ref={ref}
onClick={(e) => {
e.preventDefault()
onClick(e)
}}
className={className.replace('dropdown-toggle', '')}
>
<div className="d-flex">
<div className="mr-auto">{ children }</div>
{ icon && <div className="pr-1"><i className={`${icon} pr-1`}></i></div> }
<div><i className="fad fa-sort-circle"></i></div>
</div>
</button>
))
// forwardRef again here!
// Dropdown needs access to the DOM of the Menu to measure it
export const CustomMenu = React.forwardRef(
({ children, style, className, 'aria-labelledby': labeledBy }: { children: React.ReactNode, style?: React.CSSProperties, className: string, 'aria-labelledby'?: string }, ref: Ref<HTMLDivElement>) => {
return (
<div
ref={ref}
style={style}
className={className}
aria-labelledby={labeledBy}
>
<ul className="list-unstyled mb-0">
{
children
}
</ul>
</div>
)
},
)

@ -89,3 +89,21 @@ export const cancelProxyMsg = () => (
<b>Proxy deployment cancelled.</b> <b>Proxy deployment cancelled.</b>
</div> </div>
) )
export const cancelUpgradeMsg = () => (
<div>
<b>Upgrade with proxy cancelled.</b>
</div>
)
export const deployWithProxyMsg = () => (
<div>
NOTE: Deploy With Proxy will initiate two (2) transactions. The first is for the deployment of your implementation contract and the second will be a deployment of an ERC1967 proxy contract.
</div>
)
export const upgradeWithProxyMsg = () => (
<div>
NOTE: Upgrade With Proxy will initiate two (2) transactions. The first is for the deployment of your implementation contract and the second will intiate a call to the upgradeTo function in your proxy contract.
</div>
)

@ -47,6 +47,22 @@ export const createNonClashingNameAsync = async (name: string, fileManager, pref
return name + counter + prefix + '.' + ext return name + counter + prefix + '.' + ext
} }
export const createNonClashingTitle = async (name: string, fileManager) => {
if (!name) name = 'Undefined'
let _counter
let exist = true
do {
const isDuplicate = await fileManager.exists(name + (_counter || ''))
if (isDuplicate) _counter = (_counter || 0) + 1
else exist = false
} while (exist)
const counter = _counter || ''
return name + counter
}
export const joinPath = (...paths) => { export const joinPath = (...paths) => {
paths = paths.filter((value) => value !== '').map((path) => path.replace(/^\/|\/$/g, '')) // remove first and last slash) paths = paths.filter((value) => value !== '').map((path) => path.replace(/^\/|\/$/g, '')) // remove first and last slash)
if (paths.length === 1) return paths[0] if (paths.length === 1) return paths[0]

@ -4,10 +4,11 @@ import { PluginRecord } from '../types'
import './panel.css' import './panel.css'
export interface RemixPanelProps { export interface RemixPanelProps {
plugins: Record<string, PluginRecord>; plugins: Record<string, PluginRecord>;
} }
const RemixUIPanelHeader = (props: RemixPanelProps) => { const RemixUIPanelHeader = (props: RemixPanelProps) => {
const [plugin, setPlugin] = useState<PluginRecord>() const [plugin, setPlugin] = useState<PluginRecord>()
const [toggleExpander, setToggleExpander] = useState<boolean>(false)
useEffect(() => { useEffect(() => {
if (props.plugins) { if (props.plugins) {
@ -18,9 +19,48 @@ const RemixUIPanelHeader = (props: RemixPanelProps) => {
} }
}, [props]) }, [props])
const toggleClass = () => {
setToggleExpander(!toggleExpander)
}
return ( return (
<header className='swapitHeader'><h6 data-id='sidePanelSwapitTitle'>{plugin?.profile.displayName || plugin?.profile.name}</h6> <header className='d-flex flex-column'>
{plugin?.profile.documentation ? (<a href={plugin.profile.documentation} className="titleInfo mb-2" title="link to documentation" target="_blank" rel="noreferrer"><i aria-hidden="true" className="fas fa-book"></i></a>) : ''} <div className="swapitHeader px-3 pt-3 pb-0 d-flex flex-row">
<h6 data-id='sidePanelSwapitTitle'>{plugin?.profile.displayName || plugin?.profile.name}</h6>
<div className="d-flex flex-row">
<div className="d-flex flex-row">
{plugin?.profile?.maintainedBy?.toLowerCase() === "remix" && (<i aria-hidden="true" className="text-success fas fa-check" title="Maintained by Remix"></i>)}
{plugin?.profile.documentation && (<a href={plugin.profile.documentation} className="titleInfo mb-2" title="link to documentation" target="_blank" rel="noreferrer"><i aria-hidden="true" className="fas fa-book"></i></a>)}
</div>
<div className="swapitHeaderInfoSection d-flex justify-content-between" data-id='swapitHeaderInfoSectionId' onClick={toggleClass} title="Plugin info">
<i className={`px-2 ml-2 pt-1 ${!toggleExpander ? 'fas fa-angle-right' : 'fas fa-angle-down bg-light'}`} aria-hidden="true"></i>
</div>
</div>
</div>
<div className={`bg-light p-3 pt-1 border-bottom flex-column ${toggleExpander ? "d-flex" : "d-none"}`}>
{plugin?.profile?.author && <span className="d-flex flex-row align-items-center">
<label className="mb-0 pr-2">Author:</label>
<span> { plugin?.profile.author } </span>
</span>}
{plugin?.profile?.maintainedBy && <span className="d-flex flex-row align-items-center">
<label className="mb-0 pr-2">Maintained by:</label>
<span> { plugin?.profile.maintainedBy } </span>
</span>}
{plugin?.profile?.documentation && <span className="d-flex flex-row align-items-center">
<label className="mb-0 pr-2">Documentation:</label>
<span>
<a href={plugin?.profile?.documentation} className="titleInfo mb-2" title="link to documentation" target="_blank" rel="noreferrer"><i aria-hidden="true" className="fas fa-book"></i></a>
</span>
</span>}
{plugin?.profile?.description && <span className="d-flex flex-row align-items-baseline">
<label className="mb-0 pr-2">Description:</label>
<span> { plugin?.profile.description } </span>
</span>}
{plugin?.profile?.repo && <span className="d-flex flex-row align-items-center">
<a href={plugin?.profile?.repo} target="_blank" rel="noreferrer">
Make an issue</a>
</span>}
</div>
</header>) </header>)
} }

@ -22,7 +22,6 @@
.swapitHeader { .swapitHeader {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 16px 24px 15px;
justify-content: space-between; justify-content: space-between;
text-transform: uppercase; text-transform: uppercase;
} }

@ -19,6 +19,9 @@ function ActivePluginCard ({
<h6 className="remixui_displayName plugin-name"> <h6 className="remixui_displayName plugin-name">
<div> <div>
{ profile.displayName || profile.name } { profile.displayName || profile.name }
{ profile?.maintainedBy?.toLowerCase() == "remix" &&
<i aria-hidden="true" className="px-1 text-success fas fa-check" title="Maintained by Remix"></i>
}
{ profile.documentation && { profile.documentation &&
<a href={profile.documentation} className="px-1" title="link to documentation" target="_blank" rel="noreferrer"> <a href={profile.documentation} className="px-1" title="link to documentation" target="_blank" rel="noreferrer">
<i aria-hidden="true" className="fas fa-book"/> <i aria-hidden="true" className="fas fa-book"/>

@ -22,6 +22,9 @@ function InactivePluginCard ({
<h6 className="remixui_displayName plugin-name"> <h6 className="remixui_displayName plugin-name">
<div> <div>
{ profile.displayName || profile.name } { profile.displayName || profile.name }
{ profile?.maintainedBy?.toLowerCase() == "remix" &&
<i aria-hidden="true" className="px-1 text-success fas fa-check" title="Verified by Remix"></i>
}
{ profile.documentation && { profile.documentation &&
<a href={profile.documentation} className="px-1" title="link to documentation" target="_blank" rel="noreferrer"> <a href={profile.documentation} className="px-1" title="link to documentation" target="_blank" rel="noreferrer">
<i aria-hidden="true" className="fas fa-book"/> <i aria-hidden="true" className="fas fa-book"/>

@ -191,7 +191,7 @@ function LocalPluginForm ({ closeModal, visible, pluginManager }: LocalPluginFor
type="radio" type="radio"
name="location" name="location"
value="sidePanel" value="sidePanel"
id="none" id="localPluginRadioButtonsidePanelSidePanel"
data-id='localPluginRadioButtonsidePanel' data-id='localPluginRadioButtonsidePanel'
checked={location === 'sidePanel'} checked={location === 'sidePanel'}
onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} /> onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} />
@ -203,7 +203,7 @@ function LocalPluginForm ({ closeModal, visible, pluginManager }: LocalPluginFor
type="radio" type="radio"
name="location" name="location"
value="mainPanel" value="mainPanel"
id="none" id="localPluginRadioButtonsidePanelMainPanel"
data-id='localPluginRadioButtonmainPanel' data-id='localPluginRadioButtonmainPanel'
checked={location === 'mainPanel'} checked={location === 'mainPanel'}
onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} /> onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} />
@ -215,7 +215,7 @@ function LocalPluginForm ({ closeModal, visible, pluginManager }: LocalPluginFor
type="radio" type="radio"
name="location" name="location"
value="none" value="none"
id="none" id="localPluginRadioButtonsidePanelNone"
data-id='localPluginRadioButtonnone' data-id='localPluginRadioButtonnone'
checked={location === 'none'} checked={location === 'none'}
onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} /> onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} />

@ -135,6 +135,7 @@ export const createInstance = async (
args, args,
deployMode: DeployMode[]) => { deployMode: DeployMode[]) => {
const isProxyDeployment = (deployMode || []).find(mode => mode === 'Deploy with Proxy') const isProxyDeployment = (deployMode || []).find(mode => mode === 'Deploy with Proxy')
const isContractUpgrade = (deployMode || []).find(mode => mode === 'Upgrade with Proxy')
const statusCb = (msg: string) => { const statusCb = (msg: string) => {
const log = logBuilder(msg) const log = logBuilder(msg)
@ -160,7 +161,9 @@ export const createInstance = async (
if (isProxyDeployment) { if (isProxyDeployment) {
const initABI = contractObject.abi.find(abi => abi.name === 'initialize') const initABI = contractObject.abi.find(abi => abi.name === 'initialize')
plugin.call('openzeppelin-proxy', 'execute', addressToString(address), args, initABI, contractObject) plugin.call('openzeppelin-proxy', 'executeUUPSProxy', addressToString(address), args, initABI, contractObject)
} else if (isContractUpgrade) {
plugin.call('openzeppelin-proxy', 'executeUUPSContractUpgrade', args, addressToString(address), contractObject)
} }
} }
@ -176,7 +179,7 @@ export const createInstance = async (
if (selectedContract.isOverSizeLimit()) { if (selectedContract.isOverSizeLimit()) {
return dispatch(displayNotification('Contract code size over limit', isOverSizePrompt(), 'Force Send', 'Cancel', () => { return dispatch(displayNotification('Contract code size over limit', isOverSizePrompt(), 'Force Send', 'Cancel', () => {
deployContract(plugin, selectedContract, !isProxyDeployment ? args : '', contractMetadata, compilerContracts, { deployContract(plugin, selectedContract, !isProxyDeployment && !isContractUpgrade ? args : '', contractMetadata, compilerContracts, {
continueCb: (error, continueTxExecution, cancelCb) => { continueCb: (error, continueTxExecution, cancelCb) => {
continueHandler(dispatch, gasEstimationPrompt, error, continueTxExecution, cancelCb) continueHandler(dispatch, gasEstimationPrompt, error, continueTxExecution, cancelCb)
}, },
@ -192,7 +195,7 @@ export const createInstance = async (
return terminalLogger(plugin, log) return terminalLogger(plugin, log)
})) }))
} }
deployContract(plugin, selectedContract, !isProxyDeployment ? args : '', contractMetadata, compilerContracts, { deployContract(plugin, selectedContract, !isProxyDeployment && !isContractUpgrade ? args : '', contractMetadata, compilerContracts, {
continueCb: (error, continueTxExecution, cancelCb) => { continueCb: (error, continueTxExecution, cancelCb) => {
continueHandler(dispatch, gasEstimationPrompt, error, continueTxExecution, cancelCb) continueHandler(dispatch, gasEstimationPrompt, error, continueTxExecution, cancelCb)
}, },

@ -2,7 +2,7 @@ import { envChangeNotification } from "@remix-ui/helper"
import { RunTab } from "../types/run-tab" import { RunTab } from "../types/run-tab"
import { setExecutionContext, setFinalContext, updateAccountBalances } from "./account" import { setExecutionContext, setFinalContext, updateAccountBalances } from "./account"
import { addExternalProvider, addInstance, removeExternalProvider, setNetworkNameFromProvider } from "./actions" import { addExternalProvider, addInstance, removeExternalProvider, setNetworkNameFromProvider } from "./actions"
import { clearAllInstances, clearRecorderCount, fetchContractListSuccess, resetUdapp, setCurrentContract, setCurrentFile, setDeployOptions, setLoadType, setRecorderCount, setSendValue } from "./payload" import { addDeployOption, clearAllInstances, clearRecorderCount, fetchContractListSuccess, resetUdapp, setCurrentContract, setCurrentFile, setLoadType, setProxyEnvAddress, setRecorderCount, setSendValue } from "./payload"
import { CompilerAbstract } from '@remix-project/remix-solidity' import { CompilerAbstract } from '@remix-project/remix-solidity'
import * as ethJSUtil from 'ethereumjs-util' import * as ethJSUtil from 'ethereumjs-util'
import Web3 from 'web3' import Web3 from 'web3'
@ -24,7 +24,7 @@ export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch<any>) => {
plugin.blockchain.event.register('networkStatus', ({ error, network }) => { plugin.blockchain.event.register('networkStatus', ({ error, network }) => {
if (error) { if (error) {
const netUI = 'can\'t detect network ' const netUI = 'can\'t detect network'
setNetworkNameFromProvider(dispatch, netUI) setNetworkNameFromProvider(dispatch, netUI)
return return
@ -33,6 +33,8 @@ export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch<any>) => {
const netUI = (networkProvider() !== 'vm') ? `${network.name} (${network.id || '-'}) network` : 'VM' const netUI = (networkProvider() !== 'vm') ? `${network.name} (${network.id || '-'}) network` : 'VM'
setNetworkNameFromProvider(dispatch, netUI) setNetworkNameFromProvider(dispatch, netUI)
if (network.name === 'VM') dispatch(setProxyEnvAddress(plugin.config.get('vm/proxy')))
else dispatch(setProxyEnvAddress(plugin.config.get(`${network.name}/${network.currentFork}/${network.id}/proxy`)))
}) })
plugin.blockchain.event.register('addProvider', provider => addExternalProvider(dispatch, provider)) plugin.blockchain.event.register('addProvider', provider => addExternalProvider(dispatch, provider))
@ -106,11 +108,12 @@ const broadcastCompilationResult = async (plugin: RunTab, dispatch: React.Dispat
const isUpgradeable = await plugin.call('openzeppelin-proxy', 'isConcerned', data.sources[file] ? data.sources[file].ast : {}) const isUpgradeable = await plugin.call('openzeppelin-proxy', 'isConcerned', data.sources[file] ? data.sources[file].ast : {})
if (isUpgradeable) { if (isUpgradeable) {
const options = await plugin.call('openzeppelin-proxy', 'getDeployOptions', data.contracts[file]) const options = await plugin.call('openzeppelin-proxy', 'getProxyOptions', data, file)
dispatch(setDeployOptions({ options: [{ title: 'Deploy with Proxy', active: false }], initializeOptions: options })) dispatch(addDeployOption({ [file]: options }))
} else {
dispatch(addDeployOption({ [file]: {} }))
} }
else dispatch(setDeployOptions({} as any))
dispatch(fetchContractListSuccess({ [file]: contracts })) dispatch(fetchContractListSuccess({ [file]: contracts }))
dispatch(setCurrentFile(file)) dispatch(setCurrentFile(file))
// TODO: set current contract // TODO: set current contract

@ -1,6 +1,6 @@
import { ContractList } from '../reducers/runTab' import { ContractList } from '../reducers/runTab'
import { ContractData } from '@remix-project/core-plugin' import { ContractData } from '@remix-project/core-plugin'
import { ADD_DEPLOY_OPTION, ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_DEPLOY_OPTION, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_TX_FEE_CONTENT } from '../constants' import { ADD_DEPLOY_OPTION, ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_DEPLOY_OPTION, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_PROXY_ENV_ADDRESS, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_TX_FEE_CONTENT } from '../constants'
import { DeployMode, DeployOptions } from '../types' import { DeployMode, DeployOptions } from '../types'
export const fetchAccountsListRequest = () => { export const fetchAccountsListRequest = () => {
@ -280,21 +280,21 @@ export const resetUdapp = () => {
} }
} }
export const addDeployOption = (deployOption: DeployOptions) => { export const addDeployOption = (deployOption: { [file: string]: { [name: string]: DeployOptions } }) => {
return { return {
payload: deployOption, payload: deployOption,
type: ADD_DEPLOY_OPTION type: ADD_DEPLOY_OPTION
} }
} }
export const removeDeployOption = (title: DeployMode) => { export const removeDeployOption = (file: string) => {
return { return {
payload: title, payload: file,
type: REMOVE_DEPLOY_OPTION type: REMOVE_DEPLOY_OPTION
} }
} }
export const setDeployOptions = (deployOptions: DeployOptions) => { export const setDeployOptions = (deployOptions: { [file: string]: { [name: string]: DeployOptions } }) => {
return { return {
payload: deployOptions, payload: deployOptions,
type: SET_DEPLOY_OPTIONS type: SET_DEPLOY_OPTIONS
@ -307,3 +307,10 @@ export const setCurrentContract = (contractName: string) => {
type: SET_CURRENT_CONTRACT type: SET_CURRENT_CONTRACT
} }
} }
export const setProxyEnvAddress = (key: string) => {
return {
payload: key,
type: SET_PROXY_ENV_ADDRESS
}
}

@ -22,7 +22,7 @@ export function AccountUI (props: AccountProps) {
case 'injected': case 'injected':
setPlusOpt({ setPlusOpt({
classList: 'udapp_disableMouseEvents', classList: 'udapp_disableMouseEvents',
title: "Unfortunately it's not possible to create an account using injected web3. Please create the account directly from your provider (i.e metamask or other of the same type)." title: "Unfortunately it's not possible to create an account using injected provider. Please create the account directly from your provider (i.e metamask or other of the same type)."
}) })
break break

@ -4,6 +4,7 @@ import { ContractDropdownProps, DeployMode } from '../types'
import { ContractData, FuncABI } from '@remix-project/core-plugin' import { ContractData, FuncABI } from '@remix-project/core-plugin'
import * as ethJSUtil from 'ethereumjs-util' import * as ethJSUtil from 'ethereumjs-util'
import { ContractGUI } from './contractGUI' import { ContractGUI } from './contractGUI'
import { deployWithProxyMsg, upgradeWithProxyMsg } from '@remix-ui/helper'
export function ContractDropdownUI (props: ContractDropdownProps) { export function ContractDropdownUI (props: ContractDropdownProps) {
const [abiLabel, setAbiLabel] = useState<{ const [abiLabel, setAbiLabel] = useState<{
@ -27,7 +28,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
const [constructorInterface, setConstructorInterface] = useState<FuncABI>(null) const [constructorInterface, setConstructorInterface] = useState<FuncABI>(null)
const [constructorInputs, setConstructorInputs] = useState(null) const [constructorInputs, setConstructorInputs] = useState(null)
const contractsRef = useRef<HTMLSelectElement>(null) const contractsRef = useRef<HTMLSelectElement>(null)
const { contractList, loadType, currentFile, currentContract, compilationCount, deployOptions } = props.contracts const { contractList, loadType, currentFile, currentContract, compilationCount, deployOptions, proxyKey } = props.contracts
useEffect(() => { useEffect(() => {
enableAtAddress(false) enableAtAddress(false)
@ -155,7 +156,17 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
if (selectedContract.bytecodeObject.length === 0) { if (selectedContract.bytecodeObject.length === 0) {
return props.modal('Alert', 'This contract may be abstract, it may not implement an abstract parent\'s methods completely or it may not invoke an inherited contract\'s constructor correctly.', 'OK', () => {}) return props.modal('Alert', 'This contract may be abstract, it may not implement an abstract parent\'s methods completely or it may not invoke an inherited contract\'s constructor correctly.', 'OK', () => {})
} }
props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode) if ((selectedContract.name !== currentContract) && (selectedContract.name === 'ERC1967Proxy')) selectedContract.name = currentContract
const isProxyDeployment = (deployMode || []).find(mode => mode === 'Deploy with Proxy')
const isContractUpgrade = (deployMode || []).find(mode => mode === 'Upgrade with Proxy')
if (isProxyDeployment || isContractUpgrade) {
props.modal('ERC1967', isProxyDeployment ? deployWithProxyMsg() : upgradeWithProxyMsg(), 'Proceed', () => {
props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode)
}, 'Cancel', () => {})
} else {
props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode)
}
} }
const atAddressChanged = (event) => { const atAddressChanged = (event) => {
@ -232,14 +243,15 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
<ContractGUI <ContractGUI
title='Deploy' title='Deploy'
isDeploy={true} isDeploy={true}
deployOption={deployOptions.options} deployOption={deployOptions[currentFile] && deployOptions[currentFile][currentContract] ? deployOptions[currentFile][currentContract].options : null}
initializerOptions={deployOptions.initializeOptions ? deployOptions.initializeOptions[currentContract] : null} initializerOptions={deployOptions[currentFile] && deployOptions[currentFile][currentContract] ? deployOptions[currentFile][currentContract].initializeOptions : null}
funcABI={constructorInterface} funcABI={constructorInterface}
clickCallBack={clickCallback} clickCallBack={clickCallback}
inputs={constructorInputs} inputs={constructorInputs}
widthClass='w-50' widthClass='w-50'
evmBC={loadedContractData.bytecodeObject} evmBC={loadedContractData.bytecodeObject}
lookupOnly={false} lookupOnly={false}
savedProxyAddress={proxyKey}
/> />
<div className="d-flex py-1 align-items-center custom-control custom-checkbox"> <div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input <input

@ -3,8 +3,6 @@ import React, { useEffect, useRef, useState } from 'react'
import * as remixLib from '@remix-project/remix-lib' import * as remixLib from '@remix-project/remix-lib'
import { ContractGUIProps } from '../types' import { ContractGUIProps } from '../types'
import { CopyToClipboard } from '@remix-ui/clipboard' import { CopyToClipboard } from '@remix-ui/clipboard'
import { MultiDeployInput } from './multiDeployInput'
import { DeployInput } from './deployInput'
const txFormat = remixLib.execution.txFormat const txFormat = remixLib.execution.txFormat
export function ContractGUI (props: ContractGUIProps) { export function ContractGUI (props: ContractGUIProps) {
@ -15,20 +13,15 @@ export function ContractGUI (props: ContractGUIProps) {
title: string, title: string,
content: string, content: string,
classList: string, classList: string,
dataId: string, dataId: string
widthClass: string }>({ title: '', content: '', classList: '', dataId: '' })
}>({ title: '', content: '', classList: '', dataId: '', widthClass: '' }) const [toggleDeployProxy, setToggleDeployProxy] = useState<boolean>(false)
const [selectedDeployIndex, setSelectedDeployIndex] = useState<number>(null) const [toggleUpgradeImp, setToggleUpgradeImp] = useState<boolean>(false)
const [showOptions, setShowOptions] = useState<boolean>(false) const [deployState, setDeployState] = useState<{ deploy: boolean, upgrade: boolean }>({ deploy: false, upgrade: false })
const [hasArgs, setHasArgs] = useState<boolean>(false) const [useLastProxy, setUseLastProxy] = useState<boolean>(false)
const [isMultiField, setIsMultiField] = useState<boolean>(false) const [proxyAddress, setProxyAddress] = useState<string>('')
const [deployInputs, setDeployInputs] = useState<{
internalType?: string,
name: string,
type: string
}[]>([])
const [deployPlaceholder, setDeployPlaceholder] = useState<string>('')
const multiFields = useRef<Array<HTMLInputElement | null>>([]) const multiFields = useRef<Array<HTMLInputElement | null>>([])
const initializeFields = useRef<Array<HTMLInputElement | null>>([])
const basicInputRef = useRef<HTMLInputElement>() const basicInputRef = useRef<HTMLInputElement>()
useEffect(() => { useEffect(() => {
@ -41,7 +34,7 @@ export function ContractGUI (props: ContractGUIProps) {
} }
setBasicInput('') setBasicInput('')
// we have the reset the fields before reseting the previous references. // we have the reset the fields before reseting the previous references.
if (basicInputRef.current) basicInputRef.current.value = '' basicInputRef.current.value = ''
multiFields.current.filter((el) => el !== null && el !== undefined).forEach((el) => el.value = '') multiFields.current.filter((el) => el !== null && el !== undefined).forEach((el) => el.value = '')
multiFields.current = [] multiFields.current = []
}, [props.title, props.funcABI]) }, [props.title, props.funcABI])
@ -53,8 +46,7 @@ export function ContractGUI (props: ContractGUIProps) {
title: title + ' - call', title: title + ' - call',
content: 'call', content: 'call',
classList: 'btn-info', classList: 'btn-info',
dataId: title + ' - call', dataId: title + ' - call'
widthClass: props.widthClass
}) })
} else if (props.funcABI.stateMutability === 'payable' || props.funcABI.payable) { } else if (props.funcABI.stateMutability === 'payable' || props.funcABI.payable) {
// // transact. stateMutability = payable // // transact. stateMutability = payable
@ -62,8 +54,7 @@ export function ContractGUI (props: ContractGUIProps) {
title: title + ' - transact (payable)', title: title + ' - transact (payable)',
content: 'transact', content: 'transact',
classList: 'btn-danger', classList: 'btn-danger',
dataId: title + ' - transact (payable)', dataId: title + ' - transact (payable)'
widthClass: props.widthClass
}) })
} else { } else {
// // transact. stateMutability = nonpayable // // transact. stateMutability = nonpayable
@ -71,59 +62,13 @@ export function ContractGUI (props: ContractGUIProps) {
title: title + ' - transact (not payable)', title: title + ' - transact (not payable)',
content: 'transact', content: 'transact',
classList: 'btn-warning', classList: 'btn-warning',
dataId: title + ' - transact (not payable)', dataId: title + ' - transact (not payable)'
widthClass: props.widthClass
}) })
} }
}, [props.lookupOnly, props.funcABI, title]) }, [props.lookupOnly, props.funcABI, title])
useEffect(() => { const getContentOnCTC = () => {
if (props.deployOption && props.deployOption[selectedDeployIndex]) { const multiString = getMultiValsString(multiFields.current)
if (props.deployOption[selectedDeployIndex].title === 'Deploy with Proxy') {
if (props.initializerOptions) {
setDeployInputs(props.initializerOptions.inputs.inputs)
setDeployPlaceholder(props.initializerOptions.initializeInputs)
setHasArgs(true)
if (props.initializerOptions.inputs.inputs.length > 1) setIsMultiField(true)
else setIsMultiField(false)
} else {
setDeployInputs([])
setDeployPlaceholder('')
setHasArgs(false)
setIsMultiField(false)
}
} else {
if (props.funcABI) {
setDeployInputs(props.funcABI.inputs)
setDeployPlaceholder(props.inputs)
setHasArgs(true)
if (props.funcABI.inputs.length > 1) setIsMultiField(true)
else setIsMultiField(false)
} else {
setDeployInputs([])
setDeployPlaceholder('')
setHasArgs(false)
setIsMultiField(false)
}
}
} else {
if (props.funcABI) {
setDeployInputs(props.funcABI.inputs)
setDeployPlaceholder(props.inputs)
setHasArgs(true)
if (props.funcABI.inputs.length > 1) setIsMultiField(true)
else setIsMultiField(false)
} else {
setDeployInputs([])
setDeployPlaceholder('')
setHasArgs(false)
setIsMultiField(false)
}
}
}, [selectedDeployIndex, props.funcABI, props.initializerOptions])
const getContentOnCTC = (fields: HTMLInputElement[]) => {
const multiString = getMultiValsString(fields)
// copy-to-clipboard icon is only visible for method requiring input params // copy-to-clipboard icon is only visible for method requiring input params
if (!multiString) { if (!multiString) {
return 'cannot encode empty arguments' return 'cannot encode empty arguments'
@ -191,8 +136,7 @@ export function ContractGUI (props: ContractGUIProps) {
if (inputString) { if (inputString) {
inputString = inputString.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number inputString = inputString.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number
inputString = inputString.replace(/(^|,\s+|,)(0[xX][0-9a-fA-F]+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted hex string by quoted hex string inputString = inputString.replace(/(^|,\s+|,)(0[xX][0-9a-fA-F]+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted hex string by quoted hex string
inputString = JSON.stringify([inputString]) const inputJSON = JSON.parse('[' + inputString + ']')
const inputJSON = JSON.parse(inputString)
const multiInputs = multiFields.current const multiInputs = multiFields.current
for (let k = 0; k < multiInputs.length; k++) { for (let k = 0; k < multiInputs.length; k++) {
@ -204,9 +148,15 @@ export function ContractGUI (props: ContractGUIProps) {
} }
const handleActionClick = () => { const handleActionClick = () => {
const deployMode = selectedDeployIndex !== null ? [props.deployOption[selectedDeployIndex].title] : [] if (deployState.deploy) {
const proxyInitializeString = getMultiValsString(initializeFields.current)
props.clickCallBack(props.funcABI.inputs, basicInput, deployMode) props.clickCallBack(props.initializerOptions.inputs.inputs, proxyInitializeString, ['Deploy with Proxy'])
} else if (deployState.upgrade) {
props.clickCallBack(props.funcABI.inputs, proxyAddress, ['Upgrade with Proxy'])
} else {
props.clickCallBack(props.funcABI.inputs, basicInput)
}
} }
const handleBasicInput = (e) => { const handleBasicInput = (e) => {
@ -215,90 +165,197 @@ export function ContractGUI (props: ContractGUIProps) {
setBasicInput(value) setBasicInput(value)
} }
const handleMultiValsSubmit = (fields: HTMLInputElement[]) => { const handleExpandMultiClick = () => {
const valsString = getMultiValsString(fields) const valsString = getMultiValsString(multiFields.current)
const deployMode = selectedDeployIndex !== null ? [props.deployOption[selectedDeployIndex].title] : []
if (valsString) { if (valsString) {
props.clickCallBack(props.funcABI.inputs, valsString, deployMode) props.clickCallBack(props.funcABI.inputs, valsString)
} else { } else {
props.clickCallBack(props.funcABI.inputs, '', deployMode) props.clickCallBack(props.funcABI.inputs, '')
} }
} }
const setSelectedDeploy = (index: number) => { const handleToggleDeployProxy = () => {
setSelectedDeployIndex(index !== selectedDeployIndex ? index : null) setToggleDeployProxy(!toggleDeployProxy)
if (basicInputRef.current) basicInputRef.current.value = '' }
setBasicInput('')
const handleDeployProxySelect = (e) => {
const value = e.target.checked
if (value) setToggleUpgradeImp(false)
setToggleDeployProxy(value)
setDeployState({ upgrade: false, deploy: value })
}
const handleToggleUpgradeImp = () => {
setToggleUpgradeImp(!toggleUpgradeImp)
}
const handleUpgradeImpSelect = (e) => {
const value = e.target.checked
setToggleUpgradeImp(value)
if (value) {
setToggleDeployProxy(false)
if (useLastProxy) setProxyAddress(props.savedProxyAddress)
}
setDeployState({ deploy: false, upgrade: value })
} }
const toggleOptions = () => { const handleUseLastProxySelect = (e) => {
setShowOptions(!showOptions) const value = e.target.checked
const address = props.savedProxyAddress
setUseLastProxy(value)
setProxyAddress(address || '')
}
const handleSetProxyAddress = (e) => {
const value = e.target.value
setProxyAddress(value)
} }
return ( return (
<div className={`udapp_contractProperty ${hasArgs ? 'udapp_hasArgs' : ''}`}> <div className={`udapp_contractProperty ${(props.funcABI.inputs && props.funcABI.inputs.length > 0) || (props.funcABI.type === 'fallback') || (props.funcABI.type === 'receive') ? 'udapp_hasArgs' : ''}`}>
{ <div className="udapp_contractActionsContainerSingle pt-2" style={{ display: toggleContainer ? 'none' : 'flex' }}>
props.isDeploy ? !isMultiField ? <button onClick={handleActionClick} title={buttonOptions.title} className={`udapp_instanceButton ${props.widthClass} btn btn-sm ${buttonOptions.classList}`} data-id={buttonOptions.dataId}>{title}</button>
<DeployInput <input
buttonOptions={buttonOptions} className="form-control"
funcABI={props.initializerOptions ? props.initializerOptions.inputs : props.funcABI} data-id={props.funcABI.type === 'fallback' || props.funcABI.type === 'receive' ? `'(${props.funcABI.type}')` : 'multiParamManagerBasicInputField'}
inputs={deployPlaceholder} placeholder={props.inputs}
handleBasicInput={handleBasicInput} title={props.funcABI.type === 'fallback' || props.funcABI.type === 'receive' ? `'(${props.funcABI.type}')` : props.inputs}
basicInputRef={basicInputRef} onChange={handleBasicInput}
selectedIndex={selectedDeployIndex} ref={basicInputRef}
setSelectedIndex={setSelectedDeploy} style={{ visibility: !((props.funcABI.inputs && props.funcABI.inputs.length > 0) || (props.funcABI.type === 'fallback') || (props.funcABI.type === 'receive')) ? 'hidden' : 'visible' }} />
handleActionClick={handleActionClick} <i
deployOptions={props.deployOption} className="fas fa-angle-down udapp_methCaret"
/> : <MultiDeployInput onClick={switchMethodViewOn}
buttonOptions={buttonOptions} title={title}
selectedIndex={selectedDeployIndex} style={{ visibility: !(props.funcABI.inputs && props.funcABI.inputs.length > 0) ? 'hidden' : 'visible' }}></i>
setSelectedIndex={setSelectedDeploy} </div>
handleMultiValsSubmit={handleMultiValsSubmit} <div className="udapp_contractActionsContainerMulti" style={{ display: toggleContainer ? 'flex' : 'none' }}>
inputs={deployInputs} <div className="udapp_contractActionsContainerMultiInner text-dark">
getMultiValsString={getMultiValsString} <div onClick={switchMethodViewOff} className="udapp_multiHeader">
deployOptions={props.deployOption} <div className="udapp_multiTitle run-instance-multi-title">{title}</div>
/> : <i className='fas fa-angle-up udapp_methCaret'></i>
<> </div>
<div className="udapp_contractActionsContainerSingle pt-2" style={{ display: toggleContainer ? 'none' : 'flex' }}> <div>
<button onClick={handleActionClick} title={buttonOptions.title} className={`udapp_instanceButton ${props.widthClass} btn btn-sm ${buttonOptions.classList}`} data-id={buttonOptions.dataId}>{title}</button> {props.funcABI.inputs.map((inp, index) => {
<input return (
className="form-control" <div className="udapp_multiArg" key={index}>
data-id={props.funcABI.type === 'fallback' || props.funcABI.type === 'receive' ? `'(${props.funcABI.type}')` : 'multiParamManagerBasicInputField'} <label htmlFor={inp.name}> {inp.name}: </label>
placeholder={props.inputs} <input ref={el => { multiFields.current[index] = el }} className="form-control" placeholder={inp.type} title={inp.name} data-id={`multiParamManagerInput${inp.name}`} />
title={props.funcABI.type === 'fallback' || props.funcABI.type === 'receive' ? `'(${props.funcABI.type}')` : props.inputs} </div>)
onChange={handleBasicInput} })}
ref={basicInputRef} </div>
style={{ visibility: !((props.funcABI.inputs && props.funcABI.inputs.length > 0) || (props.funcABI.type === 'fallback') || (props.funcABI.type === 'receive')) ? 'hidden' : 'visible' }} /> <div className="udapp_group udapp_multiArg">
<i <CopyToClipboard tip='Encode values of input fields & copy to clipboard' icon='fa-clipboard' direction={'bottom'} getContent={getContentOnCTC} />
className="fas fa-angle-down udapp_methCaret" <button onClick={handleExpandMultiClick} title={buttonOptions.title} data-id={buttonOptions.dataId} className={`udapp_instanceButton ${buttonOptions.classList}`}>{ buttonOptions.content }</button>
onClick={switchMethodViewOn} </div>
title={title}
style={{ visibility: !(props.funcABI.inputs && props.funcABI.inputs.length > 0) ? 'hidden' : 'visible' }}></i>
</div> </div>
<div className="udapp_contractActionsContainerMulti" style={{ display: toggleContainer ? 'flex' : 'none' }}> </div>
<div className="udapp_contractActionsContainerMultiInner text-dark"> { props.deployOption && (props.deployOption || []).length > 0 ?
<div onClick={switchMethodViewOff} className="udapp_multiHeader"> <>
<div className="udapp_multiTitle run-instance-multi-title">{title}</div> <div className='d-flex justify-content-between'>
<i className='fas fa-angle-up udapp_methCaret'></i> <div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input
id="deployWithProxy"
data-id="contractGUIDeployWithProxy"
className="form-check-input custom-control-input"
type="checkbox"
onChange={handleDeployProxySelect}
checked={deployState.deploy}
/>
<label
htmlFor="deployWithProxy"
data-id="contractGUIDeployWithProxyLabel"
className="m-0 form-check-label custom-control-label udapp_checkboxAlign"
title="An ERC1967 proxy contract will be deployed along with the selected implementation contract."
>
Deploy With Proxy
</label>
</div> </div>
<div> <div>
{props.funcABI.inputs.map((inp, index) => { {
return ( props.initializerOptions && props.initializerOptions.initializeInputs ?
<div className="udapp_multiArg" key={index}> <span onClick={handleToggleDeployProxy}>
<label htmlFor={inp.name}> {inp.name}: </label> <i className={!toggleDeployProxy ? 'fas fa-angle-right pt-2' : 'fas fa-angle-down'} aria-hidden="true"></i>
<input ref={el => { multiFields.current[index] = el }} className="form-control" placeholder={inp.type} title={inp.name} data-id={`multiParamManagerInput${inp.name}`} /> </span> : null
</div>) }
})}
</div> </div>
<div className="udapp_group udapp_multiArg"> </div>
<CopyToClipboard tip='Encode values of input fields & copy to clipboard' icon='fa-clipboard' direction={'bottom'} getContent={() => getContentOnCTC(multiFields.current)} /> {
<button onClick={() => handleMultiValsSubmit(multiFields.current)} title={buttonOptions.title} data-id={buttonOptions.dataId} className={`udapp_instanceButton ${buttonOptions.classList}`}>{ buttonOptions.content }</button> props.initializerOptions && props.initializerOptions.initializeInputs ?
<div className={`pl-4 flex-column ${toggleDeployProxy ? "d-flex" : "d-none"}`}>
<div className={`flex-column 'd-flex'}`}>{
props.initializerOptions.inputs.inputs.map((inp, index) => {
return (
<div className="mb-2" key={index}>
<label className='mt-2 text-left d-block' htmlFor={inp.name}> {inp.name}: </label>
<input ref={el => { initializeFields.current[index] = el }} style={{ height: 32 }} className="form-control udapp_input" placeholder={inp.type} title={inp.name} />
</div>
)})
}
</div>
</div> : null
}
<div className='d-flex justify-content-between'>
<div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input
id="upgradeImplementation"
data-id="contractGUIUpgradeImplementation"
className="form-check-input custom-control-input"
type="checkbox"
onChange={handleUpgradeImpSelect}
checked={deployState.upgrade}
/>
<label
htmlFor="upgradeImplementation"
data-id="contractGUIUpgradeImplementationLabel"
className="m-0 form-check-label custom-control-label udapp_checkboxAlign"
title="The implemetation address will be updated to a new address in the proxy contract."
>
Upgrade With Proxy
</label>
</div> </div>
<span onClick={handleToggleUpgradeImp}>
<i className={!toggleUpgradeImp ? 'fas fa-angle-right pt-2' : 'fas fa-angle-down'} aria-hidden="true"></i>
</span>
</div> </div>
</div> <div className={`pl-4 flex-column ${toggleUpgradeImp ? "d-flex" : "d-none"}`}>
</> <div className={`flex-column 'd-flex'}`}>
} <div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input
id="proxyAddress"
data-id="contractGUIProxyAddress"
className="form-check-input custom-control-input"
type="checkbox"
onChange={handleUseLastProxySelect}
checked={useLastProxy}
/>
<label
htmlFor="proxyAddress"
data-id="contractGUIProxyAddressLabel"
className="m-0 form-check-label custom-control-label udapp_checkboxAlign"
title="Select this option to use the last deployed ERC1967 contract on the current network."
style={{ fontSize: 12 }}
>
Use last deployed ERC1967 contract
</label>
</div>
{
!useLastProxy ?
<div className="mb-2">
<label className='mt-2 text-left d-block'>Proxy Address: </label>
<input style={{ height: 32 }} className="form-control udapp_input" placeholder='proxy address' title='Enter previously deployed proxy address on the selected network' onChange={handleSetProxyAddress} />
</div> :
<span className='text-capitalize' style={{ fontSize: '.8em' }}>{ proxyAddress || 'No proxy address available' }</span>
}
</div>
</div>
</> : null
}
</div> </div>
) )
} }

@ -1,11 +1,16 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React from 'react' import React from 'react'
import { EnvironmentProps } from '../types' import { EnvironmentProps } from '../types'
import { Dropdown } from 'react-bootstrap'
import { CustomMenu, CustomToggle } from '@remix-ui/helper'
import { OverlayTrigger, Tooltip } from 'react-bootstrap' // eslint-disable-line
export function EnvironmentUI (props: EnvironmentProps) { export function EnvironmentUI (props: EnvironmentProps) {
const handleChangeExEnv = (env: string) => { const handleChangeExEnv = (env: string) => {
const provider = props.providers.providerList.find(exEnv => exEnv.value === env) const provider = props.providers.providerList.find(exEnv => exEnv.value === env)
const fork = provider.fork // can be undefined if connected to an external source (web3 provider / injected) const fork = provider.fork // can be undefined if connected to an external source (External Http Provider / injected)
let context = provider.value let context = provider.value
context = context.startsWith('vm') ? 'vm' : context context = context.startsWith('vm') ? 'vm' : context
@ -13,22 +18,55 @@ export function EnvironmentUI (props: EnvironmentProps) {
props.setExecutionContext({ context, fork }) props.setExecutionContext({ context, fork })
} }
const currentProvider = props.providers.providerList.find(exEnv => exEnv.value === props.selectedEnv)
const bridges = {
'Optimism Provider': 'https://www.optimism.io/apps/bridges',
'Arbitrum One Provider': 'https://bridge.arbitrum.io/'
}
const isL2 = (provider) => provider && (provider.value === 'Optimism Provider' || provider.value === 'Arbitrum One Provider')
return ( return (
<div className="udapp_crow"> <div className="udapp_crow">
<label id="selectExEnv" className="udapp_settingsLabel"> <label id="selectExEnv" className="udapp_settingsLabel">
Environment Environment <OverlayTrigger placement={'right'} overlay={
<Tooltip className="text-nowrap" id="info-recorder">
<span>Open chainlist and add a new provider for the chain you want to interact to.</span>
</Tooltip>
}>
<a href='https://chainlist.org/' target='_blank'><i style={{ fontSize: 'medium' }} className={'ml-2 fad fa-plug'} aria-hidden="true"></i></a>
</OverlayTrigger>
</label> </label>
<div className="udapp_environment"> <div className="udapp_environment">
<select id="selectExEnvOptions" data-id="settingsSelectEnvOptions" className="form-control udapp_select custom-select" value={props.selectedEnv || ''} onChange={(e) => { handleChangeExEnv(e.target.value) }}>
{ <Dropdown id="selectExEnvOptions" data-id="settingsSelectEnvOptions" className='udapp_selectExEnvOptions'>
props.providers.providerList.map((provider, index) => <Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components" className="btn btn-light btn-block w-100 d-inline-block border border-dark form-control" icon={null}>
<option id={provider.id} key={index} data-id={provider.dataId} { isL2(currentProvider) && 'L2 - '}
title={provider.title} { currentProvider && currentProvider.content }
value={provider.value}> { provider.content } { currentProvider && bridges[currentProvider.value] && <OverlayTrigger placement={'right'} overlay={
</option> <Tooltip className="text-nowrap" id="info-recorder">
) <span>Click to open a bridge for converting L1 mainnet ETH to the selected network currency.</span>
} </Tooltip>
</select> }>
<i style={{ fontSize: 'medium' }} className={'ml-2 fal fa-plug'} aria-hidden="true" onClick={() => { window.open(bridges[currentProvider.value], '_blank') }}></i>
</OverlayTrigger>}
</Dropdown.Toggle>
<Dropdown.Menu as={CustomMenu} className='w-100 custom-dropdown-items' data-id="custom-dropdown-items" >
{
props.providers.providerList.map(({ content, value }, index) => (
<Dropdown.Item
key={index}
onClick={() => {
handleChangeExEnv(value)
}}
data-id={`dropdown-item-${value}`}
>
<span className="pl-3">{ isL2({ value }) && 'L2 - ' }{ content }</span>
</Dropdown.Item>
))
}
</Dropdown.Menu>
</Dropdown>
<a href="https://remix-ide.readthedocs.io/en/latest/run.html#environment" target="_blank" rel="noreferrer"><i className="udapp_infoDeployAction ml-2 fas fa-info" title="Click for docs about Environment"></i></a> <a href="https://remix-ide.readthedocs.io/en/latest/run.html#environment" target="_blank" rel="noreferrer"><i className="udapp_infoDeployAction ml-2 fas fa-info" title="Click for docs about Environment"></i></a>
</div> </div>
</div> </div>

@ -27,7 +27,7 @@ export function RecorderUI (props: RecorderProps) {
<div className="ml-2 mb-2 badge badge-pill badge-primary" title="The number of recorded transactions">{props.count}</div> <div className="ml-2 mb-2 badge badge-pill badge-primary" title="The number of recorded transactions">{props.count}</div>
<OverlayTrigger placement={'right'} overlay={ <OverlayTrigger placement={'right'} overlay={
<Tooltip className="text-nowrap" id="info-recorder"> <Tooltip className="text-nowrap" id="info-recorder">
<span>Save transactions (deployed contracts and function executions) and replay them in another environment. <br/> e.g Transactions created in Javascript VM can be replayed in the Injected Web3. <span>Save transactions (deployed contracts and function executions) and replay them in another environment. <br/> e.g Transactions created in Remix VM can be replayed in the Injected Provider.
</span> </span>
</Tooltip> </Tooltip>
}> }>

@ -44,4 +44,5 @@ export const RESET_STATE = 'RESET_STATE'
export const ADD_DEPLOY_OPTION = 'ADD_DEPLOY_OPTION' export const ADD_DEPLOY_OPTION = 'ADD_DEPLOY_OPTION'
export const REMOVE_DEPLOY_OPTION = 'REMOVE_DEPLOY_OPTION' export const REMOVE_DEPLOY_OPTION = 'REMOVE_DEPLOY_OPTION'
export const SET_DEPLOY_OPTIONS = 'SET_DEPLOY_OPTIONS' export const SET_DEPLOY_OPTIONS = 'SET_DEPLOY_OPTIONS'
export const SET_CURRENT_CONTRACT = 'SET_CURRENT_CONTRACT' export const SET_CURRENT_CONTRACT = 'SET_CURRENT_CONTRACT'
export const SET_PROXY_ENV_ADDRESS = 'SET_PROXY_ENV_ADDRESS'

@ -529,4 +529,7 @@
text-decoration: none; text-decoration: none;
background-color: #007aa6; background-color: #007aa6;
} }
.udapp_selectExEnvOptions {
width: 100%;
}

@ -1,8 +1,7 @@
import { CompilerAbstract } from '@remix-project/remix-solidity-ts' import { CompilerAbstract } from '@remix-project/remix-solidity-ts'
import { ContractData } from '@remix-project/core-plugin' import { ContractData } from '@remix-project/core-plugin'
import { DeployMode, DeployOption, DeployOptions } from '../types' import { DeployOptions } from '../types'
import { ADD_DEPLOY_OPTION, ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, FETCH_PROVIDER_LIST_FAILED, FETCH_PROVIDER_LIST_REQUEST, FETCH_PROVIDER_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_DEPLOY_OPTION, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_TX_FEE_CONTENT } from '../constants' import { ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, FETCH_PROVIDER_LIST_FAILED, FETCH_PROVIDER_LIST_REQUEST, FETCH_PROVIDER_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_TX_FEE_CONTENT, SET_PROXY_ENV_ADDRESS, ADD_DEPLOY_OPTION, REMOVE_DEPLOY_OPTION } from '../constants'
import Web3 from 'web3'
declare const window: any declare const window: any
interface Action { interface Action {
@ -67,7 +66,8 @@ export interface RunTabState {
compiler: CompilerAbstract compiler: CompilerAbstract
}[] }[]
}, },
deployOptions: DeployOptions deployOptions: { [file: string]: { [name: string]: DeployOptions } },
proxyKey: string,
loadType: 'abi' | 'sol' | 'other' loadType: 'abi' | 'sol' | 'other'
currentFile: string, currentFile: string,
currentContract: string, currentContract: string,
@ -155,6 +155,7 @@ export const runTabInitialState: RunTabState = {
contracts: { contracts: {
contractList: {}, contractList: {},
deployOptions: {} as any, deployOptions: {} as any,
proxyKey: '',
loadType: 'other', loadType: 'other',
currentFile: '', currentFile: '',
currentContract: '', currentContract: '',
@ -680,39 +681,33 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
} }
case ADD_DEPLOY_OPTION: { case ADD_DEPLOY_OPTION: {
const payload: { title: DeployMode, active: boolean } = action.payload const payload: { [file: string]: { [name: string]: DeployOptions } } = action.payload
return { return {
...state, ...state,
contracts: { contracts: {
...state.contracts, ...state.contracts,
deployOptions: { deployOptions: {...state.contracts.deployOptions, ...payload }
...state.contracts.deployOptions,
options: [...state.contracts.deployOptions.options, payload]
}
} }
} }
} }
case REMOVE_DEPLOY_OPTION: { case REMOVE_DEPLOY_OPTION: {
const payload: string = action.payload const payload: string = action.payload
const options = state.contracts.deployOptions.options.filter(val => val.title !== payload) const options = state.contracts.deployOptions
delete options[payload]
return { return {
...state, ...state,
contracts: { contracts: {
...state.contracts, ...state.contracts,
deployOptions: { deployOptions: options
...state.contracts.deployOptions,
options
}
} }
} }
} }
case SET_DEPLOY_OPTIONS: { case SET_DEPLOY_OPTIONS: {
const payload: DeployOptions = action.payload const payload: { [file: string]: { [name: string]: DeployOptions } } = action.payload
return { return {
...state, ...state,
@ -723,6 +718,18 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
} }
} }
case SET_PROXY_ENV_ADDRESS: {
const payload: string = action.payload
return {
...state,
contracts: {
...state.contracts,
proxyKey: payload
}
}
}
default: default:
return state return state
} }

@ -128,7 +128,8 @@ export interface ContractDropdownProps {
exEnvironment: string, exEnvironment: string,
contracts: { contracts: {
contractList: ContractList, contractList: ContractList,
deployOptions: DeployOptions, deployOptions: { [file: string]: { [name: string]: DeployOptions } },
proxyKey: string,
loadType: 'abi' | 'sol' | 'other', loadType: 'abi' | 'sol' | 'other',
currentFile: string, currentFile: string,
currentContract: string, currentContract: string,
@ -219,7 +220,7 @@ export interface Modal {
cancelFn: () => void cancelFn: () => void
} }
export type DeployMode = 'Deploy with Proxy' | 'Upgrade Proxy' export type DeployMode = 'Deploy with Proxy' | 'Upgrade with Proxy'
export type DeployOption = { export type DeployOption = {
initializeInputs: string, initializeInputs: string,
@ -238,10 +239,8 @@ export type DeployOption = {
} }
} }
export interface DeployOptions { export interface DeployOptions {
initializeOptions: { initializeOptions: DeployOption,
[key: string]: DeployOption options: { title: DeployMode, active: boolean }[]
},
options: { title: DeployMode, active: boolean }[],
} }
export interface ContractGUIProps { export interface ContractGUIProps {
@ -255,7 +254,8 @@ export interface ContractGUIProps {
disabled?: boolean, disabled?: boolean,
isDeploy?: boolean, isDeploy?: boolean,
deployOption?: { title: DeployMode, active: boolean }[], deployOption?: { title: DeployMode, active: boolean }[],
initializerOptions?: DeployOption initializerOptions?: DeployOption,
savedProxyAddress?: string
} }
export interface MainnetProps { export interface MainnetProps {
network: Network, network: Network,

@ -0,0 +1,81 @@
import { CopyToClipboard } from '@remix-ui/clipboard'
import React, { useEffect, useState } from 'react'
import { GithubSettingsProps } from '../types'
export function GithubSettings (props: GithubSettingsProps) {
const [githubToken, setGithubToken] = useState<string>("")
const [githubUserName, setGithubUsername] = useState<string>("")
const [githubEmail, setGithubEmail] = useState<string>("")
useEffect(() => {
if (props.config) {
const githubToken = props.config.get('settings/gist-access-token')
const githubUserName = props.config.get('settings/github-user-name')
const githubEmail = props.config.get('settings/github-email')
setGithubToken(githubToken)
setGithubUsername(githubUserName)
setGithubEmail(githubEmail)
}
}, [props.config])
const handleChangeTokenState = (event) => {
setGithubToken(event.target.value)
}
const handleChangeUserNameState = (event) => {
setGithubUsername(event.target.value)
}
const handleChangeEmailState = (event) => {
setGithubEmail(event.target.value)
}
// api key settings
const saveGithubToken = () => {
props.saveTokenToast(githubToken, githubUserName, githubEmail)
}
const removeToken = () => {
setGithubToken('')
setGithubUsername('')
setGithubEmail('')
props.removeTokenToast()
}
return (
<div className="border-top">
<div className="card-body pt-3 pb-2">
<h6 className="card-title">GitHub Credentials</h6>
<p className="mb-1">Manage your GitHub credentials used to publish to Gist and retrieve GitHub contents.</p>
<p className="">Go to github token page (link below) to create a new token and save it in Remix. Make sure this token has only \'create gist\' permission.</p>
<p className="mb-1"><a className="text-primary" target="_blank" href="https://github.com/settings/tokens">https://github.com/settings/tokens</a></p>
<div>
<label>TOKEN:</label>
<div className="input-group text-secondary mb-0 h6">
<input id="gistaccesstoken" data-id="settingsTabGistAccessToken" type="password" className="form-control" onChange={(e) => handleChangeTokenState(e)} value={ githubToken } />
<div className="input-group-append">
<CopyToClipboard content={githubToken} data-id='copyToClipboardCopyIcon' className='far fa-copy ml-1 p-2 mt-1' direction={"top"} />
</div>
</div>
</div>
<div>
<label>USERNAME:</label>
<div className="text-secondary mb-0 h6">
<input id="githubusername" data-id="settingsTabGithubUsername" type="text" className="form-control" onChange={(e) => handleChangeUserNameState(e)} value={ githubUserName } />
</div>
</div>
<div>
<label>EMAIL:</label>
<div className="text-secondary mb-0 h6">
<input id="githubemail" data-id="settingsTabGithubEmail" type="text" className="form-control" onChange={(e) => handleChangeEmailState(e)} value={ githubEmail } />
<div className="d-flex justify-content-end pt-2">
<input className="btn btn-sm btn-primary ml-2" id="savegisttoken" data-id="settingsTabSaveGistToken" onClick={saveGithubToken} value="Save" type="button" disabled={githubToken === ''}></input>
<button className="btn btn-sm btn-secondary ml-2" id="removegisttoken" data-id="settingsTabRemoveGistToken" title="Delete GitHub Credentials" onClick={removeToken}>Remove</button>
</div>
</div>
</div>
</div>
</div>
)
}

@ -7,7 +7,8 @@ import './remix-ui-settings.css'
import { ethereumVM, generateContractMetadat, personal, textWrapEventAction, useMatomoAnalytics, saveTokenToast, removeTokenToast, saveSwarmSettingsToast, saveIpfsSettingsToast, useAutoCompletion } from './settingsAction' import { ethereumVM, generateContractMetadat, personal, textWrapEventAction, useMatomoAnalytics, saveTokenToast, removeTokenToast, saveSwarmSettingsToast, saveIpfsSettingsToast, useAutoCompletion } from './settingsAction'
import { initialState, toastInitialState, toastReducer, settingReducer } from './settingsReducer' import { initialState, toastInitialState, toastReducer, settingReducer } from './settingsReducer'
import { Toaster } from '@remix-ui/toaster'// eslint-disable-line import { Toaster } from '@remix-ui/toaster'// eslint-disable-line
import { RemixUiThemeModule, ThemeModule } from '@remix-ui/theme-module' import { RemixUiThemeModule, ThemeModule} from '@remix-ui/theme-module'
import { GithubSettings } from './github-settings'
/* eslint-disable-next-line */ /* eslint-disable-next-line */
export interface RemixUiSettingsProps { export interface RemixUiSettingsProps {
@ -357,9 +358,21 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
return ( return (
<div> <div>
{state.message ? <Toaster message={state.message} /> : null} {state.message ? <Toaster message= {state.message}/> : null}
{generalConfig()} {generalConfig()}
{token('gist')} <GithubSettings
saveTokenToast={(githubToken: string, githubUserName: string, githubEmail: string) => {
saveTokenToast(props.config, dispatchToast, githubToken, "gist-access-token")
saveTokenToast(props.config, dispatchToast, githubUserName, "github-user-name")
saveTokenToast(props.config, dispatchToast, githubEmail, "github-email")
}}
removeTokenToast={() => {
removeTokenToast(props.config, dispatchToast, "gist-access-token")
removeTokenToast(props.config, dispatchToast, "github-user-name")
removeTokenToast(props.config, dispatchToast, "github-email")
}}
config={props.config}
/>
{token('etherscan')} {token('etherscan')}
{swarmSettings()} {swarmSettings()}
{ipfsSettings()} {ipfsSettings()}

@ -48,12 +48,12 @@ export const useAutoCompletion = (config, checked, dispatch) => {
export const saveTokenToast = (config, dispatch, tokenValue, key) => { export const saveTokenToast = (config, dispatch, tokenValue, key) => {
config.set('settings/' + key, tokenValue) config.set('settings/' + key, tokenValue)
dispatch({ type: 'save', payload: { message: 'Access token has been saved' } }) dispatch({ type: 'save', payload: { message: 'GitHub credentials updated' } })
} }
export const removeTokenToast = (config, dispatch, key) => { export const removeTokenToast = (config, dispatch, key) => {
config.set('settings/' + key, '') config.set('settings/' + key, '')
dispatch({ type: 'removed', payload: { message: 'Access token removed' } }) dispatch({ type: 'removed', payload: { message: 'GitHub credentials removed' } })
} }
export const saveSwarmSettingsToast = (config, dispatch, privateBeeAddress, postageStampId) => { export const saveSwarmSettingsToast = (config, dispatch, privateBeeAddress, postageStampId) => {

@ -0,0 +1,12 @@
export interface GithubSettingsProps {
saveTokenToast: (githubToken: string, githubUserName: string, githubEmail: string) => void,
removeTokenToast: () => void,
config: {
exists: (key: string) => boolean,
get: (key: string) => string,
set: (key: string, content: string) => void,
clear: () => void,
getUnpersistedProperty: (key: string) => void,
setUnpersistedProperty: (key: string, value: string) => void
}
}

@ -0,0 +1,12 @@
{
"presets": [
[
"@nrwl/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}

@ -0,0 +1,18 @@
{
"extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

@ -0,0 +1,7 @@
# remix-ui-tooltip-popup
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test remix-ui-tooltip-popup` to execute the unit tests via [Jest](https://jestjs.io).

@ -0,0 +1 @@
export * from './lib/tooltip-popup'

@ -0,0 +1,27 @@
import React, { useState } from 'react'
import { OverlayTrigger, Popover } from 'react-bootstrap'
import { TooltipPopupProps } from '../types'
import './tooltip-popup.module.css'
const popover = (title?: string, content?: string | React.ReactNode) => (
<Popover id="popover-basic" className='bg-light border-secondary'>
<Popover.Title as="h3" className='bg-dark border-0'>{ title || 'Tooltip' }</Popover.Title>
<Popover.Content>
{ content }
</Popover.Content>
</Popover>
)
export function TooltipPopup(props: TooltipPopupProps) {
const [show, setShow] = useState<boolean>(false)
return (
<OverlayTrigger trigger="click" placement={"bottom"} overlay={popover(props.title, props.children || props.content)} show={show} onToggle={(nextShow) => {
setShow(nextShow)
}}>
<i className={`${props.icon} remixui_menuicon pr-0 mr-2`}></i>
</OverlayTrigger>
)
}
export default TooltipPopup

@ -0,0 +1,6 @@
export interface TooltipPopupProps {
children?: React.ReactNode,
title?: string,
content?: string,
icon: string
}

@ -0,0 +1,20 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

@ -0,0 +1,13 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

@ -8,7 +8,6 @@ import { createWorkspaceTemplate, getWorkspaces, loadWorkspacePreset, setPlugin
import { QueryParams } from '@remix-project/remix-lib' import { QueryParams } from '@remix-project/remix-lib'
import { fetchContractFromEtherscan } from '@remix-project/core-plugin' // eslint-disable-line import { fetchContractFromEtherscan } from '@remix-project/core-plugin' // eslint-disable-line
import JSZip from 'jszip' import JSZip from 'jszip'
import axios, { AxiosResponse } from 'axios'
export * from './events' export * from './events'
export * from './workspace' export * from './workspace'
@ -21,20 +20,24 @@ let plugin, dispatch: React.Dispatch<any>
export type UrlParametersType = { export type UrlParametersType = {
gist: string, gist: string,
code: string, code: string,
url: string url: string,
address: string
} }
const basicWorkspaceInit = async (workspaces, workspaceProvider) => { const basicWorkspaceInit = async (workspaces: { name: string; isGitRepo: boolean; }[], workspaceProvider) => {
if (workspaces.length === 0) { if (workspaces.length === 0) {
await createWorkspaceTemplate('default_workspace', 'remixDefault') await createWorkspaceTemplate('default_workspace', 'remixDefault')
plugin.setWorkspace({ name: 'default_workspace', isLocalhost: false }) plugin.setWorkspace({ name: 'default_workspace', isLocalhost: false })
dispatch(setCurrentWorkspace('default_workspace')) dispatch(setCurrentWorkspace({ name: 'default_workspace', isGitRepo: false }))
await loadWorkspacePreset('remixDefault') await loadWorkspacePreset('remixDefault')
} else { } else {
if (workspaces.length > 0) { if (workspaces.length > 0) {
workspaceProvider.setWorkspace(workspaces[workspaces.length - 1]) const workspace = workspaces[workspaces.length - 1]
plugin.setWorkspace({ name: workspaces[workspaces.length - 1], isLocalhost: false }) const workspaceName = (workspace || {}).name
dispatch(setCurrentWorkspace(workspaces[workspaces.length - 1]))
workspaceProvider.setWorkspace(workspaceName)
plugin.setWorkspace({ name: workspaceName, isLocalhost: false })
dispatch(setCurrentWorkspace(workspace))
} }
} }
} }
@ -52,18 +55,17 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.
if (params.gist) { if (params.gist) {
await createWorkspaceTemplate('gist-sample', 'gist-template') await createWorkspaceTemplate('gist-sample', 'gist-template')
plugin.setWorkspace({ name: 'gist-sample', isLocalhost: false }) plugin.setWorkspace({ name: 'gist-sample', isLocalhost: false })
dispatch(setCurrentWorkspace('gist-sample')) dispatch(setCurrentWorkspace({ name: 'gist-sample', isGitRepo: false }))
await loadWorkspacePreset('gist-template') await loadWorkspacePreset('gist-template')
} else if (params.code || params.url) { } else if (params.code || params.url) {
await createWorkspaceTemplate('code-sample', 'code-template') await createWorkspaceTemplate('code-sample', 'code-template')
plugin.setWorkspace({ name: 'code-sample', isLocalhost: false }) plugin.setWorkspace({ name: 'code-sample', isLocalhost: false })
dispatch(setCurrentWorkspace('code-sample')) dispatch(setCurrentWorkspace({ name: 'code-sample', isGitRepo: false }))
const filePath = await loadWorkspacePreset('code-template') const filePath = await loadWorkspacePreset('code-template')
plugin.on('editor', 'editorMounted', async () => await plugin.fileManager.openFile(filePath)) plugin.on('editor', 'editorMounted', async () => await plugin.fileManager.openFile(filePath))
} else if (window.location.pathname && window.location.pathname !== '/') { } else if (params.address) {
let route = window.location.pathname if (params.address.startsWith('0x') && params.address.length === 42) {
if (route.startsWith('/address/0x') && route.length === 51) { const contractAddress = params.address
const contractAddress = route.split('/')[2]
plugin.call('notification', 'toast', `Looking for contract(s) verified on different networks of Etherscan for contract address ${contractAddress} .....`) plugin.call('notification', 'toast', `Looking for contract(s) verified on different networks of Etherscan for contract address ${contractAddress} .....`)
let data let data
let count = 0 let count = 0
@ -95,7 +97,7 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.
foundOnNetworks.push(network.name) foundOnNetworks.push(network.name)
await createWorkspaceTemplate('etherscan-code-sample', 'code-template') await createWorkspaceTemplate('etherscan-code-sample', 'code-template')
plugin.setWorkspace({ name: 'etherscan-code-sample', isLocalhost: false }) plugin.setWorkspace({ name: 'etherscan-code-sample', isLocalhost: false })
dispatch(setCurrentWorkspace('etherscan-code-sample')) dispatch(setCurrentWorkspace({ name: 'etherscan-code-sample', isGitRepo: false }))
let filePath let filePath
count = count + (Object.keys(data.compilationTargets)).length count = count + (Object.keys(data.compilationTargets)).length
for (filePath in data.compilationTargets) for (filePath in data.compilationTargets)
@ -106,24 +108,15 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.
} catch (error) { } catch (error) {
await basicWorkspaceInit(workspaces, workspaceProvider) await basicWorkspaceInit(workspaces, workspaceProvider)
} }
} else if (route.endsWith('.sol')) {
if (route.includes('blob')) route = route.replace('/blob', '')
let response: AxiosResponse
try {
response = await axios.get(`https://raw.githubusercontent.com${route}`)
} catch (error) {
plugin.call('notification', 'toast', `cound not find ${route} on GitHub`)
await basicWorkspaceInit(workspaces, workspaceProvider)
}
if (response && response.status === 200) {
const content = response.data
await createWorkspaceTemplate('github-code-sample', 'code-template')
plugin.setWorkspace({ name: 'github-code-sample', isLocalhost: false })
dispatch(setCurrentWorkspace('github-code-sample'))
await workspaceProvider.set(route, content)
plugin.on('editor', 'editorMounted', async () => await plugin.fileManager.openFile(route))
} else await basicWorkspaceInit(workspaces, workspaceProvider)
} else await basicWorkspaceInit(workspaces, workspaceProvider) } else await basicWorkspaceInit(workspaces, workspaceProvider)
} else if (localStorage.getItem("currentWorkspace")) {
const index = workspaces.findIndex(element => element.name == localStorage.getItem("currentWorkspace"))
if (index !== -1) {
const name = localStorage.getItem("currentWorkspace")
workspaceProvider.setWorkspace(name)
plugin.setWorkspace({ name: name, isLocalhost: false })
dispatch(setCurrentWorkspace({ name: name, isGitRepo: false }))
}
} else { } else {
await basicWorkspaceInit(workspaces, workspaceProvider) await basicWorkspaceInit(workspaces, workspaceProvider)
} }

@ -1,14 +1,14 @@
import { fileDecoration } from '@remix-ui/file-decorators' import { fileDecoration } from '@remix-ui/file-decorators'
import { action } from '../types' import { action } from '../types'
export const setCurrentWorkspace = (workspace: string) => { export const setCurrentWorkspace = (workspace: { name: string; isGitRepo: boolean; }) => {
return { return {
type: 'SET_CURRENT_WORKSPACE', type: 'SET_CURRENT_WORKSPACE',
payload: workspace payload: workspace
} }
} }
export const setWorkspaces = (workspaces: string[]) => { export const setWorkspaces = (workspaces: { name: string; isGitRepo: boolean; }[]) => {
return { return {
type: 'SET_WORKSPACES', type: 'SET_WORKSPACES',
payload: workspaces payload: workspaces
@ -126,7 +126,7 @@ export const createWorkspaceRequest = (promise: Promise<any>) => {
} }
} }
export const createWorkspaceSuccess = (workspaceName: string) => { export const createWorkspaceSuccess = (workspaceName: { name: string; isGitRepo: boolean; }) => {
return { return {
type: 'CREATE_WORKSPACE_SUCCESS', type: 'CREATE_WORKSPACE_SUCCESS',
payload: workspaceName payload: workspaceName
@ -247,3 +247,20 @@ export const setFileDecorationSuccess = (items: fileDecoration[]) => {
payload: items payload: items
} }
} }
export const cloneRepositoryRequest = () => {
return {
type: 'CLONE_REPOSITORY_REQUEST'
}
}
export const cloneRepositorySuccess = () => {
return {
type: 'CLONE_REPOSITORY_SUCCESS'
}
}
export const cloneRepositoryFailed = () => {
return {
type: 'CLONE_REPOSITORY_FAILED'
}
}

@ -1,8 +1,8 @@
import React from 'react' import React from 'react'
import { bufferToHex, keccakFromString } from 'ethereumjs-util' import { bufferToHex, keccakFromString } from 'ethereumjs-util'
import axios, { AxiosResponse } from 'axios' import axios, { AxiosResponse } from 'axios'
import { addInputFieldSuccess, createWorkspaceError, createWorkspaceRequest, createWorkspaceSuccess, displayNotification, fetchWorkspaceDirectoryError, fetchWorkspaceDirectoryRequest, fetchWorkspaceDirectorySuccess, hideNotification, setCurrentWorkspace, setDeleteWorkspace, setMode, setReadOnlyMode, setRenameWorkspace } from './payload' import { addInputFieldSuccess, cloneRepositoryFailed, cloneRepositoryRequest, cloneRepositorySuccess, createWorkspaceError, createWorkspaceRequest, createWorkspaceSuccess, displayNotification, displayPopUp, fetchWorkspaceDirectoryError, fetchWorkspaceDirectoryRequest, fetchWorkspaceDirectorySuccess, hideNotification, setCurrentWorkspace, setDeleteWorkspace, setMode, setReadOnlyMode, setRenameWorkspace } from './payload'
import { checkSlash, checkSpecialChars } from '@remix-ui/helper' import { checkSlash, checkSpecialChars, createNonClashingTitle } from '@remix-ui/helper'
import { JSONStandardInput, WorkspaceTemplate } from '../types' import { JSONStandardInput, WorkspaceTemplate } from '../types'
import { QueryParams } from '@remix-project/remix-lib' import { QueryParams } from '@remix-project/remix-lib'
@ -42,13 +42,13 @@ export const addInputField = async (type: 'file' | 'folder', path: string, cb?:
return promise return promise
} }
export const createWorkspace = async (workspaceName: string, workspaceTemplateName: WorkspaceTemplate, isEmpty = false, cb?: (err: Error, result?: string | number | boolean | Record<string, any>) => void) => { export const createWorkspace = async (workspaceName: string, workspaceTemplateName: WorkspaceTemplate, isEmpty = false, cb?: (err: Error, result?: string | number | boolean | Record<string, any>) => void, isGitRepo: boolean = false) => {
await plugin.fileManager.closeAllFiles() await plugin.fileManager.closeAllFiles()
const promise = createWorkspaceTemplate(workspaceName, workspaceTemplateName) const promise = createWorkspaceTemplate(workspaceName, workspaceTemplateName)
dispatch(createWorkspaceRequest(promise)) dispatch(createWorkspaceRequest(promise))
promise.then(async () => { promise.then(async () => {
dispatch(createWorkspaceSuccess(workspaceName)) dispatch(createWorkspaceSuccess({ name: workspaceName, isGitRepo }))
await plugin.setWorkspace({ name: workspaceName, isLocalhost: false }) await plugin.setWorkspace({ name: workspaceName, isLocalhost: false })
await plugin.setWorkspaces(await getWorkspaces()) await plugin.setWorkspaces(await getWorkspaces())
await plugin.workspaceCreated(workspaceName) await plugin.workspaceCreated(workspaceName)
@ -254,8 +254,10 @@ export const switchToWorkspace = async (name: string) => {
if (isActive) await plugin.call('manager', 'deactivatePlugin', 'remixd') if (isActive) await plugin.call('manager', 'deactivatePlugin', 'remixd')
await plugin.fileProviders.workspace.setWorkspace(name) await plugin.fileProviders.workspace.setWorkspace(name)
await plugin.setWorkspace({ name, isLocalhost: false }) await plugin.setWorkspace({ name, isLocalhost: false })
const isGitRepo = await plugin.fileManager.isGitRepo()
dispatch(setMode('browser')) dispatch(setMode('browser'))
dispatch(setCurrentWorkspace(name)) dispatch(setCurrentWorkspace({ name, isGitRepo }))
dispatch(setReadOnlyMode(false)) dispatch(setReadOnlyMode(false))
} }
} }
@ -302,22 +304,69 @@ export const uploadFile = async (target, targetFolder: string, cb?: (err: Error,
}) })
} }
export const getWorkspaces = async (): Promise<string[]> | undefined => { export const getWorkspaces = async (): Promise<{name: string, isGitRepo: boolean}[]> | undefined => {
try { try {
const workspaces: string[] = await new Promise((resolve, reject) => { const workspaces: {name: string, isGitRepo: boolean}[] = await new Promise((resolve, reject) => {
const workspacesPath = plugin.fileProviders.workspace.workspacesPath const workspacesPath = plugin.fileProviders.workspace.workspacesPath
plugin.fileProviders.browser.resolveDirectory('/' + workspacesPath, (error, items) => { plugin.fileProviders.browser.resolveDirectory('/' + workspacesPath, (error, items) => {
if (error) { if (error) {
return reject(error) return reject(error)
} }
resolve(Object.keys(items) Promise.all(Object.keys(items)
.filter((item) => items[item].isDirectory) .filter((item) => items[item].isDirectory)
.map((folder) => folder.replace(workspacesPath + '/', ''))) .map(async (folder) => {
const isGitRepo: boolean = await plugin.fileProviders.browser.exists('/' + folder + '/.git')
return {
name: folder.replace(workspacesPath + '/', ''),
isGitRepo
}
})).then(workspacesList => resolve(workspacesList))
}) })
}) })
await plugin.setWorkspaces(workspaces) await plugin.setWorkspaces(workspaces)
return workspaces return workspaces
} catch (e) {} } catch (e) {}
}
export const cloneRepository = async (url: string) => {
const config = plugin.registry.get('config').api
const token = config.get('settings/gist-access-token')
const repoConfig = { url, token }
const urlArray = url.split('/')
let repoName = urlArray.length > 0 ? urlArray[urlArray.length - 1] : ''
try {
repoName = await createNonClashingTitle(repoName, plugin.fileManager)
await createWorkspace(repoName, 'blank', true, null, true)
const promise = plugin.call('dGitProvider', 'clone', repoConfig, repoName, true)
dispatch(cloneRepositoryRequest())
promise.then(async () => {
const isActive = await plugin.call('manager', 'isActive', 'dgit')
if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit')
await fetchWorkspaceDirectory(repoName)
dispatch(cloneRepositorySuccess())
}).catch((e) => {
const cloneModal = {
id: 'cloneGitRepository',
title: 'Clone Git Repository',
message: 'An error occured: ' + e,
modalType: 'modal',
okLabel: 'OK',
okFn: async () => {
await deleteWorkspace(repoName)
dispatch(cloneRepositoryFailed())
},
hideFn: async () => {
await deleteWorkspace(repoName)
dispatch(cloneRepositoryFailed())
}
}
plugin.call('notification', 'modal', cloneModal)
})
} catch (e) {
dispatch(displayPopUp('An error occured: ' + e))
}
} }

@ -4,7 +4,7 @@ import { BrowserState } from '../reducers/workspace'
export const FileSystemContext = createContext<{ export const FileSystemContext = createContext<{
fs: BrowserState, fs: BrowserState,
modal:(title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void, modal:(title: string | JSX.Element, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void,
dispatchInitWorkspace:() => Promise<void>, dispatchInitWorkspace:() => Promise<void>,
dispatchFetchDirectory:(path: string) => Promise<void>, dispatchFetchDirectory:(path: string) => Promise<void>,
dispatchAddInputField:(path: string, type: 'file' | 'folder') => Promise<void>, dispatchAddInputField:(path: string, type: 'file' | 'folder') => Promise<void>,
@ -29,5 +29,6 @@ export const FileSystemContext = createContext<{
dispatchHandleClickFile: (path: string, type: 'file' | 'folder' | 'gist') => Promise<void> dispatchHandleClickFile: (path: string, type: 'file' | 'folder' | 'gist') => Promise<void>
dispatchHandleExpandPath: (paths: string[]) => Promise<void>, dispatchHandleExpandPath: (paths: string[]) => Promise<void>,
dispatchHandleDownloadFiles: () => Promise<void>, dispatchHandleDownloadFiles: () => Promise<void>,
dispatchHandleRestoreBackup: () => Promise<void> dispatchHandleRestoreBackup: () => Promise<void>,
dispatchCloneRepository: (url: string) => Promise<void>
}>(null) }>(null)

@ -62,4 +62,42 @@
.remixui_menuicon:hover { .remixui_menuicon:hover {
transform: scale(1.3); transform: scale(1.3);
} }
.remixui_cloneContainer {
display: flex;
align-items: center;
height: 32px;
}
.remixui_cloneContainer input {
height: 32px;
border-top-left-radius: 0 !important;
border-bottom-left-radius: 0 !important;
width: 250px;
font-size: 10px !important;
padding: .25rem;
}
.remixui_menuicon .bs-popover-auto[x-placement^="bottom"] .popover-header::before, .bs-popover-bottom .popover-header::before {
border-bottom-color: var(--dark) !important
}
.remixui_menuicon .bs-popover-auto[x-placement^="bottom"] > .arrow::after, .bs-popover-bottom > .arrow::after {
border-bottom-color: var(--dark) !important
}
.custom-dropdown-items {
padding: 0.25rem 0.25rem;
border-radius: .25rem;
background: var(--light);
}
.custom-dropdown-items a {
border-radius: .25rem;
text-transform: none;
text-decoration: none;
font-weight: normal;
font-size: 0.875rem;
padding: 0.25rem 0.25rem;
width: auto;
color: var(--text);
}

@ -5,7 +5,9 @@ import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
import { FileSystemContext } from '../contexts' import { FileSystemContext } from '../contexts'
import { browserReducer, browserInitialState } from '../reducers/workspace' import { browserReducer, browserInitialState } from '../reducers/workspace'
import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder, deletePath, renamePath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, handleDownloadFiles, restoreBackupZip } from '../actions' import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder,
deletePath, renamePath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace,
fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, handleDownloadFiles, restoreBackupZip, cloneRepository } from '../actions'
import { Modal, WorkspaceProps, WorkspaceTemplate } from '../types' import { Modal, WorkspaceProps, WorkspaceTemplate } from '../types'
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
import { Workspace } from '../remix-ui-workspace' import { Workspace } from '../remix-ui-workspace'
@ -123,6 +125,10 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
await restoreBackupZip() await restoreBackupZip()
} }
const dispatchCloneRepository = async (url: string) => {
await cloneRepository(url)
}
useEffect(() => { useEffect(() => {
dispatchInitWorkspace() dispatchInitWorkspace()
}, []) }, [])
@ -224,7 +230,8 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
dispatchHandleClickFile, dispatchHandleClickFile,
dispatchHandleExpandPath, dispatchHandleExpandPath,
dispatchHandleDownloadFiles, dispatchHandleDownloadFiles,
dispatchHandleRestoreBackup dispatchHandleRestoreBackup,
dispatchCloneRepository
} }
return ( return (
<FileSystemContext.Provider value={value}> <FileSystemContext.Provider value={value}>

@ -9,13 +9,18 @@ interface Action {
export interface BrowserState { export interface BrowserState {
browser: { browser: {
currentWorkspace: string, currentWorkspace: string,
workspaces: string[], workspaces: {
name: string;
isGitRepo: boolean;
}[],
files: { [x: string]: Record<string, FileType> }, files: { [x: string]: Record<string, FileType> },
expandPath: string[] expandPath: string[]
isRequestingDirectory: boolean, isRequestingDirectory: boolean,
isSuccessfulDirectory: boolean, isSuccessfulDirectory: boolean,
isRequestingWorkspace: boolean, isRequestingWorkspace: boolean,
isSuccessfulWorkspace: boolean, isSuccessfulWorkspace: boolean,
isRequestingCloning: boolean,
isSuccessfulCloning: boolean,
error: string, error: string,
contextMenu: { contextMenu: {
registeredMenuItems: action[], registeredMenuItems: action[],
@ -66,6 +71,8 @@ export const browserInitialState: BrowserState = {
isSuccessfulDirectory: false, isSuccessfulDirectory: false,
isRequestingWorkspace: false, isRequestingWorkspace: false,
isSuccessfulWorkspace: false, isSuccessfulWorkspace: false,
isRequestingCloning: false,
isSuccessfulCloning: false,
error: null, error: null,
contextMenu: { contextMenu: {
registeredMenuItems: [], registeredMenuItems: [],
@ -109,21 +116,21 @@ export const browserInitialState: BrowserState = {
export const browserReducer = (state = browserInitialState, action: Action) => { export const browserReducer = (state = browserInitialState, action: Action) => {
switch (action.type) { switch (action.type) {
case 'SET_CURRENT_WORKSPACE': { case 'SET_CURRENT_WORKSPACE': {
const payload = action.payload as string const payload = action.payload as { name: string; isGitRepo: boolean; }
const workspaces = state.browser.workspaces.includes(payload) ? state.browser.workspaces : [...state.browser.workspaces, action.payload] const workspaces = state.browser.workspaces.find(({ name }) => name === payload.name) ? state.browser.workspaces : [...state.browser.workspaces, action.payload]
return { return {
...state, ...state,
browser: { browser: {
...state.browser, ...state.browser,
currentWorkspace: payload, currentWorkspace: payload.name,
workspaces: workspaces.filter(workspace => workspace) workspaces: workspaces.filter(workspace => workspace)
} }
} }
} }
case 'SET_WORKSPACES': { case 'SET_WORKSPACES': {
const payload = action.payload as string[] const payload = action.payload as { name: string; isGitRepo: boolean; }[]
return { return {
...state, ...state,
@ -421,14 +428,14 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
} }
case 'CREATE_WORKSPACE_SUCCESS': { case 'CREATE_WORKSPACE_SUCCESS': {
const payload = action.payload as string const payload = action.payload as { name: string; isGitRepo: boolean; }
const workspaces = state.browser.workspaces.includes(payload) ? state.browser.workspaces : [...state.browser.workspaces, action.payload] const workspaces = state.browser.workspaces.find(({ name }) => name === payload.name) ? state.browser.workspaces : [...state.browser.workspaces, action.payload]
return { return {
...state, ...state,
browser: { browser: {
...state.browser, ...state.browser,
currentWorkspace: payload, currentWorkspace: payload.name,
workspaces: workspaces.filter(workspace => workspace), workspaces: workspaces.filter(workspace => workspace),
isRequestingWorkspace: false, isRequestingWorkspace: false,
isSuccessfulWorkspace: true, isSuccessfulWorkspace: true,
@ -451,14 +458,25 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
case 'RENAME_WORKSPACE': { case 'RENAME_WORKSPACE': {
const payload = action.payload as { oldName: string, workspaceName: string } const payload = action.payload as { oldName: string, workspaceName: string }
const workspaces = state.browser.workspaces.filter(name => name && (name !== payload.oldName)) let renamedWorkspace
const workspaces = state.browser.workspaces.filter(({ name, isGitRepo }) => {
if (name && (name !== payload.oldName)) {
return true
} else {
renamedWorkspace = {
name: payload.workspaceName,
isGitRepo
}
return false
}
})
return { return {
...state, ...state,
browser: { browser: {
...state.browser, ...state.browser,
currentWorkspace: payload.workspaceName, currentWorkspace: payload.workspaceName,
workspaces: [...workspaces, payload.workspaceName], workspaces: [...workspaces, renamedWorkspace],
expandPath: [] expandPath: []
} }
} }
@ -466,7 +484,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
case 'DELETE_WORKSPACE': { case 'DELETE_WORKSPACE': {
const payload = action.payload as string const payload = action.payload as string
const workspaces = state.browser.workspaces.filter(name => name && (name !== payload)) const workspaces = state.browser.workspaces.filter(({ name }) => name && (name !== payload))
return { return {
...state, ...state,
@ -597,6 +615,39 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
} }
} }
case 'CLONE_REPOSITORY_REQUEST': {
return {
...state,
browser: {
...state.browser,
isRequestingCloning: true,
isSuccessfulCloning: false
}
}
}
case 'CLONE_REPOSITORY_SUCCESS': {
return {
...state,
browser: {
...state.browser,
isRequestingCloning: false,
isSuccessfulCloning: true
}
}
}
case 'CLONE_REPOSITORY_FAILED': {
return {
...state,
browser: {
...state.browser,
isRequestingCloning: false,
isSuccessfulCloning: false
}
}
}
case 'FS_INITIALIZATION_COMPLETED': { case 'FS_INITIALIZATION_COMPLETED': {
return { return {
...state, ...state,

@ -1,7 +1,9 @@
import React, { useState, useEffect, useRef, useContext } from 'react' // eslint-disable-line import React, { useState, useEffect, useRef, useContext } from 'react' // eslint-disable-line
import { Dropdown } from 'react-bootstrap'
import { CustomMenu, CustomToggle } from '@remix-ui/helper'
import { FileExplorer } from './components/file-explorer' // eslint-disable-line import { FileExplorer } from './components/file-explorer' // eslint-disable-line
import './css/remix-ui-workspace.css'
import { FileSystemContext } from './contexts' import { FileSystemContext } from './contexts'
import './css/remix-ui-workspace.css'
const canUpload = window.File || window.FileReader || window.FileList || window.Blob const canUpload = window.File || window.FileReader || window.FileList || window.Blob
@ -9,12 +11,16 @@ export function Workspace () {
const LOCALHOST = ' - connect to localhost - ' const LOCALHOST = ' - connect to localhost - '
const NO_WORKSPACE = ' - none - ' const NO_WORKSPACE = ' - none - '
const [currentWorkspace, setCurrentWorkspace] = useState<string>(NO_WORKSPACE) const [currentWorkspace, setCurrentWorkspace] = useState<string>(NO_WORKSPACE)
const [selectedWorkspace, setSelectedWorkspace] = useState<{ name: string, isGitRepo: boolean}>(null)
const [showDropdown, setShowDropdown] = useState<boolean>(false)
const global = useContext(FileSystemContext) const global = useContext(FileSystemContext)
const workspaceRenameInput = useRef() const workspaceRenameInput = useRef()
const workspaceCreateInput = useRef() const workspaceCreateInput = useRef()
const workspaceCreateTemplateInput = useRef() const workspaceCreateTemplateInput = useRef()
const cloneUrlRef = useRef<HTMLInputElement>()
useEffect(() => { useEffect(() => {
setCurrentWorkspace(localStorage.getItem('currentWorkspace') ? localStorage.getItem('currentWorkspace') : '')
resetFocus() resetFocus()
}, []) }, [])
@ -30,15 +36,21 @@ export function Workspace () {
}, [global.fs.browser.currentWorkspace, global.fs.localhost.sharedFolder, global.fs.mode]) }, [global.fs.browser.currentWorkspace, global.fs.localhost.sharedFolder, global.fs.mode])
useEffect(() => { useEffect(() => {
if (global.fs.browser.currentWorkspace && !global.fs.browser.workspaces.includes(global.fs.browser.currentWorkspace)) { if (global.fs.browser.currentWorkspace && !global.fs.browser.workspaces.find(({ name }) => name === global.fs.browser.currentWorkspace)) {
if (global.fs.browser.workspaces.length > 0) { if (global.fs.browser.workspaces.length > 0) {
switchWorkspace(global.fs.browser.workspaces[global.fs.browser.workspaces.length - 1]) switchWorkspace(global.fs.browser.workspaces[global.fs.browser.workspaces.length - 1].name)
} else { } else {
switchWorkspace(NO_WORKSPACE) switchWorkspace(NO_WORKSPACE)
} }
} }
}, [global.fs.browser.workspaces]) }, [global.fs.browser.workspaces])
useEffect(() => {
const workspace = global.fs.browser.workspaces.find(workspace => workspace.name === currentWorkspace)
setSelectedWorkspace(workspace)
}, [currentWorkspace])
const renameCurrentWorkspace = () => { const renameCurrentWorkspace = () => {
global.modal('Rename Current Workspace', renameModalMessage(), 'OK', onFinishRenameWorkspace, '') global.modal('Rename Current Workspace', renameModalMessage(), 'OK', onFinishRenameWorkspace, '')
} }
@ -51,6 +63,10 @@ export function Workspace () {
global.modal('Delete Current Workspace', 'Are you sure to delete the current workspace?', 'OK', onFinishDeleteWorkspace, '') global.modal('Delete Current Workspace', 'Are you sure to delete the current workspace?', 'OK', onFinishDeleteWorkspace, '')
} }
const cloneGitRepository = () => {
global.modal('Clone Git Repository', cloneModalMessage(), 'OK', handleTypingUrl, '')
}
const downloadWorkspaces = async () => { const downloadWorkspaces = async () => {
try { try {
await global.dispatchHandleDownloadFiles() await global.dispatchHandleDownloadFiles()
@ -124,6 +140,20 @@ export function Workspace () {
workspaceCreateInput.current.value = `${workspaceCreateTemplateInput.current.value || 'remixDefault'}_${Date.now()}` workspaceCreateInput.current.value = `${workspaceCreateTemplateInput.current.value || 'remixDefault'}_${Date.now()}`
} }
const handleTypingUrl = () => {
const url = cloneUrlRef.current.value
if (url) {
global.dispatchCloneRepository(url)
} else {
global.modal('Clone Git Repository', 'Please provide a valid git repository url.', 'OK', () => {}, '')
}
}
const toggleDropdown = (isOpen: boolean) => {
setShowDropdown(isOpen)
}
const createModalMessage = () => { const createModalMessage = () => {
return ( return (
<> <>
@ -149,121 +179,160 @@ export function Workspace () {
) )
} }
const cloneModalMessage = () => {
return (
<>
<input type="text" data-id="modalDialogCustomPromptTextClone" placeholder='Enter git repository url' ref={cloneUrlRef} className="form-control" />
</>
)
}
return ( return (
<div className='remixui_container'> <div className='remixui_container'>
<div className='remixui_fileexplorer' data-id="remixUIWorkspaceExplorer" onClick={resetFocus}> <div className='remixui_fileexplorer' data-id="remixUIWorkspaceExplorer" onClick={resetFocus}>
<div> <div>
<header> <header>
<div className="mb-2"> <div className="mb-2">
<label className="form-check-label" htmlFor="workspacesSelect"> <label className="pl-1 form-check-label" htmlFor="workspacesSelect">
Workspaces Workspaces
</label> </label>
<span className="remixui_menu"> <span className="remixui_menu">
<span <span
hidden={currentWorkspace === LOCALHOST} hidden={currentWorkspace === LOCALHOST}
id='workspaceCreate' id='workspaceCreate'
data-id='workspaceCreate' data-id='workspaceCreate'
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
createWorkspace() createWorkspace()
}} }}
className='far fa-plus-square remixui_menuicon' className='far fa-plus-square remixui_menuicon'
title='Create'> title='Create'>
</span> </span>
<span <span
hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE} hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE}
id='workspaceRename' id='workspaceRename'
data-id='workspaceRename' data-id='workspaceRename'
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
renameCurrentWorkspace() renameCurrentWorkspace()
}} }}
className='far fa-edit remixui_menuicon' className='far fa-edit remixui_menuicon'
title='Rename'> title='Rename'>
</span> </span>
<span <span
hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE} hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE}
id='workspaceDelete' id='workspaceDelete'
data-id='workspaceDelete' data-id='workspaceDelete'
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
deleteCurrentWorkspace() deleteCurrentWorkspace()
}} }}
className='fas fa-trash remixui_menuicon' className='fas fa-trash remixui_menuicon'
title='Delete'> title='Delete'>
</span>
<span
hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE}
id='workspacesDownload'
data-id='workspacesDownload'
onClick={(e) => {
e.stopPropagation()
downloadWorkspaces()
}}
className='far fa-download remixui_menuicon'
title='Download Workspaces'>
</span>
<span
hidden={currentWorkspace === LOCALHOST}
id='workspacesRestore'
data-id='workspacesRestore'
onClick={(e) => {
e.stopPropagation()
restoreBackup()
}}
className='far fa-upload remixui_menuicon'
title='Restore Workspaces Backup'>
</span>
<span
id='cloneGitRepository'
data-id='cloneGitRepository'
onClick={(e) => {
e.stopPropagation()
cloneGitRepository()
}}
className='far fa-clone remixui_menuicon'
title='Clone Git Repository'>
</span>
</span> </span>
<span <Dropdown id="workspacesSelect" data-id="workspacesSelect" onToggle={toggleDropdown} show={showDropdown}>
hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE} <Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components" className="btn btn-light btn-block w-100 d-inline-block border border-dark form-control" icon={selectedWorkspace && selectedWorkspace.isGitRepo && !(currentWorkspace === LOCALHOST) ? 'far fa-code-branch' : null}>
id='workspacesDownload' { selectedWorkspace ? selectedWorkspace.name : currentWorkspace === LOCALHOST ? 'localhost' : NO_WORKSPACE }
data-id='workspacesDownload' </Dropdown.Toggle>
onClick={(e) => {
e.stopPropagation() <Dropdown.Menu as={CustomMenu} className='w-100 custom-dropdown-items' data-id="custom-dropdown-items">
downloadWorkspaces() {
}} global.fs.browser.workspaces.map(({ name, isGitRepo }, index) => (
className='far fa-download remixui_menuicon' <Dropdown.Item
title='Download Workspaces'> key={index}
</span> onClick={() => {
<span switchWorkspace(name)
hidden={currentWorkspace === LOCALHOST} }}
id='workspacesRestore' data-id={`dropdown-item-${name}`}
data-id='workspacesRestore' >
onClick={(e) => { { isGitRepo ?
e.stopPropagation() <div className='d-flex justify-content-between'>
restoreBackup() <span>{ currentWorkspace === name ? <span>&#10003; { name } </span> : <span className="pl-3">{ name }</span> }</span>
}} <i className='fas fa-code-branch pt-1'></i>
className='far fa-upload remixui_menuicon' </div> :
title='Restore Workspaces Backup'> <span>{ currentWorkspace === name ? <span>&#10003; { name } </span> : <span className="pl-3">{ name }</span> }</span>
</span> }
</span> </Dropdown.Item>
<select id="workspacesSelect" value={currentWorkspace} data-id="workspacesSelect" onChange={(e) => switchWorkspace(e.target.value)} className="form-control custom-select"> ))
{ }
global.fs.browser.workspaces <Dropdown.Item onClick={() => { switchWorkspace(LOCALHOST) }}>{currentWorkspace === LOCALHOST ? <span>&#10003; localhost </span> : <span className="pl-3"> { LOCALHOST } </span>}</Dropdown.Item>
.map((folder, index) => { { ((global.fs.browser.workspaces.length <= 0) || currentWorkspace === NO_WORKSPACE) && <Dropdown.Item onClick={() => { switchWorkspace(NO_WORKSPACE) }}>{ <span className="pl-3">NO_WORKSPACE</span> }</Dropdown.Item> }
return <option key={index} value={folder}>{folder}</option> </Dropdown.Menu>
}) </Dropdown>
}
<option value={LOCALHOST}>{currentWorkspace === LOCALHOST ? 'localhost' : LOCALHOST}</option>
{ global.fs.browser.workspaces.length <= 0 && <option value={NO_WORKSPACE}>{NO_WORKSPACE}</option> }
</select>
</div> </div>
</header> </header>
</div> </div>
<div className='h-100 remixui_fileExplorerTree'> <div className='h-100 remixui_fileExplorerTree' onFocus={() => { toggleDropdown(false) }}>
<div className='h-100'> <div className='h-100'>
<div className='pl-2 remixui_treeview' data-id='filePanelFileExplorerTree'> { global.fs.browser.isRequestingWorkspace || global.fs.browser.isRequestingCloning ? <div className="text-center py-5"><i className="fas fa-spinner fa-pulse fa-2x"></i></div>
{ (global.fs.mode === 'browser') && (currentWorkspace !== NO_WORKSPACE) && : <div className='pl-2 remixui_treeview' data-id='filePanelFileExplorerTree'>
<FileExplorer { (global.fs.mode === 'browser') && (currentWorkspace !== NO_WORKSPACE) &&
name={currentWorkspace} <FileExplorer
menuItems={['createNewFile', 'createNewFolder', 'publishToGist', canUpload ? 'uploadFile' : '']} name={currentWorkspace}
contextMenuItems={global.fs.browser.contextMenu.registeredMenuItems} menuItems={['createNewFile', 'createNewFolder', 'publishToGist', canUpload ? 'uploadFile' : '']}
removedContextMenuItems={global.fs.browser.contextMenu.removedMenuItems} contextMenuItems={global.fs.browser.contextMenu.registeredMenuItems}
files={global.fs.browser.files} removedContextMenuItems={global.fs.browser.contextMenu.removedMenuItems}
fileState={global.fs.browser.fileState} files={global.fs.browser.files}
expandPath={global.fs.browser.expandPath} fileState={global.fs.browser.fileState}
focusEdit={global.fs.focusEdit} expandPath={global.fs.browser.expandPath}
focusElement={global.fs.focusElement} focusEdit={global.fs.focusEdit}
dispatchCreateNewFile={global.dispatchCreateNewFile} focusElement={global.fs.focusElement}
modal={global.modal} dispatchCreateNewFile={global.dispatchCreateNewFile}
dispatchCreateNewFolder={global.dispatchCreateNewFolder} modal={global.modal}
readonly={global.fs.readonly} dispatchCreateNewFolder={global.dispatchCreateNewFolder}
toast={global.toast} readonly={global.fs.readonly}
dispatchDeletePath={global.dispatchDeletePath} toast={global.toast}
dispatchRenamePath={global.dispatchRenamePath} dispatchDeletePath={global.dispatchDeletePath}
dispatchUploadFile={global.dispatchUploadFile} dispatchRenamePath={global.dispatchRenamePath}
dispatchCopyFile={global.dispatchCopyFile} dispatchUploadFile={global.dispatchUploadFile}
dispatchCopyFolder={global.dispatchCopyFolder} dispatchCopyFile={global.dispatchCopyFile}
dispatchPublishToGist={global.dispatchPublishToGist} dispatchCopyFolder={global.dispatchCopyFolder}
dispatchRunScript={global.dispatchRunScript} dispatchPublishToGist={global.dispatchPublishToGist}
dispatchEmitContextMenuEvent={global.dispatchEmitContextMenuEvent} dispatchRunScript={global.dispatchRunScript}
dispatchHandleClickFile={global.dispatchHandleClickFile} dispatchEmitContextMenuEvent={global.dispatchEmitContextMenuEvent}
dispatchSetFocusElement={global.dispatchSetFocusElement} dispatchHandleClickFile={global.dispatchHandleClickFile}
dispatchFetchDirectory={global.dispatchFetchDirectory} dispatchSetFocusElement={global.dispatchSetFocusElement}
dispatchRemoveInputField={global.dispatchRemoveInputField} dispatchFetchDirectory={global.dispatchFetchDirectory}
dispatchAddInputField={global.dispatchAddInputField} dispatchRemoveInputField={global.dispatchRemoveInputField}
dispatchHandleExpandPath={global.dispatchHandleExpandPath} dispatchAddInputField={global.dispatchAddInputField}
/> dispatchHandleExpandPath={global.dispatchHandleExpandPath}
} />
</div> }
</div>
}
{ {
global.fs.localhost.isRequestingLocalhost ? <div className="text-center py-5"><i className="fas fa-spinner fa-pulse fa-2x"></i></div> global.fs.localhost.isRequestingLocalhost ? <div className="text-center py-5"><i className="fas fa-spinner fa-pulse fa-2x"></i></div>
: <div className='pl-2 filesystemexplorer remixui_treeview'> : <div className='pl-2 filesystemexplorer remixui_treeview'>

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remixd", "name": "@remix-project/remixd",
"version": "0.6.2", "version": "0.6.3",
"description": "remix server: allow accessing file system from remix.ethereum.org and start a dev environment (see help section)", "description": "remix server: allow accessing file system from remix.ethereum.org and start a dev environment (see help section)",
"main": "index.js", "main": "index.js",
"types": "./index.d.ts", "types": "./index.d.ts",

@ -169,6 +169,9 @@
}, },
"remix-ui-file-decorators": { "remix-ui-file-decorators": {
"tags": [] "tags": []
},
"remix-ui-tooltip-popup": {
"tags": []
} }
}, },
"targetDependencies": { "targetDependencies": {

@ -45,7 +45,7 @@
"workspace-schematic": "nx workspace-schematic", "workspace-schematic": "nx workspace-schematic",
"dep-graph": "nx dep-graph", "dep-graph": "nx dep-graph",
"help": "nx help", "help": "nx help",
"lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remix-ws-templates,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-helper,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings,remix-core-plugin,remix-ui-renderer,remix-ui-publish-to-storage,remix-ui-solidity-compiler,solidity-unit-testing,remix-ui-plugin-manager,remix-ui-terminal,remix-ui-editor,remix-ui-app,remix-ui-tabs,remix-ui-panel,remix-ui-run-tab,remix-ui-permission-handler,remix-ui-search,remix-ui-file-decorators", "lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remix-ws-templates,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-helper,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings,remix-core-plugin,remix-ui-renderer,remix-ui-publish-to-storage,remix-ui-solidity-compiler,solidity-unit-testing,remix-ui-plugin-manager,remix-ui-terminal,remix-ui-editor,remix-ui-app,remix-ui-tabs,remix-ui-panel,remix-ui-run-tab,remix-ui-permission-handler,remix-ui-search,remix-ui-file-decorators,remix-ui-tooltip-popup",
"build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remix-ws-templates,remixd", "build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remix-ws-templates,remixd",
"test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd", "test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"publish:libs": "yarn run build:libs && lerna publish --skip-git && yarn run bumpVersion:libs", "publish:libs": "yarn run build:libs && lerna publish --skip-git && yarn run bumpVersion:libs",
@ -153,13 +153,13 @@
"@ethersphere/bee-js": "^3.2.0", "@ethersphere/bee-js": "^3.2.0",
"@isomorphic-git/lightning-fs": "^4.4.1", "@isomorphic-git/lightning-fs": "^4.4.1",
"@monaco-editor/react": "4.4.5", "@monaco-editor/react": "4.4.5",
"@remixproject/engine": "^0.3.28", "@remixproject/engine": "^0.3.31",
"@remixproject/engine-web": "^0.3.28", "@remixproject/engine-web": "^0.3.31",
"@remixproject/plugin": "^0.3.28", "@remixproject/plugin": "^0.3.31",
"@remixproject/plugin-api": "^0.3.28", "@remixproject/plugin-api": "^0.3.31",
"@remixproject/plugin-utils": "^0.3.28", "@remixproject/plugin-utils": "^0.3.31",
"@remixproject/plugin-webview": "^0.3.28", "@remixproject/plugin-webview": "^0.3.31",
"@remixproject/plugin-ws": "^0.3.28", "@remixproject/plugin-ws": "^0.3.31",
"@types/nightwatch": "^2.0.9", "@types/nightwatch": "^2.0.9",
"ansi-gray": "^0.1.1", "ansi-gray": "^0.1.1",
"async": "^2.6.2", "async": "^2.6.2",

@ -85,7 +85,8 @@
"@remix-ui/run-tab": ["libs/remix-ui/run-tab/src/index.ts"], "@remix-ui/run-tab": ["libs/remix-ui/run-tab/src/index.ts"],
"@remix-ui/permission-handler": [ "@remix-ui/permission-handler": [
"libs/remix-ui/permission-handler/src/index.ts" "libs/remix-ui/permission-handler/src/index.ts"
] ],
"@remix-ui/tooltip-popup": ["libs/remix-ui/tooltip-popup/src/index.ts"]
} }
}, },
"exclude": ["node_modules", "tmp"] "exclude": ["node_modules", "tmp"]

@ -18,6 +18,7 @@
"assets": [ "assets": [
"apps/remix-ide/src/assets", "apps/remix-ide/src/assets",
"apps/remix-ide/src/index.html", "apps/remix-ide/src/index.html",
"apps/remix-ide/src/404.html",
"apps/remix-ide/src/favicon.ico" "apps/remix-ide/src/favicon.ico"
], ],
"styles": [], "styles": [],
@ -1250,6 +1251,22 @@
} }
} }
} }
},
"remix-ui-tooltip-popup": {
"root": "libs/remix-ui/tooltip-popup",
"sourceRoot": "libs/remix-ui/tooltip-popup/src",
"projectType": "library",
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"config": "libs/remix-ui/tooltip-popup/.eslintrc.json",
"tsConfig": ["libs/remix-ui/tooltip-popup/tsconfig.lib.json"],
"exclude": ["**/node_modules/**"]
}
}
}
} }
}, },
"cli": { "cli": {

@ -3761,63 +3761,63 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.2.tgz#0798c03351f0dea1a5a4cabddf26a55a7cbee590" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.2.tgz#0798c03351f0dea1a5a4cabddf26a55a7cbee590"
integrity sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ== integrity sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ==
"@remixproject/engine-web@^0.3.28": "@remixproject/engine-web@^0.3.31":
version "0.3.28" version "0.3.31"
resolved "https://registry.yarnpkg.com/@remixproject/engine-web/-/engine-web-0.3.28.tgz#9a47c979a89764cb96d3f5da0d6802d1ca875c94" resolved "https://registry.yarnpkg.com/@remixproject/engine-web/-/engine-web-0.3.31.tgz#42ee5c5d874ef9646090342ca8b430e3fe1046e8"
integrity sha512-JTwTuonW+pI7WsSsgGHAzPynGjIdG34VnPrspR9XKBQO4+NX7m3lbNIMUUviyrbqIoGiGtgYd/ry98uLpTHr4g== integrity sha512-GYeUB4Y0NaVc23mSmVByhx6pCvjzfz8iBbJCwaUB3q61uyRQ1wMM0P/47qqwZMukPi7x4bFUpN41C6rKq7ECug==
dependencies: dependencies:
"@remixproject/engine" "0.3.28" "@remixproject/engine" "0.3.31"
"@remixproject/plugin-api" "0.3.28" "@remixproject/plugin-api" "0.3.31"
"@remixproject/plugin-utils" "0.3.28" "@remixproject/plugin-utils" "0.3.31"
"@remixproject/engine@0.3.28", "@remixproject/engine@^0.3.28": "@remixproject/engine@0.3.31", "@remixproject/engine@^0.3.31":
version "0.3.28" version "0.3.31"
resolved "https://registry.yarnpkg.com/@remixproject/engine/-/engine-0.3.28.tgz#3656ee029d3cbc22f8ad95bd925176a75057a475" resolved "https://registry.yarnpkg.com/@remixproject/engine/-/engine-0.3.31.tgz#6dcc209dbd993646430335c9454ce1a739d1a45f"
integrity sha512-27SHaCrG3KrPfMa1MYud4tE9xUIJKITEUsql99fhN7x12lOmgGZFjiqIG/WWjCMmT7OMG3vtLayiZrsYkzHCVw== integrity sha512-5VTl9bkeU3fd3IUV8wy7kXHMd3RfnUsf6dlmORM9Np1oMZ9nhdhUsyJO/qXyrvR1PbyJIxPz/qM4+W5MGvOrnQ==
dependencies: dependencies:
"@remixproject/plugin-api" "0.3.28" "@remixproject/plugin-api" "0.3.31"
"@remixproject/plugin-utils" "0.3.28" "@remixproject/plugin-utils" "0.3.31"
"@remixproject/plugin-api@0.3.28", "@remixproject/plugin-api@^0.3.28": "@remixproject/plugin-api@0.3.31", "@remixproject/plugin-api@^0.3.31":
version "0.3.28" version "0.3.31"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-api/-/plugin-api-0.3.28.tgz#660f068681bc1780284768e4ce1b4f70ea4ba859" resolved "https://registry.yarnpkg.com/@remixproject/plugin-api/-/plugin-api-0.3.31.tgz#86e7c458c58ff200bd927fd3d642877f4b5a0013"
integrity sha512-tOzVFR504037weEkNHQGZsk+Ebxcu/xnBsVCkJaQaBMb+H/i6YevKQmoR1aGcH+JfQxc8eS8LGC65MaV8zmcWg== integrity sha512-LOJRHxORNp7zW8k7//DQz5aZ7eqB7TwhYXrvzqvaryDTvtvJGWrlTHg81hzALynaxZKEWneohxjUxKvGp/eA4g==
dependencies: dependencies:
"@remixproject/plugin-utils" "0.3.28" "@remixproject/plugin-utils" "0.3.31"
"@remixproject/plugin-utils@0.3.28", "@remixproject/plugin-utils@^0.3.28": "@remixproject/plugin-utils@0.3.31", "@remixproject/plugin-utils@^0.3.31":
version "0.3.28" version "0.3.31"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-utils/-/plugin-utils-0.3.28.tgz#eefd3b603f223cf6fba9b914ef3c9a62bd8435b6" resolved "https://registry.yarnpkg.com/@remixproject/plugin-utils/-/plugin-utils-0.3.31.tgz#80771e00c1a1b776432abb17b1f4b2e25600d4f6"
integrity sha512-5CayTgMqRiOguanTy6kpuRgCmjEFLUu2K4Rs7Zmt+GOzHucmwkDxYQO+3hFv0Jz/M/6yC5wkKYTx8vfPLLdEBQ== integrity sha512-OOAjoSd+ErBMrcNQEh80NU3BjJ9fHXuftRfy5Ul9aGXN3b1LJSNVvfrG+FxX6lpyaAK5JBj+aB9pgFozgb2wlw==
dependencies: dependencies:
tslib "2.0.1" tslib "2.0.1"
"@remixproject/plugin-webview@^0.3.28": "@remixproject/plugin-webview@^0.3.31":
version "0.3.28" version "0.3.31"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-webview/-/plugin-webview-0.3.28.tgz#ce03ad0801e0fd1e5bce59565b77b6698ad07596" resolved "https://registry.yarnpkg.com/@remixproject/plugin-webview/-/plugin-webview-0.3.31.tgz#e5cce7d0089439b35aee4ab2a724add1e5d36b40"
integrity sha512-DodSkN0vMSo2DEEoBOWyKxC/ElvBA826vhX+JVGXA8HGS45aavplZIN967hsc+SdzjV1UZfSNKEHttLGwM1BIw== integrity sha512-8yoKwIkRi9S+rqvFShNt01FfXX0H/Fijn7UkWFWJ/V6ULcw2cw9ViCz8cYZLhNUpxqezyu/LKDQL9g1TbJJoYw==
dependencies: dependencies:
"@remixproject/plugin" "0.3.28" "@remixproject/plugin" "0.3.31"
"@remixproject/plugin-api" "0.3.28" "@remixproject/plugin-api" "0.3.31"
"@remixproject/plugin-utils" "0.3.28" "@remixproject/plugin-utils" "0.3.31"
axios "^0.21.1" axios "^0.21.1"
"@remixproject/plugin-ws@^0.3.28": "@remixproject/plugin-ws@^0.3.31":
version "0.3.28" version "0.3.31"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-ws/-/plugin-ws-0.3.28.tgz#4f6022b1c8ef6356299c545f6164bce48aac60fe" resolved "https://registry.yarnpkg.com/@remixproject/plugin-ws/-/plugin-ws-0.3.31.tgz#a2bb415cc32a5b036b9b3759520088b8ceaa0d5a"
integrity sha512-i3He9t4qvcBQxzshFx66D6drqLlySmV7Cb+NeYtMOYlWxQSJtUakp/Px1Tl3IDFQXDfpXxvDvYhn2w0AWPBOqw== integrity sha512-Z4G6vkGxxCP+ibGNHAvNaFjYE4hbsazOEL50pMnda6LZNci9akMSiI/1MIZscVSMU8l2sYOoNUsWvLfLkIkYKQ==
dependencies: dependencies:
"@remixproject/plugin" "0.3.28" "@remixproject/plugin" "0.3.31"
"@remixproject/plugin-api" "0.3.28" "@remixproject/plugin-api" "0.3.31"
"@remixproject/plugin-utils" "0.3.28" "@remixproject/plugin-utils" "0.3.31"
"@remixproject/plugin@0.3.28", "@remixproject/plugin@^0.3.28": "@remixproject/plugin@0.3.31", "@remixproject/plugin@^0.3.31":
version "0.3.28" version "0.3.31"
resolved "https://registry.yarnpkg.com/@remixproject/plugin/-/plugin-0.3.28.tgz#3e40de6d0441e6b47ca17c904e57562cb285a4d3" resolved "https://registry.yarnpkg.com/@remixproject/plugin/-/plugin-0.3.31.tgz#b6c6b58d2c7964e37024eeca4819c70ece1f3953"
integrity sha512-sw+cow3GKAcEacXLvdJwtHHIphPty4KRMZ6tcQHdtx/iCKTMNdw2hribKPnvTO+6mXE+4oAKCrFqeTdVp/rd1w== integrity sha512-9ntMU9CzStloahm/wXt4V8n64ERgJzY5nG0bzQfjnI12knrdTmUo+LC42M2xaTBDDP9CzMPdqClg7XhhRLzohA==
dependencies: dependencies:
"@remixproject/plugin-api" "0.3.28" "@remixproject/plugin-api" "0.3.31"
"@remixproject/plugin-utils" "0.3.28" "@remixproject/plugin-utils" "0.3.31"
events "3.2.0" events "3.2.0"
"@restart/context@^2.1.4": "@restart/context@^2.1.4":

Loading…
Cancel
Save