Merge branch 'apply-refactored-tooltips' of github.com:ethereum/remix-project into apply-refactored-tooltips

pull/5370/head
Joseph Izang 2 years ago
commit 09284d1c2f
  1. 16
      apps/remix-ide-e2e/src/tests/proxy.test.ts
  2. 97
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  3. 241
      apps/remix-ide-e2e/src/tests/workspace_git.test.ts
  4. 2
      apps/remix-ide/ci/browser_test.sh
  5. 3
      apps/remix-ide/src/app/editor/editor.js
  6. 4
      apps/remix-ide/src/app/plugins/notification.tsx
  7. 4
      apps/remix-ide/src/app/tabs/compile-and-run.ts
  8. 2
      apps/remix-ide/src/app/tabs/web3-provider.js
  9. 2
      apps/remix-ide/src/app/udapp/run-tab.js
  10. 32
      apps/remix-ide/src/blockchain/blockchain.js
  11. 30
      apps/remix-ide/src/blockchain/execution-context.js
  12. 2
      libs/remix-debug/src/debugger/VmDebugger.ts
  13. 2
      libs/remix-lib/src/types/ICompilerApi.ts
  14. 2
      libs/remix-ui/app/src/index.ts
  15. 2
      libs/remix-ui/app/src/lib/remix-app/actions/modals.ts
  16. 2
      libs/remix-ui/app/src/lib/remix-app/components/modals/dialogs.tsx
  17. 2
      libs/remix-ui/app/src/lib/remix-app/context/provider.tsx
  18. 4
      libs/remix-ui/app/src/lib/remix-app/interface/index.ts
  19. 3
      libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts
  20. 4
      libs/remix-ui/app/src/lib/remix-app/state/modals.ts
  21. 36
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  22. 264
      libs/remix-ui/editor/src/lib/syntaxes/move.ts
  23. 2
      libs/remix-ui/home-tab/src/lib/components/homeTabGetStarted.tsx
  24. 1
      libs/remix-ui/panel/src/lib/plugins/panel.css
  25. 17
      libs/remix-ui/run-tab/src/lib/actions/deploy.ts
  26. 3
      libs/remix-ui/run-tab/src/lib/actions/index.ts
  27. 5
      libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx
  28. 64
      libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx
  29. 5
      libs/remix-ui/run-tab/src/lib/reducers/runTab.ts
  30. 3
      libs/remix-ui/run-tab/src/lib/run-tab.tsx
  31. 6
      libs/remix-ui/run-tab/src/lib/types/index.ts
  32. 8
      libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx
  33. 8
      libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts
  34. 4
      libs/remix-ui/solidity-unit-testing/src/lib/solidity-unit-testing.tsx
  35. 8
      libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx
  36. 16
      libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx
  37. 8
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  38. 5
      libs/remix-ui/toaster/src/lib/toaster.tsx
  39. 8
      libs/remix-ui/workspace/src/lib/components/file-explorer-context-menu.tsx
  40. 38
      libs/remix-ui/workspace/src/lib/components/file-explorer-menu.tsx
  41. 13
      libs/remix-ui/workspace/src/lib/components/file-label.tsx
  42. 207
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  43. 4
      libs/remixd/src/services/foundryClient.ts
  44. 6
      libs/remixd/src/services/hardhatClient.ts
  45. 4
      libs/remixd/src/services/truffleClient.ts
  46. 143
      release-process.md

@ -70,12 +70,13 @@ module.exports = {
browser browser
.openFile('myTokenV1.sol') .openFile('myTokenV1.sol')
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.pause(2000) .pause(5000)
.click('[data-id="compilerContainerCompileBtn"]') .click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyToken]', 60000) .waitForElementPresent('select[id="compiledContracts"] option[value=MyToken]', 60000)
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('select.udapp_contractNames') .click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyToken]') .click('select.udapp_contractNames option[value=MyToken]')
.pause(5000)
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]') .waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.click('[data-id="contractGUIDeployWithProxyLabel"]') .click('[data-id="contractGUIDeployWithProxyLabel"]')
.createContract('') .createContract('')
@ -105,7 +106,8 @@ module.exports = {
done() done()
}) })
}) })
}, },//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[1]/input
// #runTabView > div > div.udapp_container > div:nth-child(3) > div.udapp_deployDropdown > div > div.udapp_contractProperty > div.pl-4.flex-column.d-flex > div > div:nth-child(1) > input
'Should deploy proxy with initialize parameters #group1': function (browser: NightwatchBrowser) { 'Should deploy proxy with initialize parameters #group1': function (browser: NightwatchBrowser) {
browser browser
@ -121,10 +123,12 @@ module.exports = {
.click('select.udapp_contractNames option[value=MyInitializedToken]') .click('select.udapp_contractNames option[value=MyInitializedToken]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]') .waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.click('[data-id="contractGUIDeployWithProxyLabel"]') .click('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('input[title="tokenName"]') .useXpath()
.waitForElementPresent('input[title="tokenSymbol"]') .waitForElementPresent('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[1]/input')
.setValue('input[title="tokenName"]', 'Remix') .waitForElementPresent('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[2]/input')
.setValue('input[title="tokenSymbol"]', "R") .setValue('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[1]/input', 'Remix')
.setValue('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[2]/input', "R")
.useCss()
.createContract('') .createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)') .waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]') .waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')

@ -383,106 +383,9 @@ module.exports = {
.click('//*[@id="workspacesSelect"]') .click('//*[@id="workspacesSelect"]')
.useCss() .useCss()
.waitForElementNotPresent(`[data-id="dropdown-item-workspace_name_1"]`) .waitForElementNotPresent(`[data-id="dropdown-item-workspace_name_1"]`)
},
// CLONE REPOSITORY E2E START
'Should clone a repository #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
.setValue('[data-id="modalDialogCustomPromptTextClone"]', 'https://github.com/ethereum/awesome-remix')
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.waitForElementPresent('.fa-spinner')
.pause(5000)
.waitForElementNotPresent('.fa-spinner')
.waitForElementVisible('*[data-id="treeViewLitreeViewItem.git"]')
.waitForElementContainsText('[data-id="workspacesSelect"]', 'awesome-remix')
},
'Should display dgit icon for cloned workspace #group2': function (browser: NightwatchBrowser) {
browser
.switchWorkspace('default_workspace')
.waitForElementNotVisible('[data-id="workspacesSelect"] .fa-code-branch')
.switchWorkspace('awesome-remix')
.waitForElementVisible('[data-id="workspacesSelect"] .fa-code-branch')
},
'Should display non-clashing names for duplicate clone #group2': '' + function (browser: NightwatchBrowser) {
browser
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
.setValue('[data-id="modalDialogCustomPromptTextClone"]', 'https://github.com/ethereum/awesome-remix')
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.pause(5000)
.waitForElementContainsText('[data-id="workspacesSelect"]', 'awesome-remix1')
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
.setValue('[data-id="modalDialogCustomPromptTextClone"]', 'https://github.com/ethereum/awesome-remix')
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.pause(5000)
.waitForElementContainsText('[data-id="workspacesSelect"]', 'awesome-remix2')
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
.setValue('[data-id="modalDialogCustomPromptTextClone"]', 'https://github.com/ethereum/awesome-remix')
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.pause(5000)
.waitForElementContainsText('[data-id="workspacesSelect"]', 'awesome-remix3')
.switchWorkspace('awesome-remix')
.switchWorkspace('awesome-remix1')
.switchWorkspace('awesome-remix2')
.switchWorkspace('awesome-remix3')
},
'Should display error message in modal for failed clone #group2': function (browser: NightwatchBrowser) {
browser
.useXpath()
.waitForElementPresent({
selector: '//i[@data-icon="workspaceDropdownMenuIcon"]',
locateStrategy: 'xpath',
})
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
.setValue('[data-id="modalDialogCustomPromptTextClone"]', 'https://github.com/ethereum/non-existent-repo')
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.pause(5000)
.waitForElementVisible('[data-id="cloneGitRepositoryModalDialogModalBody-react"]')
.waitForElementContainsText('[data-id="cloneGitRepositoryModalDialogModalBody-react"]', 'An error occurred: Please check that you have the correct URL for the repo. If the repo is private, you need to add your github credentials (with the valid token permissions) in Settings plugin')
.click('[data-id="cloneGitRepository-modal-footer-ok-react"]')
.end() .end()
}, },
// CLONE REPOSITORY E2E END
tearDown: sauce tearDown: sauce
} }

@ -0,0 +1,241 @@
'use strict'
import { NightwatchBrowser } from "nightwatch"
import init from "../helpers/init"
import sauce from "./sauce"
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'Should create and initialize a GIT repository #group1': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.waitForElementNotVisible('[data-id="workspaceGitPanel"]')
.click('*[data-id="workspaceCreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.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"]')
.click('select[id="wstemplate"] option[value=blank]')
.click('[data-id="initGitRepositoryLabel"]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(100)
.waitForElementVisible('[data-id="workspaceGitPanel"]')
.waitForElementContainsText('[data-id="workspaceGitBranchesDropdown"]', 'main')
},
// CLONE REPOSITORY E2E START
'Should clone a repository #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
.setValue('[data-id="modalDialogCustomPromptTextClone"]', 'https://github.com/ethereum/awesome-remix')
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.waitForElementPresent('.fa-spinner')
.pause(5000)
.waitForElementNotPresent('.fa-spinner')
.waitForElementVisible('*[data-id="treeViewLitreeViewItem.git"]')
.waitForElementContainsText('[data-id="workspacesSelect"]', 'awesome-remix')
},
'Should display dgit icon for cloned workspace #group2': function (browser: NightwatchBrowser) {
browser
.switchWorkspace('default_workspace')
.waitForElementNotVisible('[data-id="workspacesSelect"] .fa-code-branch')
.switchWorkspace('awesome-remix')
.waitForElementVisible('[data-id="workspacesSelect"] .fa-code-branch')
},
'Should display non-clashing names for duplicate clone #group2': '' + function (browser: NightwatchBrowser) {
browser
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
.setValue('[data-id="modalDialogCustomPromptTextClone"]', 'https://github.com/ethereum/awesome-remix')
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.pause(5000)
.waitForElementContainsText('[data-id="workspacesSelect"]', 'awesome-remix1')
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
.setValue('[data-id="modalDialogCustomPromptTextClone"]', 'https://github.com/ethereum/awesome-remix')
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.pause(5000)
.waitForElementContainsText('[data-id="workspacesSelect"]', 'awesome-remix2')
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
.setValue('[data-id="modalDialogCustomPromptTextClone"]', 'https://github.com/ethereum/awesome-remix')
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.pause(5000)
.waitForElementContainsText('[data-id="workspacesSelect"]', 'awesome-remix3')
.switchWorkspace('awesome-remix')
.switchWorkspace('awesome-remix1')
.switchWorkspace('awesome-remix2')
.switchWorkspace('awesome-remix3')
},
'Should display error message in modal for failed clone #group2': function (browser: NightwatchBrowser) {
browser
.useXpath()
.waitForElementPresent({
selector: '//i[@data-icon="workspaceDropdownMenuIcon"]',
locateStrategy: 'xpath',
})
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
.setValue('[data-id="modalDialogCustomPromptTextClone"]', 'https://github.com/ethereum/non-existent-repo')
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.pause(5000)
.waitForElementVisible('[data-id="cloneGitRepositoryModalDialogModalBody-react"]')
.waitForElementContainsText('[data-id="cloneGitRepositoryModalDialogModalBody-react"]', 'An error occurred: Please check that you have the correct URL for the repo. If the repo is private, you need to add your github credentials (with the valid token permissions) in Settings plugin')
.click('[data-id="cloneGitRepository-modal-footer-ok-react"]')
},
// CLONE REPOSITORY E2E END
// GIT BRANCHES E2E START
'Should show all cloned repo branches #group3': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.waitForElementNotVisible('[data-id="workspaceGitPanel"]')
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
.setValue('[data-id="modalDialogCustomPromptTextClone"]', 'https://github.com/ioedeveloper/test-branch-change')
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.waitForElementPresent('.fa-spinner')
.pause(5000)
.waitForElementNotPresent('.fa-spinner')
.waitForElementContainsText('[data-id="workspacesSelect"]', 'test-branch-change')
.waitForElementVisible('[data-id="workspaceGitPanel"]')
.click('[data-id="workspaceGitBranchesDropdown"]')
.waitForElementVisible('[data-id="custom-dropdown-menu"]')
.waitForElementContainsText('[data-id="custom-dropdown-items"]', 'origin/dev')
.waitForElementContainsText('[data-id="custom-dropdown-items"]', 'origin/production')
.waitForElementContainsText('[data-id="custom-dropdown-items"]', 'origin/setup')
.expect.element('[data-id="workspaceGit-main"]').text.to.contain('✓ ')
},
'Should a checkout to a remote branch #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('[data-id="custom-dropdown-menu"]')
.waitForElementContainsText('[data-id="custom-dropdown-items"]', 'origin/dev')
.waitForElementPresent('[data-id="workspaceGit-origin/dev"]')
.click('[data-id="workspaceGit-origin/dev"]')
.pause(5000)
.waitForElementPresent('[data-id="treeViewDivtreeViewItemdev.ts"]')
.click('[data-id="workspaceGitBranchesDropdown"]')
.expect.element('[data-id="workspaceGit-dev"]').text.to.contain('✓ ')
},
'Should search for a branch (local and remote) #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('[data-id="custom-dropdown-menu"]')
.waitForElementPresent('[data-id="workspaceGitInput"]')
.sendKeys('[data-id="workspaceGitInput"]', 'setup')
.waitForElementNotPresent('[data-id="workspaceGit-origin/dev"]')
.waitForElementNotPresent('[data-id="workspaceGit-origin/production"]')
.waitForElementNotPresent('[data-id="workspaceGit-dev"]')
.waitForElementNotPresent('[data-id="workspaceGit-main"]')
.waitForElementPresent('[data-id="workspaceGit-origin/setup"]')
},
'Should checkout to a new local branch #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('[data-id="custom-dropdown-menu"]')
.waitForElementPresent('[data-id="workspaceGitInput"]')
.clearValue('[data-id="workspaceGitInput"]')
.sendKeys('[data-id="workspaceGitInput"]', 'newLocalBranch')
.waitForElementContainsText('[data-id="workspaceGitCreateNewBranch"]', `Create branch: newLocalBranch from 'dev'`)
.click('[data-id="workspaceGitCreateNewBranch"]')
.pause(2000)
.click('[data-id="workspaceGitBranchesDropdown"]')
.waitForElementVisible('[data-id="custom-dropdown-menu"]')
.expect.element('[data-id="workspaceGit-newLocalBranch"]').text.to.contain('✓ ')
},
'Should checkout to an exisiting local branch #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('[data-id="custom-dropdown-menu"]')
.waitForElementPresent('[data-id="workspaceGitInput"]')
.clearValue('[data-id="workspaceGitInput"]')
.sendKeys('[data-id="workspaceGitInput"]', [browser.Keys.SPACE, browser.Keys.BACK_SPACE])
.waitForElementPresent('[data-id="workspaceGit-main"]')
.click('[data-id="workspaceGit-main"]')
.pause(2000)
.waitForElementNotPresent('[data-id="treeViewDivtreeViewItemdev.ts"]')
.waitForElementPresent('[data-id="treeViewDivtreeViewItemmain.ts"]')
.click('[data-id="workspaceGitBranchesDropdown"]')
.expect.element('[data-id="workspaceGit-main"]').text.to.contain('✓ ')
},
'Should prevent checkout to a branch if local changes exists #group3': function (browser: NightwatchBrowser) {
browser
.renamePath('README.md', 'README.txt', 'README.txt')
.waitForElementVisible('[data-id="workspaceGitBranchesDropdown"]')
.click('[data-id="workspaceGitBranchesDropdown"]')
.waitForElementVisible('[data-id="workspaceGit-dev"]')
.click('[data-id="workspaceGit-dev"]')
.waitForElementVisible('[data-id="switchBranchModalDialogContainer-react"]')
.waitForElementContainsText('[data-id="switchBranchModalDialogModalBody-react"]', 'Your local changes to the following files would be overwritten by checkout.')
.click('[data-id="switchBranchModalDialogModalFooter-react"]')
.click('[data-id="switchBranch-modal-footer-cancel-react"]')
.pause(2000)
.click('[data-id="workspaceGitBranchesDropdown"]')
.expect.element('[data-id="workspaceGit-main"]').text.to.contain('✓ ')
},
'Should force checkout to a branch with exisiting local changes #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('[data-id="workspaceGit-dev"]')
.click('[data-id="workspaceGit-dev"]')
.waitForElementVisible('[data-id="switchBranchModalDialogContainer-react"]')
.waitForElementContainsText('[data-id="switchBranchModalDialogModalBody-react"]', 'Your local changes to the following files would be overwritten by checkout.')
.click('[data-id="switchBranchModalDialogModalFooter-react"]')
.click('[data-id="switchBranch-modal-footer-ok-react"]')
.pause(2000)
.click('[data-id="workspaceGitBranchesDropdown"]')
.expect.element('[data-id="workspaceGit-dev"]').text.to.contain('✓ ')
},
// GIT BRANCHES E2E END
tearDown: sauce
}

@ -20,7 +20,7 @@ yarn run build:e2e
node apps/remix-ide/ci/splice_tests.js $2 $3 node apps/remix-ide/ci/splice_tests.js $2 $3
TESTFILES=$(node apps/remix-ide/ci/splice_tests.js $2 $3 | circleci tests split --split-by=timings) TESTFILES=$(node apps/remix-ide/ci/splice_tests.js $2 $3 | circleci tests split --split-by=timings)
for TESTFILE in $TESTFILES; do for TESTFILE in $TESTFILES; do
npx nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/${TESTFILE}.js --env=$1 || TEST_EXITCODE=1 npx nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/${TESTFILE}.js --env=$1 || npx nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/${TESTFILE}.js --env=$1 || TEST_EXITCODE=1
done done
echo "$TEST_EXITCODE" echo "$TEST_EXITCODE"

@ -50,7 +50,8 @@ class Editor extends Plugin {
abi: 'json', abi: 'json',
rs: 'rust', rs: 'rust',
cairo: 'cairo', cairo: 'cairo',
ts: 'typescript' ts: 'typescript',
move: 'move'
} }
this.activated = false this.activated = false

@ -1,8 +1,8 @@
import { Plugin } from '@remixproject/engine' import { Plugin } from '@remixproject/engine'
import { LibraryProfile, MethodApi, StatusEvents } from '@remixproject/plugin-utils' import { LibraryProfile, MethodApi, StatusEvents } from '@remixproject/plugin-utils'
import { AppModal } from '@remix-ui/app' import { AppModal } from '@remix-ui/app'
import { AlertModal } from 'libs/remix-ui/app/src/lib/remix-app/interface' import { AlertModal } from '@remix-ui/app'
import { dispatchModalInterface } from 'libs/remix-ui/app/src/lib/remix-app/context/context' import { dispatchModalInterface } from '@remix-ui/app'
interface INotificationApi { interface INotificationApi {
events: StatusEvents, events: StatusEvents,

@ -51,11 +51,11 @@ export class CompileAndRun extends Plugin {
} }
async runScript (fileName, clearAllInstances) { async runScript (fileName, clearAllInstances) {
await this.call('terminal', 'log', `running ${fileName} ...`) await this.call('terminal', 'log', { value: `running ${fileName} ...`, type: 'info' })
try { try {
const exists = await this.call('fileManager', 'exists', fileName) const exists = await this.call('fileManager', 'exists', fileName)
if (!exists) { if (!exists) {
await this.call('terminal', 'log', `${fileName} does not exist.`) await this.call('terminal', 'log', { value: `${fileName} does not exist.`, type: 'info' } )
return return
} }
const content = await this.call('fileManager', 'readFile', fileName) const content = await this.call('fileManager', 'readFile', fileName)

@ -32,7 +32,7 @@ export class Web3ProviderModule extends Plugin {
if (error) { if (error) {
const errorData = error.data ? error.data : error.message ? error.message : error const errorData = error.data ? error.data : error.message ? error.message : error
// See: https://github.com/ethers-io/ethers.js/issues/901 // See: https://github.com/ethers-io/ethers.js/issues/901
if (!(typeof errorData === 'string' && errorData.includes("unknown method eth_chainId"))) this.call('terminal', 'log', error.data ? error.data : error.message) if (!(typeof errorData === 'string' && errorData.includes("unknown method eth_chainId"))) this.call('terminal', 'log', { value: error.data ? error.data : error.message, type: 'error' } )
return reject(errorData) return reject(errorData)
} }
if (payload.method === 'eth_sendTransaction') { if (payload.method === 'eth_sendTransaction') {

@ -78,7 +78,7 @@ export class RunTab extends ViewPlugin {
} }
sendTransaction (tx) { sendTransaction (tx) {
_paq.push(['trackEvent', 'udapp', 'sendTx']) _paq.push(['trackEvent', 'udapp', 'sendTx', 'udappTransaction'])
return this.blockchain.sendTransaction(tx) return this.blockchain.sendTransaction(tx)
} }

@ -674,22 +674,24 @@ export class Blockchain extends Plugin {
const hhlogs = await this.web3().eth.getHHLogsForTx(txResult.transactionHash) const hhlogs = await this.web3().eth.getHHLogsForTx(txResult.transactionHash)
if (hhlogs && hhlogs.length) { if (hhlogs && hhlogs.length) {
let finalLogs = '<b>console.log:</b>\n' let finalLogs = <div><div><b>console.log:</b></div>
for (const log of hhlogs) { {
let formattedLog hhlogs.map((log) => {
// Hardhat implements the same formatting options that can be found in Node.js' console.log, let formattedLog
// which in turn uses util.format: https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args // Hardhat implements the same formatting options that can be found in Node.js' console.log,
// For example: console.log("Name: %s, Age: %d", remix, 6) will log 'Name: remix, Age: 6' // which in turn uses util.format: https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args
// We check first arg to determine if 'util.format' is needed // For example: console.log("Name: %s, Age: %d", remix, 6) will log 'Name: remix, Age: 6'
if (typeof log[0] === 'string' && (log[0].includes('%s') || log[0].includes('%d'))) { // We check first arg to determine if 'util.format' is needed
formattedLog = format(log[0], ...log.slice(1)) if (typeof log[0] === 'string' && (log[0].includes('%s') || log[0].includes('%d'))) {
} else { formattedLog = format(log[0], ...log.slice(1))
formattedLog = log.join(' ') } else {
} formattedLog = log.join(' ')
finalLogs = finalLogs + '&emsp;' + formattedLog + '\n' }
} return <div>{formattedLog}</div>
})}
</div>
_paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log']) _paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log'])
this.call('terminal', 'log', { type: 'info', value: finalLogs }) this.call('terminal', 'logHtml', finalLogs)
} }
execResult = await this.web3().eth.getExecutionResultFromSimulator(txResult.transactionHash) execResult = await this.web3().eth.getExecutionResultFromSimulator(txResult.transactionHash)
if (execResult) { if (execResult) {

@ -14,6 +14,8 @@ if (typeof window !== 'undefined' && typeof window.ethereum !== 'undefined') {
web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')) web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
} }
const noInjectedProviderMsg = 'No injected provider found. Make sure your provider (e.g. MetaMask) is active and running (when recently activated you may have to reload the page).'
/* /*
trigger contextChanged, web3EndpointChanged trigger contextChanged, web3EndpointChanged
*/ */
@ -38,12 +40,16 @@ export class ExecutionContext {
this.executionContext = 'vm' this.executionContext = 'vm'
} else { } else {
this.executionContext = injectedProvider ? 'injected' : 'vm' this.executionContext = injectedProvider ? 'injected' : 'vm'
if (this.executionContext === 'injected') this.askPermission() if (this.executionContext === 'injected') this.askPermission(false)
} }
} }
askPermission () { askPermission (throwIfNoInjectedProvider) {
if (ethereum && typeof ethereum.request === "function") ethereum.request({ method: "eth_requestAccounts" }); if (typeof ethereum !== "undefined" && typeof ethereum.request === "function") {
ethereum.request({ method: "eth_requestAccounts" })
} else if (throwIfNoInjectedProvider) {
throw new Error(noInjectedProviderMsg)
}
} }
getProvider () { getProvider () {
@ -80,7 +86,7 @@ export class ExecutionContext {
} }
if (web3.currentProvider.isConnected && !web3.currentProvider.isConnected()) { if (web3.currentProvider.isConnected && !web3.currentProvider.isConnected()) {
if (web3.currentProvider.isMetaMask) { if (web3.currentProvider.isMetaMask) {
this.askPermission() this.askPermission(false)
} }
return callback('Provider not connected') return callback('Provider not connected')
} }
@ -152,13 +158,18 @@ export class ExecutionContext {
if (context === 'injected') { if (context === 'injected') {
if (injectedProvider === undefined) { if (injectedProvider === undefined) {
infoCb('No injected provider found. Make sure your provider (e.g. MetaMask) is active and running (when recently activated you may have to reload the page).') infoCb(noInjectedProviderMsg)
return cb() return cb()
} else { } else {
if (injectedProvider && injectedProvider._metamask && injectedProvider._metamask.isUnlocked) { if (injectedProvider && injectedProvider._metamask && injectedProvider._metamask.isUnlocked) {
if (!await injectedProvider._metamask.isUnlocked()) infoCb('Please make sure the injected provider is unlocked (e.g Metamask).') if (!await injectedProvider._metamask.isUnlocked()) infoCb('Please make sure the injected provider is unlocked (e.g Metamask).')
} }
this.askPermission() try {
this.askPermission(true)
} catch (e) {
infoCb(e.message)
return cb()
}
this.executionContext = context this.executionContext = context
web3.setProvider(injectedProvider) web3.setProvider(injectedProvider)
await this._updateChainContext() await this._updateChainContext()
@ -176,7 +187,12 @@ export class ExecutionContext {
}) })
} else { } else {
// injected // injected
this.askPermission() try {
this.askPermission(true)
} catch (e) {
infoCb(e.message)
return cb()
}
this.executionContext = context this.executionContext = context
web3.setProvider(network.provider) web3.setProvider(network.provider)
await this._updateChainContext() await this._updateChainContext()

@ -114,7 +114,7 @@ export class VmDebuggerLogic {
try { try {
const memory = this._traceManager.getMemoryAt(index) const memory = this._traceManager.getMemoryAt(index)
if (this.stepManager.currentStepIndex === index) { if (this.stepManager.currentStepIndex === index) {
this.event.trigger('traceManagerMemoryUpdate', [ui.formatMemory(memory, 16)]) this.event.trigger('traceManagerMemoryUpdate', [ui.formatMemory(memory, 32)])
} }
} catch (error) { } catch (error) {
this.event.trigger('traceManagerMemoryUpdate', [{}]) this.event.trigger('traceManagerMemoryUpdate', [{}])

@ -49,7 +49,7 @@ export interface ICompilerApi {
} }
export type terminalLog = { export type terminalLog = {
type: 'info' | 'error' | 'warning' type: 'info' | 'error' | 'warning' | 'log'
value: string value: string
} }

@ -1,5 +1,5 @@
export { default as RemixApp } from './lib/remix-app/remix-app' export { default as RemixApp } from './lib/remix-app/remix-app'
export { dispatchModalContext, AppContext } from './lib/remix-app/context/context' export { dispatchModalContext, dispatchModalInterface, AppContext } from './lib/remix-app/context/context'
export { ModalProvider, useDialogDispatchers } from './lib/remix-app/context/provider' export { ModalProvider, useDialogDispatchers } from './lib/remix-app/context/provider'
export { AppModal } from './lib/remix-app/interface/index' export { AppModal } from './lib/remix-app/interface/index'
export { AlertModal } from './lib/remix-app/interface/index' export { AlertModal } from './lib/remix-app/interface/index'

@ -22,7 +22,7 @@ export const enum modalActionTypes {
type ModalPayload = { type ModalPayload = {
[modalActionTypes.setModal]: AppModal [modalActionTypes.setModal]: AppModal
[modalActionTypes.handleHideModal]: any [modalActionTypes.handleHideModal]: any
[modalActionTypes.setToast]: string | JSX.Element [modalActionTypes.setToast]: { message: string | JSX.Element, timestamp: number }
[modalActionTypes.handleToaster]: any, [modalActionTypes.handleToaster]: any,
[modalActionTypes.processQueue]: any [modalActionTypes.processQueue]: any
} }

@ -10,7 +10,7 @@ const AppDialogs = () => {
return ( return (
<> <>
<ModalWrapper {...focusModal} handleHide={handleHideModal}></ModalWrapper> <ModalWrapper {...focusModal} handleHide={handleHideModal}></ModalWrapper>
<Toaster message={focusToaster} handleHide={handleToaster} /> <Toaster message={focusToaster.message} timestamp={focusToaster.timestamp} handleHide={handleToaster} />
</>) </>)
} }
export default AppDialogs export default AppDialogs

@ -39,7 +39,7 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
const toast = (message: string | JSX.Element) => { const toast = (message: string | JSX.Element) => {
dispatch({ dispatch({
type: modalActionTypes.setToast, type: modalActionTypes.setToast,
payload: message payload: { message, timestamp: Date.now() }
}) })
} }

@ -33,7 +33,7 @@ export interface AlertModal {
export interface ModalState { export interface ModalState {
modals: AppModal[], modals: AppModal[],
toasters: (string | JSX.Element)[], toasters: {message: (string | JSX.Element), timestamp: number }[],
focusModal: AppModal, focusModal: AppModal,
focusToaster: string | JSX.Element focusToaster: {message: (string | JSX.Element), timestamp: number }
} }

@ -62,8 +62,7 @@ export const modalReducer = (state: ModalState = ModalInitialState, action: Moda
} }
case modalActionTypes.setToast: { case modalActionTypes.setToast: {
const toasterList = state.toasters.slice() const toasterList = state.toasters.slice()
const message = action.payload toasterList.push(action.payload)
toasterList.push(message)
if (toasterList.length === 1) { if (toasterList.length === 1) {
return { ...state, toasters: toasterList, focusToaster: action.payload } return { ...state, toasters: toasterList, focusToaster: action.payload }
} else { } else {

@ -8,11 +8,11 @@ export const ModalInitialState: ModalState = {
hide: true, hide: true,
title: '', title: '',
message: '', message: '',
validationFn: () => { return {valid: true, message: ''} }, validationFn: () => { return { valid: true, message: '' } },
okLabel: '', okLabel: '',
okFn: () => { }, okFn: () => { },
cancelLabel: '', cancelLabel: '',
cancelFn: () => { } cancelFn: () => { }
}, },
focusToaster: '' focusToaster: { message: '', timestamp: 0 }
} }

@ -1,10 +1,12 @@
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import Editor, { loader, Monaco } from '@monaco-editor/react' import Editor, { loader, Monaco } from '@monaco-editor/react'
import { AlertModal } from '@remix-ui/app'
import { reducerActions, reducerListener, initialState } from './actions/editor' import { reducerActions, reducerListener, initialState } from './actions/editor'
import { solidityTokensProvider, solidityLanguageConfig } from './syntaxes/solidity' import { solidityTokensProvider, solidityLanguageConfig } from './syntaxes/solidity'
import { cairoTokensProvider, cairoLanguageConfig } from './syntaxes/cairo' import { cairoTokensProvider, cairoLanguageConfig } from './syntaxes/cairo'
import { zokratesTokensProvider, zokratesLanguageConfig } from './syntaxes/zokrates' import { zokratesTokensProvider, zokratesLanguageConfig } from './syntaxes/zokrates'
import { moveTokenProvider, moveLanguageConfig } from './syntaxes/move'
import './remix-ui-editor.css' import './remix-ui-editor.css'
import { loadTypes } from './web-types' import { loadTypes } from './web-types'
@ -135,6 +137,7 @@ export const EditorUI = (props: EditorUIProps) => {
\t\t\t\t\t\t\t\tMedium: https://medium.com/remix-ide\n \t\t\t\t\t\t\t\tMedium: https://medium.com/remix-ide\n
\t\t\t\t\t\t\t\tTwitter: https://twitter.com/ethereumremix\n \t\t\t\t\t\t\t\tTwitter: https://twitter.com/ethereumremix\n
` `
const pasteCodeRef = useRef(false)
const editorRef = useRef(null) const editorRef = useRef(null)
const monacoRef = useRef<Monaco>(null) const monacoRef = useRef<Monaco>(null)
const currentFileRef = useRef('') const currentFileRef = useRef('')
@ -301,6 +304,8 @@ export const EditorUI = (props: EditorUIProps) => {
monacoRef.current.editor.setModelLanguage(file.model, 'remix-cairo') monacoRef.current.editor.setModelLanguage(file.model, 'remix-cairo')
} else if (file.language === 'zokrates') { } else if (file.language === 'zokrates') {
monacoRef.current.editor.setModelLanguage(file.model, 'remix-zokrates') monacoRef.current.editor.setModelLanguage(file.model, 'remix-zokrates')
} else if (file.language === 'move') {
monacoRef.current.editor.setModelLanguage(file.model, 'remix-move')
} }
}, [props.currentFile]) }, [props.currentFile])
@ -541,6 +546,33 @@ export const EditorUI = (props: EditorUIProps) => {
} }
}) })
editor.onDidPaste((e) => {
if (!pasteCodeRef.current && e && e.range && e.range.startLineNumber >= 0 && e.range.endLineNumber >= 0 && e.range.endLineNumber - e.range.startLineNumber > 10) {
const modalContent: AlertModal = {
id: 'newCodePasted',
title: 'Pasted Code Alert',
message: (
<div> <i className="fas fa-exclamation-triangle text-danger mr-1"></i>
You have just pasted a code snippet or contract in the editor.
<div>
Make sure you fully understand this code before deploying or interacting with it. Don't get scammed!
<div className='mt-2'>
Running untrusted code can put your wallet <span className='text-warning'> at risk </span>. In a worst-case scenario, you could <span className='text-warning'>loose all your money</span>.
</div>
<div className='text-warning mt-2'>If you don't fully understand it, please don't run this code.</div>
<div className='mt-2'>
If you are not a smart contract developer, ask someone you trust who has the skills to determine if this code is safe to use.
</div>
<div className='mt-2'>See <a target="_blank" href='https://remix-ide.readthedocs.io/en/latest/security.html'> these recommendations </a> for more information.</div>
</div>
</div>
),
}
props.plugin.call('notification', 'alert', modalContent)
pasteCodeRef.current = true
}
})
// zoomin zoomout // zoomin zoomout
editor.addCommand(monacoRef.current.KeyMod.CtrlCmd | (monacoRef.current.KeyCode as any).US_EQUAL, () => { editor.addCommand(monacoRef.current.KeyMod.CtrlCmd | (monacoRef.current.KeyCode as any).US_EQUAL, () => {
editor.updateOptions({ fontSize: editor.getOption(43).fontSize + 1 }) editor.updateOptions({ fontSize: editor.getOption(43).fontSize + 1 })
@ -618,6 +650,7 @@ export const EditorUI = (props: EditorUIProps) => {
monacoRef.current.languages.register({ id: 'remix-solidity' }) monacoRef.current.languages.register({ id: 'remix-solidity' })
monacoRef.current.languages.register({ id: 'remix-cairo' }) monacoRef.current.languages.register({ id: 'remix-cairo' })
monacoRef.current.languages.register({ id: 'remix-zokrates' }) monacoRef.current.languages.register({ id: 'remix-zokrates' })
monacoRef.current.languages.register({ id: 'remix-move' })
// Register a tokens provider for the language // Register a tokens provider for the language
monacoRef.current.languages.setMonarchTokensProvider('remix-solidity', solidityTokensProvider as any) monacoRef.current.languages.setMonarchTokensProvider('remix-solidity', solidityTokensProvider as any)
@ -629,6 +662,9 @@ export const EditorUI = (props: EditorUIProps) => {
monacoRef.current.languages.setMonarchTokensProvider('remix-zokrates', zokratesTokensProvider as any) monacoRef.current.languages.setMonarchTokensProvider('remix-zokrates', zokratesTokensProvider as any)
monacoRef.current.languages.setLanguageConfiguration('remix-zokrates', zokratesLanguageConfig as any) monacoRef.current.languages.setLanguageConfiguration('remix-zokrates', zokratesLanguageConfig as any)
monacoRef.current.languages.setMonarchTokensProvider('remix-move', moveTokenProvider as any)
monacoRef.current.languages.setLanguageConfiguration('remix-move', moveLanguageConfig as any)
monacoRef.current.languages.registerDefinitionProvider('remix-solidity', new RemixDefinitionProvider(props, monaco)) monacoRef.current.languages.registerDefinitionProvider('remix-solidity', new RemixDefinitionProvider(props, monaco))
monacoRef.current.languages.registerDocumentHighlightProvider('remix-solidity', new RemixHighLightProvider(props, monaco)) monacoRef.current.languages.registerDocumentHighlightProvider('remix-solidity', new RemixHighLightProvider(props, monaco))
monacoRef.current.languages.registerReferenceProvider('remix-solidity', new RemixReferenceProvider(props, monaco)) monacoRef.current.languages.registerReferenceProvider('remix-solidity', new RemixReferenceProvider(props, monaco))

@ -0,0 +1,264 @@
/* eslint-disable no-useless-escape */
export const moveLanguageConfig = {
comments: {
lineComment: "//",
blockComment: ["/*", "*/"],
},
brackets: [
["{", "}"],
["[", "]"],
["(", ")"],
],
autoClosingPairs: [
{ open: "[", close: "]" },
{ open: "{", close: "}" },
{ open: "(", close: ")" },
{ open: '"', close: '"', notIn: ["string"] },
],
surroundingPairs: [
{ open: "{", close: "}" },
{ open: "[", close: "]" },
{ open: "(", close: ")" },
{ open: '"', close: '"' },
{ open: "'", close: "'" },
],
folding: {
markers: {
start: new RegExp("^\\s*#pragma\\s+region\\b"),
end: new RegExp("^\\s*#pragma\\s+endregion\\b"),
},
},
};
export const moveTokenProvider = {
// Set defaultToken to invalid to see what you do not tokenize yet
// defaultToken: 'invalid',
keywords: [
"as",
"break",
"const",
"crate",
"enum",
"extern",
"false",
"fun",
"script",
"in",
"let",
"module",
"move",
"mut",
"pub",
"ref",
"return",
"self",
"Self",
"static",
"struct",
"super",
"trait",
"true",
"type",
"unsafe",
"use",
"where",
"use",
"macro_rules",
],
controlFlowKeywords: [
"continue",
"else",
"for",
"if",
"while",
"loop",
"match",
],
typeKeywords: [
"Self",
"m32",
"m64",
"m128",
"f80",
"f16",
"f128",
"int",
"uint",
"float",
"char",
"bool",
"u8",
"u16",
"u32",
"u64",
"f32",
"f64",
"i8",
"i16",
"i32",
"i64",
"str",
"Option",
"Either",
"c_float",
"c_double",
"c_void",
"FILE",
"fpos_t",
"DIR",
"dirent",
"c_char",
"c_schar",
"c_uchar",
"c_short",
"c_ushort",
"c_int",
"c_uint",
"c_long",
"c_ulong",
"size_t",
"ptrdiff_t",
"clock_t",
"time_t",
"c_longlong",
"c_ulonglong",
"intptr_t",
"uintptr_t",
"off_t",
"dev_t",
"ino_t",
"pid_t",
"mode_t",
"ssize_t",
],
operators: [
"=",
">",
"<",
"!",
"~",
"?",
":",
"==",
"<=",
">=",
"!=",
"&&",
"||",
"++",
"--",
"+",
"-",
"*",
"/",
"&",
"|",
"^",
"%",
"<<",
">>",
">>>",
"+=",
"-=",
"*=",
"/=",
"&=",
"|=",
"^=",
"%=",
"<<=",
">>=",
">>>=",
],
// we include these common regular expressions
symbols: /[=><!~?:&|+\-*\/\^%]+/,
// C# style strings
escapes:
/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
// The main tokenizer for languages
tokenizer: {
root: [
// identifiers and keywords
[
/[a-z_$][\w$]*/,
{
cases: {
"@typeKeywords": "type.identifier",
"@keywords": {
cases: {
fun: { token: "keyword", next: "@func_decl" },
let: { token: "keyword", next: "@func_decl" },
if: { token: "keyword", next: "@func_decl" },
use: { token: "keyword", next: "@func_decl" },
"@default": "keyword",
},
},
"@controlFlowKeywords": "keyword.control",
"@default": "variable",
},
},
],
[/[A-Z][\w\$]*/, "type.identifier"], // to show class names nicely
// whitespace
{ include: "@whitespace" },
// delimiters and operators
[/[{}()\[\]]/, "@brackets"],
[/[<>](?!@symbols)/, "@brackets"],
[/@symbols/, { cases: { "@operators": "operator", "@default": "" } }],
// @ annotations.
// As an example, we emit a debugging log message on these tokens.
// Note: message are supressed during the first load -- change some lines to see them.
[
/@\s*[a-zA-Z_\$][\w\$]*/,
{ token: "annotation", log: "annotation token: $0" },
],
// numbers
[/\d*\.\d+([eE][\-+]?\d+)?/, "number.float"],
[/0[xX][0-9a-fA-F]+/, "number.hex"],
[/\d+/, "number"],
// delimiter: after number because of .\d floats
[/[;,.]/, "delimiter"],
// strings
[/"([^"\\]|\\.)*$/, "string.invalid"], // non-teminated string
[/"/, { token: "string.quote", bracket: "@open", next: "@string" }],
// characters
[/'[^\\']'/, "string"],
[/(')(@escapes)(')/, ["string", "string.escape", "string"]],
[/'/, "string.invalid"],
],
comment: [
[/[^\/*]+/, "comment"],
[/\/\*/, "comment", "@push"], // nested comment
["\\*/", "comment", "@pop"],
[/[\/*]/, "comment"],
],
string: [
[/[^\\"]+/, "string"],
[/@escapes/, "string.escape"],
[/\\./, "string.escape.invalid"],
[/"/, { token: "string.quote", bracket: "@close", next: "@pop" }],
],
whitespace: [
[/[ \t\r\n]+/, "white"],
[/\/\*/, "comment", "@comment"],
[/\/\/.*$/, "comment"],
],
func_decl: [[/[a-z_$][\w$]*/, "support.function", "@pop"]],
},
};

@ -60,7 +60,7 @@ function HomeTabGetStarted ({plugin}: HomeTabGetStartedProps) {
await plugin.call('filePanel', 'createWorkspace', templateName + "_" + timeStamp, templateName) await plugin.call('filePanel', 'createWorkspace', templateName + "_" + timeStamp, templateName)
await plugin.call('filePanel', 'setWorkspace', templateName + "_" + timeStamp) await plugin.call('filePanel', 'setWorkspace', templateName + "_" + timeStamp)
plugin.verticalIcons.select('filePanel') plugin.verticalIcons.select('filePanel')
_paq.push(['trackEvent', 'homeGetStarted', templateName]) _paq.push(['trackEvent', 'homeTab', 'homeGetStarted', templateName])
} }
return ( return (

@ -53,6 +53,7 @@ iframe {
height: 100%; height: 100%;
width: 100%; width: 100%;
border: 0; border: 0;
display: block;
} }
.plugins { .plugins {

@ -6,6 +6,7 @@ import { DeployMode, MainnetPrompt } from "../types"
import { displayNotification, displayPopUp, setDecodedResponse } from "./payload" import { displayNotification, displayPopUp, setDecodedResponse } from "./payload"
import { addInstance } from "./actions" import { addInstance } from "./actions"
import { addressToString, logBuilder } from "@remix-ui/helper" import { addressToString, logBuilder } from "@remix-ui/helper"
import Web3 from "web3"
declare global { declare global {
interface Window { interface Window {
@ -26,11 +27,11 @@ const loadContractFromAddress = (plugin: RunTab, address, confirmCb, cb) => {
} catch (e) { } catch (e) {
return cb('Failed to parse the current file as JSON ABI.') return cb('Failed to parse the current file as JSON ABI.')
} }
_paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithABI']) _paq.push(['trackEvent', 'udapp', 'useAtAddress' , 'AtAddressLoadWithABI'])
cb(null, 'abi', abi) cb(null, 'abi', abi)
}) })
} else { } else {
_paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithArtifacts']) _paq.push(['trackEvent', 'udapp', 'useAtAddress', 'AtAddressLoadWithArtifacts'])
cb(null, 'instance') cb(null, 'instance')
} }
} }
@ -323,3 +324,15 @@ export const updateInstanceBalance = (plugin: RunTab) => {
} }
} }
} }
export const isValidContractAddress = async (plugin: RunTab, address: string) => {
if (!address) {
return false
} else {
if (Web3.utils.isAddress(address)) {
return await plugin.blockchain.web3().eth.getCode(address) !== '0x'
} else {
return false
}
}
}

@ -6,7 +6,7 @@ import { createNewBlockchainAccount, fillAccountsList, setExecutionContext, sign
import { clearInstances, clearPopUp, removeInstance, setAccount, setGasFee, setMatchPassphrasePrompt, import { clearInstances, clearPopUp, removeInstance, setAccount, setGasFee, setMatchPassphrasePrompt,
setNetworkNameFromProvider, setPassphrasePrompt, setSelectedContract, setSendTransactionValue, setUnit, setNetworkNameFromProvider, setPassphrasePrompt, setSelectedContract, setSendTransactionValue, setUnit,
updateBaseFeePerGas, updateConfirmSettings, updateGasPrice, updateGasPriceStatus, updateMaxFee, updateMaxPriorityFee, updateScenarioPath } from './actions' updateBaseFeePerGas, updateConfirmSettings, updateGasPrice, updateGasPriceStatus, updateMaxFee, updateMaxPriorityFee, updateScenarioPath } from './actions'
import { createInstance, getContext, getFuncABIInputs, getSelectedContract, loadAddress, runTransactions, updateInstanceBalance, syncContractsInternal } from './deploy' import { createInstance, getContext, getFuncABIInputs, getSelectedContract, loadAddress, runTransactions, updateInstanceBalance, syncContractsInternal, isValidContractAddress } from './deploy'
import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity-ts' import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity-ts'
import { ContractData, FuncABI } from "@remix-project/core-plugin" import { ContractData, FuncABI } from "@remix-project/core-plugin"
import { DeployMode, MainnetPrompt } from '../types' import { DeployMode, MainnetPrompt } from '../types'
@ -62,3 +62,4 @@ export const getFuncABIValues = (funcABI: FuncABI) => getFuncABIInputs(plugin, f
export const setNetworkName = (networkName: string) => setNetworkNameFromProvider(dispatch, networkName) export const setNetworkName = (networkName: string) => setNetworkNameFromProvider(dispatch, networkName)
export const updateSelectedContract = (contractName) => setSelectedContract(dispatch, contractName) export const updateSelectedContract = (contractName) => setSelectedContract(dispatch, contractName)
export const syncContracts = () => syncContractsInternal(plugin) export const syncContracts = () => syncContractsInternal(plugin)
export const isValidProxyAddress = (address: string) => isValidContractAddress(plugin, address)

@ -255,7 +255,7 @@ export function ContractDropdownUI(props: ContractDropdownProps) {
<div className='d-flex justify-content-between'> <div className='d-flex justify-content-between'>
<div className="d-flex justify-content-between align-items-end"> <div className="d-flex justify-content-between align-items-end">
<label className="udapp_settingsLabel pr-1">Contract</label> <label className="udapp_settingsLabel pr-1">Contract</label>
<div className="d-flex">{compilerName && compilerName !== '' && <label className="text-capitalize" style={{ maxHeight: '0.6rem', lineHeight: '1rem' }} data-id="udappCompiledBy">(Compiled by {compilerName})</label>}</div> <div className="d-flex">{compilerName && compilerName !== '' && <label style={{ maxHeight: '0.6rem', lineHeight: '1rem' }} data-id="udappCompiledBy">(Compiled by <span className="text-capitalize"> {compilerName}</span>)</label>}</div>
</div> </div>
{props.remixdActivated ? {props.remixdActivated ?
(<CustomTooltip (<CustomTooltip
@ -267,7 +267,7 @@ export function ContractDropdownUI(props: ContractDropdownProps) {
> >
<button className="btn d-flex py-0" onClick={_ => { <button className="btn d-flex py-0" onClick={_ => {
props.syncContracts() props.syncContracts()
_paq.push(['trackEvent', 'udapp', 'syncContracts', compilationSource]) _paq.push(['trackEvent', 'udapp', 'syncContracts', compilationSource ? compilationSource : 'compilationSourceNotYetSet'])
}}> }}>
<i style={{ cursor: 'pointer' }} className="fa fa-refresh mr-2 mt-2" aria-hidden="true"></i> <i style={{ cursor: 'pointer' }} className="fa fa-refresh mr-2 mt-2" aria-hidden="true"></i>
</button> </button>
@ -307,6 +307,7 @@ export function ContractDropdownUI(props: ContractDropdownProps) {
evmBC={loadedContractData.bytecodeObject} evmBC={loadedContractData.bytecodeObject}
lookupOnly={false} lookupOnly={false}
savedProxyAddress={proxyKey} savedProxyAddress={proxyKey}
isValidProxyAddress={props.isValidProxyAddress}
/> />
<div className="d-flex py-1 align-items-center custom-control custom-checkbox"> <div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input <input

@ -1,6 +1,7 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import * as remixLib from '@remix-project/remix-lib' import * as remixLib from '@remix-project/remix-lib'
import Web3 from 'web3'
import { ContractGUIProps } from '../types' import { ContractGUIProps } from '../types'
import { CopyToClipboard } from '@remix-ui/clipboard' import { CopyToClipboard } from '@remix-ui/clipboard'
import { CustomTooltip } from '@remix-ui/helper' import { CustomTooltip } from '@remix-ui/helper'
@ -22,6 +23,7 @@ export function ContractGUI (props: ContractGUIProps) {
const [deployState, setDeployState] = useState<{ deploy: boolean, upgrade: boolean }>({ deploy: false, upgrade: false }) const [deployState, setDeployState] = useState<{ deploy: boolean, upgrade: boolean }>({ deploy: false, upgrade: false })
const [useLastProxy, setUseLastProxy] = useState<boolean>(false) const [useLastProxy, setUseLastProxy] = useState<boolean>(false)
const [proxyAddress, setProxyAddress] = useState<string>('') const [proxyAddress, setProxyAddress] = useState<string>('')
const [proxyAddressError, setProxyAddressError] = useState<string>('')
const multiFields = useRef<Array<HTMLInputElement | null>>([]) const multiFields = useRef<Array<HTMLInputElement | null>>([])
const initializeFields = useRef<Array<HTMLInputElement | null>>([]) const initializeFields = useRef<Array<HTMLInputElement | null>>([])
const basicInputRef = useRef<HTMLInputElement>() const basicInputRef = useRef<HTMLInputElement>()
@ -174,7 +176,7 @@ export function ContractGUI (props: ContractGUIProps) {
props.clickCallBack(props.initializerOptions.inputs.inputs, proxyInitializeString, ['Deploy with Proxy']) props.clickCallBack(props.initializerOptions.inputs.inputs, proxyInitializeString, ['Deploy with Proxy'])
} else if (deployState.upgrade) { } else if (deployState.upgrade) {
props.clickCallBack(props.funcABI.inputs, proxyAddress, ['Upgrade with Proxy']) !proxyAddressError && props.clickCallBack(props.funcABI.inputs, proxyAddress, ['Upgrade with Proxy'])
} else { } else {
props.clickCallBack(props.funcABI.inputs, basicInput) props.clickCallBack(props.funcABI.inputs, basicInput)
} }
@ -223,8 +225,16 @@ export function ContractGUI (props: ContractGUIProps) {
const value = e.target.checked const value = e.target.checked
const address = props.savedProxyAddress const address = props.savedProxyAddress
if (value) {
if (address) {
setProxyAddress(address)
setProxyAddressError('')
} else {
setProxyAddressError('No proxy address available')
setProxyAddress('')
}
}
setUseLastProxy(value) setUseLastProxy(value)
setProxyAddress(address || '')
} }
const handleSetProxyAddress = (e) => { const handleSetProxyAddress = (e) => {
@ -233,6 +243,18 @@ export function ContractGUI (props: ContractGUIProps) {
setProxyAddress(value) setProxyAddress(value)
} }
const validateProxyAddress = async (address: string) => {
if (address === '') {
setProxyAddressError('proxy address cannot be empty')
} else {
if (await props.isValidProxyAddress(address)) {
setProxyAddressError('')
} else {
setProxyAddressError('not a valid contract address')
}
}
}
return ( return (
<div <div
className={`udapp_contractProperty ${ className={`udapp_contractProperty ${
@ -403,7 +425,7 @@ export function ContractGUI (props: ContractGUIProps) {
</div> </div>
{props.deployOption && (props.deployOption || []).length > 0 ? ( {props.deployOption && (props.deployOption || []).length > 0 ? (
<> <>
<div className="d-flex justify-content-between"> <div className="d-flex justify-content-between mt-3">
<div className="d-flex py-1 align-items-center custom-control custom-checkbox"> <div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input <input
id="deployWithProxy" id="deployWithProxy"
@ -413,20 +435,21 @@ export function ContractGUI (props: ContractGUIProps) {
onChange={(e) => handleDeployProxySelect(e.target.checked)} onChange={(e) => handleDeployProxySelect(e.target.checked)}
checked={deployState.deploy} checked={deployState.deploy}
/> />
<CustomTooltip {/* <CustomTooltip
tooltipText="An ERC1967 proxy contract will be deployed along with the selected implementation contract." tooltipText={<span>An ERC1967 proxy contract<br/> will be deployed along<br/> with the selected <br/>implementation contract.</span>}
placement={"right"} placement={"bottom-start"}
tooltipClasses="text-nowrap" tooltipClasses="text-wrap"
tooltipId="deployWithProxyTooltip" tooltipId="deployWithProxyTooltip"
> > */}
<label <label
// title="An ERC1967 proxy contract will be deployed along with the selected implementation contract"
htmlFor="deployWithProxy" htmlFor="deployWithProxy"
data-id="contractGUIDeployWithProxyLabel" data-id="contractGUIDeployWithProxyLabel"
className="m-0 form-check-label custom-control-label udapp_checkboxAlign" className="m-0 form-check-label w-100 custom-control-label udapp_checkboxAlign"
> >
Deploy with Proxy Deploy with Proxy
</label> </label>
</CustomTooltip> {/* </CustomTooltip> */}
</div> </div>
<div> <div>
{props.initializerOptions && {props.initializerOptions &&
@ -462,12 +485,12 @@ export function ContractGUI (props: ContractGUIProps) {
{" "} {" "}
{inp.name}:{" "} {inp.name}:{" "}
</label> </label>
<CustomTooltip {/* <CustomTooltip
tooltipText={inp.name} tooltipText={inp.name}
tooltipClasses="text-wrap" tooltipClasses="text-wrap"
tooltipId="initializeFieldsTooltip" tooltipId="initializeFieldsTooltip"
placement="right" placement="top-start"
> > */}
<input <input
ref={(el) => { ref={(el) => {
initializeFields.current[index] = el; initializeFields.current[index] = el;
@ -476,7 +499,7 @@ export function ContractGUI (props: ContractGUIProps) {
className="form-control udapp_input" className="form-control udapp_input"
placeholder={inp.type} placeholder={inp.type}
/> />
</CustomTooltip> {/* </CustomTooltip> */}
</div> </div>
); );
})} })}
@ -493,12 +516,12 @@ export function ContractGUI (props: ContractGUIProps) {
onChange={(e) => handleUpgradeImpSelect(e.target.checked)} onChange={(e) => handleUpgradeImpSelect(e.target.checked)}
checked={deployState.upgrade} checked={deployState.upgrade}
/> />
<CustomTooltip {/* <CustomTooltip
tooltipText="The implementation contract will be deployed and then the proxy contract will be updated with new implementation's address." tooltipText="The implementation contract will be deployed and then the proxy contract will be updated with new implementation's address."
tooltipClasses="text-wrap" tooltipClasses="text-wrap"
tooltipId="upgradeImplementationTooltip" tooltipId="upgradeImplementationTooltip"
placement="right" placement="top-start"
> > */}
<label <label
htmlFor="upgradeImplementation" htmlFor="upgradeImplementation"
data-id="contractGUIUpgradeImplementationLabel" data-id="contractGUIUpgradeImplementationLabel"
@ -506,7 +529,7 @@ export function ContractGUI (props: ContractGUIProps) {
> >
Upgrade with Proxy Upgrade with Proxy
</label> </label>
</CustomTooltip> {/* </CustomTooltip> */}
</div> </div>
<span onClick={handleToggleUpgradeImp}> <span onClick={handleToggleUpgradeImp}>
<i <i
@ -550,7 +573,8 @@ export function ContractGUI (props: ContractGUIProps) {
</label> </label>
</CustomTooltip> </CustomTooltip>
</div> </div>
{!useLastProxy ? ( {
!useLastProxy ?
<div className="mb-2"> <div className="mb-2">
<label className="mt-2 text-left d-block"> <label className="mt-2 text-left d-block">
Proxy Address:{" "} Proxy Address:{" "}
@ -570,7 +594,7 @@ export function ContractGUI (props: ContractGUIProps) {
/> />
</CustomTooltip> </CustomTooltip>
</div> </div>
) : ( : (
<span <span
className="text-capitalize" className="text-capitalize"
data-id="lastDeployedERC1967Address" data-id="lastDeployedERC1967Address"

@ -138,7 +138,10 @@ export const runTabInitialState: RunTabState = {
dataId: 'settingsInjectedMode', dataId: 'settingsInjectedMode',
title: 'Execution environment has been provided by Metamask or similar provider.', title: 'Execution environment has been provided by Metamask or similar provider.',
value: 'injected', value: 'injected',
content: `Injected Provider${(window && window.ethereum && window.ethereum.isMetaMask) ? ' - Metamask' : ''}` content: `Injected Provider${(window && window.ethereum && !(window.ethereum.providers && !window.ethereum.selectedProvider)) ?
window.ethereum.isCoinbaseWallet || window.ethereum.selectedProvider?.isCoinbaseWallet ? ' - Coinbase' :
window.ethereum.isBraveWallet || window.ethereum.selectedProvider?.isBraveWallet ? ' - Brave' :
window.ethereum.isMetaMask || window.ethereum.selectedProvider?.isMetaMask ? ' - MetaMask' : '' : ''}`
}], }],
isRequesting: false, isRequesting: false,
isSuccessful: false, isSuccessful: false,

@ -27,7 +27,7 @@ import {
storeNewScenario, runScenario, storeNewScenario, runScenario,
setScenarioPath, getFuncABIValues, setScenarioPath, getFuncABIValues,
setNetworkName, updateSelectedContract, setNetworkName, updateSelectedContract,
syncContracts syncContracts, isValidProxyAddress
} from './actions' } from './actions'
import './css/run-tab.css' import './css/run-tab.css'
import { PublishToStorage } from '@remix-ui/publish-to-storage' import { PublishToStorage } from '@remix-ui/publish-to-storage'
@ -242,6 +242,7 @@ export function RunTabUI (props: RunTabProps) {
setNetworkName={setNetworkName} setNetworkName={setNetworkName}
setSelectedContract={updateSelectedContract} setSelectedContract={updateSelectedContract}
remixdActivated={runTab.remixdActivated} remixdActivated={runTab.remixdActivated}
isValidProxyAddress={isValidProxyAddress}
/> />
<RecorderUI <RecorderUI
gasEstimationPrompt={gasEstimationPrompt} gasEstimationPrompt={gasEstimationPrompt}

@ -165,7 +165,8 @@ export interface ContractDropdownProps {
networkName: string, networkName: string,
setNetworkName: (name: string) => void, setNetworkName: (name: string) => void,
setSelectedContract: (contractName: string) => void setSelectedContract: (contractName: string) => void
remixdActivated: boolean remixdActivated: boolean,
isValidProxyAddress?: (address: string) => Promise<boolean>
} }
export interface RecorderProps { export interface RecorderProps {
@ -260,7 +261,8 @@ export interface ContractGUIProps {
isDeploy?: boolean, isDeploy?: boolean,
deployOption?: { title: DeployMode, active: boolean }[], deployOption?: { title: DeployMode, active: boolean }[],
initializerOptions?: DeployOption, initializerOptions?: DeployOption,
savedProxyAddress?: string savedProxyAddress?: string,
isValidProxyAddress?: (address: string) => Promise<boolean>
} }
export interface MainnetProps { export interface MainnetProps {
network: Network, network: Network,

@ -472,10 +472,8 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
compileIcon.current.classList.remove('remixui_spinningIcon') compileIcon.current.classList.remove('remixui_spinningIcon')
compileIcon.current.classList.remove('remixui_bouncingIcon') compileIcon.current.classList.remove('remixui_bouncingIcon')
if (!state.autoCompile || (state.autoCompile && state.matomoAutocompileOnce)) { if (!state.autoCompile || (state.autoCompile && state.matomoAutocompileOnce)) {
if (state.useFileConfiguration) _paq.push(['trackEvent', 'compiler', 'compiled', 'with_config_file_' + state.useFileConfiguration])
_paq.push(['trackEvent', 'compiler', 'compiled_with_config_file']) _paq.push(['trackEvent', 'compiler', 'compiled', 'with_version_' + _retrieveVersion()])
_paq.push(['trackEvent', 'compiler', 'compiled_with_version', _retrieveVersion()])
if (state.autoCompile && state.matomoAutocompileOnce) { if (state.autoCompile && state.matomoAutocompileOnce) {
setState(prevState => { setState(prevState => {
return { ...prevState, matomoAutocompileOnce: false } return { ...prevState, matomoAutocompileOnce: false }
@ -887,7 +885,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
} }
}} }}
/> />
{!showFilePathInput && <button disabled={!state.useFileConfiguration} data-id="scConfigChangeFilePath" className="btn-secondary" onClick={() => { setShowFilePathInput(true) }}>Change</button>} {!showFilePathInput && <button disabled={!state.useFileConfiguration} data-id="scConfigChangeFilePath" className="btn btn-sm btn-secondary" onClick={() => { setShowFilePathInput(true) }}>Change</button>}
</div> </div>
</div> </div>
<div className="px-4"> <div className="px-4">

@ -155,9 +155,9 @@ export class CompileTabLogic {
` `
const configFilePath = 'remix-compiler.config.js' const configFilePath = 'remix-compiler.config.js'
this.api.writeFile(configFilePath, fileContent) this.api.writeFile(configFilePath, fileContent)
_paq.push(['trackEvent', 'compiler', 'compileWithHardhat']) _paq.push(['trackEvent', 'compiler', 'runCompile', 'compileWithHardhat'])
this.api.compileWithHardhat(configFilePath).then((result) => { this.api.compileWithHardhat(configFilePath).then((result) => {
this.api.logToTerminal({ type: 'info', value: result }) this.api.logToTerminal({ type: 'log', value: result })
}).catch((error) => { }).catch((error) => {
this.api.logToTerminal({ type: 'error', value: error }) this.api.logToTerminal({ type: 'error', value: error })
}) })
@ -181,9 +181,9 @@ export class CompileTabLogic {
}` }`
const configFilePath = 'remix-compiler.config.js' const configFilePath = 'remix-compiler.config.js'
this.api.writeFile(configFilePath, fileContent) this.api.writeFile(configFilePath, fileContent)
_paq.push(['trackEvent', 'compiler', 'compileWithTruffle']) _paq.push(['trackEvent', 'compiler', 'runCompile', 'compileWithTruffle'])
this.api.compileWithTruffle(configFilePath).then((result) => { this.api.compileWithTruffle(configFilePath).then((result) => {
this.api.logToTerminal({ type: 'info', value: result }) this.api.logToTerminal({ type: 'log', value: result })
}).catch((error) => { }).catch((error) => {
this.api.logToTerminal({ type: 'error', value: error }) this.api.logToTerminal({ type: 'error', value: error })
}) })

@ -259,7 +259,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
finalLogs = finalLogs + '&emsp;' + formattedLog + '\n' finalLogs = finalLogs + '&emsp;' + formattedLog + '\n'
} }
_paq.push(['trackEvent', 'solidityUnitTesting', 'hardhat', 'console.log']) _paq.push(['trackEvent', 'solidityUnitTesting', 'hardhat', 'console.log'])
testTab.call('terminal', 'log', { type: 'info', value: finalLogs }) testTab.call('terminal', 'log', { type: 'log', value: finalLogs })
} }
const discardHighlight = async () => { const discardHighlight = async () => {
@ -586,7 +586,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
const tests: string[] = selectedTests.current const tests: string[] = selectedTests.current
if (!tests || !tests.length) return if (!tests || !tests.length) return
else setProgressBarHidden(false) else setProgressBarHidden(false)
_paq.push(['trackEvent', 'solidityUnitTesting', 'runTests']) _paq.push(['trackEvent', 'solidityUnitTesting', 'runTests', 'nbTestsRunning' + tests.length])
eachOfSeries(tests, (value: string, key: string, callback: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any eachOfSeries(tests, (value: string, key: string, callback: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
if (hasBeenStopped.current) return if (hasBeenStopped.current) return
runTest(value, callback) runTest(value, callback)

@ -215,7 +215,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
const warningErrors = [] const warningErrors = []
// Remix Analysis // Remix Analysis
_paq.push(['trackEvent', 'solidityStaticAnalyzer', 'analyzeWithRemixAnalyzer']) _paq.push(['trackEvent', 'solidityStaticAnalyzer', 'analyze', 'remixAnalyzer'])
const results = runner.run(lastCompilationResult, categoryIndex) const results = runner.run(lastCompilationResult, categoryIndex)
for (const result of results) { for (const result of results) {
let moduleName let moduleName
@ -280,11 +280,11 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
try { try {
const compilerState = await props.analysisModule.call('solidity', 'getCompilerState') const compilerState = await props.analysisModule.call('solidity', 'getCompilerState')
const { currentVersion, optimize, evmVersion } = compilerState const { currentVersion, optimize, evmVersion } = compilerState
await props.analysisModule.call('terminal', 'log', { type: 'info', value: '[Slither Analysis]: Running...' }) await props.analysisModule.call('terminal', 'log', { type: 'log', value: '[Slither Analysis]: Running...' })
_paq.push(['trackEvent', 'solidityStaticAnalyzer', 'analyzeWithSlither']) _paq.push(['trackEvent', 'solidityStaticAnalyzer', 'analyze', 'slitherAnalyzer'])
const result = await props.analysisModule.call('slither', 'analyse', state.file, { currentVersion, optimize, evmVersion }) const result = await props.analysisModule.call('slither', 'analyse', state.file, { currentVersion, optimize, evmVersion })
if (result.status) { if (result.status) {
props.analysisModule.call('terminal', 'log', { type: 'info', value: `[Slither Analysis]: Analysis Completed!! ${result.count} warnings found.` }) props.analysisModule.call('terminal', 'log', { type: 'log', value: `[Slither Analysis]: Analysis Completed!! ${result.count} warnings found.` })
const report = result.data const report = result.data
for (const item of report) { for (const item of report) {
let location: any = {} let location: any = {}

@ -1,8 +1,8 @@
import { fileDecoration, FileDecorationIcons } from '@remix-ui/file-decorators' import { fileDecoration, FileDecorationIcons } from '@remix-ui/file-decorators'
import { CustomTooltip } from '@remix-ui/helper'
import { Plugin } from '@remixproject/engine' import { Plugin } from '@remixproject/engine'
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import { OverlayTrigger, Tooltip } from 'react-bootstrap' // eslint-disable-line
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs' import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'
import './remix-ui-tabs.css' import './remix-ui-tabs.css'
const _paq = window._paq = window._paq || [] const _paq = window._paq = window._paq || []
@ -160,16 +160,16 @@ export const TabsUI = (props: TabsUIProps) => {
} }
}} }}
> >
<OverlayTrigger placement="bottom" overlay={ <CustomTooltip
<Tooltip id="overlay-tooltip-run-script"> placement="bottom"
<span> tooltipId="overlay-tooltip-run-script"
tooltipText={<span>
{(tabsState.currentExt === 'js' || tabsState.currentExt === 'ts') ? "Run script (CTRL + SHIFT + S)" : {(tabsState.currentExt === 'js' || tabsState.currentExt === 'ts') ? "Run script (CTRL + SHIFT + S)" :
tabsState.currentExt === 'sol' || tabsState.currentExt === 'yul'? "Compile CTRL + S" : "Select .sol or .yul file to compile or a .ts or .js file and run it"} tabsState.currentExt === 'sol' || tabsState.currentExt === 'yul'? "Compile CTRL + S" : "Select .sol or .yul file to compile or a .ts or .js file and run it"}
</span> </span>}
</Tooltip> >
}>
<i className="fad fa-play"></i> <i className="fad fa-play"></i>
</OverlayTrigger> </CustomTooltip>
</button> </button>
<span data-id="tabProxyZoomOut" className="btn btn-sm px-2 fas fa-search-minus text-dark" title="Zoom out" onClick={() => props.onZoomOut()}></span> <span data-id="tabProxyZoomOut" className="btn btn-sm px-2 fas fa-search-minus text-dark" title="Zoom out" onClick={() => props.onZoomOut()}></span>
<span data-id="tabProxyZoomIn" className="btn btn-sm px-2 fas fa-search-plus text-dark" title="Zoom in" onClick={() => props.onZoomIn()}></span> <span data-id="tabProxyZoomIn" className="btn btn-sm px-2 fas fa-search-plus text-dark" title="Zoom in" onClick={() => props.onZoomIn()}></span>

@ -87,7 +87,13 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
}, },
log: (message) => { log: (message) => {
scriptRunnerDispatch({ type: 'log', payload: { message: [message] } }) if (typeof message === 'string') {
message = {
value: message,
type: 'log'
}
}
scriptRunnerDispatch({ type: message.type ? message.type : 'log', payload: { message: [message.value] } })
} }
}) })
}, []) }, [])

@ -7,7 +7,8 @@ import './toaster.css'
export interface ToasterProps { export interface ToasterProps {
message: string | JSX.Element message: string | JSX.Element
timeOut?: number, timeOut?: number,
handleHide?: () => void handleHide?: () => void,
timestamp?: number
} }
export const Toaster = (props: ToasterProps) => { export const Toaster = (props: ToasterProps) => {
@ -49,7 +50,7 @@ export const Toaster = (props: ToasterProps) => {
} }
}) })
} }
}, [props.message]) }, [props.message, props.timestamp])
useEffect(() => { useEffect(() => {
if (state.hiding) { if (state.hiding) {

@ -87,19 +87,19 @@ export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) =>
_paq.push(['trackEvent', 'fileExplorer', 'contextMenu', 'delete']) _paq.push(['trackEvent', 'fileExplorer', 'contextMenu', 'delete'])
break break
case 'Push changes to gist': case 'Push changes to gist':
_paq.push(['trackEvent', 'fileExplorer', 'pushToChangesoGist']) _paq.push(['trackEvent', 'fileExplorer', 'contextMenu', 'pushToChangesoGist'])
pushChangesToGist(path, type) pushChangesToGist(path, type)
break break
case 'Publish folder to gist': case 'Publish folder to gist':
_paq.push(['trackEvent', 'fileExplorer', 'publishFolderToGist']) _paq.push(['trackEvent', 'fileExplorer', 'contextMenu', 'publishFolderToGist'])
publishFolderToGist(path, type) publishFolderToGist(path, type)
break break
case 'Publish file to gist': case 'Publish file to gist':
_paq.push(['trackEvent', 'fileExplorer', 'publishFileToGist']) _paq.push(['trackEvent', 'fileExplorer', 'contextMenu', 'publishFileToGist'])
publishFileToGist(path, type) publishFileToGist(path, type)
break break
case 'Run': case 'Run':
_paq.push(['trackEvent', 'fileExplorer', 'runScript']) _paq.push(['trackEvent', 'fileExplorer', 'contextMenu', 'runScript'])
runScript(path) runScript(path)
break break
case 'Copy': case 'Copy':

@ -1,5 +1,5 @@
import { CustomTooltip } from '@remix-ui/helper'
import React, { useState, useEffect } from 'react' //eslint-disable-line import React, { useState, useEffect } from 'react' //eslint-disable-line
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
import { Placement } from 'react-bootstrap/esm/Overlay' import { Placement } from 'react-bootstrap/esm/Overlay'
import { FileExplorerMenuProps } from '../types' import { FileExplorerMenuProps } from '../types'
const _paq = window._paq = window._paq || [] const _paq = window._paq = window._paq || []
@ -53,27 +53,23 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
return ( return (
<> <>
<OverlayTrigger <CustomTooltip
placement="top-start" placement="top-start"
overlay={ tooltipId="remixuilabelTooltip"
<Tooltip id="remixuilabelTooltip" className="text-nowrap"> tooltipClasses="text-nowrap"
<span>{props.title}</span> tooltipText={props.title}
</Tooltip>
}
> >
<span className='remixui_label' data-path={props.title} style={{ fontWeight: 'bold' }}>{ props.title }</span> <span className='remixui_label' data-path={props.title} style={{ fontWeight: 'bold' }}>{ props.title }</span>
</OverlayTrigger> </CustomTooltip>
<span className="pl-2">{ <span className="pl-2">{
state.menuItems.map(({ action, title, icon, placement }, index) => { state.menuItems.map(({ action, title, icon, placement }, index) => {
if (action === 'uploadFile') { if (action === 'uploadFile') {
return ( return (
<OverlayTrigger <CustomTooltip
placement="right" placement="right"
overlay={ tooltipId="uploadFileTooltip"
<Tooltip id="uploadFileTooltip" className="text-nowrap"> tooltipClasses="text-nowrap"
<span>{title}</span> tooltipText={title}
</Tooltip>
}
> >
<label <label
id={action} id={action}
@ -88,17 +84,15 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
}} }}
multiple /> multiple />
</label> </label>
</OverlayTrigger> </CustomTooltip>
) )
} else { } else {
return ( return (
<OverlayTrigger <CustomTooltip
placement={placement as Placement} placement={placement as Placement}
overlay={ tooltipId={`${action}-${title}-${icon}-${index}`}
<Tooltip id={`${action}-${title}-${icon}-${index}`} className="text-nowrap"> tooltipClasses="text-nowrap"
<span>{title}</span> tooltipText={title}
</Tooltip>
}
> >
<span <span
id={action} id={action}
@ -120,7 +114,7 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
key={`${action}-${title}-${index}`} key={`${action}-${title}-${index}`}
> >
</span> </span>
</OverlayTrigger> </CustomTooltip>
) )
} }
})} })}

@ -70,13 +70,12 @@ export const FileLabel = (props: FileLabelProps) => {
onKeyDown={handleEditInput} onKeyDown={handleEditInput}
onBlur={handleEditBlur} onBlur={handleEditBlur}
> >
<span <span
title={file.path} className={`text-nowrap remixui_label ${fileStateClasses} ` + (file.isDirectory ? 'folder' : 'remixui_leaf')}
className={`text-nowrap remixui_label ${fileStateClasses} ` + (file.isDirectory ? 'folder' : 'remixui_leaf')} data-path={file.path} title={file.path}
data-path={file.path} >
> {file.name}
{file.name} </span>
</span>
</div> </div>
) )
} }

@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef, useContext, SyntheticEvent, ChangeEvent, KeyboardEvent } from 'react' // eslint-disable-line import React, { useState, useEffect, useRef, useContext, SyntheticEvent, ChangeEvent, KeyboardEvent } from 'react' // eslint-disable-line
import { Dropdown, OverlayTrigger, Tooltip } from 'react-bootstrap' import { Dropdown } from 'react-bootstrap'
import { CustomIconsToggle, CustomMenu, CustomToggle } from '@remix-ui/helper' import { CustomIconsToggle, CustomMenu, CustomToggle, CustomTooltip } from '@remix-ui/helper'
import { FileExplorer } from './components/file-explorer' // eslint-disable-line import { FileExplorer } from './components/file-explorer' // eslint-disable-line
import { FileSystemContext } from './contexts' import { FileSystemContext } from './contexts'
import './css/remix-ui-workspace.css' import './css/remix-ui-workspace.css'
@ -221,8 +221,10 @@ export function Workspace () {
try { try {
if (branch.remote) { if (branch.remote) {
await global.dispatchCheckoutRemoteBranch(branch.name, branch.remote) await global.dispatchCheckoutRemoteBranch(branch.name, branch.remote)
_paq.push(['trackEvent', 'Workspace', 'GIT', 'checkout_remote_branch'])
} else { } else {
await global.dispatchSwitchToBranch(branch.name) await global.dispatchSwitchToBranch(branch.name)
_paq.push(['trackEvent', 'Workspace', 'GIT', 'switch_to_exisiting_branch'])
} }
} catch (e) { } catch (e) {
console.error(e) console.error(e)
@ -233,6 +235,7 @@ export function Workspace () {
const switchToNewBranch = async () => { const switchToNewBranch = async () => {
try { try {
await global.dispatchCreateNewBranch(branchFilter) await global.dispatchCreateNewBranch(branchFilter)
_paq.push(['trackEvent', 'Workspace', 'GIT', 'switch_to_new_branch'])
} catch (e) { } catch (e) {
global.modal('Checkout Git Branch', e.message, 'OK', () => {}) global.modal('Checkout Git Branch', e.message, 'OK', () => {})
} }
@ -333,18 +336,15 @@ export function Workspace () {
} }
const workspaceMenuIcons = [ const workspaceMenuIcons = [
<OverlayTrigger <CustomTooltip
placement="right" placement="right"
overlay={ tooltipId="createWorkspaceTooltip"
<Tooltip id="createWorkspaceTooltip" className="text-nowrap"> tooltipClasses="text-nowrap"
<span>Create</span> tooltipText="Create"
</Tooltip>
}
> >
<div <div
data-id='workspaceCreate' data-id='workspaceCreate'
onClick={(e) => { onClick={() => {
e.stopPropagation()
createWorkspace() createWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceCreate']) _paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceCreate'])
hideIconsMenu(!showIconsMenu) hideIconsMenu(!showIconsMenu)
@ -354,8 +354,7 @@ export function Workspace () {
hidden={currentWorkspace === LOCALHOST} hidden={currentWorkspace === LOCALHOST}
id='workspaceCreate' id='workspaceCreate'
data-id='workspaceCreate' data-id='workspaceCreate'
onClick={(e) => { onClick={() => {
e.stopPropagation()
createWorkspace() createWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceCreate']) _paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceCreate'])
hideIconsMenu(!showIconsMenu) hideIconsMenu(!showIconsMenu)
@ -365,19 +364,16 @@ export function Workspace () {
</span> </span>
<span className="pl-3">Create</span> <span className="pl-3">Create</span>
</div> </div>
</OverlayTrigger>, </CustomTooltip>,
<OverlayTrigger <CustomTooltip
placement="right-start" placement="right-start"
overlay={ tooltipId="createWorkspaceTooltip"
<Tooltip id="createWorkspaceTooltip" className="text-nowrap"> tooltipClasses="text-nowrap"
<span>Delete Workspace</span> tooltipText="Delete Workspace"
</Tooltip>
}
> >
<div <div
data-id='workspaceDelete' data-id='workspaceDelete'
onClick={(e) => { onClick={() => {
e.stopPropagation()
deleteCurrentWorkspace() deleteCurrentWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceDelete']) _paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceDelete'])
hideIconsMenu(!showIconsMenu) hideIconsMenu(!showIconsMenu)
@ -387,8 +383,7 @@ export function Workspace () {
hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE} hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE}
id='workspaceDelete' id='workspaceDelete'
data-id='workspaceDelete' data-id='workspaceDelete'
onClick={(e) => { onClick={() => {
e.stopPropagation()
deleteCurrentWorkspace() deleteCurrentWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceDelete']) _paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceDelete'])
hideIconsMenu(!showIconsMenu) hideIconsMenu(!showIconsMenu)
@ -398,17 +393,14 @@ export function Workspace () {
</span> </span>
<span className="pl-3">{'Delete'}</span> <span className="pl-3">{'Delete'}</span>
</div> </div>
</OverlayTrigger>, </CustomTooltip>,
<OverlayTrigger <CustomTooltip
placement='right-start' placement='right-start'
overlay={ tooltipClasses="text-nowrap"
<Tooltip id="workspaceRenametooltip"> tooltipId="workspaceRenametooltip"
<span>Rename Workspace</span> tooltipText="Rename Workspace"
</Tooltip>
}
> >
<div onClick={(e) => { <div onClick={() => {
e.stopPropagation()
renameCurrentWorkspace() renameCurrentWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceRename']) _paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceRename'])
hideIconsMenu(!showIconsMenu) hideIconsMenu(!showIconsMenu)
@ -419,8 +411,7 @@ export function Workspace () {
hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE} hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE}
id='workspaceRename' id='workspaceRename'
data-id='workspaceRename' data-id='workspaceRename'
onClick={(e) => { onClick={() => {
e.stopPropagation()
renameCurrentWorkspace() renameCurrentWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceRename']) _paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceRename'])
hideIconsMenu(!showIconsMenu) hideIconsMenu(!showIconsMenu)
@ -429,20 +420,17 @@ export function Workspace () {
</span> </span>
<span className="pl-3">{'Rename'}</span> <span className="pl-3">{'Rename'}</span>
</div> </div>
</OverlayTrigger>, </CustomTooltip>,
<Dropdown.Divider className="border mb-0 mt-0" />, <Dropdown.Divider className="border mb-0 mt-0" />,
<OverlayTrigger <CustomTooltip
placement="right-start" placement="right-start"
overlay={ tooltipId="cloneWorkspaceTooltip"
<Tooltip id="cloneWorkspaceTooltip" className="text-nowrap"> tooltipClasses="text-nowrap"
<span>Clone Git Repository</span> tooltipText="Clone Git Repository"
</Tooltip>
}
> >
<div <div
data-id='cloneGitRepository' data-id='cloneGitRepository'
onClick={(e) => { onClick={() => {
e.stopPropagation()
cloneGitRepository() cloneGitRepository()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'cloneGitRepository']) _paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'cloneGitRepository'])
hideIconsMenu(!showIconsMenu) hideIconsMenu(!showIconsMenu)
@ -452,8 +440,7 @@ export function Workspace () {
hidden={currentWorkspace === LOCALHOST} hidden={currentWorkspace === LOCALHOST}
id='cloneGitRepository' id='cloneGitRepository'
data-id='cloneGitRepository' data-id='cloneGitRepository'
onClick={(e) => { onClick={() => {
e.stopPropagation()
cloneGitRepository() cloneGitRepository()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'cloneGitRepository']) _paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'cloneGitRepository'])
hideIconsMenu(!showIconsMenu) hideIconsMenu(!showIconsMenu)
@ -463,20 +450,17 @@ export function Workspace () {
</span> </span>
<span className="pl-3">{'Clone'}</span> <span className="pl-3">{'Clone'}</span>
</div> </div>
</OverlayTrigger>, </CustomTooltip>,
<Dropdown.Divider className="border mt-0 mb-0 remixui_menuhr" style={{ pointerEvents: 'none' }}/>, <Dropdown.Divider className="border mt-0 mb-0 remixui_menuhr" style={{ pointerEvents: 'none' }}/>,
<OverlayTrigger <CustomTooltip
placement="right-start" placement="right-start"
overlay={ tooltipId="createWorkspaceTooltip"
<Tooltip id="createWorkspaceTooltip" className="text-nowrap"> tooltipClasses="text-nowrap"
<span>Download Workspace</span> tooltipText="Download Workspace"
</Tooltip>
}
> >
<div <div
data-id='workspacesDownload' data-id='workspacesDownload'
onClick={(e) => { onClick={() => {
e.stopPropagation()
downloadWorkspaces() downloadWorkspaces()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesDownload']) _paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesDownload'])
hideIconsMenu(!showIconsMenu) hideIconsMenu(!showIconsMenu)
@ -486,8 +470,7 @@ export function Workspace () {
hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE} hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE}
id='workspacesDownload' id='workspacesDownload'
data-id='workspacesDownload' data-id='workspacesDownload'
onClick={(e) => { onClick={() => {
e.stopPropagation()
downloadWorkspaces() downloadWorkspaces()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesDownload']) _paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesDownload'])
hideIconsMenu(!showIconsMenu) hideIconsMenu(!showIconsMenu)
@ -497,19 +480,16 @@ export function Workspace () {
</span> </span>
<span className="pl-3">{'Download'}</span> <span className="pl-3">{'Download'}</span>
</div> </div>
</OverlayTrigger>, </CustomTooltip>,
<OverlayTrigger <CustomTooltip
placement="right-start" placement="right-start"
overlay={ tooltipId="createWorkspaceTooltip"
<Tooltip id="createWorkspaceTooltip" className="text-nowrap"> tooltipClasses="text-nowrap"
<span>Restore Workspace Backup</span> tooltipText="Restore Workspace Backup"
</Tooltip>
}
> >
<div <div
data-id='workspacesRestore' data-id='workspacesRestore'
onClick={(e) => { onClick={() => {
e.stopPropagation()
restoreBackup() restoreBackup()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesRestore']) _paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesRestore'])
hideIconsMenu(!showIconsMenu) hideIconsMenu(!showIconsMenu)
@ -519,8 +499,7 @@ export function Workspace () {
hidden={currentWorkspace === LOCALHOST} hidden={currentWorkspace === LOCALHOST}
id='workspacesRestore' id='workspacesRestore'
data-id='workspacesRestore' data-id='workspacesRestore'
onClick={(e) => { onClick={() => {
e.stopPropagation()
restoreBackup() restoreBackup()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesRestore']) _paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesRestore'])
hideIconsMenu(!showIconsMenu) hideIconsMenu(!showIconsMenu)
@ -530,7 +509,7 @@ export function Workspace () {
</span> </span>
<span className="pl-3">{'Restore'}</span> <span className="pl-3">{'Restore'}</span>
</div> </div>
</OverlayTrigger>, </CustomTooltip>,
] ]
return ( return (
@ -546,14 +525,12 @@ export function Workspace () {
WORKSPACES WORKSPACES
</label> </label>
</span> </span>
<span className="remixui_menu remixui_topmenu d-flex justify-content-between align-items-end w-75"> {currentWorkspace !== LOCALHOST ? (<span className="remixui_menu remixui_topmenu d-flex justify-content-between align-items-end w-75">
<OverlayTrigger <CustomTooltip
placement="top-end" placement="top-end"
overlay={ tooltipId="createWorkspaceTooltip"
<Tooltip id="createWorkspaceTooltip" className="text-nowrap"> tooltipClasses="text-nowrap"
<span>Create</span> tooltipText="Create"
</Tooltip>
}
> >
<span <span
hidden={currentWorkspace === LOCALHOST} hidden={currentWorkspace === LOCALHOST}
@ -568,7 +545,7 @@ export function Workspace () {
className='far fa-plus remixui_menuicon d-flex align-self-end' className='far fa-plus remixui_menuicon d-flex align-self-end'
> >
</span> </span>
</OverlayTrigger> </CustomTooltip>
<Dropdown id="workspacesMenuDropdown" data-id="workspacesMenuDropdown" onToggle={() => hideIconsMenu(!showIconsMenu)} show={showIconsMenu}> <Dropdown id="workspacesMenuDropdown" data-id="workspacesMenuDropdown" onToggle={() => hideIconsMenu(!showIconsMenu)} show={showIconsMenu}>
<Dropdown.Toggle <Dropdown.Toggle
as={CustomIconsToggle} as={CustomIconsToggle}
@ -589,7 +566,7 @@ export function Workspace () {
} }
</Dropdown.Menu> </Dropdown.Menu>
</Dropdown> </Dropdown>
</span> </span>) : null}
</div> </div>
<Dropdown id="workspacesSelect" data-id="workspacesSelect" onToggle={toggleDropdown} show={showDropdown}> <Dropdown id="workspacesSelect" data-id="workspacesSelect" onToggle={toggleDropdown} show={showDropdown}>
@ -627,7 +604,6 @@ export function Workspace () {
</Dropdown.Item> </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> } { ((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.Menu>
</Dropdown> </Dropdown>
@ -721,52 +697,57 @@ export function Workspace () {
</div> </div>
{ {
selectedWorkspace && selectedWorkspace &&
<div className={`bg-light border-top ${selectedWorkspace.isGitRepo ? 'd-block' : 'd-none'}`}> <div className={`bg-light border-top ${selectedWorkspace.isGitRepo ? 'd-block' : 'd-none'}`} data-id="workspaceGitPanel">
<div className='d-flex justify-space-between p-1'> <div className='d-flex justify-space-between p-1'>
<div className="mr-auto text-uppercase text-dark pt-2 pl-2">GIT</div> <div className="mr-auto text-uppercase text-dark pt-2 pl-2">GIT</div>
<div className="pt-1 mr-1"> <div className="pt-1 mr-1" data-id="workspaceGitBranchesDropdown">
<Dropdown style={{ height: 30, minWidth: 80 }} onToggle={toggleBranches} show={showBranches} drop={'up'}> <Dropdown style={{ height: 30, minWidth: 80 }} onToggle={toggleBranches} show={showBranches} drop={'up'}>
<Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components" className="btn btn-light btn-block w-100 d-inline-block border border-dark form-control h-100 p-0 pl-2 pr-2 text-dark" icon={null}> <Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components" className="btn btn-light btn-block w-100 d-inline-block border border-dark form-control h-100 p-0 pl-2 pr-2 text-dark" icon={null}>
{ global.fs.browser.isRequestingCloning ? <i className="fad fa-spinner fa-spin"></i> : currentBranch || '-none-' } { global.fs.browser.isRequestingCloning ? <i className="fad fa-spinner fa-spin"></i> : currentBranch || '-none-' }
</Dropdown.Toggle> </Dropdown.Toggle>
<Dropdown.Menu as={CustomMenu} className='custom-dropdown-items branches-dropdown' data-id="custom-dropdown-items"> <Dropdown.Menu as={CustomMenu} className='custom-dropdown-items branches-dropdown'>
<div className='d-flex text-dark' style={{ fontSize: 14, fontWeight: 'bold' }}> <div data-id="custom-dropdown-menu">
<span className='mt-2 ml-2 mr-auto'>Switch branches</span> <div className='d-flex text-dark' style={{ fontSize: 14, fontWeight: 'bold' }}>
<div className='pt-2 pr-2' onClick={() => { toggleBranches(false) }}><i className='fa fa-close'></i> <span className='mt-2 ml-2 mr-auto'>Switch branches</span>
<div className='pt-2 pr-2' onClick={() => { toggleBranches(false) }}><i className='fa fa-close'></i>
</div>
</div>
<div className='border-top py-2'>
<input
className='form-control border checkout-input bg-light'
placeholder='Find or create a branch.'
style={{ minWidth: 225 }}
onChange={handleBranchFilterChange}
data-id='workspaceGitInput'
/>
</div>
<div className='border-top' style={{ maxHeight: 120, overflowY: 'scroll' }} data-id="custom-dropdown-items">
{
filteredBranches.length > 0 ? filteredBranches.map((branch, index) => {
return (
<Dropdown.Item key={index} onClick={() => { switchToBranch(branch) }} title={branch.remote ? 'Checkout new branch from remote branch' : 'Checkout to local branch'}>
<div data-id={`workspaceGit-${ branch.remote ? `${branch.remote}/${branch.name}` : branch.name }`}>
{
(currentBranch === branch.name) && !branch.remote ?
<span>&#10003; <i className='far fa-code-branch'></i><span className='pl-1'>{ branch.name }</span></span> :
<span className='pl-3'><i className={`far ${ branch.remote ? 'fa-cloud' : 'fa-code-branch'}`}></i><span className='pl-1'>{ branch.remote ? `${branch.remote}/${branch.name}` : branch.name }</span></span>
}
</div>
</Dropdown.Item>
)
}) :
<Dropdown.Item onClick={switchToNewBranch}>
<div className="pl-1 pr-1" data-id="workspaceGitCreateNewBranch">
<i className="fas fa-code-branch pr-2"></i><span>Create branch: { branchFilter } from '{currentBranch}'</span>
</div>
</Dropdown.Item>
}
</div> </div>
</div>
<div className='border-top py-2'>
<input
className='form-control border checkout-input bg-light'
placeholder='Find or create a branch.'
style={{ minWidth: 225 }}
onChange={handleBranchFilterChange}
/>
</div>
<div className='border-top' style={{ maxHeight: 120, overflowY: 'scroll' }}>
{ {
filteredBranches.length > 0 ? filteredBranches.map((branch, index) => { (selectedWorkspace.branches || []).length > 4 && <div className='text-center border-top pt-2'><a href='#' style={{ fontSize: 12 }} onClick={showAllBranches}>view all branches</a></div>
return (
<Dropdown.Item key={index} onClick={() => { switchToBranch(branch) }} title={branch.remote ? 'Checkout new branch from remote branch' : 'Checkout to local branch'}>
{
(currentBranch === branch.name) && !branch.remote ?
<span>&#10003; <i className='far fa-code-branch'></i><span className='pl-1'>{ branch.name }</span></span> :
<span className='pl-3'><i className={`far ${ branch.remote ? 'fa-cloud' : 'fa-code-branch'}`}></i><span className='pl-1'>{ branch.remote ? `${branch.remote}/${branch.name}` : branch.name }</span></span>
}
</Dropdown.Item>
)
}) :
<Dropdown.Item onClick={switchToNewBranch}>
<div className="pl-1 pr-1">
<i className="fas fa-code-branch pr-2"></i><span>Create branch: { branchFilter } from '{currentBranch}'</span>
</div>
</Dropdown.Item>
} }
</div> </div>
{
(selectedWorkspace.branches || []).length > 4 && <div className='text-center border-top pt-2'><a href='#' style={{ fontSize: 12 }} onClick={showAllBranches}>view all branches</a></div>
}
</Dropdown.Menu> </Dropdown.Menu>
</Dropdown> </Dropdown>
</div> </div>

@ -83,7 +83,7 @@ export class FoundryClient extends PluginClient {
} }
if (!this.warnlog) { if (!this.warnlog) {
// @ts-ignore // @ts-ignore
this.call('terminal', 'log', 'receiving compilation result from Foundry') this.call('terminal', 'log', { type: 'log', value: 'receiving compilation result from Foundry' })
this.warnlog = true this.warnlog = true
} }
} }
@ -167,6 +167,6 @@ export class FoundryClient extends PluginClient {
console.log('syncing from Foundry') console.log('syncing from Foundry')
this.processArtifact() this.processArtifact()
// @ts-ignore // @ts-ignore
this.call('terminal', 'log', 'synced with Foundry') this.call('terminal', 'log', { type: 'log', value: 'synced with Foundry'})
} }
} }

@ -105,14 +105,12 @@ export class HardhatClient extends PluginClient {
} }
} }
if (!this.warnLog) { if (!this.warnLog) {
// @ts-ignore this.call('terminal', 'log', { value: 'receiving compilation result from Hardhat', type: 'log'} )
this.call('terminal', 'log', 'receiving compilation result from Hardhat')
this.warnLog = true this.warnLog = true
} }
if (targetsSynced.length) { if (targetsSynced.length) {
console.log(`Processing artifacts for files: ${[...new Set(targetsSynced)].join(', ')}`) console.log(`Processing artifacts for files: ${[...new Set(targetsSynced)].join(', ')}`)
// @ts-ignore this.call('terminal', 'log', { type: 'log', value: `synced with Hardhat: ${[...new Set(targetsSynced)].join(', ')}` })
this.call('terminal', 'log', `synced with Hardhat: ${[...new Set(targetsSynced)].join(', ')}`)
} }
} }

@ -81,7 +81,7 @@ export class TruffleClient extends PluginClient {
} }
if (!this.warnLog) { if (!this.warnLog) {
// @ts-ignore // @ts-ignore
this.call('terminal', 'log', 'receiving compilation result from Truffle') this.call('terminal', 'log', { type: 'log', value: 'receiving compilation result from Truffle' })
this.warnLog = true this.warnLog = true
} }
} }
@ -141,6 +141,6 @@ export class TruffleClient extends PluginClient {
console.log('syncing from Truffle') console.log('syncing from Truffle')
this.processArtifact() this.processArtifact()
// @ts-ignore // @ts-ignore
this.call('terminal', 'log', 'synced with Truffle') this.call('terminal', 'log', { type: 'log', value: 'synced with Truffle' })
} }
} }

@ -1,79 +1,102 @@
# Release process # Release process
This document includes: This document includes the release instructions for:
- how to release the remixd - Feature freeze phase
- how to publish remix libs to NPM - Publishing `remixd` to NPM
- how to update remix.ethereum.org - Publishing remix libraries to NPM
- how to update remix-alpha.ethereum.org - Updating Remix's live version on remix.ethereum.org
- how to update remix-beta.ethereum.org - Updating Remix's alpha version on remix-alpha.ethereum.org
- how to release remix IDE - Updating Remix's beta version on remix-beta.ethereum.org
## RemixD release
- update new version number in remixd libs/remixd/package.json
- nx build remixd
- cd into ./dist/libs/remixd
- npm publish
- create bump PR to master.
## Remix libs release
(remove dist and login to npm)
- git fetch origin master
- git checkout origin/master
- git checkout -b bumpLibsVersion
- yarn run publish:libs (this command uses lerna)
- commit
## Remix IDE release Part 1. First push master to beta. Feature Freeze ## Feature Freeze
- git co -b remix_beta origin/remix_beta Once feature freeze is done, `remix_beta` should be updated latest to the master which will automatically update `remix-beta.ethereum.org` through a CI job.
- git reset --hard -master-commit-hash-
- git push -f origin remix_beta
## Testing phase - `git checkout remix_beta`
## In case of fixing bugs push PR's also to beta to include in Release - `git pull origin remix_beta`
- `git reset --hard <master-commit-hash>` (`master-commit-hash` will be latest commid id from `master` branch)
- `git push -f origin remix_beta`
## Remix IDE release Part 2. Bump the version in beta and release ## Testing
Testing is performed after the Feature Freeze on `remix-beta.ethereum.org`. `build-qa-doc.js` script can be used to generate the list of QA tasks. Instructions to use the file are given in the file itself : https://github.com/ethereum/remix-project/blob/master/build-qa-doc.js#L18 .
- git fetch origin remix_beta Once ready to run, it can be run using the Node.js: `node build-qa-doc.js`
- git checkout origin/remix_beta
- git checkout -b bumpVersion
- update package.json version
- update version in yarn.lock
- merge PR to **origin/remix_beta**
- git fetch origin remix_beta
- git checkout origin/remix_beta
- git tag v(version-number)
- git push --tags
- github-changes -o ethereum -r remix-project -a --only-pulls --use-commit-body --branch remix_beta --only-merges --between-tags previous_version...next_version
- publish a release in github using the changelog
## Remix IDE release Part 3. Bump dev branch (master) ## remixd NPM release
Once testing is completed, release will start by publishing `remixd`.if required, `remixd` can be also released individually
- git fetch origin master - Bump version for remixd in `./libs/remixd/package.json`
- git checkout origin/master - Run: `nx build remixd`
- git checkout -b bumpDevVersion - Move to build directory: `cd ./dist/libs/remixd`
- update package.json version: bump the version and add the tag `dev` if not already present. - Publish to NPM: `npm publish` (Make sure you are logged in to NPM)
- update version in yarn.lock - create bump PR to master
- create a PR and merge it to origin/master
## Remix libraries NPM release
- Make sure you are on latest `master` branch
- `git pull origin master`
- `git checkout -b bumpLibsVersion`
- `yarn run publish:libs `
This command uses `lerna` and is solely responsible for publishing all the remix libraries. It will ask for a new version of each library. Make sure you are logged in to NPM.
Once this command has been run, the versions for each remix library will be updated to latest in the libs' package.json file.
- Create and merge bump PR to master
## Remix IDE Release
### Part 1. Bump the version and update Beta
#### Make sure `remix_beta` is up-to-date with `master` branch:
- `git checkout remix_beta`
- `git pull origin remix_beta`
- `git reset --hard <master-commit-hash>`
- `git push -f origin remix_beta`
#### Create bump version PR:
- Create a new branch from `remix_beta`: `git checkout -b bumpVersion`
- Update package.json version. Usually, you need to remove `-dev` from the version string
- Create a PR with base branch as `remix_beta`
- Merge PR to **origin/remix_beta**
#### Create git tag from beta branch:
- `git checkout remix_beta`
- `git pull origin remix_beta`
- Create tag: `git tag v<version-number>`, `<version-number>` should be same as in package.json of `remix_beta` branch
- Push tag: `git push --tags`
- Generate changelog using `build-changelog.js` script as described in the script itself
- Publish a release in GitHub using the generated changelog
### Part 2. Update the Remix Live
Updating the `remix_live` branch latest to the `remix_beta` runs the CircleCI build which updates liver version of Remix IDE on `remix.ethereum.org`
- `git checkout remix_live`
- `git pull origin remix_live`
- `git reset --hard <remix_beta-commit-hash>` or `<master-commit-hash>` sometimes
- `git push -f origin remix_live`
## Remix IDE release Part 4. remix.ethereum.org update CircleCI will build automaticaly and remix.ethereum.org will be updated to the latest.
This is not strictly speaking a release. Updating the remix site is done through the Travis build: ### Part 3. Upload zip file in GitHub release
- Once CI is successful for `remix_live` branch, Go to https://github.com/ethereum/remix-live
- Download the zip file with the name starting with `remix-`
- Upload the zip file in GitHub assets by editing the release for this version
- git co -b remix_live origin/remix_live ## Bump master branch
- git reset --hard -master-commit-hash- (or remix_beta-commit-hash-)
- git push -f origin remix_live
CircleCI will build automaticaly and remix.ethereum.org will be updated - `git checkout master`
- `git pull origin master`
- Create a new branch from `master`: `git checkout -b bumpDevVersion`
- Bump the package.json version, add the tag `-dev` if not already present.
- Create and merge PR to `master`
## Remix IDE release Part 5. Update Zip in release
- after remix_live is updated, drop the zip (from https://github.com/ethereum/remix-live/) to the release.
## Remix-ide beta release ## Remix IDE Beta Release
- git fetch origin master - git fetch origin master
- git checkout origin/master - git checkout origin/master
- git checkout -b bumpVersion - git checkout -b bumpVersion
- update package.json version to the new version "vx.x.x-beta.1" - update package.json version to the new version "vx.x.x-beta.1"
- update version in yarn.lock
- merge PR - merge PR
- git fetch origin master - git fetch origin master
- git checkout origin/master - git checkout origin/master
@ -83,6 +106,6 @@ This is not strictly speaking a release. Updating the remix site is done through
- publish a beta release in github using the changelog - publish a beta release in github using the changelog
- drop zip file to the beta release (from https://github.com/ethereum/remix-live-alpha) - drop zip file to the beta release (from https://github.com/ethereum/remix-live-alpha)
## remix-alpha.ethereum.org update ## Remix IDE Alpha Release
remix-alpha.ethereum.org is automaticaly updated every time commits are pushed to master remix-alpha.ethereum.org is automaticaly updated every time a commit is pushed to `master` branch

Loading…
Cancel
Save