Merge branch 'master' into installSlitherCmd

pull/2450/head
Aniket 3 years ago committed by GitHub
commit 4b1ce9a116
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      .github/workflows/publish-action.yml
  2. 30
      apps/remix-ide-e2e/src/commands/verifyContracts.ts
  3. 38
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  4. 1
      apps/remix-ide-e2e/src/tests/compiler_api.test.ts
  5. 6
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  6. 15
      apps/remix-ide-e2e/src/tests/publishContract.test.ts
  7. 4
      apps/remix-ide-e2e/src/tests/search.test.ts
  8. 8
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  9. 15
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  10. 2
      apps/remix-ide-e2e/src/types/index.d.ts
  11. 10
      apps/remix-ide/ci/publishIpfs
  12. 55
      apps/remix-ide/src/app/editor/editor.js
  13. 4
      apps/remix-ide/src/app/files/fileManager.ts
  14. 3
      apps/remix-ide/src/app/panels/layout.ts
  15. 7
      apps/remix-ide/src/app/panels/tab-proxy.js
  16. 25
      apps/remix-ide/src/app/plugins/remixd-handle.tsx
  17. 4
      apps/remix-ide/src/app/tabs/compile-tab.js
  18. 62
      apps/solidity-compiler/src/app/compiler-api.ts
  19. 8
      libs/remix-analyzer/package.json
  20. 6
      libs/remix-astwalker/package.json
  21. 10
      libs/remix-debug/package.json
  22. 4
      libs/remix-lib/package.json
  23. 1
      libs/remix-lib/src/types/ICompilerApi.ts
  24. 6
      libs/remix-simulator/package.json
  25. 2
      libs/remix-simulator/src/methods/blocks.ts
  26. 6
      libs/remix-solidity/package.json
  27. 10
      libs/remix-tests/package.json
  28. 2
      libs/remix-ui/editor/src/lib/remix-plugin-types.ts
  29. 4
      libs/remix-ui/editor/src/lib/web-types.ts
  30. 6
      libs/remix-ui/helper/src/lib/helper-components.tsx
  31. 12
      libs/remix-ui/home-tab/src/lib/components/rssFeed.css
  32. 42
      libs/remix-ui/home-tab/src/lib/components/rssFeed.tsx
  33. 19
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx
  34. 61
      libs/remix-ui/publish-to-storage/src/lib/publish-to-storage.tsx
  35. 9
      libs/remix-ui/publish-to-storage/src/lib/publishOnSwarm.tsx
  36. 44
      libs/remix-ui/publish-to-storage/src/lib/publishToIPFS.tsx
  37. 2
      libs/remix-ui/run-tab/src/lib/components/environment.tsx
  38. 4
      libs/remix-ui/search/src/lib/context/context.tsx
  39. 1
      libs/remix-ui/settings/src/lib/constants.ts
  40. 111
      libs/remix-ui/settings/src/lib/remix-ui-settings.tsx
  41. 9
      libs/remix-ui/settings/src/lib/settingsAction.ts
  42. 102
      libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx
  43. 4
      libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts
  44. 17
      libs/remix-ui/solidity-compiler/src/lib/solidity-compiler.tsx
  45. 4
      libs/remix-ui/solidity-compiler/src/lib/types/index.ts
  46. 1
      libs/remix-ui/workspace/src/lib/css/remix-ui-workspace.css
  47. 4
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  48. 4
      libs/remix-url-resolver/package.json
  49. 10
      libs/remix-ws-templates/package.json
  50. 23
      libs/remix-ws-templates/src/templates/ozerc20/scripts/ethers-lib.ts
  51. 19
      libs/remix-ws-templates/src/templates/ozerc20/scripts/web3-lib.ts
  52. 23
      libs/remix-ws-templates/src/templates/ozerc721/scripts/ethers-lib.ts
  53. 19
      libs/remix-ws-templates/src/templates/ozerc721/scripts/web3-lib.ts
  54. 21
      libs/remix-ws-templates/src/templates/remixDefault/scripts/ethers-lib.ts
  55. 21
      libs/remix-ws-templates/src/templates/remixDefault/scripts/web3-lib.ts
  56. 23
      libs/remix-ws-templates/src/templates/zeroxErc20/scripts/ethers-lib.ts
  57. 19
      libs/remix-ws-templates/src/templates/zeroxErc20/scripts/web3-lib.ts
  58. 2
      libs/remixd/package.json
  59. 5
      package.json

@ -9,18 +9,20 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 14.17.6
- run: yarn install - run: yarn install
- run: ls - run: ls
- run: pwd - run: pwd
- run: yarn run downloadsolc_assets - run: yarn run downloadsolc_assets
- run: yarn run build:production - run: yarn run build:production
- run: echo "action_state=$('./apps/remix-ide/ci/publishIpfs')" >> $GITHUB_ENV - run: echo "action_state=$('./apps/remix-ide/ci/publishIpfs' ${{ secrets.IPFS_PROJET_ID }} ${{ secrets.IPFS_PROJECT_SECRET }})" >> $GITHUB_ENV
- uses: mshick/add-pr-comment@v1 - uses: mshick/add-pr-comment@v1
with: with:
message: | message: |
ipfs://${{ env.action_state }} ipfs://${{ env.action_state }}
https://ipfs.remixproject.org/ipfs/${{ env.action_state }} https://remix-project.infura-ipfs.io/ipfs/${{ env.action_state }}
https://gateway.ipfs.io/ipfs/${{ env.action_state }}
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
repo-token-user-login: 'github-actions[bot]' # The user.login for temporary GitHub tokens repo-token-user-login: 'github-actions[bot]' # The user.login for temporary GitHub tokens
allow-repeats: false # This is the default allow-repeats: false # This is the default

@ -2,7 +2,7 @@ import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events' import EventEmitter from 'events'
class VerifyContracts extends EventEmitter { class VerifyContracts extends EventEmitter {
command (this: NightwatchBrowser, compiledContractNames: string[], opts = { wait: 1000, version: null }): NightwatchBrowser { command (this: NightwatchBrowser, compiledContractNames: string[], opts = { wait: 1000, version: null, runs: '200' }): NightwatchBrowser {
this.api.perform((done) => { this.api.perform((done) => {
verifyContracts(this.api, compiledContractNames, opts, () => { verifyContracts(this.api, compiledContractNames, opts, () => {
done() done()
@ -13,13 +13,13 @@ class VerifyContracts extends EventEmitter {
} }
} }
function verifyContracts (browser: NightwatchBrowser, compiledContractNames: string[], opts: { wait: number, version?: string }, callback: VoidFunction) { function verifyContracts (browser: NightwatchBrowser, compiledContractNames: string[], opts: { wait: number, version?: string, runs?: string }, callback: VoidFunction) {
browser browser
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.pause(opts.wait) .pause(opts.wait)
.pause(5000) .pause(5000)
.waitForElementPresent('*[data-id="compiledContracts"] option', 60000) .waitForElementPresent('*[data-id="compiledContracts"] option', 60000)
.perform((done) => { .perform(async (done) => {
if (opts.version) { if (opts.version) {
browser browser
.click('*[data-id="compilation-details"]') .click('*[data-id="compilation-details"]')
@ -36,10 +36,28 @@ function verifyContracts (browser: NightwatchBrowser, compiledContractNames: str
done() done()
callback() callback()
}) })
} else { } if (opts.runs) {
compiledContractNames.forEach((name) => { browser
browser.waitForElementContainsText('[data-id="compiledContracts"]', name, 60000) .click('*[data-id="compilation-details"]')
.waitForElementVisible('*[data-id="remixui_treeviewitem_metadata"]')
.pause(2000)
.click('*[data-id="remixui_treeviewitem_metadata"]')
.waitForElementVisible('*[data-id="treeViewDivtreeViewItemsettings"]')
.pause(2000)
.click('*[data-id="treeViewDivtreeViewItemsettings"]')
.waitForElementVisible('*[data-id="treeViewDivtreeViewItemoptimizer"]')
.click('*[data-id="treeViewDivtreeViewItemoptimizer"]')
.waitForElementVisible('*[data-id="treeViewDivruns"]')
.assert.containsText('*[data-id="treeViewDivruns"]', `${opts.runs}`)
.click('[data-id="workspacesModalDialog-modal-footer-ok-react"]')
.perform(() => {
done()
callback()
}) })
} else {
for (const index in compiledContractNames) {
await browser.waitForElementContainsText('[data-id="compiledContracts"]', compiledContractNames[index], 60000)
}
done() done()
callback() callback()
} }

@ -122,6 +122,24 @@ module.exports = {
}) })
// Test in Udapp UI , treeViewDiv0 shows returned value on method click // Test in Udapp UI , treeViewDiv0 shows returned value on method click
.assert.containsText('*[data-id="treeViewDiv0"]', 'bytes32: winnerName_ 0x48656c6c6f20576f726c64210000000000000000000000000000000000000000') .assert.containsText('*[data-id="treeViewDiv0"]', 'bytes32: winnerName_ 0x48656c6c6f20576f726c64210000000000000000000000000000000000000000')
},
'Compile Ballot using config file': function (browser: NightwatchBrowser) {
browser
.addFile('cf.json', {content: configFile})
.clickLaunchIcon('solidity')
.waitForElementVisible('*[data-id="scConfigExpander"]')
.click('*[data-id="scConfigExpander"]')
.waitForElementVisible('*[data-id="scFileConfiguration"]', 10000)
.click('*[data-id="scFileConfiguration"]')
.waitForElementVisible('*[data-id="scConfigChangeFilePath"]', 10000)
.click('*[data-id="scConfigChangeFilePath"]')
.waitForElementVisible('*[data-id="scConfigFilePathInput"]', 10000)
.clearValue('*[data-id="scConfigFilePathInput"]')
.setValue('*[data-id="scConfigFilePathInput"]', 'cf.json')
.sendKeys('*[data-id$="scConfigFilePathInput"]', browser.Keys.ENTER)
.openFile('Untitled.sol')
.verifyContracts(['Ballot'], {wait: 2000, runs: '300'})
.end() .end()
} }
} }
@ -190,6 +208,7 @@ const stateCheck = {
immutable: false immutable: false
} }
} }
const ballotABI = `[ const ballotABI = `[
{ {
"inputs": [ "inputs": [
@ -356,3 +375,22 @@ const ballotABI = `[
"type": "function" "type": "function"
} }
]` ]`
const configFile = `
{
"language": "Solidity",
"settings": {
"optimizer": {
"enabled": true,
"runs": 300
},
"outputSelection": {
"*": {
"": ["ast"],
"*": ["abi", "metadata", "devdoc", "userdoc", "storageLayout", "evm.legacyAssembly", "evm.bytecode", "evm.deployedBytecode", "evm.methodIdentifiers", "evm.gasEstimates", "evm.assembly"]
}
},
"evmVersion": "byzantium"
}
}
`

@ -168,3 +168,4 @@ contract DoesNotCompile {
function fStackLimit(uint u1, uint u2, uint u3, uint u4, uint u5, uint u6, uint u7, uint u8, uint u9, uint u10, uint u11, uint u12) public { function fStackLimit(uint u1, uint u2, uint u3, uint u4, uint u5, uint u6, uint u7, uint u8, uint u9, uint u10, uint u11, uint u12) public {
} }
}` }`

@ -231,7 +231,6 @@ module.exports = {
'Should get current files #group7': async function (browser: NightwatchBrowser) { 'Should get current files #group7': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:readdir', { await clickAndCheckLog(browser, 'fileManager:readdir', {
'compiler_config.json': { isDirectory: false },
contracts: { isDirectory: true }, contracts: { isDirectory: true },
scripts: { isDirectory: true }, scripts: { isDirectory: true },
tests: { isDirectory: true }, tests: { isDirectory: true },
@ -286,15 +285,12 @@ module.exports = {
'Should create empty workspace #group2': async function (browser: NightwatchBrowser) { 'Should create empty workspace #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, ['emptyworkspace', true]) await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, ['emptyworkspace', true])
await clickAndCheckLog(browser, 'filePanel:getCurrentWorkspace', { name: 'emptyworkspace', isLocalhost: false, absolutePath: '.workspaces/emptyworkspace' }, null, null) await clickAndCheckLog(browser, 'filePanel:getCurrentWorkspace', { name: 'emptyworkspace', isLocalhost: false, absolutePath: '.workspaces/emptyworkspace' }, null, null)
await clickAndCheckLog(browser, 'fileManager:readdir', { await clickAndCheckLog(browser, 'fileManager:readdir', {}, null, '/')
'compiler_config.json': { isDirectory: false }
}, null, '/')
}, },
'Should create workspace #group2': async function (browser: NightwatchBrowser) { 'Should create workspace #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, 'testspace') await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, 'testspace')
await clickAndCheckLog(browser, 'filePanel:getCurrentWorkspace', { name: 'testspace', isLocalhost: false, absolutePath: '.workspaces/testspace' }, null, null) await clickAndCheckLog(browser, 'filePanel:getCurrentWorkspace', { name: 'testspace', isLocalhost: false, absolutePath: '.workspaces/testspace' }, null, null)
await clickAndCheckLog(browser, 'fileManager:readdir', { await clickAndCheckLog(browser, 'fileManager:readdir', {
'compiler_config.json': { isDirectory: false },
contracts: { isDirectory: true }, contracts: { isDirectory: true },
scripts: { isDirectory: true }, scripts: { isDirectory: true },
tests: { isDirectory: true }, tests: { isDirectory: true },

@ -19,17 +19,22 @@ module.exports = {
.openFile('contracts/3_Ballot.sol') .openFile('contracts/3_Ballot.sol')
.verifyContracts(['Ballot']) .verifyContracts(['Ballot'])
.click('#publishOnIpfs') .click('#publishOnIpfs')
.pause(2000)
.waitForElementVisible('[data-id="publishToStorageModalDialogModalBody-react"]', 60000)
.click('[data-id="publishToStorage-modal-footer-ok-react"]')
.pause(8000) .pause(8000)
.waitForElementVisible('[data-id="publishToStorageModalDialogModalBody-react"]', 60000) .waitForElementVisible('[data-id="publishToStorageModalDialogModalBody-react"]', 60000)
.getText('[data-id="publishToStorageModalDialogModalBody-react"]', (result) => { .getText('[data-id="publishToStorageModalDialogModalBody-react"]', (result) => {
const value = <string>(result.value) const value = <string>(result.value)
browser.perform((done) => { browser.perform((done) => {
if (value.indexOf('Metadata of "ballot" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed') if (value.indexOf('Metadata and sources of "ballot" were published successfully.') === -1) browser.assert.fail('ipfs deploy failed')
done() done()
}) })
}) })
.click('[data-id="publishToStorage-modal-footer-ok-react"]') .click('[data-id="publishToStorage-modal-footer-ok-react"]')
.openFile('ipfs/QmSUodhSvoorFL5m5CNqve8YvmuBpjCq17NbTf4GUX8ydw')
.openFile('ipfs/QmXYUS1ueS22EqNVRaKuZa31EgHLjKZ8uTM8vWhQLxa3pw')
}, },
/* Disableing the test untill refactoring and the new swarm usage /* Disableing the test untill refactoring and the new swarm usage
@ -41,7 +46,7 @@ module.exports = {
const value = <string>(result.value) const value = <string>(result.value)
browser.perform((done) => { browser.perform((done) => {
if (value.indexOf('Metadata of "ballot" was published successfully.') === -1) browser.assert.fail('swarm deploy failed') if (value.indexOf('Metadata and sources of "ballot" were published successfully.') === -1) browser.assert.fail('swarm deploy failed')
if (value.indexOf('bzz') === -1) browser.assert.fail('swarm deploy failed') if (value.indexOf('bzz') === -1) browser.assert.fail('swarm deploy failed')
done() done()
}) })
@ -61,11 +66,13 @@ module.exports = {
.waitForElementVisible('*[data-id="Deploy - transact (not payable)"]') .waitForElementVisible('*[data-id="Deploy - transact (not payable)"]')
.click('*[data-id="Deploy - transact (not payable)"]') .click('*[data-id="Deploy - transact (not payable)"]')
.pause(5000) .pause(5000)
.waitForElementVisible('[data-id="udappModalDialogModalBody-react"]') .waitForElementVisible('[data-id="udappModalDialogModalBody-react"]', 60000)
.modalFooterOKClick('udapp')
.pause(8000)
.getText('[data-id="udappModalDialogModalBody-react"]', (result) => { .getText('[data-id="udappModalDialogModalBody-react"]', (result) => {
const value = typeof result.value === 'string' ? result.value : null const value = typeof result.value === 'string' ? result.value : null
if (value.indexOf('Metadata of "storage" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed') if (value.indexOf('Metadata and sources of "storage" were published successfully.') === -1) browser.assert.fail('ipfs deploy failed')
}) })
.modalFooterOKClick('udapp') .modalFooterOKClick('udapp')
}, },

@ -33,11 +33,11 @@ module.exports = {
.clearValue('*[id="search_include"]').pause(2000) .clearValue('*[id="search_include"]').pause(2000)
.setValue('*[id="search_include"]', '**').sendKeys('*[id="search_include"]', browser.Keys.ENTER).pause(4000) .setValue('*[id="search_include"]', '**').sendKeys('*[id="search_include"]', browser.Keys.ENTER).pause(4000)
.elements('css selector', '.search_plugin_search_line', (res) => { .elements('css selector', '.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 48) Array.isArray(res.value) && browser.assert.equal(res.value.length, 61)
}) })
.setValue('*[id="search_exclude"]', ',contracts/**').sendKeys('*[id="search_exclude"]', browser.Keys.ENTER).pause(4000) .setValue('*[id="search_exclude"]', ',contracts/**').sendKeys('*[id="search_exclude"]', browser.Keys.ENTER).pause(4000)
.elements('css selector', '.search_plugin_search_line', (res) => { .elements('css selector', '.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 42) Array.isArray(res.value) && browser.assert.equal(res.value.length, 55)
}) })
.clearValue('*[id="search_include"]').setValue('*[id="search_include"]', '*.sol, *.js, *.txt') .clearValue('*[id="search_include"]').setValue('*[id="search_include"]', '*.sol, *.js, *.txt')
.clearValue('*[id="search_exclude"]').setValue('*[id="search_exclude"]', '.*/**/*') .clearValue('*[id="search_exclude"]').setValue('*[id="search_exclude"]', '.*/**/*')

@ -291,7 +291,7 @@ const deployWithEthersJs = `
// 'web3Provider' is a remix global variable object // 'web3Provider' is a remix global variable object
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner() const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
let factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer); let factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
let contract = await factory.deploy(...constructorArgs); let contract = await factory.deploy(...constructorArgs);
@ -320,7 +320,7 @@ describe("Storage with lib", function () {
// Make sure contract is compiled and artifacts are generated // Make sure contract is compiled and artifacts are generated
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json')) const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json'))
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner() const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer); let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
let storage = await Storage.deploy(); let storage = await Storage.deploy();
console.log('storage contract Address: ' + storage.address); console.log('storage contract Address: ' + storage.address);
await storage.deployed() await storage.deployed()
@ -330,7 +330,7 @@ describe("Storage with lib", function () {
it("test updating and retrieving updated value", async function () { it("test updating and retrieving updated value", async function () {
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json')) const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json'))
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner() const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer); let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
let storage = await Storage.deploy(); let storage = await Storage.deploy();
await storage.deployed() await storage.deployed()
const setValue = await storage.store(56); const setValue = await storage.store(56);
@ -341,7 +341,7 @@ describe("Storage with lib", function () {
it("fail test updating and retrieving updated value", async function () { it("fail test updating and retrieving updated value", async function () {
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json')) const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json'))
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner() const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer); let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
let storage = await Storage.deploy(); let storage = await Storage.deploy();
await storage.deployed() await storage.deployed()
const setValue = await storage.store(56); const setValue = await storage.store(56);

@ -68,14 +68,14 @@ module.exports = {
.click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.pause(2000) .pause(2000)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<any> => {`) !== -1, browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {`) !== -1,
'Incorrect content') 'Incorrect content')
}) })
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.pause(100) .pause(100)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string): Promise<any> => { `) !== -1, browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {`) !== -1,
'Incorrect content') 'Incorrect content')
}) })
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]')
@ -107,8 +107,7 @@ module.exports = {
const fileList = document.querySelector('*[data-id="treeViewUltreeViewMenu"]') const fileList = document.querySelector('*[data-id="treeViewUltreeViewMenu"]')
return fileList.getElementsByTagName('li').length; return fileList.getElementsByTagName('li').length;
}, [], function(result){ }, [], function(result){
// check there are no files in FE except config file browser.assert.equal(result.value, 0, 'Incorrect number of files');
browser.assert.equal(result.value, 1, 'Incorrect number of files');
}); });
}, },
@ -146,14 +145,14 @@ module.exports = {
.click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.pause(100) .pause(100)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<any> => {`) !== -1, browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {`) !== -1,
'Incorrect content') 'Incorrect content')
}) })
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.pause(100) .pause(100)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string): Promise<any> => { `) !== -1, browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {`) !== -1,
'Incorrect content') 'Incorrect content')
}) })
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]')
@ -194,14 +193,14 @@ module.exports = {
.click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.pause(100) .pause(100)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<any> => {`) !== -1, browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {`) !== -1,
'Incorrect content') 'Incorrect content')
}) })
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.pause(100) .pause(100)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string): Promise<any> => { `) !== -1, browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {`) !== -1,
'Incorrect content') 'Incorrect content')
}) })
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]') .assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]')

@ -11,7 +11,7 @@ declare module 'nightwatch' {
testContracts(fileName: string, contractCode: NightwatchContractContent, compiledContractNames: string[]): NightwatchBrowser, testContracts(fileName: string, contractCode: NightwatchContractContent, compiledContractNames: string[]): NightwatchBrowser,
setEditorValue(value: string, callback?: () => void): NightwatchBrowser, setEditorValue(value: string, callback?: () => void): NightwatchBrowser,
addFile(name: string, content: NightwatchContractContent): NightwatchBrowser, addFile(name: string, content: NightwatchContractContent): NightwatchBrowser,
verifyContracts(compiledContractNames: string[], opts?: { wait: number, version?: string }): NightwatchBrowser, verifyContracts(compiledContractNames: string[], opts?: { wait: number, version?: string, runs?: string }): NightwatchBrowser,
selectAccount(account?: string): NightwatchBrowser, selectAccount(account?: string): NightwatchBrowser,
clickFunction(fnFullName: string, expectedInput?: NightwatchClickFunctionExpectedInput): NightwatchBrowser, clickFunction(fnFullName: string, expectedInput?: NightwatchClickFunctionExpectedInput): NightwatchBrowser,
testFunction(txHash: string, expectedInput: NightwatchTestFunctionExpectedInput): NightwatchBrowser, testFunction(txHash: string, expectedInput: NightwatchTestFunctionExpectedInput): NightwatchBrowser,

@ -5,8 +5,14 @@ const { globSource } = IpfsHttpClient
const folder = process.cwd() + '/dist/apps/remix-ide'; const folder = process.cwd() + '/dist/apps/remix-ide';
(async () => { (async () => {
const host = 'ipfs.remixproject.org' const host = 'ipfs.infura.io'
const ipfs = IpfsHttpClient({ host, port: 443, protocol: 'https' }) const projectId = process.argv[2]
const projectSecret = process.argv[3]
const auth = 'Basic ' + Buffer.from(projectId + ':' + projectSecret).toString('base64')
const ipfs = IpfsHttpClient({ port: 5001, host, protocol: 'https', headers: {
authorization: auth
} })
try { try {
let result = await ipfs.add(globSource(folder, { recursive: true}), { pin: false }) let result = await ipfs.add(globSource(folder, { recursive: true}), { pin: false })
const hash = result.cid.toString() const hash = result.cid.toString()

@ -1,5 +1,6 @@
'use strict' 'use strict'
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { resolve } from 'path'
import { EditorUI } from '@remix-ui/editor' // eslint-disable-line import { EditorUI } from '@remix-ui/editor' // eslint-disable-line
import { Plugin } from '@remixproject/engine' import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
@ -140,14 +141,60 @@ class Editor extends Plugin {
this.on('sidePanel', 'pluginDisabled', (name) => { this.on('sidePanel', 'pluginDisabled', (name) => {
this.clearAllDecorationsFor(name) this.clearAllDecorationsFor(name)
}) })
this.on('fileManager', 'fileClosed', (name) => { this.on('theme', 'themeLoaded', (theme) => {
if (name === this.currentFile) { this.currentThemeType = theme.quality
this.renderComponent()
})
this.on('fileManager', 'currentFileChanged', async (name) => {
if (name.endsWith('.ts')) {
// extract the import, resolve their content
// and add the imported files to Monaco through the `addModel`
// so Monaco can provide auto completion
let content = await this.call('fileManager', 'readFile', name)
const paths = name.split('/')
paths.pop()
const fromPath = paths.join('/') // get current execution context path
for (const match of content.matchAll(/import\s+.*\s+from\s+(?:"(.*?)"|'(.*?)')/g)) {
let path = match[2]
if (path.startsWith('./') || path.startsWith('../')) path = resolve(fromPath, path)
if (path.startsWith('/')) path = path.substring(1)
if (!path.endsWith('.ts')) path = path + '.ts'
if (await this.call('fileManager', 'exists', path)) {
content = await this.call('fileManager', 'readFile', path)
this.emit('addModel', content, 'typescript', path, false)
}
}
}
})
this.on('fileManager', 'noFileSelected', async () => {
this.currentFile = null this.currentFile = null
this.renderComponent() this.renderComponent()
})
this.on('fileManager', 'currentFileChanged', async (name) => {
if (name.endsWith('.ts')) {
// extract the import, resolve their content
// and add the imported files to Monaco through the `addModel`
// so Monaco can provide auto completion
let content = await this.call('fileManager', 'readFile', name)
const paths = name.split('/')
paths.pop()
const fromPath = paths.join('/') // get current execution context path
for (const match of content.matchAll(/import\s+.*\s+from\s+(?:"(.*?)"|'(.*?)')/g)) {
let path = match[2]
if (path.startsWith('./') || path.startsWith('../')) path = resolve(fromPath, path)
if (path.startsWith('/')) path = path.substring(1)
if (!path.endsWith('.ts')) path = path + '.ts'
if (await this.call('fileManager', 'exists', path)) {
content = await this.call('fileManager', 'readFile', path)
this.emit('addModel', content, 'typescript', path, false)
}
}
} }
}) })
this.on('theme', 'themeLoaded', (theme) => {
this.currentThemeType = theme.quality this.on('fileManager', 'noFileSelected', async () => {
this.currentFile = null
this.renderComponent() this.renderComponent()
}) })
try { try {

@ -4,7 +4,7 @@ import * as packageJson from '../../../../../package.json'
import Registry from '../state/registry' import Registry from '../state/registry'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import { RemixAppManager } from '../../../../../libs/remix-ui/plugin-manager/src/types' import { RemixAppManager } from '../../../../../libs/remix-ui/plugin-manager/src/types'
import { fileChangedToastMsg, storageFullMessage } from '@remix-ui/helper' import { fileChangedToastMsg, recursivePasteToastMsg, storageFullMessage } from '@remix-ui/helper'
import helper from '../../lib/helper.js' import helper from '../../lib/helper.js'
/* /*
@ -275,7 +275,7 @@ class FileManager extends Plugin {
const provider = this.fileProviderOf(src) const provider = this.fileProviderOf(src)
if (provider.isSubDirectory(src, dest)) { if (provider.isSubDirectory(src, dest)) {
this.call('notification', 'toast', 'File(s) to paste is an ancestor of the destination folder') this.call('notification', 'toast', recursivePasteToastMsg())
} else { } else {
await this.inDepthCopy(src, dest) await this.inDepthCopy(src, dest)
} }

@ -57,9 +57,6 @@ export class Layout extends Plugin {
this.panels.main.active = false this.panels.main.active = false
this.event.emit('change', null) this.event.emit('change', null)
}) })
this.on('tabs', 'tabCountChanged', async count => {
if (!count) await this.call('manager', 'activatePlugin', 'home')
})
this.on('manager', 'activate', (profile: Profile) => { this.on('manager', 'activate', (profile: Profile) => {
switch (profile.name) { switch (profile.name) {
case 'filePanel': case 'filePanel':

@ -283,7 +283,12 @@ export class TabProxy extends Plugin {
delete this._handlers[name] delete this._handlers[name]
let previous = currentFileTab let previous = currentFileTab
this.loadedTabs = this.loadedTabs.filter((tab, index) => { this.loadedTabs = this.loadedTabs.filter((tab, index) => {
if (!previous && tab.name === name) previous = this.loadedTabs[index - 1] if (!previous && tab.name === name) {
if(index - 1 >= 0 && this.loadedTabs[index - 1])
previous = this.loadedTabs[index - 1]
else if (index + 1 && this.loadedTabs[index + 1])
previous = this.loadedTabs[index + 1]
}
return tab.name !== name return tab.name !== name
}) })
this.renderComponent() this.renderComponent()

@ -135,23 +135,28 @@ export class RemixdHandle extends WebsocketPlugin {
function remixdDialog () { function remixdDialog () {
const commandText = 'remixd' const commandText = 'remixd'
const fullCommandText = 'remixd -s <path-to-the-shared-folder> -u <remix-ide-instance-URL>'
return (<> return (<>
<div className=''> <div className=''>
<div className='mb-2 text-break'> <div className='mb-2 text-break'>
Access your local file system from Remix IDE using <a target="_blank" href="https://www.npmjs.com/package/@remix-project/remixd">Remixd NPM package</a>.<br/><br/> Access your local file system from Remix IDE using <a target="_blank" href="https://www.npmjs.com/package/@remix-project/remixd">Remixd NPM package</a>.
Remixd needs to be running in the background to load the files in localhost workspace. For more info, please check the <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">Remixd documentation</a>.
</div> </div>
<div className='mb-2 text-break'> <div className='mb-2 text-break'>
If you are just looking for the remixd command, here it is: Remixd <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">documentation</a>.
<br></br>
Go to your working directory and then run:
<br></br><b>{commandText}</b>
<CopyToClipboard data-id='remixdCopyCommand' content={commandText}></CopyToClipboard>
</div> </div>
<div className='mb-2 text-break'> <div className='mb-2 text-break'>
When connected, a session will be started between <em>{window.location.origin}</em> and your local file system at <i>ws://127.0.0.1:65520</i>. The remixd command is:
The shared folder will be in the "File Explorers" workspace named "localhost". <br/><b>{commandText}</b>
<br/>Read more about other <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#ports-usage">Remixd ports usage</a> </div>
<div className='mb-2 text-break'>
The remixd command without options uses the terminal's current directory as the shared directory and the shared Remix domain can only be https://remix.ethereum.org, https://remix-alpha.ethereum.org, or https://remix-beta.ethereum.org
</div>
<div className='mb-2 text-break'>
Example command with flags: <br/>
<b>{fullCommandText}</b>
</div>
<div className='mb-2 text-break'>
For info about ports, see <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#ports-usage">Remixd ports usage</a>
</div> </div>
<div className='mb-2 text-break'> <div className='mb-2 text-break'>
This feature is still in Alpha. We recommend to keep a backup of the shared folder. This feature is still in Alpha. We recommend to keep a backup of the shared folder.

@ -56,6 +56,10 @@ class CompileTab extends CompilerApiMixin(ViewPlugin) { // implements ICompilerA
this.renderComponent() this.renderComponent()
} }
onFileRemoved () {
this.renderComponent()
}
onNoFileSelected () { onNoFileSelected () {
this.renderComponent() this.renderComponent()
} }

@ -19,6 +19,7 @@ export const CompilerApiMixin = (Base) => class extends Base {
onCurrentFileChanged: (fileName: string) => void onCurrentFileChanged: (fileName: string) => void
// onResetResults: () => void // onResetResults: () => void
onSetWorkspace: (isLocalhost: boolean, workspaceName: string) => void onSetWorkspace: (isLocalhost: boolean, workspaceName: string) => void
onFileRemoved: (path: string) => void
onNoFileSelected: () => void onNoFileSelected: () => void
onCompilationFinished: (compilationDetails: { contractMap: { file: string } | Record<string, any>, contractsDetails: Record<string, any> }) => void onCompilationFinished: (compilationDetails: { contractMap: { file: string } | Record<string, any>, contractsDetails: Record<string, any> }) => void
onSessionSwitched: () => void onSessionSwitched: () => void
@ -240,6 +241,10 @@ export const CompilerApiMixin = (Base) => class extends Base {
if (this.onSetWorkspace) this.onSetWorkspace(workspace.isLocalhost, workspace.name) if (this.onSetWorkspace) this.onSetWorkspace(workspace.isLocalhost, workspace.name)
}) })
this.on('fileManager', 'fileRemoved', (path) => {
if (this.onFileRemoved) this.onFileRemoved(path)
})
this.on('remixd', 'rootFolderChanged', () => { this.on('remixd', 'rootFolderChanged', () => {
this.resetResults() this.resetResults()
if (this.onSetWorkspace) this.onSetWorkspace(true, 'localhost') if (this.onSetWorkspace) this.onSetWorkspace(true, 'localhost')
@ -282,36 +287,34 @@ export const CompilerApiMixin = (Base) => class extends Base {
type: 'warning' type: 'warning'
}) })
} else this.statusChanged({ key: 'succeed', title: 'compilation successful', type: 'success' }) } else this.statusChanged({ key: 'succeed', title: 'compilation successful', type: 'success' })
// Store the contracts
this.compilationDetails.contractsDetails = {}
this.compiler.visitContracts((contract) => {
this.compilationDetails.contractsDetails[contract.name] = parseContracts(
contract.name,
contract.object,
this.compiler.getSource(contract.file)
)
})
} else { } else {
const count = (data.errors ? data.errors.filter(error => error.severity === 'error').length : 0 + (data.error ? 1 : 0)) const count = (data.errors ? data.errors.filter(error => error.severity === 'error').length : 0 + (data.error ? 1 : 0))
this.statusChanged({ key: count, title: `compilation failed with ${count} error${count > 1 ? 's' : ''}`, type: 'error' }) this.statusChanged({ key: count, title: `compilation failed with ${count} error${count > 1 ? 's' : ''}`, type: 'error' })
} }
// Update contract Selection // Store the contracts and Update contract Selection
this.compilationDetails.contractMap = {} if (success) {
if (success) this.compiler.visitContracts((contract) => { this.compilationDetails.contractMap[contract.name] = contract }) this.compilationDetails = await this.visitsContractApi(source, data)
this.compilationDetails.target = source.target } else {
this.compilationDetails = {
contractMap: {},
contractsDetails: {},
target: source.target
}
}
if (this.onCompilationFinished) this.onCompilationFinished(this.compilationDetails) if (this.onCompilationFinished) this.onCompilationFinished(this.compilationDetails)
// set annotations // set annotations
if (data.errors) { if (data.errors) {
for (const error of data.errors) { for (const error of data.errors) {
let pos = helper.getPositionDetails(error.formattedMessage) let pos = helper.getPositionDetails(error.formattedMessage)
if (pos.errFile) { const file = pos.errFile
if (file) {
pos = { pos = {
row: pos.errLine, row: pos.errLine,
column: pos.errCol, column: pos.errCol,
text: error.formattedMessage, text: error.formattedMessage,
type: error.severity type: error.severity
} }
await this.call('editor', 'addAnnotation', pos, pos.errFile) await this.call('editor', 'addAnnotation', pos, file)
} }
} }
} }
@ -332,11 +335,40 @@ export const CompilerApiMixin = (Base) => class extends Base {
// ctrl+s or command+s // ctrl+s or command+s
if ((e.metaKey || e.ctrlKey) && !e.shiftKey && e.keyCode === 83 && this.currentFile !== '') { if ((e.metaKey || e.ctrlKey) && !e.shiftKey && e.keyCode === 83 && this.currentFile !== '') {
e.preventDefault() e.preventDefault()
if (this.currentFile && (this.currentFile.endsWith('.sol') || this.currentFile.endsWith('.yul'))) {
if(await this.getAppParameter('hardhat-compilation')) this.compileTabLogic.runCompiler('hardhat') if(await this.getAppParameter('hardhat-compilation')) this.compileTabLogic.runCompiler('hardhat')
else if(await this.getAppParameter('truffle-compilation')) this.compileTabLogic.runCompiler('truffle') else if(await this.getAppParameter('truffle-compilation')) this.compileTabLogic.runCompiler('truffle')
else this.compileTabLogic.runCompiler(undefined) else this.compileTabLogic.runCompiler(undefined)
} }
} }
}
window.document.addEventListener('keydown', this.data.eventHandlers.onKeyDown) window.document.addEventListener('keydown', this.data.eventHandlers.onKeyDown)
} }
async visitsContractApi (source, data): Promise<{ contractMap: { file: string } | Record<string, any>, contractsDetails: Record<string, any>, target?: string }> {
return new Promise((resolve) => {
if (!data.contracts || (data.contracts && Object.keys(data.contracts).length === 0)) {
return resolve({
contractMap: {},
contractsDetails: {},
target: source.target
})
}
const contractMap = {}
const contractsDetails = {}
this.compiler.visitContracts((contract) => {
contractMap[contract.name] = contract
contractsDetails[contract.name] = parseContracts(
contract.name,
contract.object,
this.compiler.getSource(contract.file)
)
})
return resolve({
contractMap,
contractsDetails,
target: source.target
})
})
}
} }

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-analyzer", "name": "@remix-project/remix-analyzer",
"version": "0.5.22", "version": "0.5.23",
"description": "Tool to perform static analysis on Solidity smart contracts", "description": "Tool to perform static analysis on Solidity smart contracts",
"main": "src/index.js", "main": "src/index.js",
"types": "src/index.d.ts", "types": "src/index.d.ts",
@ -22,8 +22,8 @@
"@ethereumjs/block": "^3.5.1", "@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-astwalker": "^0.0.43", "@remix-project/remix-astwalker": "^0.0.44",
"@remix-project/remix-lib": "^0.5.13", "@remix-project/remix-lib": "^0.5.14",
"async": "^2.6.2", "async": "^2.6.2",
"ethereumjs-util": "^7.0.10", "ethereumjs-util": "^7.0.10",
"ethers": "^5.4.2", "ethers": "^5.4.2",
@ -52,5 +52,5 @@
"typescript": "^3.7.5" "typescript": "^3.7.5"
}, },
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78" "gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
} }

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-astwalker", "name": "@remix-project/remix-astwalker",
"version": "0.0.43", "version": "0.0.44",
"description": "Tool to walk through Solidity AST", "description": "Tool to walk through Solidity AST",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {
@ -37,7 +37,7 @@
"@ethereumjs/block": "^3.5.1", "@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.13", "@remix-project/remix-lib": "^0.5.14",
"@types/tape": "^4.2.33", "@types/tape": "^4.2.33",
"async": "^2.6.2", "async": "^2.6.2",
"ethereumjs-util": "^7.0.10", "ethereumjs-util": "^7.0.10",
@ -54,5 +54,5 @@
"tap-spec": "^5.0.0" "tap-spec": "^5.0.0"
}, },
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78" "gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
} }

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-debug", "name": "@remix-project/remix-debug",
"version": "0.5.13", "version": "0.5.14",
"description": "Tool to debug Ethereum transactions", "description": "Tool to debug Ethereum transactions",
"contributors": [ "contributors": [
{ {
@ -22,9 +22,9 @@
"@ethereumjs/common": "^2.5.0", "@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-astwalker": "^0.0.43", "@remix-project/remix-astwalker": "^0.0.44",
"@remix-project/remix-lib": "^0.5.13", "@remix-project/remix-lib": "^0.5.14",
"@remix-project/remix-simulator": "^0.2.13", "@remix-project/remix-simulator": "^0.2.14",
"ansi-gray": "^0.1.1", "ansi-gray": "^0.1.1",
"async": "^2.6.2", "async": "^2.6.2",
"color-support": "^1.1.3", "color-support": "^1.1.3",
@ -68,5 +68,5 @@
}, },
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-debug#readme", "homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-debug#readme",
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78" "gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
} }

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-lib", "name": "@remix-project/remix-lib",
"version": "0.5.13", "version": "0.5.14",
"description": "Library to various Remix tools", "description": "Library to various Remix tools",
"contributors": [ "contributors": [
{ {
@ -54,5 +54,5 @@
}, },
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-lib#readme", "homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-lib#readme",
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78" "gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
} }

@ -25,6 +25,7 @@ export interface ICompilerApi {
onCurrentFileChanged: (fileName: string) => void onCurrentFileChanged: (fileName: string) => void
// onResetResults: () => void, // onResetResults: () => void,
onSetWorkspace: (isLocalhost: boolean, workspaceName: string) => void onSetWorkspace: (isLocalhost: boolean, workspaceName: string) => void
onFileRemoved: (path: string) => void
onNoFileSelected: () => void onNoFileSelected: () => void
onCompilationFinished: (contractsDetails: any, contractMap: any) => void onCompilationFinished: (contractsDetails: any, contractMap: any) => void
onSessionSwitched: () => void onSessionSwitched: () => void

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-simulator", "name": "@remix-project/remix-simulator",
"version": "0.2.13", "version": "0.2.14",
"description": "Ethereum IDE and tools for the web", "description": "Ethereum IDE and tools for the web",
"contributors": [ "contributors": [
{ {
@ -18,7 +18,7 @@
"@ethereumjs/common": "^2.5.0", "@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.13", "@remix-project/remix-lib": "^0.5.14",
"ansi-gray": "^0.1.1", "ansi-gray": "^0.1.1",
"async": "^3.1.0", "async": "^3.1.0",
"body-parser": "^1.18.2", "body-parser": "^1.18.2",
@ -67,5 +67,5 @@
}, },
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-simulator#readme", "homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-simulator#readme",
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78" "gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
} }

@ -40,7 +40,6 @@ export class Blocks {
return cb(new Error('block not found')) return cb(new Error('block not found'))
} }
console.log(block.transactions)
const transactions = block.transactions.map((t) => { const transactions = block.transactions.map((t) => {
const hash = '0x' + t.hash().toString('hex') const hash = '0x' + t.hash().toString('hex')
const tx = this.vmContext.txByHash[hash] const tx = this.vmContext.txByHash[hash]
@ -95,7 +94,6 @@ export class Blocks {
eth_getBlockByHash (payload, cb) { eth_getBlockByHash (payload, cb) {
const block = this.vmContext.blocks[payload.params[0]] const block = this.vmContext.blocks[payload.params[0]]
console.log(block.transactions)
const transactions = block.transactions.map((t) => { const transactions = block.transactions.map((t) => {
const hash = '0x' + t.hash().toString('hex') const hash = '0x' + t.hash().toString('hex')
const tx = this.vmContext.txByHash[hash] const tx = this.vmContext.txByHash[hash]

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-solidity", "name": "@remix-project/remix-solidity",
"version": "0.4.13", "version": "0.5.0",
"description": "Tool to load and run Solidity compiler", "description": "Tool to load and run Solidity compiler",
"main": "src/index.js", "main": "src/index.js",
"types": "src/index.d.ts", "types": "src/index.d.ts",
@ -18,7 +18,7 @@
"@ethereumjs/block": "^3.5.1", "@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.13", "@remix-project/remix-lib": "^0.5.14",
"async": "^2.6.2", "async": "^2.6.2",
"eslint-scope": "^5.0.0", "eslint-scope": "^5.0.0",
"ethereumjs-util": "^7.0.10", "ethereumjs-util": "^7.0.10",
@ -61,5 +61,5 @@
}, },
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-solidity#readme", "homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-solidity#readme",
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78" "gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
} }

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-tests", "name": "@remix-project/remix-tests",
"version": "0.2.13", "version": "0.2.14",
"description": "Tool to test Solidity smart contracts", "description": "Tool to test Solidity smart contracts",
"main": "src/index.js", "main": "src/index.js",
"types": "./src/index.d.ts", "types": "./src/index.d.ts",
@ -39,9 +39,9 @@
"@ethereumjs/common": "^2.5.0", "@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.13", "@remix-project/remix-lib": "^0.5.14",
"@remix-project/remix-simulator": "^0.2.13", "@remix-project/remix-simulator": "^0.2.14",
"@remix-project/remix-solidity": "^0.4.13", "@remix-project/remix-solidity": "^0.5.0",
"ansi-gray": "^0.1.1", "ansi-gray": "^0.1.1",
"async": "^2.6.0", "async": "^2.6.0",
"axios": ">=0.21.1", "axios": ">=0.21.1",
@ -79,5 +79,5 @@
"typescript": "^3.3.1" "typescript": "^3.3.1"
}, },
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78" "gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
} }

@ -226,7 +226,7 @@ declare interface CondensedCompilationInput {
optimize: boolean optimize: boolean
/** e.g: 0.6.8+commit.0bbfe453 */ /** e.g: 0.6.8+commit.0bbfe453 */
version: string version: string
evmVersion?: 'istanbul' | 'petersburg' | 'constantinople' | 'byzantium' | 'spuriousDragon' | 'tangerineWhistle' | 'homestead' evmVersion?: 'berlin' | 'istanbul' | 'petersburg' | 'constantinople' | 'byzantium' | 'spuriousDragon' | 'tangerineWhistle' | 'homestead'
} }
declare interface ContentImport { declare interface ContentImport {

@ -199,6 +199,10 @@ export const loadTypes = async (monaco) => {
const indexWeb3Personal = await import('raw-loader!web3-eth-personal/types/index.d.ts') const indexWeb3Personal = await import('raw-loader!web3-eth-personal/types/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(indexWeb3Personal.default, `file:///node_modules/@types/web3-eth-personal/index.d.ts`) monaco.languages.typescript.typescriptDefaults.addExtraLib(indexWeb3Personal.default, `file:///node_modules/@types/web3-eth-personal/index.d.ts`)
// @ts-ignore
const indexWeb3Contract = await import('raw-loader!web3-eth-contract/types/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(indexWeb3Contract.default, `file:///node_modules/@types/web3-eth-contract/index.d.ts`)
// @ts-ignore // @ts-ignore
const indexWeb3Net = await import('raw-loader!web3-net/types/index.d.ts') const indexWeb3Net = await import('raw-loader!web3-net/types/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(indexWeb3Net.default, `file:///node_modules/@types/web3-net/index.d.ts`) monaco.languages.typescript.typescriptDefaults.addExtraLib(indexWeb3Net.default, `file:///node_modules/@types/web3-net/index.d.ts`)

@ -78,3 +78,9 @@ export const storageFullMessage = () => (
</span> </span>
</div> </div>
) )
export const recursivePasteToastMsg = () => (
<div>
File(s) to paste is an ancestor of the destination folder
</div>
)

@ -0,0 +1,12 @@
.RSSFeed-item img {
width: 100%;
}
.RSSFeed-item .truncate {
max-height: 500px;
overflow: hidden;
}
.RSSFeed-item .more-button {
}

@ -0,0 +1,42 @@
import React, { useState, useEffect } from "react";
import Parser from "rss-parser";
import './rssFeed.css';
interface RSSFeedProps {
feedUrl: string,
maxItems: number,
}
export function RSSFeed({ feedUrl, maxItems }: RSSFeedProps) {
const [feed, setFeed] = useState(null);
useEffect(() => {
const fetchData = async () => {
const parser = new Parser()
const feed = await parser.parseURL(feedUrl);
for (const item of feed.items) {
item.content = item['content:encoded']
item.date = new Date(item.pubDate).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric'
})
}
setFeed(feed);
};
fetchData();
}, [feedUrl]);
return (<>
{feed && feed.items.slice(0, maxItems).map((item: any, index: any) => (
<div className='RSSFeed-item' key={index}>
<a target="_blank" href={item.link}><h3>{item.title}</h3></a>
<p>Author: {item.creator}</p>
<h4>{item.date}</h4>
<div className="truncate" dangerouslySetInnerHTML={{ __html: item.content }} />
<a className="more-button btn mb-3" target="_blank" href={item.link}>READ MORE</a>
<hr></hr>
</div>
))}
</>)
}

@ -5,6 +5,7 @@ import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
import PluginButton from './components/pluginButton' // eslint-disable-line import PluginButton from './components/pluginButton' // eslint-disable-line
import { ThemeContext, themes } from './themeContext' import { ThemeContext, themes } from './themeContext'
import { RSSFeed } from './components/rssFeed'
declare global { declare global {
interface Window { interface Window {
_paq: any _paq: any
@ -107,14 +108,8 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
scriptTwitter.src = 'https://platform.twitter.com/widgets.js' scriptTwitter.src = 'https://platform.twitter.com/widgets.js'
scriptTwitter.async = true scriptTwitter.async = true
document.body.appendChild(scriptTwitter) document.body.appendChild(scriptTwitter)
// to retrieve medium publications
const scriptMedium = document.createElement('script')
scriptMedium.src = 'https://www.twilik.com/assets/retainable/rss-embed/retainable-rss-embed.js'
scriptMedium.async = true
document.body.appendChild(scriptMedium)
return () => { return () => {
document.body.removeChild(scriptTwitter) document.body.removeChild(scriptTwitter)
document.body.removeChild(scriptMedium)
} }
}, []) }, [])
@ -340,17 +335,7 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
> >
<div id="remixIDE_MediumBlock" className="p-2 mx-1 mt-3 mb-0 remixui_home_remixHomeMedia" style={ { maxHeight: maxHeight } }> <div id="remixIDE_MediumBlock" className="p-2 mx-1 mt-3 mb-0 remixui_home_remixHomeMedia" style={ { maxHeight: maxHeight } }>
<div id="medium-widget" className="px-3 remixui_home_media" hidden={state.showMediaPanel !== 'medium'} style={ { maxHeight: '10000px' } }> <div id="medium-widget" className="px-3 remixui_home_media" hidden={state.showMediaPanel !== 'medium'} style={ { maxHeight: '10000px' } }>
<div <RSSFeed feedUrl='https://rss.remixproject.org/' maxItems={10} />
id="retainable-rss-embed"
data-rss="https://medium.com/feed/remix-ide"
data-maxcols="1"
data-layout="grid"
data-poststyle="external"
data-readmore="More..."
data-buttonclass="btn mb-3"
data-offset="-100"
>
</div>
</div> </div>
</div> </div>
<div id="remixIDE_TwitterBlock" className="p-2 mx-1 mt-3 mb-0 remixui_home_remixHomeMedia" hidden={state.showMediaPanel !== 'twitter'} style={ { maxHeight: maxHeight, marginRight: '28px' } } > <div id="remixIDE_TwitterBlock" className="p-2 mx-1 mt-3 mb-0 remixui_home_remixHomeMedia" hidden={state.showMediaPanel !== 'twitter'} style={ { maxHeight: maxHeight, marginRight: '28px' } } >

@ -6,6 +6,7 @@ import { publishToSwarm } from './publishOnSwarm'
export const PublishToStorage = (props: RemixUiPublishToStorageProps) => { export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
const { api, storage, contract, resetStorage } = props const { api, storage, contract, resetStorage } = props
const [modalShown, setModalShown] = useState(false)
const [state, setState] = useState({ const [state, setState] = useState({
modal: { modal: {
title: '', title: '',
@ -28,9 +29,7 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
try { try {
const result = await publishToSwarm(contract, api) const result = await publishToSwarm(contract, api)
modal(`Published ${contract.name}'s Metadata`, publishMessage(result.uploaded)) modal(`Published ${contract.name}'s Metadata and Sources`, publishMessage(result.uploaded))
// triggered each time there's a new verified publish (means hash correspond)
api.writeFile('swarm/' + result.item.hash, result.item.content)
} catch (err) { } catch (err) {
let parseError = err let parseError = err
try { try {
@ -40,14 +39,32 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
modal('Swarm Publish Failed', publishMessageFailed(storage, parseError)) modal('Swarm Publish Failed', publishMessageFailed(storage, parseError))
} }
} else { } else {
try { if (!api.config.get('settings/ipfs-url') && !modalShown) {
const result = await publishToIPFS(contract, api) modal('IPFS Settings', <div>You have not set your own custom IPFS settings.<br></br>
<br></br>
modal(`Published ${contract.name}'s Metadata`, publishMessage(result.uploaded)) We wont be providing a public endpoint anymore for publishing your contracts to IPFS.<br></br>Instead of that, 4 options are now available:<br></br>
// triggered each time there's a new verified publish (means hash correspond) <br></br>
api.writeFile('ipfs/' + result.item.hash, result.item.content) <ul className='pl-3'>
} catch (err) { <li>
modal('IPFS Publish Failed', publishMessageFailed(storage, err)) DEFAULT OPTION:
Use the public INFURA node. This will not guarantee your data will persist.
</li>
<li>
Use your own INFURA IPFS node. This requires a subscription. <a href='https://infura.io/product/ipfs' target={'_blank'}>Learn more</a>
</li>
<li>
Use any external IPFS which doesnt require any authentification.
</li>
<li>
Use your own local ipfs node (which usually runs under http://localhost:5001)
</li>
</ul>
You can update your IPFS settings in the SETTINGS tab.
<br></br>
Now the default option will be used.
</div>, async () => await ipfs(contract, api))
} else {
await ipfs(contract, api)
} }
} }
} }
@ -58,8 +75,19 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
} }
}, [storage]) }, [storage])
const ipfs = async (contract, api) => {
try {
const result = await publishToIPFS(contract, api)
modal(`Published ${contract.name}'s Metadata and Sources`, publishMessage(result.uploaded))
} catch (err) {
modal('IPFS Publish Failed', publishMessageFailed(storage, err))
}
setModalShown(true)
}
const publishMessage = (uploaded) => ( const publishMessage = (uploaded) => (
<span> Metadata of "{contract.name.toLowerCase()}" was published successfully. <br /> <span> Metadata and sources of "{contract.name.toLowerCase()}" were published successfully. <br />
<pre> <pre>
<div> <div>
{uploaded.map((value, index) => <div key={index}><b>{value.filename}</b> : <pre>{value.output.url}</pre></div>)} {uploaded.map((value, index) => <div key={index}><b>{value.filename}</b> : <pre>{value.output.url}</pre></div>)}
@ -69,7 +97,7 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
) )
const publishMessageFailed = (storage, err) => ( const publishMessageFailed = (storage, err) => (
<span>Failed to publish metadata file to { storage }, please check the { storage } gateways is available. <br /> <span>Failed to publish metadata file and sources to {storage}, please check the {storage} gateways is available. <br />
{err} {err}
</span> </span>
) )
@ -81,7 +109,7 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
resetStorage() resetStorage()
} }
const modal = async (title: string, message: string | JSX.Element) => { const modal = async (title: string, message: string | JSX.Element, okFn: any = () => { }) => {
await setState(prevState => { await setState(prevState => {
return { return {
...prevState, ...prevState,
@ -89,7 +117,8 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
...prevState.modal, ...prevState.modal,
hide: false, hide: false,
message, message,
title title,
okFn
} }
} }
}) })
@ -102,7 +131,7 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
message={state.modal.message} message={state.modal.message}
hide={state.modal.hide} hide={state.modal.hide}
okLabel='OK' okLabel='OK'
okFn={() => {}} okFn={state.modal.okFn}
handleHide={handleHideModal}> handleHide={handleHideModal}>
{(typeof state.modal.message !== 'string') && state.modal.message} {(typeof state.modal.message !== 'string') && state.modal.message}
</ModalDialog> </ModalDialog>

@ -79,7 +79,7 @@ export const publishToSwarm = async (contract, api) => {
// publish the list of sources in order, fail if any failed // publish the list of sources in order, fail if any failed
await Promise.all(sources.map(async (item) => { await Promise.all(sources.map(async (item) => {
try { try {
const result = await swarmVerifiedPublish(beeNodes, postageStampId, item.content, item.hash) const result = await swarmVerifiedPublish(beeNodes, postageStampId, item.content, item.hash, api)
try { try {
item.hash = result.url.match('bzz-raw://(.+)')[1] item.hash = result.url.match('bzz-raw://(.+)')[1]
@ -96,9 +96,9 @@ export const publishToSwarm = async (contract, api) => {
} }
})) }))
const metadataContent = JSON.stringify(metadata) const metadataContent = JSON.stringify(metadata, null, '\t')
try { try {
const result = await swarmVerifiedPublish(beeNodes, postageStampId, metadataContent, '') const result = await swarmVerifiedPublish(beeNodes, postageStampId, metadataContent, '', api)
try { try {
contract.metadataHash = result.url.match('bzz-raw://(.+)')[1] contract.metadataHash = result.url.match('bzz-raw://(.+)')[1]
@ -121,7 +121,7 @@ export const publishToSwarm = async (contract, api) => {
return { uploaded, item } return { uploaded, item }
} }
const swarmVerifiedPublish = async (beeNodes: Bee[], postageStampId: string, content, expectedHash): Promise<Record<string, any>> => { const swarmVerifiedPublish = async (beeNodes: Bee[], postageStampId: string, content, expectedHash, api): Promise<Record<string, any>> => {
try { try {
const results = await uploadToBeeNodes(beeNodes, postageStampId, content) const results = await uploadToBeeNodes(beeNodes, postageStampId, content)
const hash = hashFromResults(results) const hash = hashFromResults(results)
@ -129,6 +129,7 @@ const swarmVerifiedPublish = async (beeNodes: Bee[], postageStampId: string, con
if (expectedHash && hash !== expectedHash) { if (expectedHash && hash !== expectedHash) {
return { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'bzz-raw://' + hash, hash } return { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'bzz-raw://' + hash, hash }
} else { } else {
api.writeFile('swarm/' + hash, content)
return { message: 'ok', url: 'bzz-raw://' + hash, hash } return { message: 'ok', url: 'bzz-raw://' + hash, hash }
} }
} catch (error) { } catch (error) {

@ -1,12 +1,26 @@
import IpfsClient from 'ipfs-mini' import IpfsHttpClient from 'ipfs-http-client'
const ipfsNodes = [
new IpfsClient({ host: 'ipfs.remixproject.org', port: 443, protocol: 'https' }),
new IpfsClient({ host: 'ipfs.infura.io', port: 5001, protocol: 'https' }), let ipfsNodes = []
new IpfsClient({ host: '127.0.0.1', port: 5001, protocol: 'http' })
]
export const publishToIPFS = async (contract, api) => { export const publishToIPFS = async (contract, api) => {
ipfsNodes = [
IpfsHttpClient({ host: 'ipfs.infura.io', port: 5001, protocol: 'https' })
]
if (api.config.get('settings/ipfs-url')) {
const auth = api.config.get('settings/ipfs-project-id') ? 'Basic ' + Buffer.from(api.config.get('settings/ipfs-project-id') + ':' + api.config.get('settings/ipfs-project-secret')).toString('base64') : null
const ipfs = IpfsHttpClient({
host: api.config.get('settings/ipfs-url'),
port: api.config.get('settings/ipfs-port'),
protocol: api.config.get('settings/ipfs-protocol'),
headers: {
Authorization: auth
}
})
ipfsNodes.push(ipfs)
}
// gather list of files to publish // gather list of files to publish
const sources = [] const sources = []
let metadata let metadata
@ -63,8 +77,7 @@ export const publishToIPFS = async (contract, api) => {
// publish the list of sources in order, fail if any failed // publish the list of sources in order, fail if any failed
await Promise.all(sources.map(async (item) => { await Promise.all(sources.map(async (item) => {
try { try {
const result = await ipfsVerifiedPublish(item.content, item.hash) const result = await ipfsVerifiedPublish(item.content, item.hash, api)
try { try {
item.hash = result.url.match('dweb:/ipfs/(.+)')[1] item.hash = result.url.match('dweb:/ipfs/(.+)')[1]
} catch (e) { } catch (e) {
@ -76,10 +89,10 @@ export const publishToIPFS = async (contract, api) => {
throw new Error(error) throw new Error(error)
} }
})) }))
const metadataContent = JSON.stringify(metadata) const metadataContent = JSON.stringify(metadata, null, '\t')
try { try {
const result = await ipfsVerifiedPublish(metadataContent, '') const result = await ipfsVerifiedPublish(metadataContent, '', api)
try { try {
contract.metadataHash = result.url.match('dweb:/ipfs/(.+)')[1] contract.metadataHash = result.url.match('dweb:/ipfs/(.+)')[1]
@ -101,14 +114,15 @@ export const publishToIPFS = async (contract, api) => {
return { uploaded, item } return { uploaded, item }
} }
const ipfsVerifiedPublish = async (content, expectedHash) => { const ipfsVerifiedPublish = async (content, expectedHash, api) => {
try { try {
const results = await severalGatewaysPush(content) const results = await severalGatewaysPush(content)
const hash: any = (results as any).path
if (expectedHash && results !== expectedHash) { if (expectedHash && hash !== expectedHash) {
return { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'dweb:/ipfs/' + results, hash: results } return { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'dweb:/ipfs/' + hash, hash }
} else { } else {
return { message: 'ok', url: 'dweb:/ipfs/' + results, hash: results } api.writeFile('ipfs/' + hash, content)
return { message: 'ok', url: 'dweb:/ipfs/' + hash, hash }
} }
} catch (error) { } catch (error) {
throw new Error(error) throw new Error(error)

@ -19,7 +19,7 @@ export function EnvironmentUI (props: EnvironmentProps) {
Environment Environment
</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) }}> <select id="selectExEnvOptions" data-id="settingsSelectEnvOptions" className="form-control udapp_select custom-select" value={props.selectedEnv || ''} onChange={(e) => { handleChangeExEnv(e.target.value) }}>
{ {
props.providers.providerList.map((provider, index) => props.providers.providerList.map((provider, index) =>
<option id={provider.id} key={index} data-id={provider.dataId} <option id={provider.id} key={index} data-id={provider.dataId}

@ -344,8 +344,10 @@ export const SearchProvider = ({
console.log(e) console.log(e)
} }
} }
setTimeout(async () => {
await fetchWorkspace()
}, 500)
fetchWorkspace()
return () => { return () => {
plugin.off('fileManager', 'fileChanged') plugin.off('fileManager', 'fileChanged')

@ -17,6 +17,7 @@ export const enablePersonalModeText = ' Enable Personal Mode for web3 provider.
export const matomoAnalytics = 'Enable Matomo Analytics. We do not collect personally identifiable information (PII). The info is used to improve the site’s UX & UI. See more about ' export const matomoAnalytics = 'Enable Matomo Analytics. We do not collect personally identifiable information (PII). The info is used to improve the site’s UX & UI. See more about '
export const swarmSettingsTitle = 'Swarm Settings' export const swarmSettingsTitle = 'Swarm Settings'
export const swarmSettingsText = 'Swarm Settings' export const swarmSettingsText = 'Swarm Settings'
export const ipfsSettingsText = 'IPFS Settings'
export const labels = { export const labels = {
'gist': { 'gist': {
'link': gitAccessTokenLink, 'link': gitAccessTokenLink,

@ -1,10 +1,10 @@
import React, { useState, useReducer, useEffect, useCallback } from 'react' // eslint-disable-line import React, { useState, useReducer, useEffect, useCallback } from 'react' // eslint-disable-line
import { CopyToClipboard } from '@remix-ui/clipboard' // eslint-disable-line import { CopyToClipboard } from '@remix-ui/clipboard' // eslint-disable-line
import { enablePersonalModeText, ethereunVMText, labels, generateContractMetadataText, matomoAnalytics, textDark, textSecondary, warnText, wordWrapText, swarmSettingsTitle } from './constants' import { enablePersonalModeText, ethereunVMText, labels, generateContractMetadataText, matomoAnalytics, textDark, textSecondary, warnText, wordWrapText, swarmSettingsTitle, ipfsSettingsText } from './constants'
import './remix-ui-settings.css' import './remix-ui-settings.css'
import { ethereumVM, generateContractMetadat, personal, textWrapEventAction, useMatomoAnalytics, saveTokenToast, removeTokenToast, saveSwarmSettingsToast } from './settingsAction' import { ethereumVM, generateContractMetadat, personal, textWrapEventAction, useMatomoAnalytics, saveTokenToast, removeTokenToast, saveSwarmSettingsToast, saveIpfsSettingsToast } 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'
@ -26,6 +26,12 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const [privateBeeAddress, setPrivateBeeAddress] = useState('') const [privateBeeAddress, setPrivateBeeAddress] = useState('')
const [postageStampId, setPostageStampId] = useState('') const [postageStampId, setPostageStampId] = useState('')
const [resetState, refresh] = useState(0) const [resetState, refresh] = useState(0)
const [ipfsUrl, setipfsUrl] = useState('')
const [ipfsPort, setipfsPort] = useState('')
const [ipfsProtocol, setipfsProtocol] = useState('')
const [ipfsProjectId, setipfsProjectId] = useState('')
const [ipfsProjectSecret, setipfsProjectSecret] = useState('')
const initValue = () => { const initValue = () => {
const metadataConfig = props.config.get('settings/generate-contract-metadata') const metadataConfig = props.config.get('settings/generate-contract-metadata')
@ -59,6 +65,29 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
if (configPostageStampId) { if (configPostageStampId) {
setPostageStampId(configPostageStampId) setPostageStampId(configPostageStampId)
} }
const configipfsUrl = props.config.get('settings/ipfs-url')
if (configipfsUrl) {
setipfsUrl(configipfsUrl)
}
const configipfsPort = props.config.get('settings/ipfs-port')
if (configipfsPort) {
setipfsPort(configipfsPort)
}
const configipfsProtocol = props.config.get('settings/ipfs-protocol')
if (configipfsProtocol) {
setipfsProtocol(configipfsProtocol)
}
const configipfsProjectId = props.config.get('settings/ipfs-project-id')
if (configipfsProjectId) {
setipfsProjectId(configipfsProjectId)
}
const configipfsProjectSecret = props.config.get('settings/ipfs-project-secret')
if (configipfsProjectSecret) {
setipfsProjectSecret(configipfsProjectSecret)
}
}, [themeName, state.message]) }, [themeName, state.message])
useEffect(() => { useEffect(() => {
@ -237,6 +266,83 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
</div> </div>
) )
// ipfs settings
const handleSaveIpfsProjectId = useCallback(
(event) => {
setipfsProjectId(event.target.value)
}
, [ipfsProjectId]
)
const handleSaveIpfsSecret = useCallback(
(event) => {
setipfsProjectSecret(event.target.value)
}
, [ipfsProjectSecret]
)
const handleSaveIpfsUrl = useCallback(
(event) => {
setipfsUrl(event.target.value)
}
, [ipfsUrl]
)
const handleSaveIpfsPort = useCallback(
(event) => {
setipfsPort(event.target.value)
}
, [ipfsPort]
)
const handleSaveIpfsProtocol = useCallback(
(event) => {
setipfsProtocol(event.target.value)
}
, [ipfsProtocol]
)
const saveIpfsSettings = () => {
saveIpfsSettingsToast(props.config, dispatchToast, ipfsUrl, ipfsProtocol, ipfsPort, ipfsProjectId, ipfsProjectSecret)
}
const ipfsSettings = () => (
<div className="border-top">
<div className="card-body pt-3 pb-2">
<h6 className="card-title">{ ipfsSettingsText }</h6>
<div className="pt-2 mb-1"><label>IPFS HOST:</label>
<div className="text-secondary mb-0 h6">
<input placeholder='e.g. ipfs.infura.io' id="settingsIpfsUrl" data-id="settingsIpfsUrl" className="form-control" onChange={handleSaveIpfsUrl} value={ ipfsUrl } />
</div>
</div>
<div className=""><label>IPFS PROTOCOL:</label>
<div className="text-secondary mb-0 h6">
<input placeholder='e.g. https' id="settingsIpfsProtocol" data-id="settingsIpfsProtocol" className="form-control" onChange={handleSaveIpfsProtocol} value={ ipfsProtocol } />
</div>
</div>
<div className=""><label>IPFS PORT:</label>
<div className="text-secondary mb-0 h6">
<input placeholder='e.g. 5001' id="settingsIpfsPort" data-id="settingsIpfsPort" className="form-control" onChange={handleSaveIpfsPort} value={ ipfsPort } />
</div>
</div>
<div className=""><label>IPFS PROJECT ID [ INFURA ]:</label>
<div className="text-secondary mb-0 h6">
<input id="settingsIpfsProjectId" data-id="settingsIpfsProjectId" className="form-control" onChange={handleSaveIpfsProjectId} value={ ipfsProjectId } />
</div>
</div>
<div className=""><label>IPFS PROJECT SECRET [ INFURA ]:</label>
<div className="text-secondary mb-0 h6">
<input id="settingsIpfsProjectSecret" data-id="settingsIpfsProjectSecret" className="form-control" type="password" onChange={handleSaveIpfsSecret} value={ ipfsProjectSecret } />
</div>
</div>
<div className="d-flex justify-content-end pt-2">
<input className="btn btn-sm btn-primary ml-2" id="saveIpfssettings" data-id="settingsTabSaveIpfsSettings" onClick={() => saveIpfsSettings()} value="Save" type="button"></input>
</div>
</div>
</div>)
return ( return (
<div> <div>
{state.message ? <Toaster message= {state.message}/> : null} {state.message ? <Toaster message= {state.message}/> : null}
@ -244,6 +350,7 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
{token('gist')} {token('gist')}
{token('etherscan')} {token('etherscan')}
{swarmSettings()} {swarmSettings()}
{ipfsSettings()}
<RemixUiThemeModule themeModule={props._deps.themeModule} /> <RemixUiThemeModule themeModule={props._deps.themeModule} />
</div> </div>
) )

@ -56,3 +56,12 @@ export const saveSwarmSettingsToast = (config, dispatch, privateBeeAddress, post
config.set('settings/swarm-postage-stamp-id', postageStampId) config.set('settings/swarm-postage-stamp-id', postageStampId)
dispatch({ type: 'save', payload: { message: 'Swarm settings have been saved' } }) dispatch({ type: 'save', payload: { message: 'Swarm settings have been saved' } })
} }
export const saveIpfsSettingsToast = (config, dispatch, ipfsURL, ipfsProtocol, ipfsPort, ipfsProjectId, ipfsProjectSecret) => {
config.set('settings/ipfs-url', ipfsURL)
config.set('settings/ipfs-protocol', ipfsProtocol)
config.set('settings/ipfs-port', ipfsPort)
config.set('settings/ipfs-project-id', ipfsProjectId)
config.set('settings/ipfs-project-secret', ipfsProjectSecret)
dispatch({ type: 'save', payload: { message: 'IPFS settings have been saved' } })
}

@ -12,6 +12,7 @@ import { CopyToClipboard } from '@remix-ui/clipboard'
import { configFileContent } from './compilerConfiguration' import { configFileContent } from './compilerConfiguration'
import './css/style.css' import './css/style.css'
const defaultPath = "compiler_config.json"
declare global { declare global {
interface Window { interface Window {
@ -22,11 +23,23 @@ declare global {
const _paq = window._paq = window._paq || [] //eslint-disable-line const _paq = window._paq = window._paq || [] //eslint-disable-line
export const CompilerContainer = (props: CompilerContainerProps) => { export const CompilerContainer = (props: CompilerContainerProps) => {
const { api, compileTabLogic, tooltip, modal, compiledFileName, updateCurrentVersion, configurationSettings, isHardhatProject, isTruffleProject, workspaceName } = props // eslint-disable-line const {
api,
compileTabLogic,
tooltip,
modal,
compiledFileName,
updateCurrentVersion,
configurationSettings,
isHardhatProject,
isTruffleProject,
workspaceName,
configFilePath,
setConfigFilePath,
} = props // eslint-disable-line
const [state, setState] = useState({ const [state, setState] = useState({
hideWarnings: false, hideWarnings: false,
autoCompile: false, autoCompile: false,
configFilePath: "compiler_config.json",
useFileConfiguration: false, useFileConfiguration: false,
matomoAutocompileOnce: true, matomoAutocompileOnce: true,
optimize: false, optimize: false,
@ -40,7 +53,8 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
compiledFileName: '', compiledFileName: '',
includeNightlies: false, includeNightlies: false,
language: 'Solidity', language: 'Solidity',
evmVersion: '' evmVersion: '',
createFileOnce: true
}) })
const [showFilePathInput, setShowFilePathInput] = useState<boolean>(false) const [showFilePathInput, setShowFilePathInput] = useState<boolean>(false)
const [toggleExpander, setToggleExpander] = useState<boolean>(false) const [toggleExpander, setToggleExpander] = useState<boolean>(false)
@ -53,17 +67,27 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
const [compilerContainer, dispatch] = useReducer(compilerReducer, compilerInitialState) const [compilerContainer, dispatch] = useReducer(compilerReducer, compilerInitialState)
useEffect(() => { useEffect(() => {
api.setAppParameter('configFilePath', "/compiler_config.json") if (workspaceName) {
api.fileExists("/compiler_config.json").then((exists) => { api.setAppParameter('configFilePath', defaultPath)
if (!exists) createNewConfigFile() if (state.useFileConfiguration) {
else { api.fileExists(defaultPath).then((exists) => {
// what to do? discuss if (!exists && state.useFileConfiguration) createNewConfigFile()
}
}) })
api.setAppParameter('configFilePath', "/compiler_config.json") }
setShowFilePathInput(false) setShowFilePathInput(false)
}
}, [workspaceName]) }, [workspaceName])
useEffect(() => {
if (state.useFileConfiguration) {
api.fileExists(defaultPath).then((exists) => {
if (!exists) createNewConfigFile()
})
setToggleExpander(true)
}
}, [state.useFileConfiguration])
useEffect(() => { useEffect(() => {
const listener = (event) => { const listener = (event) => {
if (configFilePathInput.current !== event.target) { if (configFilePathInput.current !== event.target) {
@ -106,8 +130,10 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
const hideWarnings = await api.getAppParameter('hideWarnings') as boolean || false const hideWarnings = await api.getAppParameter('hideWarnings') as boolean || false
const includeNightlies = await api.getAppParameter('includeNightlies') as boolean || false const includeNightlies = await api.getAppParameter('includeNightlies') as boolean || false
const useFileConfiguration = await api.getAppParameter('useFileConfiguration') as boolean || false const useFileConfiguration = await api.getAppParameter('useFileConfiguration') as boolean || false
let configFilePath = await api.getAppParameter('configFilePath') let configFilePathSaved = await api.getAppParameter('configFilePath')
if (!configFilePath || configFilePath == '') configFilePath = "/compiler_config.json" if (!configFilePathSaved || configFilePathSaved == '') configFilePathSaved = defaultPath
setConfigFilePath(configFilePathSaved)
setState(prevState => { setState(prevState => {
const params = api.getCompilerParameters() const params = api.getCompilerParameters()
@ -122,7 +148,6 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
autoCompile: autocompile, autoCompile: autocompile,
includeNightlies: includeNightlies, includeNightlies: includeNightlies,
useFileConfiguration: useFileConfiguration, useFileConfiguration: useFileConfiguration,
configFilePath: configFilePath,
optimize: optimize, optimize: optimize,
runs: runs, runs: runs,
evmVersion: (evmVersion !== null) && (evmVersion !== 'null') && (evmVersion !== undefined) && (evmVersion !== 'undefined') ? evmVersion : 'default', evmVersion: (evmVersion !== null) && (evmVersion !== 'null') && (evmVersion !== undefined) && (evmVersion !== 'undefined') ? evmVersion : 'default',
@ -181,7 +206,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
useEffect(() => { useEffect(() => {
compileTabLogic.setUseFileConfiguration(state.useFileConfiguration) compileTabLogic.setUseFileConfiguration(state.useFileConfiguration)
if (state.useFileConfiguration) compileTabLogic.setConfigFilePath(state.configFilePath) if (state.useFileConfiguration) compileTabLogic.setConfigFilePath(configFilePath)
}, [state.useFileConfiguration]) }, [state.useFileConfiguration])
useEffect(() => { useEffect(() => {
@ -191,6 +216,16 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
}, [configurationSettings]) }, [configurationSettings])
const toggleConfigType = () => { const toggleConfigType = () => {
if (state.useFileConfiguration)
if (state.createFileOnce) {
api.fileExists(defaultPath).then((exists) => {
if (!exists || state.useFileConfiguration ) createNewConfigFile()
})
setState(prevState => {
return { ...prevState, createFileOnce: false }
})
}
setState(prevState => { setState(prevState => {
api.setAppParameter('useFileConfiguration', !state.useFileConfiguration) api.setAppParameter('useFileConfiguration', !state.useFileConfiguration)
return { ...prevState, useFileConfiguration: !state.useFileConfiguration } return { ...prevState, useFileConfiguration: !state.useFileConfiguration }
@ -198,18 +233,17 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
} }
const openFile = async () => { const openFile = async () => {
api.open(state.configFilePath) api.open(configFilePath)
} }
const createNewConfigFile = async () => { const createNewConfigFile = async () => {
let filePath = configFilePathInput.current && configFilePathInput.current.value !== '' ? configFilePathInput.current.value : state.configFilePath let filePath = configFilePathInput.current && configFilePathInput.current.value !== '' ? configFilePathInput.current.value : configFilePath
if (filePath === '') filePath = defaultPath
if (!filePath.endsWith('.json')) filePath = filePath + '.json' if (!filePath.endsWith('.json')) filePath = filePath + '.json'
await api.writeFile(filePath, configFileContent) await api.writeFile(filePath, configFileContent)
api.setAppParameter('configFilePath', filePath) api.setAppParameter('configFilePath', filePath)
setState(prevState => { setConfigFilePath(filePath)
return { ...prevState, configFilePath: filePath }
})
compileTabLogic.setConfigFilePath(filePath) compileTabLogic.setConfigFilePath(filePath)
setShowFilePathInput(false) setShowFilePathInput(false)
} }
@ -220,9 +254,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
if (await api.fileExists(configFilePathInput.current.value)) { if (await api.fileExists(configFilePathInput.current.value)) {
api.setAppParameter('configFilePath', configFilePathInput.current.value) api.setAppParameter('configFilePath', configFilePathInput.current.value)
setState(prevState => { setConfigFilePath(configFilePathInput.current.value)
return { ...prevState, configFilePath: configFilePathInput.current.value }
})
compileTabLogic.setConfigFilePath(configFilePathInput.current.value) compileTabLogic.setConfigFilePath(configFilePathInput.current.value)
setShowFilePathInput(false) setShowFilePathInput(false)
@ -394,6 +426,9 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
compileIcon.current.classList.remove('remixui_spinningIcon') compileIcon.current.classList.remove('remixui_spinningIcon')
compileIcon.current.classList.remove('remixui_bouncingIcon') compileIcon.current.classList.remove('remixui_bouncingIcon')
if (!state.autoCompile || (state.autoCompile && state.matomoAutocompileOnce)) { if (!state.autoCompile || (state.autoCompile && state.matomoAutocompileOnce)) {
if (state.useFileConfiguration)
_paq.push(['trackEvent', 'compiler', 'compiled_with_config_file'])
_paq.push(['trackEvent', 'compiler', 'compiled_with_version', _retrieveVersion()]) _paq.push(['trackEvent', 'compiler', 'compiled_with_version', _retrieveVersion()])
if (state.autoCompile && state.matomoAutocompileOnce) { if (state.autoCompile && state.matomoAutocompileOnce) {
setState(prevState => { setState(prevState => {
@ -750,36 +785,38 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
</div> </div>
<div className="d-flex pb-1 remixui_compilerConfig custom-control custom-radio"> <div className="d-flex pb-1 remixui_compilerConfig custom-control custom-radio">
<input className="custom-control-input" type="radio" name="configradio" value="file" onChange={toggleConfigType} checked={state.useFileConfiguration} id="scFileConfig" /> <input className="custom-control-input" type="radio" name="configradio" value="file" onChange={toggleConfigType} checked={state.useFileConfiguration} id="scFileConfig" />
<label className="form-check-label custom-control-label" htmlFor="scFileConfig">Use configuration file</label> <label className="form-check-label custom-control-label" htmlFor="scFileConfig" data-id="scFileConfiguration">Use configuration file</label>
</div> </div>
<div className={`pt-2 ml-4 ml-2 align-items-start justify-content-between d-flex`}> <div className={`pt-2 ml-4 ml-2 align-items-start justify-content-between d-flex`}>
{ (!showFilePathInput && state.useFileConfiguration) && <span { (!showFilePathInput && state.useFileConfiguration) && <span
title="Click to open the config file." title="Click to open the config file."
onClick={openFile} onClick={configFilePath === '' ? () => {} : openFile}
className="py-2 text-primary remixui_compilerConfigPath" className="py-2 remixui_compilerConfigPath"
>{state.configFilePath}</span> } >{configFilePath === '' ? 'No file selected.' : configFilePath}</span> }
{ (!showFilePathInput&& !state.useFileConfiguration) && <span className="py-2 text-secondary">{state.configFilePath}</span> } { (!showFilePathInput && !state.useFileConfiguration) && <span className="py-2 text-secondary">{configFilePath}</span> }
<input <input
ref={configFilePathInput} ref={configFilePathInput}
className={`py-0 my-0 form-control ${showFilePathInput ? "d-flex" : "d-none"}`} className={`py-0 my-0 form-control ${showFilePathInput ? "d-flex" : "d-none"}`}
placeholder={"Enter the new path"} placeholder={"Enter the new path"}
title="If the file you entered does not exist you will be able to create one in the next step." title="If the file you entered does not exist you will be able to create one in the next step."
disabled={!state.useFileConfiguration} disabled={!state.useFileConfiguration}
data-id="scConfigFilePathInput"
onKeyPress={event => { onKeyPress={event => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
handleConfigPathChange() handleConfigPathChange()
} }
}} }}
/> />
{ !showFilePathInput && <button disabled={!state.useFileConfiguration} className="btn-secondary" onClick={() => {setShowFilePathInput(true)}}>Change</button> } { !showFilePathInput && <button disabled={!state.useFileConfiguration} data-id="scConfigChangeFilePath" className="btn-secondary" onClick={() => {setShowFilePathInput(true)}}>Change</button> }
</div> </div>
</div> </div>
<div className="px-4"> <div className="px-4">
<button id="compileBtn" data-id="compilerContainerCompileBtn" className="btn btn-primary btn-block d-block w-100 text-break remixui_disabled mb-1 mt-3" onClick={compile} disabled={disableCompileButton}> <button id="compileBtn" data-id="compilerContainerCompileBtn" className="btn btn-primary btn-block d-block w-100 text-break remixui_disabled mb-1 mt-3" onClick={compile} disabled={(configFilePath === '' && state.useFileConfiguration) || disableCompileButton}>
<OverlayTrigger overlay={ <OverlayTrigger overlay={
<Tooltip id="overlay-tooltip-compile"> <Tooltip id="overlay-tooltip-compile">
<div className="text-left"> <div className="text-left">
<div><b>Ctrl+S</b> for compiling</div> { !(configFilePath === '' && state.useFileConfiguration) && <div><b>Ctrl+S</b> for compiling</div> }
{ (configFilePath === '' && state.useFileConfiguration) && <div> No config file selected</div> }
</div> </div>
</Tooltip> </Tooltip>
}> }>
@ -790,11 +827,12 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
</OverlayTrigger> </OverlayTrigger>
</button> </button>
<div className='d-flex align-items-center'> <div className='d-flex align-items-center'>
<button id="compileAndRunBtn" data-id="compilerContainerCompileAndRunBtn" className="btn btn-secondary btn-block d-block w-100 text-break remixui_solidityCompileAndRunButton d-inline-block remixui_disabled mb-1 mt-3" onClick={compileAndRun} disabled={disableCompileButton}> <button id="compileAndRunBtn" data-id="compilerContainerCompileAndRunBtn" className="btn btn-secondary btn-block d-block w-100 text-break remixui_solidityCompileAndRunButton d-inline-block remixui_disabled mb-1 mt-3" onClick={compileAndRun} disabled={(configFilePath === '' && state.useFileConfiguration) || disableCompileButton}>
<OverlayTrigger overlay={ <OverlayTrigger overlay={
<Tooltip id="overlay-tooltip-compile-run"> <Tooltip id="overlay-tooltip-compile-run">
<div className="text-left"> <div className="text-left">
<div><b>Ctrl+Shift+S</b> for compiling and script execution</div> { !(configFilePath === '' && state.useFileConfiguration) && <div><b>Ctrl+Shift+S</b> for compiling and script execution</div> }
{ (configFilePath === '' && state.useFileConfiguration) && <div> No config file selected</div> }
</div> </div>
</Tooltip> </Tooltip>
}> }>

@ -24,7 +24,7 @@ export class CompileTabLogic {
constructor (public api: ICompilerApi, public contentImport) { constructor (public api: ICompilerApi, public contentImport) {
this.event = new EventEmitter() this.event = new EventEmitter()
this.compiler = new Compiler((url, cb) => api.resolveContentAndSave(url).then((result) => cb(null, result)).catch((error) => cb(error.message))) this.compiler = new Compiler((url, cb) => api.resolveContentAndSave(url).then((result) => cb(null, result)).catch((error) => cb(error.message)))
this.evmVersions = ['default', 'london', 'istanbul', 'petersburg', 'constantinople', 'byzantium', 'spuriousDragon', 'tangerineWhistle', 'homestead'] this.evmVersions = ['default', 'berlin', 'london', 'istanbul', 'petersburg', 'constantinople', 'byzantium', 'spuriousDragon', 'tangerineWhistle', 'homestead']
} }
init () { init () {
@ -106,9 +106,11 @@ export class CompileTabLogic {
const sources = { [target]: { content } } const sources = { [target]: { content } }
this.event.emit('removeAnnotations') this.event.emit('removeAnnotations')
this.event.emit('startingCompilation') this.event.emit('startingCompilation')
if (this.configFilePath) {
this.api.readFile(this.configFilePath).then( contentConfig => { this.api.readFile(this.configFilePath).then( contentConfig => {
this.compiler.set('configFileContent', contentConfig) this.compiler.set('configFileContent', contentConfig)
}) })
}
// setTimeout fix the animation on chrome... (animation triggered by 'staringCompilation') // setTimeout fix the animation on chrome... (animation triggered by 'staringCompilation')
setTimeout(() => { this.compiler.compile(sources, target); resolve(true) }, 100) setTimeout(() => { this.compiler.compile(sources, target); resolve(true) }, 100)
}).catch((error) => { }).catch((error) => {

@ -15,6 +15,7 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => {
isTruffleProject: false, isTruffleProject: false,
workspaceName: '', workspaceName: '',
currentFile, currentFile,
configFilePath: 'compiler_config.json',
loading: false, loading: false,
compileTabLogic: null, compileTabLogic: null,
compiler: null, compiler: null,
@ -72,6 +73,13 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => {
}) })
} }
api.onFileRemoved = (path: string) => {
if (path === state.configFilePath)
setState(prevState => {
return { ...prevState, configFilePath: '' }
})
}
api.onNoFileSelected = () => { api.onNoFileSelected = () => {
setState(prevState => { setState(prevState => {
return { ...prevState, currentFile: '' } return { ...prevState, currentFile: '' }
@ -103,6 +111,13 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => {
setBadgeStatus({ ...badgeStatus, [currentFile]: data }) setBadgeStatus({ ...badgeStatus, [currentFile]: data })
} }
const setConfigFilePath = (path: string) => {
setState(prevState => {
return { ...prevState, configFilePath: path }
})
}
const toast = (message: string) => { const toast = (message: string) => {
setState(prevState => { setState(prevState => {
return { ...prevState, toasterMsg: message } return { ...prevState, toasterMsg: message }
@ -162,6 +177,8 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => {
compiledFileName={currentFile} compiledFileName={currentFile}
updateCurrentVersion={updateCurrentVersion} updateCurrentVersion={updateCurrentVersion}
configurationSettings={configurationSettings} configurationSettings={configurationSettings}
configFilePath={state.configFilePath}
setConfigFilePath={setConfigFilePath}
/> />
{ contractsFile[currentFile] && contractsFile[currentFile].contractsDetails && <ContractSelection api={api} contractsDetails={contractsFile[currentFile].contractsDetails} contractList={contractsFile[currentFile].contractList} modal={modal} /> } { contractsFile[currentFile] && contractsFile[currentFile].contractsDetails && <ContractSelection api={api} contractsDetails={contractsFile[currentFile].contractsDetails} contractList={contractsFile[currentFile].contractList} modal={modal} /> }
{ compileErrors[currentFile] && { compileErrors[currentFile] &&

@ -16,7 +16,9 @@ export interface CompilerContainerProps {
modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void, modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void,
compiledFileName: string, compiledFileName: string,
updateCurrentVersion: any, updateCurrentVersion: any,
configurationSettings: ConfigurationSettings configurationSettings: ConfigurationSettings,
configFilePath: string,
setConfigFilePath: (path: string) => void
} }
export interface ContractSelectionProps { export interface ContractSelectionProps {
api: ICompilerApi, api: ICompilerApi,

@ -13,6 +13,7 @@
padding-left : 6px; padding-left : 6px;
padding-right : 6px; padding-right : 6px;
padding-top : 6px; padding-top : 6px;
overflow-y : auto;
} }
.remixui_fileExplorerTree { .remixui_fileExplorerTree {
cursor : default; cursor : default;

@ -228,8 +228,8 @@ export function Workspace () {
</div> </div>
</header> </header>
</div> </div>
<div className='remixui_fileExplorerTree'> <div className='h-100 remixui_fileExplorerTree'>
<div> <div className='h-100'>
<div className='pl-2 remixui_treeview' data-id='filePanelFileExplorerTree'> <div className='pl-2 remixui_treeview' data-id='filePanelFileExplorerTree'>
{ (global.fs.mode === 'browser') && (currentWorkspace !== NO_WORKSPACE) && { (global.fs.mode === 'browser') && (currentWorkspace !== NO_WORKSPACE) &&
<FileExplorer <FileExplorer

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-url-resolver", "name": "@remix-project/remix-url-resolver",
"version": "0.0.34", "version": "0.0.35",
"description": "Solidity import url resolver engine", "description": "Solidity import url resolver engine",
"main": "src/index.js", "main": "src/index.js",
"types": "src/index.d.ts", "types": "src/index.d.ts",
@ -41,5 +41,5 @@
"typescript": "^3.1.6" "typescript": "^3.1.6"
}, },
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78" "gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
} }

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-ws-templates", "name": "@remix-project/remix-ws-templates",
"version": "1.0.0", "version": "1.0.1",
"description": "Create a Remix IDE workspace using different templates", "description": "Create a Remix IDE workspace using different templates",
"main": "src/index.js", "main": "src/index.js",
"types": "src/index.d.ts", "types": "src/index.d.ts",
@ -19,5 +19,11 @@
"bugs": { "bugs": {
"url": "https://github.com/ethereum/remix-project/issues" "url": "https://github.com/ethereum/remix-project/issues"
}, },
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-ws-templates#readme" "homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-ws-templates#readme",
"typings": "./src/index.d.ts",
"dependencies": {
"ethers": "^5.4.2",
"web3": "^1.5.1"
},
"gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
} }

@ -1,24 +1,27 @@
import { ethers } from 'ethers' import { ethers } from 'ethers'
export const deploy = async (contractName: string, args: Array<any>, from?: string): Promise<any> => { /**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array<any>} args list of constructor' parameters
* @param {Number} accountIndex account index from the exposed account
* @return {Contract} deployed contract
*/
export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {
console.log(`deploying ${contractName}`) console.log(`deploying ${contractName}`)
// Note that the script needs the ABI which is generated from the compilation artifact. // Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated // Make sure contract is compiled and artifacts are generated
const artifactsPath = `browser/contracts/artifacts/${contractName}.json` const artifactsPath = `browser/contracts/artifacts/${contractName}.json` // Change this for different path
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath))
// 'web3Provider' is a remix global variable object // 'web3Provider' is a remix global variable object
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer); const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex)
let contract const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
if (from) {
contract = await factory.connect(from).deploy(...args); const contract = await factory.deploy(...args)
} else {
contract = await factory.deploy(...args);
}
// The contract is NOT deployed yet; we must wait until it is mined // The contract is NOT deployed yet; we must wait until it is mined
await contract.deployed() await contract.deployed()

@ -1,8 +1,17 @@
import Web3 from 'web3' import Web3 from 'web3'
import { Contract, ContractSendMethod, Options } from 'web3-eth-contract'
export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<any> => { /**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array<any>} args list of constructor' parameters
* @param {string} from account used to send the transaction
* @param {number} gas gas limit
* @return {Options} deployed contract
*/
export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {
const web3 = new Web3(window.web3Provider) const web3 = new Web3(web3Provider)
console.log(`deploying ${contractName}`) console.log(`deploying ${contractName}`)
// Note that the script needs the ABI which is generated from the compilation artifact. // Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated // Make sure contract is compiled and artifacts are generated
@ -12,14 +21,14 @@ export const deploy = async (contractName: string, args: Array<any>, from?: stri
const accounts = await web3.eth.getAccounts() const accounts = await web3.eth.getAccounts()
let contract = new web3.eth.Contract(metadata.abi) const contract: Contract = new web3.eth.Contract(metadata.abi)
contract = contract.deploy({ const contractSend: ContractSendMethod = contract.deploy({
data: metadata.data.bytecode.object, data: metadata.data.bytecode.object,
arguments: args arguments: args
}) })
const newContractInstance = await contract.send({ const newContractInstance = await contractSend.send({
from: from || accounts[0], from: from || accounts[0],
gas: gas || 1500000 gas: gas || 1500000
}) })

@ -1,24 +1,27 @@
import { ethers } from 'ethers' import { ethers } from 'ethers'
export const deploy = async (contractName: string, args: Array<any>, from?: string): Promise<any> => { /**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array<any>} args list of constructor' parameters
* @param {Number} accountIndex account index from the exposed account
* @return {Contract} deployed contract
*/
export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {
console.log(`deploying ${contractName}`) console.log(`deploying ${contractName}`)
// Note that the script needs the ABI which is generated from the compilation artifact. // Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated // Make sure contract is compiled and artifacts are generated
const artifactsPath = `browser/contracts/artifacts/${contractName}.json` const artifactsPath = `browser/contracts/artifacts/${contractName}.json` // Change this for different path
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath))
// 'web3Provider' is a remix global variable object // 'web3Provider' is a remix global variable object
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer); const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex)
let contract const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
if (from) {
contract = await factory.connect(from).deploy(...args); const contract = await factory.deploy(...args)
} else {
contract = await factory.deploy(...args);
}
// The contract is NOT deployed yet; we must wait until it is mined // The contract is NOT deployed yet; we must wait until it is mined
await contract.deployed() await contract.deployed()

@ -1,8 +1,17 @@
import Web3 from 'web3' import Web3 from 'web3'
import { Contract, ContractSendMethod, Options } from 'web3-eth-contract'
export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<any> => { /**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array<any>} args list of constructor' parameters
* @param {string} from account used to send the transaction
* @param {number} gas gas limit
* @return {Options} deployed contract
*/
export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {
const web3 = new Web3(window.web3Provider) const web3 = new Web3(web3Provider)
console.log(`deploying ${contractName}`) console.log(`deploying ${contractName}`)
// Note that the script needs the ABI which is generated from the compilation artifact. // Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated // Make sure contract is compiled and artifacts are generated
@ -12,14 +21,14 @@ export const deploy = async (contractName: string, args: Array<any>, from?: stri
const accounts = await web3.eth.getAccounts() const accounts = await web3.eth.getAccounts()
let contract = new web3.eth.Contract(metadata.abi) const contract: Contract = new web3.eth.Contract(metadata.abi)
contract = contract.deploy({ const contractSend: ContractSendMethod = contract.deploy({
data: metadata.data.bytecode.object, data: metadata.data.bytecode.object,
arguments: args arguments: args
}) })
const newContractInstance = await contract.send({ const newContractInstance = await contractSend.send({
from: from || accounts[0], from: from || accounts[0],
gas: gas || 1500000 gas: gas || 1500000
}) })

@ -1,6 +1,13 @@
import { ethers } from 'ethers' import { ethers } from 'ethers'
export const deploy = async (contractName: string, args: Array<any>, from?: string): Promise<any> => { /**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array<any>} args list of constructor' parameters
* @param {Number} accountIndex account index from the exposed account
* @return {Contract} deployed contract
*/
export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {
console.log(`deploying ${contractName}`) console.log(`deploying ${contractName}`)
// Note that the script needs the ABI which is generated from the compilation artifact. // Note that the script needs the ABI which is generated from the compilation artifact.
@ -9,16 +16,12 @@ export const deploy = async (contractName: string, args: Array<any>, from?: stri
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath))
// 'web3Provider' is a remix global variable object // 'web3Provider' is a remix global variable object
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer); const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex)
let contract const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
if (from) {
contract = await factory.connect(from).deploy(...args); const contract = await factory.deploy(...args)
} else {
contract = await factory.deploy(...args);
}
// The contract is NOT deployed yet; we must wait until it is mined // The contract is NOT deployed yet; we must wait until it is mined
await contract.deployed() await contract.deployed()

@ -1,25 +1,34 @@
import Web3 from 'web3' import Web3 from 'web3'
import { Contract, ContractSendMethod, Options } from 'web3-eth-contract'
export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<any> => { /**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array<any>} args list of constructor' parameters
* @param {string} from account used to send the transaction
* @param {number} gas gas limit
* @return {Options} deployed contract
*/
export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {
const web3 = new Web3(window.web3Provider) const web3 = new Web3(web3Provider)
console.log(`deploying ${contractName}`) console.log(`deploying ${contractName}`)
// Note that the script needs the ABI which is generated from the compilation artifact. // Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated // Make sure contract is compiled and artifacts are generated
const artifactsPath = `browser/contracts/artifacts/${contractName}.json` // Change this for different path const artifactsPath = `browser/contracts/artifacts/${contractName}.json`
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath))
const accounts = await web3.eth.getAccounts() const accounts = await web3.eth.getAccounts()
let contract = new web3.eth.Contract(metadata.abi) const contract: Contract = new web3.eth.Contract(metadata.abi)
contract = contract.deploy({ const contractSend: ContractSendMethod = contract.deploy({
data: metadata.data.bytecode.object, data: metadata.data.bytecode.object,
arguments: args arguments: args
}) })
const newContractInstance = await contract.send({ const newContractInstance = await contractSend.send({
from: from || accounts[0], from: from || accounts[0],
gas: gas || 1500000 gas: gas || 1500000
}) })

@ -1,24 +1,27 @@
import { ethers } from 'ethers' import { ethers } from 'ethers'
export const deploy = async (contractName: string, args: Array<any>, from?: string): Promise<any> => { /**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array<any>} args list of constructor' parameters
* @param {Number} accountIndex account index from the exposed account
* @return {Contract} deployed contract
*/
export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {
console.log(`deploying ${contractName}`) console.log(`deploying ${contractName}`)
// Note that the script needs the ABI which is generated from the compilation artifact. // Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated // Make sure contract is compiled and artifacts are generated
const artifactsPath = `browser/contracts/artifacts/${contractName}.json` const artifactsPath = `browser/contracts/artifacts/${contractName}.json` // Change this for different path
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath))
// 'web3Provider' is a remix global variable object // 'web3Provider' is a remix global variable object
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer); const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex)
let contract const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
if (from) {
contract = await factory.connect(from).deploy(...args); const contract = await factory.deploy(...args)
} else {
contract = await factory.deploy(...args);
}
// The contract is NOT deployed yet; we must wait until it is mined // The contract is NOT deployed yet; we must wait until it is mined
await contract.deployed() await contract.deployed()

@ -1,8 +1,17 @@
import Web3 from 'web3' import Web3 from 'web3'
import { Contract, ContractSendMethod, Options } from 'web3-eth-contract'
export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<any> => { /**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array<any>} args list of constructor' parameters
* @param {string} from account used to send the transaction
* @param {number} gas gas limit
* @return {Options} deployed contract
*/
export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {
const web3 = new Web3(window.web3Provider) const web3 = new Web3(web3Provider)
console.log(`deploying ${contractName}`) console.log(`deploying ${contractName}`)
// Note that the script needs the ABI which is generated from the compilation artifact. // Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated // Make sure contract is compiled and artifacts are generated
@ -12,14 +21,14 @@ export const deploy = async (contractName: string, args: Array<any>, from?: stri
const accounts = await web3.eth.getAccounts() const accounts = await web3.eth.getAccounts()
let contract = new web3.eth.Contract(metadata.abi) const contract: Contract = new web3.eth.Contract(metadata.abi)
contract = contract.deploy({ const contractSend: ContractSendMethod = contract.deploy({
data: metadata.data.bytecode.object, data: metadata.data.bytecode.object,
arguments: args arguments: args
}) })
const newContractInstance = await contract.send({ const newContractInstance = await contractSend.send({
from: from || accounts[0], from: from || accounts[0],
gas: gas || 1500000 gas: gas || 1500000
}) })

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remixd", "name": "@remix-project/remixd",
"version": "0.6.1", "version": "0.6.2",
"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",

@ -1,6 +1,6 @@
{ {
"name": "remix-project", "name": "remix-project",
"version": "0.24.0-dev", "version": "0.25.0-dev",
"license": "MIT", "license": "MIT",
"description": "Ethereum Remix Monorepo", "description": "Ethereum Remix Monorepo",
"keywords": [ "keywords": [
@ -60,7 +60,7 @@
"minify": "uglifyjs --in-source-map inline --source-map-inline -c warnings=false", "minify": "uglifyjs --in-source-map inline --source-map-inline -c warnings=false",
"build:production": "NODE_ENV=production nx build remix-ide --skip-nx-cache", "build:production": "NODE_ENV=production nx build remix-ide --skip-nx-cache",
"serve:production": "npx http-server ./dist/apps/remix-ide", "serve:production": "npx http-server ./dist/apps/remix-ide",
"select_test": "sh apps/remix-ide-e2e/src/select_tests.sh", "select_test": "bash apps/remix-ide-e2e/src/select_tests.sh",
"group_test": "yarn run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/${npm_config_test}_group${npm_config_group}.test.js --env=${npm_config_env}", "group_test": "yarn run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/${npm_config_test}_group${npm_config_group}.test.js --env=${npm_config_env}",
"nightwatch_parallel": "yarn run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js --env=chrome,firefox", "nightwatch_parallel": "yarn run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js --env=chrome,firefox",
"nightwatch_local_firefox": "yarn run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js --env=firefox", "nightwatch_local_firefox": "yarn run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js --env=firefox",
@ -198,6 +198,7 @@
"react-draggable": "^4.4.4", "react-draggable": "^4.4.4",
"react-tabs": "^3.2.2", "react-tabs": "^3.2.2",
"regenerator-runtime": "0.13.7", "regenerator-runtime": "0.13.7",
"rss-parser": "^3.12.0",
"selenium": "^2.20.0", "selenium": "^2.20.0",
"signale": "^1.4.0", "signale": "^1.4.0",
"string-similarity": "^4.0.4", "string-similarity": "^4.0.4",

Loading…
Cancel
Save