Merge branch 'master' into intl

pull/2581/head
drafish 2 years ago
commit 5eeb22a0e1
  1. 18
      apps/remix-ide-e2e/src/commands/currentWorkspaceIs.ts
  2. 18
      apps/remix-ide-e2e/src/commands/switchWorkspace.ts
  3. 8
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  4. 6
      apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts
  5. 8
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  6. 4
      apps/remix-ide-e2e/src/tests/generalSettings.test.ts
  7. 4
      apps/remix-ide-e2e/src/tests/migrateFileSystem.test.ts
  8. 10
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  9. 3
      apps/remix-ide-e2e/src/tests/providers.test.ts
  10. 2
      apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
  11. 2
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  12. 6
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  13. 4
      apps/remix-ide-e2e/src/tests/url.test.ts
  14. 26
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  15. 3
      apps/remix-ide-e2e/src/types/index.d.ts
  16. 3
      apps/remix-ide/src/app.js
  17. 13
      apps/remix-ide/src/app/files/dgitProvider.js
  18. 9
      apps/remix-ide/src/app/files/fileManager.ts
  19. 8
      apps/remix-ide/src/app/plugins/storage.ts
  20. 14
      apps/remix-ide/src/app/tabs/abstract-provider.tsx
  21. 41
      apps/remix-ide/src/app/tabs/external-http-provider.tsx
  22. 13
      apps/remix-ide/src/app/tabs/runTab/model/recorder.js
  23. 2
      apps/remix-ide/src/app/tabs/theme-module.js
  24. 14
      apps/remix-ide/src/app/udapp/run-tab.js
  25. 18
      apps/remix-ide/src/blockchain/blockchain.js
  26. 3
      apps/remix-ide/src/blockchain/execution-context.js
  27. 2
      apps/remix-ide/src/remixAppManager.js
  28. 1
      libs/remix-core-plugin/src/index.ts
  29. 9
      libs/remix-core-plugin/src/lib/helpers/fetch-etherscan.ts
  30. 2
      libs/remix-lib/src/execution/txRunnerWeb3.ts
  31. 33
      libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx
  32. 4
      libs/remix-ui/app/src/lib/remix-app/context/provider.tsx
  33. 6
      libs/remix-ui/app/src/lib/remix-app/interface/index.ts
  34. 1
      libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts
  35. 1
      libs/remix-ui/app/src/lib/remix-app/state/modals.ts
  36. 14
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  37. 4
      libs/remix-ui/helper/src/lib/components/web3Dialog.tsx
  38. 5
      libs/remix-ui/helper/src/lib/helper-components.tsx
  39. 16
      libs/remix-ui/helper/src/lib/remix-ui-helper.ts
  40. 10
      libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx
  41. 7
      libs/remix-ui/modal-dialog/src/lib/types/index.ts
  42. 25
      libs/remix-ui/run-tab/src/lib/actions/account.ts
  43. 16
      libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx
  44. 2
      libs/remix-ui/run-tab/src/lib/components/environment.tsx
  45. 2
      libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx
  46. 2
      libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx
  47. 1
      libs/remix-ui/run-tab/src/lib/css/run-tab.css
  48. 15
      libs/remix-ui/run-tab/src/lib/reducers/runTab.ts
  49. 9
      libs/remix-ui/run-tab/src/lib/types/blockchain.d.ts
  50. 2
      libs/remix-ui/search/src/lib/reducers/Reducer.ts
  51. 4
      libs/remix-ui/settings/src/lib/constants.ts
  52. 81
      libs/remix-ui/settings/src/lib/github-settings.tsx
  53. 15
      libs/remix-ui/settings/src/lib/remix-ui-settings.tsx
  54. 4
      libs/remix-ui/settings/src/lib/settingsAction.ts
  55. 12
      libs/remix-ui/settings/src/types/index.ts
  56. 20
      libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx
  57. 6
      libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts
  58. 7
      libs/remix-ui/solidity-compiler/src/lib/solidity-compiler.tsx
  59. 1
      libs/remix-ui/solidity-compiler/src/lib/types/index.ts
  60. 2
      libs/remix-ui/terminal/src/lib/components/RenderCall.tsx
  61. 2
      libs/remix-ui/terminal/src/lib/components/RenderKnownTransactions.tsx
  62. 2
      libs/remix-ui/terminal/src/lib/components/RenderUnknownTransactions.tsx
  63. 12
      libs/remix-ui/tooltip-popup/.babelrc
  64. 18
      libs/remix-ui/tooltip-popup/.eslintrc.json
  65. 7
      libs/remix-ui/tooltip-popup/README.md
  66. 1
      libs/remix-ui/tooltip-popup/src/index.ts
  67. 0
      libs/remix-ui/tooltip-popup/src/lib/tooltip-popup.module.css
  68. 27
      libs/remix-ui/tooltip-popup/src/lib/tooltip-popup.tsx
  69. 6
      libs/remix-ui/tooltip-popup/src/types/index.ts
  70. 20
      libs/remix-ui/tooltip-popup/tsconfig.json
  71. 13
      libs/remix-ui/tooltip-popup/tsconfig.lib.json
  72. 102
      libs/remix-ui/workspace/src/lib/actions/index.ts
  73. 24
      libs/remix-ui/workspace/src/lib/actions/payload.ts
  74. 71
      libs/remix-ui/workspace/src/lib/actions/workspace.ts
  75. 42
      libs/remix-ui/workspace/src/lib/components/custom-dropdown.tsx
  76. 5
      libs/remix-ui/workspace/src/lib/contexts/index.ts
  77. 38
      libs/remix-ui/workspace/src/lib/css/remix-ui-workspace.css
  78. 9
      libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
  79. 73
      libs/remix-ui/workspace/src/lib/reducers/workspace.ts
  80. 267
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  81. 3
      libs/remixd/package.json
  82. 6
      libs/remixd/src/bin/remixd.ts
  83. 25
      libs/remixd/src/scripts/installSlither.ts
  84. 3
      nx.json
  85. 2
      package.json
  86. 3
      tsconfig.base.json
  87. 16
      workspace.json
  88. 69
      yarn.lock

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

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

@ -83,10 +83,10 @@ module.exports = {
browser
.openFile('Untitled.sol')
.clickLaunchIcon('udapp')
.click('*[data-id="settingsWeb3Mode"]')
.waitForElementPresent('[data-id="envNotification-modal-footer-ok-react"]')
.click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]')
.waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]')
.execute(function () {
const modal = document.querySelector('[data-id="envNotification-modal-footer-ok-react"]') as any
const modal = document.querySelector('[data-id="basic-http-provider-modal-footer-ok-react"]') as any
modal.click()
})
@ -96,7 +96,7 @@ module.exports = {
return env.value
}, [], function (result) {
browser.assert.ok(result.value === 'web3', 'Web3 Provider not selected')
browser.assert.ok(result.value === 'External Http Provider', 'Web3 Provider not selected')
})
.clickLaunchIcon('solidity')
.clickLaunchIcon('udapp')

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

@ -214,10 +214,10 @@ module.exports = {
.setSolidityCompilerVersion('soljson-v0.8.7+commit.e28d00a7.js')
.addFile('useDebugNodes.sol', sources[5]['useDebugNodes.sol']) // compile contract
.clickLaunchIcon('udapp')
.click('*[data-id="settingsWeb3Mode"]') // select web3 provider with debug nodes URL
.clearValue('*[data-id="modalDialogCustomPromptText"]')
.setValue('*[data-id="modalDialogCustomPromptText"]', 'https://remix-rinkeby.ethdevops.io')
.modalFooterOKClick()
.click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') // select web3 provider with debug nodes URL
.clearValue('*[data-id="modalDialogCustomPromp"]')
.setValue('*[data-id="modalDialogCustomPromp"]', 'https://remix-rinkeby.ethdevops.io')
.modalFooterOKClick('basic-http-provider')
.waitForElementPresent('*[title="Deploy - transact (not payable)"]', 65000) // wait for the compilation to succeed
.clickLaunchIcon('debugger')
.clearValue('*[data-id="debuggerTransactionInput"]')

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

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

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

@ -44,9 +44,6 @@ module.exports = {
.waitForElementContainsText('*[data-id="foundry-providerModalDialogModalBody-react"]', 'Error while connecting to the provider')
.modalFooterOKClick('foundry-provider')
.waitForElementNotVisible('*[data-id="foundry-providerModalDialogModalBody-react"]')
.waitForElementVisible('*[data-id="PermissionHandler-modal-footer-ok-react"]')
.click('*[data-id="PermissionHandler-modal-footer-ok-react"]')
.waitForElementNotVisible('*[data-id="PermissionHandler-modal-footer-ok-react"]')
.pause(1000)
},

@ -198,7 +198,7 @@ module.exports = {
.assert.containsText('*[data-id="solidityLocals"]', 'to: 0x6C3CCC7FBA111707D5A1AAF2758E9D4F4AC5E7B1')
},
'Call web3.eth.getAccounts() using Injected web3 (Metamask)': '' + function (browser: NightwatchBrowser) {
'Call web3.eth.getAccounts() using Injected Provider (Metamask)': '' + function (browser: NightwatchBrowser) {
browser
.executeScript('web3.eth.getAccounts()')
.pause(2000)

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

@ -40,7 +40,7 @@ module.exports = {
.waitForElementContainsText('*[data-id="terminalJournal"]', 'contract Ballot {', 60000)
},
'Call web3.eth.getAccounts() using JavaScript VM #group2': function (browser: NightwatchBrowser) {
'Call web3.eth.getAccounts() using Remix VM #group2': function (browser: NightwatchBrowser) {
browser
.executeScript('web3.eth.getAccounts()')
.waitForElementContainsText('*[data-id="terminalJournal"]', '["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db","0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB","0x617F2E2fD72FD9D5503197092aC168c91465E7f2","0x17F6AD8Ef982297579C203069C1DbfFE4348c372","0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678","0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7","0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C","0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC","0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c","0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C","0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB","0x583031D1113aD414F02576BD6afaBfb302140225","0xdD870fA1b7C4700F2BD7f44238821C26f7392148"]')
@ -50,8 +50,8 @@ module.exports = {
browser
.click('*[data-id="terminalClearConsole"]') // clear the terminal
.clickLaunchIcon('udapp')
.click('*[data-id="settingsWeb3Mode"]')
.modalFooterOKClick('envNotification')
.click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]')
.modalFooterOKClick('basic-http-provider')
.executeScript('web3.eth.getAccounts()')
.waitForElementContainsText('*[data-id="terminalJournal"]', '["', 60000) // we check if an array is present, don't need to check for the content
.waitForElementContainsText('*[data-id="terminalJournal"]', '"]', 60000)

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

@ -38,7 +38,7 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspaceCreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_remix_default' })
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
@ -94,7 +94,7 @@ module.exports = {
browser
.click('*[data-id="workspaceCreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_blank' })
.click('select[id="wstemplate"]')
@ -115,7 +115,7 @@ module.exports = {
browser
.click('*[data-id="workspaceCreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc20' })
.click('select[id="wstemplate"]')
@ -163,7 +163,7 @@ module.exports = {
browser
.click('*[data-id="workspaceCreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc721' })
.click('select[id="wstemplate"]')
@ -213,7 +213,7 @@ module.exports = {
browser
.click('*[data-id="workspaceCreate"]') // create workspace_name
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('*[data-id="modalDialogCustomPromptTextCreate"]')
.clearValue('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name')
@ -225,7 +225,7 @@ module.exports = {
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.sol"]')
.click('*[data-id="workspaceCreate"]') // create workspace_name_1
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('*[data-id="modalDialogCustomPromptTextCreate"]')
.clearValue('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name_1')
@ -235,7 +235,7 @@ module.exports = {
.pause(2000)
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]')
.pause(2000)
.click('*[data-id="workspacesSelect"] option[value="workspace_name"]')
.switchWorkspace('workspace_name')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
},
@ -249,23 +249,23 @@ module.exports = {
.setValue('*[data-id="modalDialogCustomPromptTextRename"]', 'workspace_name_renamed')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.waitForElementPresent('*[data-id="workspacesSelect"] option[value="workspace_name_1"]')
.click('*[data-id="workspacesSelect"] option[value="workspace_name_1"]')
.switchWorkspace('workspace_name_1')
.pause(2000)
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]')
.waitForElementPresent('*[data-id="workspacesSelect"] option[value="workspace_name_renamed"]')
.click('*[data-id="workspacesSelect"] option[value="workspace_name_renamed"]')
.switchWorkspace('workspace_name_renamed')
.pause(2000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.sol"]')
},
'Should delete a workspace #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="workspacesSelect"] option[value="workspace_name_1"]')
.switchWorkspace('workspace_name_1')
.click('*[data-id="workspaceDelete"]') // delete workspace_name_1
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.waitForElementNotPresent('*[data-id="workspacesSelect"] option[value="workspace_name_1"]')
.waitForElementVisible('[data-id="workspacesSelect"]')
.click('[data-id="workspacesSelect"]')
.waitForElementNotPresent(`[data-id="dropdown-item-workspace_name_1"]`)
.end()
},

@ -62,7 +62,8 @@ declare module 'nightwatch' {
clearConsole (this: NightwatchBrowser): NightwatchBrowser
clearTransactions (this: NightwatchBrowser): NightwatchBrowser
getBrowserLogs (this: NightwatchBrowser): NightwatchBrowser
currentSelectedFileIs (name: string): NightwatchBrowser
currentSelectedFileIs (name: string): NightwatchBrowser,
switchWorkspace: (workspaceName: string) => NightwatchBrowser
}
export interface NightwatchBrowser {

@ -29,6 +29,7 @@ import { Blockchain } from './blockchain/blockchain.js'
import { HardhatProvider } from './app/tabs/hardhat-provider'
import { GanacheProvider } from './app/tabs/ganache-provider'
import { FoundryProvider } from './app/tabs/foundry-provider'
import { ExternalHttpProvider } from './app/tabs/external-http-provider'
const isElectron = require('is-electron')
@ -183,6 +184,7 @@ class AppComponent {
const hardhatProvider = new HardhatProvider(blockchain)
const ganacheProvider = new GanacheProvider(blockchain)
const foundryProvider = new FoundryProvider(blockchain)
const externalHttpProvider = new ExternalHttpProvider(blockchain)
// ----------------- convert offset to line/column service -----------
const offsetToLineColumnConverter = new OffsetToLineColumnConverter()
Registry.getInstance().put({
@ -241,6 +243,7 @@ class AppComponent {
hardhatProvider,
ganacheProvider,
foundryProvider,
externalHttpProvider,
this.walkthroughService,
search
])

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

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

@ -15,14 +15,14 @@ export class StoragePlugin extends Plugin {
async getStorage() {
let storage = null
if ('storage' in navigator && 'estimate' in navigator.storage && (window as any).remixFileSystem.name !== 'localstorage') {
storage = navigator.storage.estimate()
storage = await navigator.storage.estimate()
} else {
storage ={
usage: parseFloat(this.calculateLocalStorage()) * 1000,
quota: 5000000,
}
}
const _paq = window._paq = window._paq || []
const _paq = (window as any)._paq = (window as any)._paq || []
_paq.push(['trackEvent', 'Storage', 'used', this.formatString(storage)]);
return storage
}
@ -32,8 +32,8 @@ export class StoragePlugin extends Plugin {
}
calculateLocalStorage() {
var _lsTotal = 0
var _xLen; var _x
let _lsTotal = 0
let _xLen; let _x
for (_x in localStorage) {
// eslint-disable-next-line no-prototype-builtins
if (!localStorage.hasOwnProperty(_x)) {

@ -58,6 +58,20 @@ export abstract class AbstractProvider extends Plugin {
modalType: ModalTypes.prompt,
okLabel: 'OK',
cancelLabel: 'Cancel',
validationFn: (value) => {
if (!value) return { valid: false, message: "value is empty" }
if (value.startsWith('https://') || value.startsWith('http://')) {
return {
valid: true,
message: ''
}
} else {
return {
valid: false,
message: 'the provided value should contain the protocol ( e.g starts with http:// or https:// )'
}
}
},
okFn: (value: string) => {
setTimeout(() => resolve(value), 0)
},

@ -0,0 +1,41 @@
import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line
import { AbstractProvider } from './abstract-provider'
const profile = {
name: 'basic-http-provider',
displayName: 'External Http Provider',
kind: 'provider',
description: '',
methods: ['sendAsync'],
version: packageJson.version
}
export class ExternalHttpProvider extends AbstractProvider {
constructor (blockchain) {
super(profile, blockchain, 'http://127.0.0.1:8545')
}
body (): JSX.Element {
const thePath = '<path/to/local/folder/for/test/chain>'
return (
<>
<div className="">
Note: To use Geth & https://remix.ethereum.org, configure it to allow requests from Remix:(see <a href="https://geth.ethereum.org/docs/rpc/server" target="_blank" rel="noreferrer">Geth Docs on rpc server</a>)
<div className="border p-1">geth --http --http.corsdomain https://remix.ethereum.org</div>
<br />
To run Remix & a local Geth test node, use this command: (see <a href="https://geth.ethereum.org/getting-started/dev-mode" target="_blank" rel="noreferrer">Geth Docs on Dev mode</a>)
<div className="border p-1">geth --http --http.corsdomain="{window.origin}" --http.api web3,eth,debug,personal,net --vmdebug --datadir {thePath} --dev console</div>
<br />
<br />
<b>WARNING:</b> It is not safe to use the --http.corsdomain flag with a wildcard: <b>--http.corsdomain *</b>
<br />
<br />For more info: <a href="https://remix-ide.readthedocs.io/en/latest/run.html#more-about-web3-provider" target="_blank" rel="noreferrer">Remix Docs on Web3 Provider</a>
<br />
<br />
External HTTP Provider Endpoint
</div>
</>
)
}
}

@ -33,7 +33,13 @@ class Recorder extends Plugin {
// convert to and from to tokens
if (this.data._listen) {
var record = { value, parameters: payLoad.funArgs }
var record = {
value,
inputs: txHelper.serializeInputs(payLoad.funAbi),
parameters: payLoad.funArgs,
name: payLoad.funAbi.name,
type: payLoad.funAbi.type
}
if (!to) {
var abi = payLoad.contractABI
var keccak = ethutil.bufferToHex(ethutil.keccakFromString(JSON.stringify(abi)))
@ -55,10 +61,7 @@ class Recorder extends Plugin {
var creationTimestamp = this.data._createdContracts[to]
record.to = `created{${creationTimestamp}}`
record.abi = this.data._contractABIReferences[creationTimestamp]
}
record.name = payLoad.funAbi.name
record.inputs = txHelper.serializeInputs(payLoad.funAbi)
record.type = payLoad.funAbi.type
}
for (var p in record.parameters) {
var thisarg = record.parameters[p]
var thistimestamp = this.data._createdContracts[thisarg]

@ -37,7 +37,7 @@ export class ThemeModule extends Plugin {
themes.map((theme) => {
this.themes[theme.name.toLocaleLowerCase()] = {
...theme,
url: window.location.origin + window.location.pathname + theme.url
url: window.location.origin + ( window.location.pathname.startsWith('/address/') || window.location.pathname.endsWith('.sol') ? '/' : window.location.pathname ) + theme.url
}
})
this._paq = _paq

@ -156,6 +156,20 @@ export class RunTab extends ViewPlugin {
}
}
})
await this.call('blockchain', 'addProvider', {
name: 'External Http Provider',
provider: {
async sendAsync (payload, callback) {
try {
const result = await udapp.call('basic-http-provider', 'sendAsync', payload)
callback(null, result)
} catch (e) {
callback(e)
}
}
}
})
}
writeFile (fileName, content) {

@ -464,7 +464,7 @@ export class Blockchain extends Plugin {
}
/**
* This function send a tx only to javascript VM or testnet, will return an error for the mainnet
* This function send a tx only to Remix VM or testnet, will return an error for the mainnet
* SHOULD BE TAKEN CAREFULLY!
*
* @param {Object} tx - transaction.
@ -530,6 +530,7 @@ export class Blockchain extends Plugin {
if (this.transactionContextAPI.getAddress) {
return this.transactionContextAPI.getAddress(function (err, address) {
if (err) return reject(err)
if (!address) return reject('"from" is not defined. Please make sure an account is selected. If you are using a public node, it is likely that no account will be provided. In that case, add the public node to your injected provider (type Metamask) and use injected provider in Remix.')
return resolve(address)
})
}
@ -548,9 +549,18 @@ export class Blockchain extends Plugin {
const runTransaction = async () => {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
const fromAddress = await getAccount()
const value = await queryValue()
const gasLimit = await getGasLimit()
let fromAddress
let value
let gasLimit
try {
fromAddress = await getAccount()
value = await queryValue()
gasLimit = await getGasLimit()
} catch (e) {
reject(e)
return
}
const tx = { to: args.to, data: args.data.dataHex, useCall: args.useCall, from: fromAddress, value: value, gasLimit: gasLimit, timestamp: args.data.timestamp }
const payLoad = { funAbi: args.data.funAbi, funArgs: args.data.funArgs, contractBytecode: args.data.contractBytecode, contractName: args.data.contractName, contractABI: args.data.contractABI, linkReferences: args.data.linkReferences }

@ -164,9 +164,6 @@ export class ExecutionContext {
}
}
if (context === 'web3') {
confirmCb(cb)
}
if (this.customNetWorks[context]) {
var network = this.customNetWorks[context]
this.setProviderFromEndpoint(network.provider, { context: network.name }, (error) => {

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

@ -8,3 +8,4 @@ export { GistHandler } from './lib/gist-handler'
export * from './types/contract'
export { LinkLibraries, DeployLibraries } from './lib/link-libraries'
export { OpenZeppelinProxy } from './lib/openzeppelin-proxy'
export { fetchContractFromEtherscan } from './lib/helpers/fetch-etherscan'

@ -1,8 +1,11 @@
export const fetchContractFromEtherscan = async (plugin, network, contractAddress, targetPath) => {
export const fetchContractFromEtherscan = async (plugin, network, contractAddress, targetPath, key?) => {
let data
const compilationTargets = {}
let etherscanKey
const etherscanKey = await plugin.call('config', 'getAppParameter', 'etherscan-access-token')
if (!key) etherscanKey = await plugin.call('config', 'getAppParameter', 'etherscan-access-token')
else etherscanKey = key
if (etherscanKey) {
const endpoint = network.id == 1 ? 'api.etherscan.io' : 'api-' + network.name + '.etherscan.io'
data = await fetch('https://' + endpoint + '/api?module=contract&action=getsourcecode&address=' + contractAddress + '&apikey=' + etherscanKey)
@ -10,7 +13,7 @@ export const fetchContractFromEtherscan = async (plugin, network, contractAddres
// etherscan api doc https://docs.etherscan.io/api-endpoints/contracts
if (data.message === 'OK' && data.status === "1") {
if (data.result.length) {
if (data.result[0].SourceCode === '') throw new Error('contract not verified in Etherscan')
if (data.result[0].SourceCode === '') throw new Error(`contract not verified on Etherscan ${network.name} network`)
if (data.result[0].SourceCode.startsWith('{')) {
data.result[0].SourceCode = JSON.parse(data.result[0].SourceCode.replace(/(?:\r\n|\r|\n)/g, '').replace(/^{{/,'{').replace(/}}$/,'}'))
}

@ -85,7 +85,7 @@ export class TxRunnerWeb3 {
runInNode (from, to, data, value, gasLimit, useCall, timestamp, confirmCb, gasEstimationForceSend, promptCb, callback) {
const tx = { from: from, to: to, data: data, value: value }
if (!from) return callback('the value of "from" is not defined. Please make sure an account is selected.')
if (useCall) {
tx['gas'] = gasLimit
if (this._api && this._api.isVM()) tx['timestamp'] = timestamp

@ -1,5 +1,5 @@
import React, { useEffect, useRef, useState } from 'react'
import { ModalDialog, ModalDialogProps } from '@remix-ui/modal-dialog'
import { ModalDialog, ModalDialogProps, ValidationResult } from '@remix-ui/modal-dialog'
import { ModalTypes } from '../../types'
interface ModalWrapperProps extends ModalDialogProps {
@ -29,12 +29,23 @@ const ModalWrapper = (props: ModalWrapperProps) => {
(props.cancelFn) ? props.cancelFn() : props.resolve(false)
}
const createModalMessage = (defaultValue: string) => {
const onInputChanged = (event) => {
if (props.validationFn) {
const validation = props.validationFn(event.target.value)
setState(prevState => {
return { ...prevState, message: createModalMessage(props.defaultValue, validation), validation }
})
}
}
const createModalMessage = (defaultValue: string, validation: ValidationResult) => {
return (
<>
{props.message}
<input type={props.modalType === ModalTypes.password ? 'password' : 'text'} defaultValue={defaultValue} data-id="modalDialogCustomPromp" ref={ref} className="form-control" /></>
)
<input onChange={onInputChanged} type={props.modalType === ModalTypes.password ? 'password' : 'text'} defaultValue={defaultValue} data-id="modalDialogCustomPromp" ref={ref} className="form-control" />
{!validation.valid && <span className='text-warning'>{validation.message}</span>}
</>
)
}
useEffect(() => {
@ -47,13 +58,13 @@ const ModalWrapper = (props: ModalWrapperProps) => {
...props,
okFn: onFinishPrompt,
cancelFn: onCancelFn,
message: createModalMessage(props.defaultValue)
message: createModalMessage(props.defaultValue, { valid: true })
})
break
default:
setState({
...props,
okFn: (onOkFn),
okFn: onOkFn,
cancelFn: onCancelFn
})
break
@ -67,8 +78,16 @@ const ModalWrapper = (props: ModalWrapperProps) => {
}
}, [props])
// reset the message and input if any, so when the modal is shown again it doesn't show the previous value.
const handleHide = () => {
setState(prevState => {
return { ...prevState, message: '' }
})
props.handleHide()
}
return (
<ModalDialog id={props.id} {...state} handleHide={props.handleHide} />
<ModalDialog id={props.id} {...state} handleHide={handleHide} />
)
}
export default ModalWrapper

@ -16,11 +16,11 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
}
const modal = (modalData: AppModal) => {
const { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue, hideFn, data } = modalData
const { id, title, message, validationFn, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue, hideFn, data } = modalData
return new Promise((resolve, reject) => {
dispatch({
type: modalActionTypes.setModal,
payload: { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue, hideFn, resolve, next: onNextFn, data }
payload: { id, title, message, okLabel, validationFn, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue, hideFn, resolve, next: onNextFn, data }
})
})
}

@ -1,10 +1,16 @@
import { ModalTypes } from '../types'
export type ValidationResult = {
valid: boolean,
message?: string
}
export interface AppModal {
id: string
timestamp?: number
hide?: boolean
title: string
validationFn?: (value: string) => ValidationResult
// eslint-disable-next-line no-undef
message: string | JSX.Element
okLabel: string

@ -11,6 +11,7 @@ export const modalReducer = (state: ModalState = ModalInitialState, action: Moda
id: action.payload.id || timestamp.toString(),
hide: false,
title: action.payload.title,
validationFn: action.payload.validationFn,
message: action.payload.message,
okLabel: action.payload.okLabel,
okFn: action.payload.okFn,

@ -8,6 +8,7 @@ export const ModalInitialState: ModalState = {
hide: true,
title: '',
message: '',
validationFn: () => { return {valid: true, message: ''} },
okLabel: '',
okFn: () => { },
cancelLabel: '',

@ -416,6 +416,20 @@ export const EditorUI = (props: EditorUIProps) => {
editor.addCommand(monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.US_MINUS, () => {
editor.updateOptions({ fontSize: editor.getOption(43).fontSize - 1 })
})
const editorService = editor._codeEditorService;
const openEditorBase = editorService.openCodeEditor.bind(editorService);
editorService.openCodeEditor = async (input, source) => {
const result = await openEditorBase(input, source)
if (input && input.resource && input.resource.path) {
try {
await props.plugin.call('fileManager', 'open', input.resource.path)
} catch (e) {
console.log(e)
}
}
return result
}
}
function handleEditorWillMount (monaco) {

@ -24,10 +24,10 @@ export function Web3ProviderDialog (props: web3ProviderDialogProps) {
<br />
<b>WARNING:</b> It is not safe to use the --http.corsdomain flag with a wildcard: <b>--http.corsdomain *</b>
<br />
<br />For more info: <a href="https://remix-ide.readthedocs.io/en/latest/run.html#more-about-web3-provider" target="_blank" rel="noreferrer">Remix Docs on Web3 Provider</a>
<br />For more info: <a href="https://remix-ide.readthedocs.io/en/latest/run.html#more-about-web3-provider" target="_blank" rel="noreferrer">Remix Docs on Remix Provider</a>
<br />
<br />
Web3 Provider Endpoint
External HTTP Provider Endpoint
</div>
<input
onInput={handleInputEndpoint}

@ -1,5 +1,4 @@
import React from 'react'
import { Web3ProviderDialog } from './components/web3Dialog'
export const fileChangedToastMsg = (from: string, path: string) => (
<div><i className="fas fa-exclamation-triangle text-danger mr-1"></i>
@ -54,10 +53,6 @@ export const sourceVerificationNotAvailableToastMsg = () => (
</div>
)
export const web3Dialog = (externalEndpoint: string, setWeb3Endpoint: (value: string) => void) => (
<Web3ProviderDialog externalEndpoint={externalEndpoint} setWeb3Endpoint={setWeb3Endpoint} />
)
export const envChangeNotification = (env: { context: string, fork: string }, from: string) => (
<div>
<i className="fas fa-exclamation-triangle text-danger mr-1"></i>

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

@ -96,18 +96,20 @@ export const ModalDialog = (props: ModalDialogProps) => {
</div>
<div className="modal-footer" data-id={`${props.id}ModalDialogModalFooter-react`}>
{/* todo add autofocus ^^ */}
{ props.okLabel && <span
{ props.okLabel && <button
data-id={`${props.id}-modal-footer-ok-react`}
className={'modal-ok btn btn-sm ' + (state.toggleBtn ? 'btn-dark' : 'btn-light')}
disabled={props.validation && !props.validation.valid}
onClick={() => {
if (props.validation && !props.validation.valid) return
if (props.okFn) props.okFn()
handleHide()
}}
>
{props.okLabel ? props.okLabel : 'OK'}
</span>
</button>
}
{ props.cancelLabel && <span
{ props.cancelLabel && <button
data-id={`${props.id}-modal-footer-cancel-react`}
className={'modal-cancel btn btn-sm ' + (state.toggleBtn ? 'btn-light' : 'btn-dark')}
data-dismiss="modal"
@ -117,7 +119,7 @@ export const ModalDialog = (props: ModalDialogProps) => {
}}
>
{props.cancelLabel ? props.cancelLabel : 'Cancel'}
</span>
</button>
}
</div>
</div>

@ -1,8 +1,15 @@
export type ValidationResult = {
valid: boolean,
message?: string
}
/* eslint-disable no-undef */
export interface ModalDialogProps {
id: string
timestamp?: number,
title?: string,
validation?: ValidationResult
validationFn?: (value: string) => ValidationResult
message?: string | JSX.Element,
okLabel?: string,
okFn?: (value?:any) => void,

@ -1,4 +1,4 @@
import { shortenAddress, web3Dialog } from "@remix-ui/helper"
import { shortenAddress } from "@remix-ui/helper"
import { RunTab } from "../types/run-tab"
import { clearInstances, setAccount, setExecEnv } from "./actions"
import { displayNotification, displayPopUp, fetchAccountsListFailed, fetchAccountsListRequest, fetchAccountsListSuccess, setExternalEndpoint, setMatchPassphrase, setPassphrase } from "./payload"
@ -74,28 +74,7 @@ const _getProviderDropdownValue = (plugin: RunTab): string => {
}
export const setExecutionContext = (plugin: RunTab, dispatch: React.Dispatch<any>, executionContext: { context: string, fork: string }) => {
const displayContent = web3Dialog(plugin.REACT_API.externalEndpoint, (endpoint: string) => {
dispatch(setExternalEndpoint(endpoint))
})
plugin.blockchain.changeExecutionContext(executionContext, () => {
plugin.call('notification', 'modal', {
id: 'envNotification',
title: 'External node request',
message: displayContent,
okLabel: 'OK',
cancelLabel: 'Cancel',
okFn: () => {
plugin.blockchain.setProviderFromEndpoint(plugin.REACT_API.externalEndpoint, executionContext, (alertMsg) => {
if (alertMsg) plugin.call('notification', 'toast', alertMsg)
setFinalContext(plugin, dispatch)
})
},
cancelFn: () => {
setFinalContext(plugin, dispatch)
}
})
}, (alertMsg) => {
plugin.blockchain.changeExecutionContext(executionContext, null, (alertMsg) => {
plugin.call('notification', 'toast', alertMsg)
}, () => { setFinalContext(plugin, dispatch) })
}

@ -59,10 +59,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
content: currentFile
})
enableAtAddress(true)
} else if (/.(.sol)$/.exec(currentFile) ||
/.(.vy)$/.exec(currentFile) || // vyper
/.(.lex)$/.exec(currentFile) || // lexon
/.(.contract)$/.exec(currentFile)) {
} else if (isContractFile(currentFile)) {
setAbiLabel({
display: 'none',
content: ''
@ -117,6 +114,13 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
}
}
const isContractFile = (file) => {
return /.(.sol)$/.exec(file) ||
/.(.vy)$/.exec(file) || // vyper
/.(.lex)$/.exec(file) || // lexon
/.(.contract)$/.exec(file)
}
const enableAtAddress = (enable: boolean) => {
if (enable) {
setAtAddressOptions({
@ -218,7 +222,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
<FormattedMessage id='udapp.contract' defaultMessage='Contract' />
</label>
<div className="udapp_subcontainer">
<select ref={contractsRef} value={currentContract} onChange={handleContractChange} className="udapp_contractNames custom-select" disabled={contractOptions.disabled} title={contractOptions.title} style={{ display: loadType === 'abi' ? 'none' : 'block' }}>
<select ref={contractsRef} value={currentContract} onChange={handleContractChange} className="udapp_contractNames custom-select" disabled={contractOptions.disabled} title={contractOptions.title} style={{ display: loadType === 'abi' && !isContractFile(currentFile) ? 'none' : 'block' }}>
{ (contractList[currentFile] || []).map((contract, index) => {
return <option key={index} value={contract.alias}>{contract.alias} - {contract.file}</option>
}) }
@ -262,7 +266,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
</div> : ''
}
</div>
<div className="udapp_orLabel mt-2" style={{ display: loadType === 'abi' ? 'none' : 'block' }}>
<div className="udapp_orLabel mt-2" style={{ display: loadType === 'abi' && !isContractFile(currentFile) ? 'none' : 'block' }}>
<FormattedMessage id='udapp.or' defaultMessage='or' />
</div>
<div className="udapp_button udapp_atAddressSect">

@ -9,7 +9,7 @@ export function EnvironmentUI (props: EnvironmentProps) {
const fork = provider.fork // can be undefined if connected to an external source (web3 provider / injected)
let context = provider.value
context = context.startsWith('vm') ? 'vm' : context // context has to be 'vm', 'web3' or 'injected'
context = context.startsWith('vm') ? 'vm' : context
props.setExecutionContext({ context, fork })
}

@ -15,7 +15,7 @@ export function InstanceContainerUI (props: InstanceContainerProps) {
return (
<div className="udapp_instanceContainer mt-3 border-0 list-group-item">
<label className="udapp_deployedContracts d-flex justify-content-between align-items-center pl-2 mb-1"
<label className="udapp_deployedContracts d-flex justify-content-between align-items-center pl-2 mb-2"
title="Autogenerated generic user interfaces for interaction with deployed contracts">
<FormattedMessage id='udapp.deployedContracts' defaultMessage='Deployed Contracts' />
{ instanceList.length > 0

@ -204,7 +204,7 @@ export function UniversalDappUI (props: UdappProps) {
return (
<div className={`instance udapp_instance udapp_run-instance border-dark ${toggleExpander ? 'udapp_hidesub' : 'bg-light'}`} id={`instance${address}`} data-shared="universalDappUiInstance">
<div className="udapp_title alert alert-secondary">
<div className="udapp_title pb-0 alert alert-secondary">
<span data-id={`universalDappUiTitleExpander${props.index}`} className="btn udapp_titleExpander" onClick={toggleClass}>
<i className={`fas ${toggleExpander ? 'fa-angle-right' : 'fa-angle-down'}`} aria-hidden="true"></i>
</span>

@ -286,7 +286,6 @@
.udapp_instance {
display: block;
flex-direction: column;
margin-bottom: 12px;
background: none;
border-radius: 2px;
}

@ -2,6 +2,9 @@ import { CompilerAbstract } from '@remix-project/remix-solidity-ts'
import { ContractData } from '@remix-project/core-plugin'
import { DeployMode, DeployOption, DeployOptions } from '../types'
import { ADD_DEPLOY_OPTION, ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, FETCH_PROVIDER_LIST_FAILED, FETCH_PROVIDER_LIST_REQUEST, FETCH_PROVIDER_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_DEPLOY_OPTION, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_TX_FEE_CONTENT } from '../constants'
import Web3 from 'web3'
declare const window: any
interface Action {
type: string
payload: any
@ -118,26 +121,20 @@ export const runTabInitialState: RunTabState = {
title: 'Execution environment is local to Remix. Data is only saved to browser memory and will vanish upon reload.',
value: 'vm-london',
fork: 'london',
content: 'JavaScript VM (London)'
content: 'Remix VM (London)'
}, {
id: 'vm-mode-berlin',
dataId: 'settingsVMBerlinMode',
title: 'Execution environment is local to Remix. Data is only saved to browser memory and will vanish upon reload.',
value: 'vm-berlin',
fork: 'berlin',
content: 'JavaScript VM (Berlin)'
content: 'Remix VM (Berlin)'
}, {
id: 'injected-mode',
dataId: 'settingsInjectedMode',
title: 'Execution environment has been provided by Metamask or similar provider.',
value: 'injected',
content: 'Injected Web3'
}, {
id: 'web3-mode',
dataId: 'settingsWeb3Mode',
title: `Execution environment connects to an external node. For security, only connect to trusted networks. If Remix is served via https and your node is accessed via http, it might not work. In this case, try cloning the repository and serving it via http.`,
value: 'web3',
content: 'Web3 Provider'
content: `Injected Provider${(window && window.ethereum && window.ethereum.isMetaMask) ? ' - Metamask' : ''}`
}],
isRequesting: false,
isSuccessful: false,

@ -1,3 +1,6 @@
import { Plugin } from "@remixproject/engine/lib/abstract";
import { ExecutionContext } from "./execution-context";
import { EventEmitter } from "events";
export class Blockchain extends Plugin<any, any> {
constructor(config: any);
event: any;
@ -70,7 +73,7 @@ export class Blockchain extends Plugin<any, any> {
getBalanceInEther(address: any, cb: any): void;
pendingTransactionsCount(): number;
/**
* This function send a tx only to javascript VM or testnet, will return an error for the mainnet
* This function send a tx only to Remix VM or testnet, will return an error for the mainnet
* SHOULD BE TAKEN CAREFULLY!
*
* @param {Object} tx - transaction.
@ -78,6 +81,4 @@ export class Blockchain extends Plugin<any, any> {
sendTransaction(tx: any): any;
runTx(args: any, confirmationCb: any, continueCb: any, promptCb: any, cb: any): void;
}
import { Plugin } from "@remixproject/engine/lib/abstract";
import { ExecutionContext } from "./execution-context";
import { EventEmitter } from "events";

@ -46,7 +46,7 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action
run: true
}
case 'SET_UNDO_ENABLED':
if(state.undoBuffer[`${action.payload.workspace}/${action.payload.path}`]){
if(action.payload.workspace && state.undoBuffer[`${action.payload.workspace}/${action.payload.path}`]){
state.undoBuffer[`${action.payload.workspace}/${action.payload.path}`].enabled = (action.payload.content === state.undoBuffer[`${action.payload.workspace}/${action.payload.path}`].newContent)
state.undoBuffer[`${action.payload.workspace}/${action.payload.path}`].visible = (action.payload.content !== state.undoBuffer[`${action.payload.workspace}/${action.payload.path}`].oldContent)
}

@ -11,9 +11,9 @@ export const etherscanTokenTitle = 'EtherScan Access Token'
export const etherscanTokenLink = 'https://etherscan.io/myapikey'
export const etherscanAccessTokenText = 'Manage the api key used to interact with Etherscan.'
export const etherscanAccessTokenText2 = 'Go to Etherscan api key page (link below) to create a new api key and save it in Remix.'
export const ethereunVMText = 'Always use Javascript VM at load'
export const ethereunVMText = 'Always use Remix VM at load'
export const wordWrapText = 'Word wrap in editor'
export const enablePersonalModeText = ' Enable Personal Mode for web3 provider. Transaction sent over Web3 will use the web3.personal API.\n'
export const enablePersonalModeText = ' Enable Personal Mode for Remix Provider. Transaction sent over Web3 will use the web3.personal API.\n'
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 swarmSettingsText = 'Swarm Settings'

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

@ -10,6 +10,7 @@ import { Toaster } from '@remix-ui/toaster'// eslint-disable-line
import { RemixUiThemeModule, ThemeModule} from '@remix-ui/theme-module'
import { RemixUiLocaleModule, LocaleModule} from '@remix-ui/locale-module'
import { FormattedMessage, useIntl } from 'react-intl'
import { GithubSettings } from './github-settings'
/* eslint-disable-next-line */
export interface RemixUiSettingsProps {
@ -362,7 +363,19 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
<div>
{state.message ? <Toaster message= {state.message}/> : null}
{generalConfig()}
{token('gist')}
<GithubSettings
saveTokenToast={(githubToken: string, githubUserName: string, githubEmail: string) => {
saveTokenToast(props.config, dispatchToast, githubToken, "gist-access-token")
saveTokenToast(props.config, dispatchToast, githubUserName, "github-user-name")
saveTokenToast(props.config, dispatchToast, githubEmail, "github-email")
}}
removeTokenToast={() => {
removeTokenToast(props.config, dispatchToast, "gist-access-token")
removeTokenToast(props.config, dispatchToast, "github-user-name")
removeTokenToast(props.config, dispatchToast, "github-email")
}}
config={props.config}
/>
{token('etherscan')}
{swarmSettings()}
{ipfsSettings()}

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

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

@ -34,6 +34,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
configurationSettings,
isHardhatProject,
isTruffleProject,
isFoundryProject,
workspaceName,
configFilePath,
setConfigFilePath,
@ -74,7 +75,10 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
api.setAppParameter('configFilePath', defaultPath)
if (state.useFileConfiguration) {
api.fileExists(defaultPath).then((exists) => {
if (!exists && state.useFileConfiguration) createNewConfigFile()
if (!exists && state.useFileConfiguration) {
configFilePathInput.current.value = defaultPath
createNewConfigFile()
}
})
}
setShowFilePathInput(false)
@ -93,8 +97,9 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
useEffect(() => {
const listener = (event) => {
if (configFilePathInput.current !== event.target) {
if (configFilePathInput.current !== event.target && event.target.innerText !== "Create") {
setShowFilePathInput(false)
configFilePathInput.current.value = ""
return;
}
};
@ -244,7 +249,16 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
if (filePath === '') filePath = defaultPath
if (!filePath.endsWith('.json')) filePath = filePath + '.json'
await api.writeFile(filePath, configFileContent)
let compilerConfig = configFileContent
if (isFoundryProject && !compilerConfig.includes('remappings')) {
const config = JSON.parse(compilerConfig)
config.settings.remappings = [
'ds-test/=lib/forge-std/lib/ds-test/src/',
'forge-std/=lib/forge-std/src/'
]
compilerConfig = JSON.stringify(config, null, '\t')
}
await api.writeFile(filePath, compilerConfig)
api.setAppParameter('configFilePath', filePath)
setConfigFilePath(filePath)
compileTabLogic.setConfigFilePath(filePath)

@ -131,6 +131,12 @@ export class CompileTabLogic {
} else return false
}
async isFoundryProject () {
if (this.api.getFileManagerMode() === 'localhost') {
return await this.api.fileExists('foundry.toml')
} else return false
}
runCompiler (externalCompType) {
try {
if (this.api.getFileManagerMode() === 'localhost') {

@ -13,6 +13,7 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => {
const [state, setState] = useState({
isHardhatProject: false,
isTruffleProject: false,
isFoundryProject: false,
workspaceName: '',
currentFile,
configFilePath: 'compiler_config.json',
@ -67,9 +68,10 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => {
api.onSetWorkspace = async (isLocalhost: boolean, workspaceName: string) => {
const isHardhat = isLocalhost && await compileTabLogic.isHardhatProject()
const isTruffle = await compileTabLogic.isTruffleProject()
const isTruffle = isLocalhost && await compileTabLogic.isTruffleProject()
const isFoundry = isLocalhost && await compileTabLogic.isFoundryProject()
setState(prevState => {
return { ...prevState, currentFile, isHardhatProject: isHardhat, workspaceName: workspaceName, isTruffleProject: isTruffle }
return { ...prevState, currentFile, isHardhatProject: isHardhat, workspaceName: workspaceName, isTruffleProject: isTruffle, isFoundryProject: isFoundry }
})
}
@ -171,6 +173,7 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => {
isHardhatProject={state.isHardhatProject}
workspaceName={state.workspaceName}
isTruffleProject={state.isTruffleProject}
isFoundryProject={state.isFoundryProject}
compileTabLogic={compileTabLogic}
tooltip={toast}
modal={modal}

@ -11,6 +11,7 @@ export interface CompilerContainerProps {
compileTabLogic: CompileTabLogic,
isHardhatProject: boolean,
isTruffleProject: boolean,
isFoundryProject: boolean,
workspaceName: string,
tooltip: (message: string | JSX.Element) => void,
modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void,

@ -15,7 +15,7 @@ const RenderCall = ({ tx, resolvedData, logs, index, plugin, showTableHash, txDe
const debug = (event, tx) => {
event.stopPropagation()
if (tx.isCall && tx.envMode !== 'vm') {
modal('VM mode', 'Cannot debug this call. Debugging calls is only possible in JavaScript VM mode.', 'Ok', true, () => {}, 'Cancel', () => {})
modal('VM mode', 'Cannot debug this call. Debugging calls is only possible in Remix VM mode.', 'Ok', true, () => {}, 'Cancel', () => {})
} else {
plugin.event.trigger('debuggingRequested', [tx.hash])
}

@ -10,7 +10,7 @@ const RenderKnownTransactions = ({ tx, receipt, resolvedData, logs, index, plugi
const debug = (event, tx) => {
event.stopPropagation()
if (tx.isCall && tx.envMode !== 'vm') {
modal('VM mode', 'Cannot debug this call. Debugging calls is only possible in JavaScript VM mode.', 'Ok', true, () => {}, 'Cancel', () => {})
modal('VM mode', 'Cannot debug this call. Debugging calls is only possible in Remix VM mode.', 'Ok', true, () => {}, 'Cancel', () => {})
} else {
plugin.event.trigger('debuggingRequested', [tx.hash])
}

@ -7,7 +7,7 @@ const RenderUnKnownTransactions = ({ tx, receipt, index, plugin, showTableHash,
const debug = (event, tx) => {
event.stopPropagation()
if (tx.isCall && tx.envMode !== 'vm') {
modal('VM mode', 'Cannot debug this call. Debugging calls is only possible in JavaScript VM mode.', 'Ok', true, () => {}, 'Cancel', () => {})
modal('VM mode', 'Cannot debug this call. Debugging calls is only possible in Remix VM mode.', 'Ok', true, () => {}, 'Cancel', () => {})
} else {
plugin.event.trigger('debuggingRequested', [tx.hash])
}

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

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

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

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

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

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

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

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

@ -6,7 +6,9 @@ import { displayNotification, displayPopUp, fetchDirectoryError, fetchDirectoryR
import { listenOnPluginEvents, listenOnProviderEvents } from './events'
import { createWorkspaceTemplate, getWorkspaces, loadWorkspacePreset, setPlugin } from './workspace'
import { QueryParams } from '@remix-project/remix-lib'
import { fetchContractFromEtherscan } from '@remix-project/core-plugin' // eslint-disable-line
import JSZip from 'jszip'
import axios, { AxiosResponse } from 'axios'
export * from './events'
export * from './workspace'
@ -22,6 +24,24 @@ export type UrlParametersType = {
url: string
}
const basicWorkspaceInit = async (workspaces: { name: string; isGitRepo: boolean; }[], workspaceProvider) => {
if (workspaces.length === 0) {
await createWorkspaceTemplate('default_workspace', 'remixDefault')
plugin.setWorkspace({ name: 'default_workspace', isLocalhost: false })
dispatch(setCurrentWorkspace({ name: 'default_workspace', isGitRepo: false }))
await loadWorkspacePreset('remixDefault')
} else {
if (workspaces.length > 0) {
const workspace = workspaces[workspaces.length - 1]
const workspaceName = (workspace || {}).name
workspaceProvider.setWorkspace(workspaceName)
plugin.setWorkspace({ name: workspaceName, isLocalhost: false })
dispatch(setCurrentWorkspace(workspace))
}
}
}
export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.Dispatch<any>) => {
if (filePanelPlugin) {
plugin = filePanelPlugin
@ -31,32 +51,84 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.
const localhostProvider = filePanelPlugin.fileProviders.localhost
const params = queryParams.get() as UrlParametersType
const workspaces = await getWorkspaces() || []
dispatch(setWorkspaces(workspaces))
if (params.gist) {
await createWorkspaceTemplate('gist-sample', 'gist-template')
plugin.setWorkspace({ name: 'gist-sample', isLocalhost: false })
dispatch(setCurrentWorkspace('gist-sample'))
dispatch(setCurrentWorkspace({ name: 'gist-sample', isGitRepo: false }))
await loadWorkspacePreset('gist-template')
} else if (params.code || params.url) {
await createWorkspaceTemplate('code-sample', 'code-template')
plugin.setWorkspace({ name: 'code-sample', isLocalhost: false })
dispatch(setCurrentWorkspace('code-sample'))
dispatch(setCurrentWorkspace({ name: 'code-sample', isGitRepo: false }))
const filePath = await loadWorkspacePreset('code-template')
plugin.on('editor', 'editorMounted', async () => await plugin.fileManager.openFile(filePath))
} else {
if (workspaces.length === 0) {
await createWorkspaceTemplate('default_workspace', 'remixDefault')
plugin.setWorkspace({ name: 'default_workspace', isLocalhost: false })
dispatch(setCurrentWorkspace('default_workspace'))
await loadWorkspacePreset('remixDefault')
} else {
if (workspaces.length > 0) {
workspaceProvider.setWorkspace(workspaces[workspaces.length - 1])
plugin.setWorkspace({ name: workspaces[workspaces.length - 1], isLocalhost: false })
dispatch(setCurrentWorkspace(workspaces[workspaces.length - 1]))
} else if (window.location.pathname && window.location.pathname !== '/') {
let route = window.location.pathname
if (route.startsWith('/address/0x') && route.length === 51) {
const contractAddress = route.split('/')[2]
plugin.call('notification', 'toast', `Looking for contract(s) verified on different networks of Etherscan for contract address ${contractAddress} .....`)
let data
let count = 0
try {
let etherscanKey = await plugin.call('config', 'getAppParameter', 'etherscan-access-token')
if (!etherscanKey) etherscanKey = '2HKUX5ZVASZIKWJM8MIQVCRUVZ6JAWT531'
const networks = [
{id: 1, name: 'mainnet'},
{id: 3, name: 'ropsten'},
{id: 4, name: 'rinkeby'},
{id: 42, name: 'kovan'},
{id: 5, name: 'goerli'}
]
let found = false
const foundOnNetworks = []
for (const network of networks) {
const target = `/${network.name}/${contractAddress}`
try {
data = await fetchContractFromEtherscan(plugin, network, contractAddress, target, etherscanKey)
} catch (error) {
if ((error.message.startsWith('contract not verified on Etherscan') || error.message.startsWith('unable to retrieve contract data')) && network.id !== 5)
continue
else {
if (!found) await basicWorkspaceInit(workspaces, workspaceProvider)
break
}
}
found = true
foundOnNetworks.push(network.name)
await createWorkspaceTemplate('etherscan-code-sample', 'code-template')
plugin.setWorkspace({ name: 'etherscan-code-sample', isLocalhost: false })
dispatch(setCurrentWorkspace({ name: 'etherscan-code-sample', isGitRepo: false }))
let filePath
count = count + (Object.keys(data.compilationTargets)).length
for (filePath in data.compilationTargets)
await workspaceProvider.set(filePath, data.compilationTargets[filePath]['content'])
plugin.on('editor', 'editorMounted', async () => await plugin.fileManager.openFile(filePath))
}
plugin.call('notification', 'toast', `Added ${count} verified contract${count === 1 ? '': 's'} from ${foundOnNetworks.join(',')} network${foundOnNetworks.length === 1 ? '': 's'} of Etherscan for contract address ${contractAddress} !!`)
} catch (error) {
await basicWorkspaceInit(workspaces, workspaceProvider)
}
}
} else if (route.endsWith('.sol')) {
if (route.includes('blob')) route = route.replace('/blob', '')
let response: AxiosResponse
try {
response = await axios.get(`https://raw.githubusercontent.com${route}`)
} catch (error) {
plugin.call('notification', 'toast', `cound not find ${route} on GitHub`)
await basicWorkspaceInit(workspaces, workspaceProvider)
}
if (response && response.status === 200) {
const content = response.data
await createWorkspaceTemplate('github-code-sample', 'code-template')
plugin.setWorkspace({ name: 'github-code-sample', isLocalhost: false })
dispatch(setCurrentWorkspace({ name: 'github-code-sample', isGitRepo: false }))
await workspaceProvider.set(route, content)
plugin.on('editor', 'editorMounted', async () => await plugin.fileManager.openFile(route))
} else await basicWorkspaceInit(workspaces, workspaceProvider)
} else await basicWorkspaceInit(workspaces, workspaceProvider)
} else {
await basicWorkspaceInit(workspaces, workspaceProvider)
}
listenOnPluginEvents(plugin)

@ -1,13 +1,13 @@
import { action } from '../types'
export const setCurrentWorkspace = (workspace: string) => {
export const setCurrentWorkspace = (workspace: { name: string; isGitRepo: boolean; }) => {
return {
type: 'SET_CURRENT_WORKSPACE',
payload: workspace
}
}
export const setWorkspaces = (workspaces: string[]) => {
export const setWorkspaces = (workspaces: { name: string; isGitRepo: boolean; }[]) => {
return {
type: 'SET_WORKSPACES',
payload: workspaces
@ -125,7 +125,7 @@ export const createWorkspaceRequest = (promise: Promise<any>) => {
}
}
export const createWorkspaceSuccess = (workspaceName: string) => {
export const createWorkspaceSuccess = (workspaceName: { name: string; isGitRepo: boolean; }) => {
return {
type: 'CREATE_WORKSPACE_SUCCESS',
payload: workspaceName
@ -239,3 +239,21 @@ export const fsInitializationCompleted = () => {
type: 'FS_INITIALIZATION_COMPLETED'
}
}
export const cloneRepositoryRequest = () => {
return {
type: 'CLONE_REPOSITORY_REQUEST'
}
}
export const cloneRepositorySuccess = () => {
return {
type: 'CLONE_REPOSITORY_SUCCESS'
}
}
export const cloneRepositoryFailed = () => {
return {
type: 'CLONE_REPOSITORY_FAILED'
}
}

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

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

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

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

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

@ -8,13 +8,18 @@ interface Action {
export interface BrowserState {
browser: {
currentWorkspace: string,
workspaces: string[],
workspaces: {
name: string;
isGitRepo: boolean;
}[],
files: { [x: string]: Record<string, FileType> },
expandPath: string[]
isRequestingDirectory: boolean,
isSuccessfulDirectory: boolean,
isRequestingWorkspace: boolean,
isSuccessfulWorkspace: boolean,
isRequestingCloning: boolean,
isSuccessfulCloning: boolean,
error: string,
contextMenu: {
registeredMenuItems: action[],
@ -63,6 +68,8 @@ export const browserInitialState: BrowserState = {
isSuccessfulDirectory: false,
isRequestingWorkspace: false,
isSuccessfulWorkspace: false,
isRequestingCloning: false,
isSuccessfulCloning: false,
error: null,
contextMenu: {
registeredMenuItems: [],
@ -104,21 +111,21 @@ export const browserInitialState: BrowserState = {
export const browserReducer = (state = browserInitialState, action: Action) => {
switch (action.type) {
case 'SET_CURRENT_WORKSPACE': {
const payload = action.payload as string
const workspaces = state.browser.workspaces.includes(payload) ? state.browser.workspaces : [...state.browser.workspaces, action.payload]
const payload = action.payload as { name: string; isGitRepo: boolean; }
const workspaces = state.browser.workspaces.find(({ name }) => name === payload.name) ? state.browser.workspaces : [...state.browser.workspaces, action.payload]
return {
...state,
browser: {
...state.browser,
currentWorkspace: payload,
currentWorkspace: payload.name,
workspaces: workspaces.filter(workspace => workspace)
}
}
}
case 'SET_WORKSPACES': {
const payload = action.payload as string[]
const payload = action.payload as { name: string; isGitRepo: boolean; }[]
return {
...state,
@ -416,14 +423,14 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
}
case 'CREATE_WORKSPACE_SUCCESS': {
const payload = action.payload as string
const workspaces = state.browser.workspaces.includes(payload) ? state.browser.workspaces : [...state.browser.workspaces, action.payload]
const payload = action.payload as { name: string; isGitRepo: boolean; }
const workspaces = state.browser.workspaces.find(({ name }) => name === payload.name) ? state.browser.workspaces : [...state.browser.workspaces, action.payload]
return {
...state,
browser: {
...state.browser,
currentWorkspace: payload,
currentWorkspace: payload.name,
workspaces: workspaces.filter(workspace => workspace),
isRequestingWorkspace: false,
isSuccessfulWorkspace: true,
@ -446,14 +453,25 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
case 'RENAME_WORKSPACE': {
const payload = action.payload as { oldName: string, workspaceName: string }
const workspaces = state.browser.workspaces.filter(name => name && (name !== payload.oldName))
let renamedWorkspace
const workspaces = state.browser.workspaces.filter(({ name, isGitRepo }) => {
if (name && (name !== payload.oldName)) {
return true
} else {
renamedWorkspace = {
name: payload.workspaceName,
isGitRepo
}
return false
}
})
return {
...state,
browser: {
...state.browser,
currentWorkspace: payload.workspaceName,
workspaces: [...workspaces, payload.workspaceName],
workspaces: [...workspaces, renamedWorkspace],
expandPath: []
}
}
@ -461,7 +479,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
case 'DELETE_WORKSPACE': {
const payload = action.payload as string
const workspaces = state.browser.workspaces.filter(name => name && (name !== payload))
const workspaces = state.browser.workspaces.filter(({ name }) => name && (name !== payload))
return {
...state,
@ -592,6 +610,39 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
}
}
case 'CLONE_REPOSITORY_REQUEST': {
return {
...state,
browser: {
...state.browser,
isRequestingCloning: true,
isSuccessfulCloning: false
}
}
}
case 'CLONE_REPOSITORY_SUCCESS': {
return {
...state,
browser: {
...state.browser,
isRequestingCloning: false,
isSuccessfulCloning: true
}
}
}
case 'CLONE_REPOSITORY_FAILED': {
return {
...state,
browser: {
...state.browser,
isRequestingCloning: false,
isSuccessfulCloning: false
}
}
}
case 'FS_INITIALIZATION_COMPLETED': {
return {
...state,

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

@ -13,7 +13,8 @@
"npip": "npip",
"lint": "eslint ./src --ext .ts",
"build": "tsc -p ./ && chmod +x ./src/bin/remixd.js",
"dev": "nodemon"
"dev": "nodemon",
"postinstall": "node src/scripts/installSlither.js"
},
"publishConfig": {
"access": "public"

@ -63,6 +63,7 @@ function errorHandler (error: any, service: string) {
.description('Establish a two-way websocket connection between the local computer and Remix IDE for a folder')
.option('-u, --remix-ide <url>', 'URL of remix instance allowed to connect')
.option('-s, --shared-folder <path>', 'Folder to share with Remix IDE (Default: CWD)')
.option('-i, --install <name>', 'Module name to install locally (Supported: ["slither"])')
.option('-r, --read-only', 'Treat shared folder as read-only (experimental)')
.on('--help', function () {
console.log('\nExample:\n\n remixd -s ./shared_project -u http://localhost:8080')
@ -71,6 +72,11 @@ function errorHandler (error: any, service: string) {
await warnLatestVersion()
if(program.install && !program.readOnly) {
if (program.install.toLowerCase() === 'slither') require('./../scripts/installSlither')
process.exit(0)
}
if (!program.remixIde) {
console.log('\x1b[33m%s\x1b[0m', '[WARN] You can only connect to remixd from one of the supported origins.')
} else {

@ -0,0 +1,25 @@
const { execSync } = require('child_process') // eslint-disable-line
try {
const solcVersion = '0.8.15'
console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: requires Python3.6+ (pip3) to be installed on your system`)
console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: solc-select will be installed along with Slither to set different solc compiler versions.`)
console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: checking pip3 availability ...`)
const pip3OP = execSync('pip3 --version')
console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: pip3 found: ${pip3OP.toString()}`)
console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: installing slither...`)
const slitherOP = execSync('pip3 install slither-analyzer')
console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: slither installation output: ${slitherOP.toString()}`)
console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: installing solc-select...`)
const solcSelectOP = execSync('pip3 install solc-select')
console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: solc-select installation output: ${solcSelectOP.toString()}`)
console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: installing solc ${solcVersion}...`)
const solcInstallOP = execSync(`solc-select install ${solcVersion}`)
console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: solc installation output: ${solcInstallOP.toString()}`)
console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: setting solc version to ${solcVersion}...`)
const solcUseOP = execSync(`solc-select use ${solcVersion}`)
console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: solc setting installation output: ${solcUseOP.toString()}`)
console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: Slither is ready to use!`)
} catch (err) {
console.log('\x1b[31m%s\x1b[0m', `[Slither Installation]: Error occured: ${err}`)
}

@ -172,6 +172,9 @@
},
"remix-ui-locale-module": {
"tags": []
},
"remix-ui-tooltip-popup": {
"tags": []
}
},
"targetDependencies": {

@ -45,7 +45,7 @@
"workspace-schematic": "nx workspace-schematic",
"dep-graph": "nx dep-graph",
"help": "nx help",
"lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remix-ws-templates,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-helper,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings,remix-core-plugin,remix-ui-renderer,remix-ui-publish-to-storage,remix-ui-solidity-compiler,solidity-unit-testing,remix-ui-plugin-manager,remix-ui-terminal,remix-ui-editor,remix-ui-app,remix-ui-tabs,remix-ui-panel,remix-ui-run-tab,remix-ui-permission-handler,remix-ui-search",
"lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remix-ws-templates,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-helper,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings,remix-core-plugin,remix-ui-renderer,remix-ui-publish-to-storage,remix-ui-solidity-compiler,solidity-unit-testing,remix-ui-plugin-manager,remix-ui-terminal,remix-ui-editor,remix-ui-app,remix-ui-tabs,remix-ui-panel,remix-ui-run-tab,remix-ui-permission-handler,remix-ui-search,remix-ui-tooltip-popup",
"build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remix-ws-templates,remixd",
"test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"publish:libs": "yarn run build:libs && lerna publish --skip-git && yarn run bumpVersion:libs",

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

@ -1268,6 +1268,22 @@
}
}
}
},
"remix-ui-tooltip-popup": {
"root": "libs/remix-ui/tooltip-popup",
"sourceRoot": "libs/remix-ui/tooltip-popup/src",
"projectType": "library",
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"config": "libs/remix-ui/tooltip-popup/.eslintrc.json",
"tsConfig": ["libs/remix-ui/tooltip-popup/tsconfig.lib.json"],
"exclude": ["**/node_modules/**"]
}
}
}
}
},
"cli": {

@ -8890,7 +8890,7 @@ decimal.js@^10.2.1:
decode-uri-component@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==
decompress-response@^3.2.0, decompress-response@^3.3.0:
version "3.3.0"
@ -10776,7 +10776,7 @@ fill-range@^7.0.1:
filter-obj@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b"
integrity sha1-mzERErxsYSehbgFsbF1/GeCAXFs=
integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==
finalhandler@~1.1.2:
version "1.1.2"
@ -11321,7 +11321,16 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
get-intrinsic@^1.0.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598"
integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==
dependencies:
function-bind "^1.1.1"
has "^1.0.3"
has-symbols "^1.0.3"
get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
@ -11985,6 +11994,11 @@ has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2:
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
has-symbols@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
has-to-string-tag-x@^1.2.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d"
@ -13377,11 +13391,11 @@ is-shared-array-buffer@^1.0.1:
integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==
is-ssh@^1.3.0:
version "1.3.3"
resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.3.tgz#7f133285ccd7f2c2c7fc897b771b53d95a2b2c7e"
integrity sha512-NKzJmQzJfEEma3w5cJNcUMxoXfDjz0Zj0eyCalHn2E6VOwlzjZo0yuO2fcBSf8zhFuVCL/82/r5gRcoi6aEPVQ==
version "1.4.0"
resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.4.0.tgz#4f8220601d2839d8fa624b3106f8e8884f01b8b2"
integrity sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==
dependencies:
protocols "^1.1.0"
protocols "^2.0.1"
is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0:
version "1.1.0"
@ -17387,11 +17401,16 @@ object-copy@^0.1.0:
define-property "^0.2.5"
kind-of "^3.0.3"
object-inspect@^1.11.0, object-inspect@^1.9.0, object-inspect@~1.11.0:
object-inspect@^1.11.0, object-inspect@~1.11.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1"
integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==
object-inspect@^1.9.0:
version "1.12.2"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea"
integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==
object-is@^1.0.1:
version "1.1.5"
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"
@ -18129,10 +18148,10 @@ parse-passwd@^1.0.0:
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=
parse-path@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-4.0.3.tgz#82d81ec3e071dcc4ab49aa9f2c9c0b8966bb22bf"
integrity sha512-9Cepbp2asKnWTJ9x2kpw6Fe8y9JDbqwahGCTvklzd/cEq5C5JC59x2Xb0Kx+x0QZ8bvNquGO8/BWP0cwBHzSAA==
parse-path@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-4.0.4.tgz#4bf424e6b743fb080831f03b536af9fc43f0ffea"
integrity sha512-Z2lWUis7jlmXC1jeOG9giRO2+FsuyNipeQ43HAjqAZjwSe3SEf+q/84FGPHoso3kyntbxa4c4i77t3m6fGf8cw==
dependencies:
is-ssh "^1.3.0"
protocols "^1.4.0"
@ -18140,13 +18159,13 @@ parse-path@^4.0.0:
query-string "^6.13.8"
parse-url@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-6.0.0.tgz#f5dd262a7de9ec00914939220410b66cff09107d"
integrity sha512-cYyojeX7yIIwuJzledIHeLUBVJ6COVLeT4eF+2P6aKVzwvgKQPndCBv3+yQ7pcWjqToYwaligxzSYNNmGoMAvw==
version "6.0.2"
resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-6.0.2.tgz#4a30b057bfc452af64512dfb1a7755c103db3ea1"
integrity sha512-uCSjOvD3T+6B/sPWhR+QowAZcU/o4bjPrVBQBGFxcDF6J6FraCGIaDBsdoQawiaaAVdHvtqBe3w3vKlfBKySOQ==
dependencies:
is-ssh "^1.3.0"
normalize-url "^6.1.0"
parse-path "^4.0.0"
parse-path "^4.0.4"
protocols "^1.4.0"
parse5@4.0.0:
@ -18936,11 +18955,16 @@ protocol-buffers-schema@^3.3.1:
resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz#77bc75a48b2ff142c1ad5b5b90c94cd0fa2efd03"
integrity sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==
protocols@^1.1.0, protocols@^1.4.0:
protocols@^1.4.0:
version "1.4.8"
resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.8.tgz#48eea2d8f58d9644a4a32caae5d5db290a075ce8"
integrity sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg==
protocols@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/protocols/-/protocols-2.0.1.tgz#8f155da3fc0f32644e83c5782c8e8212ccf70a86"
integrity sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==
protoduck@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/protoduck/-/protoduck-5.0.1.tgz#03c3659ca18007b69a50fd82a7ebcc516261151f"
@ -19087,13 +19111,20 @@ qs@6.7.0:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
qs@^6.4.0, qs@^6.9.4:
qs@^6.4.0:
version "6.10.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a"
integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==
dependencies:
side-channel "^1.0.4"
qs@^6.9.4:
version "6.11.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
dependencies:
side-channel "^1.0.4"
qs@~6.4.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
@ -21417,7 +21448,7 @@ strict-uri-encode@^1.0.0:
strict-uri-encode@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY=
integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==
string-hash@^1.1.1:
version "1.1.3"

Loading…
Cancel
Save