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
.openFile('myTokenV1.sol')
.clickLaunchIcon('solidity')
.pause(2000)
.pause(5000)
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyToken]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyToken]')
.pause(5000)
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.click('[data-id="contractGUIDeployWithProxyLabel"]')
.createContract('')
@ -105,7 +106,8 @@ module.exports = {
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) {
browser
@ -121,10 +123,12 @@ module.exports = {
.click('select.udapp_contractNames option[value=MyInitializedToken]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.click('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('input[title="tokenName"]')
.waitForElementPresent('input[title="tokenSymbol"]')
.setValue('input[title="tokenName"]', 'Remix')
.setValue('input[title="tokenSymbol"]', "R")
.useXpath()
.waitForElementPresent('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[1]/input')
.waitForElementPresent('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[2]/input')
.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('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')

@ -383,106 +383,9 @@ module.exports = {
.click('//*[@id="workspacesSelect"]')
.useCss()
.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()
},
// CLONE REPOSITORY E2E END
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
TESTFILES=$(node apps/remix-ide/ci/splice_tests.js $2 $3 | circleci tests split --split-by=timings)
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
echo "$TEST_EXITCODE"

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

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

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

@ -32,7 +32,7 @@ export class Web3ProviderModule extends Plugin {
if (error) {
const errorData = error.data ? error.data : error.message ? error.message : error
// 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)
}
if (payload.method === 'eth_sendTransaction') {

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

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

@ -14,6 +14,8 @@ if (typeof window !== 'undefined' && typeof window.ethereum !== 'undefined') {
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
*/
@ -38,12 +40,16 @@ export class ExecutionContext {
this.executionContext = 'vm'
} else {
this.executionContext = injectedProvider ? 'injected' : 'vm'
if (this.executionContext === 'injected') this.askPermission()
if (this.executionContext === 'injected') this.askPermission(false)
}
}
askPermission () {
if (ethereum && typeof ethereum.request === "function") ethereum.request({ method: "eth_requestAccounts" });
askPermission (throwIfNoInjectedProvider) {
if (typeof ethereum !== "undefined" && typeof ethereum.request === "function") {
ethereum.request({ method: "eth_requestAccounts" })
} else if (throwIfNoInjectedProvider) {
throw new Error(noInjectedProviderMsg)
}
}
getProvider () {
@ -80,7 +86,7 @@ export class ExecutionContext {
}
if (web3.currentProvider.isConnected && !web3.currentProvider.isConnected()) {
if (web3.currentProvider.isMetaMask) {
this.askPermission()
this.askPermission(false)
}
return callback('Provider not connected')
}
@ -152,13 +158,18 @@ export class ExecutionContext {
if (context === 'injected') {
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()
} else {
if (injectedProvider && injectedProvider._metamask && injectedProvider._metamask.isUnlocked) {
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
web3.setProvider(injectedProvider)
await this._updateChainContext()
@ -176,7 +187,12 @@ export class ExecutionContext {
})
} else {
// injected
this.askPermission()
try {
this.askPermission(true)
} catch (e) {
infoCb(e.message)
return cb()
}
this.executionContext = context
web3.setProvider(network.provider)
await this._updateChainContext()

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

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

@ -1,5 +1,5 @@
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 { AppModal } from './lib/remix-app/interface/index'
export { AlertModal } from './lib/remix-app/interface/index'

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

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

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

@ -33,7 +33,7 @@ export interface AlertModal {
export interface ModalState {
modals: AppModal[],
toasters: (string | JSX.Element)[],
toasters: {message: (string | JSX.Element), timestamp: number }[],
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: {
const toasterList = state.toasters.slice()
const message = action.payload
toasterList.push(message)
toasterList.push(action.payload)
if (toasterList.length === 1) {
return { ...state, toasters: toasterList, focusToaster: action.payload }
} else {

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

@ -1,10 +1,12 @@
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import Editor, { loader, Monaco } from '@monaco-editor/react'
import { AlertModal } from '@remix-ui/app'
import { reducerActions, reducerListener, initialState } from './actions/editor'
import { solidityTokensProvider, solidityLanguageConfig } from './syntaxes/solidity'
import { cairoTokensProvider, cairoLanguageConfig } from './syntaxes/cairo'
import { zokratesTokensProvider, zokratesLanguageConfig } from './syntaxes/zokrates'
import { moveTokenProvider, moveLanguageConfig } from './syntaxes/move'
import './remix-ui-editor.css'
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\tTwitter: https://twitter.com/ethereumremix\n
`
const pasteCodeRef = useRef(false)
const editorRef = useRef(null)
const monacoRef = useRef<Monaco>(null)
const currentFileRef = useRef('')
@ -301,6 +304,8 @@ export const EditorUI = (props: EditorUIProps) => {
monacoRef.current.editor.setModelLanguage(file.model, 'remix-cairo')
} else if (file.language === 'zokrates') {
monacoRef.current.editor.setModelLanguage(file.model, 'remix-zokrates')
} else if (file.language === 'move') {
monacoRef.current.editor.setModelLanguage(file.model, 'remix-move')
}
}, [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
editor.addCommand(monacoRef.current.KeyMod.CtrlCmd | (monacoRef.current.KeyCode as any).US_EQUAL, () => {
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-cairo' })
monacoRef.current.languages.register({ id: 'remix-zokrates' })
monacoRef.current.languages.register({ id: 'remix-move' })
// Register a tokens provider for the language
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.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.registerDocumentHighlightProvider('remix-solidity', new RemixHighLightProvider(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', 'setWorkspace', templateName + "_" + timeStamp)
plugin.verticalIcons.select('filePanel')
_paq.push(['trackEvent', 'homeGetStarted', templateName])
_paq.push(['trackEvent', 'homeTab', 'homeGetStarted', templateName])
}
return (

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

@ -6,6 +6,7 @@ import { DeployMode, MainnetPrompt } from "../types"
import { displayNotification, displayPopUp, setDecodedResponse } from "./payload"
import { addInstance } from "./actions"
import { addressToString, logBuilder } from "@remix-ui/helper"
import Web3 from "web3"
declare global {
interface Window {
@ -26,11 +27,11 @@ const loadContractFromAddress = (plugin: RunTab, address, confirmCb, cb) => {
} catch (e) {
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)
})
} else {
_paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithArtifacts'])
_paq.push(['trackEvent', 'udapp', 'useAtAddress', 'AtAddressLoadWithArtifacts'])
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,
setNetworkNameFromProvider, setPassphrasePrompt, setSelectedContract, setSendTransactionValue, setUnit,
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 { ContractData, FuncABI } from "@remix-project/core-plugin"
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 updateSelectedContract = (contractName) => setSelectedContract(dispatch, contractName)
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 align-items-end">
<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>
{props.remixdActivated ?
(<CustomTooltip
@ -267,7 +267,7 @@ export function ContractDropdownUI(props: ContractDropdownProps) {
>
<button className="btn d-flex py-0" onClick={_ => {
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>
</button>
@ -307,6 +307,7 @@ export function ContractDropdownUI(props: ContractDropdownProps) {
evmBC={loadedContractData.bytecodeObject}
lookupOnly={false}
savedProxyAddress={proxyKey}
isValidProxyAddress={props.isValidProxyAddress}
/>
<div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input

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

@ -138,7 +138,10 @@ export const runTabInitialState: RunTabState = {
dataId: 'settingsInjectedMode',
title: 'Execution environment has been provided by Metamask or similar provider.',
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,
isSuccessful: false,

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

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

@ -472,10 +472,8 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
compileIcon.current.classList.remove('remixui_spinningIcon')
compileIcon.current.classList.remove('remixui_bouncingIcon')
if (!state.autoCompile || (state.autoCompile && state.matomoAutocompileOnce)) {
if (state.useFileConfiguration)
_paq.push(['trackEvent', 'compiler', 'compiled_with_config_file'])
_paq.push(['trackEvent', 'compiler', 'compiled_with_version', _retrieveVersion()])
_paq.push(['trackEvent', 'compiler', 'compiled', 'with_config_file_' + state.useFileConfiguration])
_paq.push(['trackEvent', 'compiler', 'compiled', 'with_version_' + _retrieveVersion()])
if (state.autoCompile && state.matomoAutocompileOnce) {
setState(prevState => {
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 className="px-4">

@ -155,9 +155,9 @@ export class CompileTabLogic {
`
const configFilePath = 'remix-compiler.config.js'
this.api.writeFile(configFilePath, fileContent)
_paq.push(['trackEvent', 'compiler', 'compileWithHardhat'])
_paq.push(['trackEvent', 'compiler', 'runCompile', 'compileWithHardhat'])
this.api.compileWithHardhat(configFilePath).then((result) => {
this.api.logToTerminal({ type: 'info', value: result })
this.api.logToTerminal({ type: 'log', value: result })
}).catch((error) => {
this.api.logToTerminal({ type: 'error', value: error })
})
@ -181,9 +181,9 @@ export class CompileTabLogic {
}`
const configFilePath = 'remix-compiler.config.js'
this.api.writeFile(configFilePath, fileContent)
_paq.push(['trackEvent', 'compiler', 'compileWithTruffle'])
_paq.push(['trackEvent', 'compiler', 'runCompile', 'compileWithTruffle'])
this.api.compileWithTruffle(configFilePath).then((result) => {
this.api.logToTerminal({ type: 'info', value: result })
this.api.logToTerminal({ type: 'log', value: result })
}).catch((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'
}
_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 () => {
@ -586,7 +586,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
const tests: string[] = selectedTests.current
if (!tests || !tests.length) return
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
if (hasBeenStopped.current) return
runTest(value, callback)

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

@ -1,8 +1,8 @@
import { fileDecoration, FileDecorationIcons } from '@remix-ui/file-decorators'
import { CustomTooltip } from '@remix-ui/helper'
import { Plugin } from '@remixproject/engine'
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 './remix-ui-tabs.css'
const _paq = window._paq = window._paq || []
@ -160,16 +160,16 @@ export const TabsUI = (props: TabsUIProps) => {
}
}}
>
<OverlayTrigger placement="bottom" overlay={
<Tooltip id="overlay-tooltip-run-script">
<span>
<CustomTooltip
placement="bottom"
tooltipId="overlay-tooltip-run-script"
tooltipText={<span>
{(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"}
</span>
</Tooltip>
}>
</span>}
>
<i className="fad fa-play"></i>
</OverlayTrigger>
</CustomTooltip>
</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="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) => {
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 {
message: string | JSX.Element
timeOut?: number,
handleHide?: () => void
handleHide?: () => void,
timestamp?: number
}
export const Toaster = (props: ToasterProps) => {
@ -49,7 +50,7 @@ export const Toaster = (props: ToasterProps) => {
}
})
}
}, [props.message])
}, [props.message, props.timestamp])
useEffect(() => {
if (state.hiding) {

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

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

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

@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef, useContext, SyntheticEvent, ChangeEvent, KeyboardEvent } from 'react' // eslint-disable-line
import { Dropdown, OverlayTrigger, Tooltip } from 'react-bootstrap'
import { CustomIconsToggle, CustomMenu, CustomToggle } from '@remix-ui/helper'
import { Dropdown } from 'react-bootstrap'
import { CustomIconsToggle, CustomMenu, CustomToggle, CustomTooltip } from '@remix-ui/helper'
import { FileExplorer } from './components/file-explorer' // eslint-disable-line
import { FileSystemContext } from './contexts'
import './css/remix-ui-workspace.css'
@ -221,8 +221,10 @@ export function Workspace () {
try {
if (branch.remote) {
await global.dispatchCheckoutRemoteBranch(branch.name, branch.remote)
_paq.push(['trackEvent', 'Workspace', 'GIT', 'checkout_remote_branch'])
} else {
await global.dispatchSwitchToBranch(branch.name)
_paq.push(['trackEvent', 'Workspace', 'GIT', 'switch_to_exisiting_branch'])
}
} catch (e) {
console.error(e)
@ -233,6 +235,7 @@ export function Workspace () {
const switchToNewBranch = async () => {
try {
await global.dispatchCreateNewBranch(branchFilter)
_paq.push(['trackEvent', 'Workspace', 'GIT', 'switch_to_new_branch'])
} catch (e) {
global.modal('Checkout Git Branch', e.message, 'OK', () => {})
}
@ -333,18 +336,15 @@ export function Workspace () {
}
const workspaceMenuIcons = [
<OverlayTrigger
<CustomTooltip
placement="right"
overlay={
<Tooltip id="createWorkspaceTooltip" className="text-nowrap">
<span>Create</span>
</Tooltip>
}
tooltipId="createWorkspaceTooltip"
tooltipClasses="text-nowrap"
tooltipText="Create"
>
<div
data-id='workspaceCreate'
onClick={(e) => {
e.stopPropagation()
onClick={() => {
createWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceCreate'])
hideIconsMenu(!showIconsMenu)
@ -354,8 +354,7 @@ export function Workspace () {
hidden={currentWorkspace === LOCALHOST}
id='workspaceCreate'
data-id='workspaceCreate'
onClick={(e) => {
e.stopPropagation()
onClick={() => {
createWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceCreate'])
hideIconsMenu(!showIconsMenu)
@ -365,19 +364,16 @@ export function Workspace () {
</span>
<span className="pl-3">Create</span>
</div>
</OverlayTrigger>,
<OverlayTrigger
</CustomTooltip>,
<CustomTooltip
placement="right-start"
overlay={
<Tooltip id="createWorkspaceTooltip" className="text-nowrap">
<span>Delete Workspace</span>
</Tooltip>
}
tooltipId="createWorkspaceTooltip"
tooltipClasses="text-nowrap"
tooltipText="Delete Workspace"
>
<div
data-id='workspaceDelete'
onClick={(e) => {
e.stopPropagation()
onClick={() => {
deleteCurrentWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceDelete'])
hideIconsMenu(!showIconsMenu)
@ -387,8 +383,7 @@ export function Workspace () {
hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE}
id='workspaceDelete'
data-id='workspaceDelete'
onClick={(e) => {
e.stopPropagation()
onClick={() => {
deleteCurrentWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceDelete'])
hideIconsMenu(!showIconsMenu)
@ -398,17 +393,14 @@ export function Workspace () {
</span>
<span className="pl-3">{'Delete'}</span>
</div>
</OverlayTrigger>,
<OverlayTrigger
</CustomTooltip>,
<CustomTooltip
placement='right-start'
overlay={
<Tooltip id="workspaceRenametooltip">
<span>Rename Workspace</span>
</Tooltip>
}
tooltipClasses="text-nowrap"
tooltipId="workspaceRenametooltip"
tooltipText="Rename Workspace"
>
<div onClick={(e) => {
e.stopPropagation()
<div onClick={() => {
renameCurrentWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceRename'])
hideIconsMenu(!showIconsMenu)
@ -419,8 +411,7 @@ export function Workspace () {
hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE}
id='workspaceRename'
data-id='workspaceRename'
onClick={(e) => {
e.stopPropagation()
onClick={() => {
renameCurrentWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceRename'])
hideIconsMenu(!showIconsMenu)
@ -429,20 +420,17 @@ export function Workspace () {
</span>
<span className="pl-3">{'Rename'}</span>
</div>
</OverlayTrigger>,
</CustomTooltip>,
<Dropdown.Divider className="border mb-0 mt-0" />,
<OverlayTrigger
<CustomTooltip
placement="right-start"
overlay={
<Tooltip id="cloneWorkspaceTooltip" className="text-nowrap">
<span>Clone Git Repository</span>
</Tooltip>
}
tooltipId="cloneWorkspaceTooltip"
tooltipClasses="text-nowrap"
tooltipText="Clone Git Repository"
>
<div
data-id='cloneGitRepository'
onClick={(e) => {
e.stopPropagation()
onClick={() => {
cloneGitRepository()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'cloneGitRepository'])
hideIconsMenu(!showIconsMenu)
@ -452,8 +440,7 @@ export function Workspace () {
hidden={currentWorkspace === LOCALHOST}
id='cloneGitRepository'
data-id='cloneGitRepository'
onClick={(e) => {
e.stopPropagation()
onClick={() => {
cloneGitRepository()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'cloneGitRepository'])
hideIconsMenu(!showIconsMenu)
@ -463,20 +450,17 @@ export function Workspace () {
</span>
<span className="pl-3">{'Clone'}</span>
</div>
</OverlayTrigger>,
</CustomTooltip>,
<Dropdown.Divider className="border mt-0 mb-0 remixui_menuhr" style={{ pointerEvents: 'none' }}/>,
<OverlayTrigger
<CustomTooltip
placement="right-start"
overlay={
<Tooltip id="createWorkspaceTooltip" className="text-nowrap">
<span>Download Workspace</span>
</Tooltip>
}
tooltipId="createWorkspaceTooltip"
tooltipClasses="text-nowrap"
tooltipText="Download Workspace"
>
<div
data-id='workspacesDownload'
onClick={(e) => {
e.stopPropagation()
onClick={() => {
downloadWorkspaces()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesDownload'])
hideIconsMenu(!showIconsMenu)
@ -486,8 +470,7 @@ export function Workspace () {
hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE}
id='workspacesDownload'
data-id='workspacesDownload'
onClick={(e) => {
e.stopPropagation()
onClick={() => {
downloadWorkspaces()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesDownload'])
hideIconsMenu(!showIconsMenu)
@ -497,19 +480,16 @@ export function Workspace () {
</span>
<span className="pl-3">{'Download'}</span>
</div>
</OverlayTrigger>,
<OverlayTrigger
</CustomTooltip>,
<CustomTooltip
placement="right-start"
overlay={
<Tooltip id="createWorkspaceTooltip" className="text-nowrap">
<span>Restore Workspace Backup</span>
</Tooltip>
}
tooltipId="createWorkspaceTooltip"
tooltipClasses="text-nowrap"
tooltipText="Restore Workspace Backup"
>
<div
data-id='workspacesRestore'
onClick={(e) => {
e.stopPropagation()
onClick={() => {
restoreBackup()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesRestore'])
hideIconsMenu(!showIconsMenu)
@ -519,8 +499,7 @@ export function Workspace () {
hidden={currentWorkspace === LOCALHOST}
id='workspacesRestore'
data-id='workspacesRestore'
onClick={(e) => {
e.stopPropagation()
onClick={() => {
restoreBackup()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesRestore'])
hideIconsMenu(!showIconsMenu)
@ -530,7 +509,7 @@ export function Workspace () {
</span>
<span className="pl-3">{'Restore'}</span>
</div>
</OverlayTrigger>,
</CustomTooltip>,
]
return (
@ -546,14 +525,12 @@ export function Workspace () {
WORKSPACES
</label>
</span>
<span className="remixui_menu remixui_topmenu d-flex justify-content-between align-items-end w-75">
<OverlayTrigger
{currentWorkspace !== LOCALHOST ? (<span className="remixui_menu remixui_topmenu d-flex justify-content-between align-items-end w-75">
<CustomTooltip
placement="top-end"
overlay={
<Tooltip id="createWorkspaceTooltip" className="text-nowrap">
<span>Create</span>
</Tooltip>
}
tooltipId="createWorkspaceTooltip"
tooltipClasses="text-nowrap"
tooltipText="Create"
>
<span
hidden={currentWorkspace === LOCALHOST}
@ -568,7 +545,7 @@ export function Workspace () {
className='far fa-plus remixui_menuicon d-flex align-self-end'
>
</span>
</OverlayTrigger>
</CustomTooltip>
<Dropdown id="workspacesMenuDropdown" data-id="workspacesMenuDropdown" onToggle={() => hideIconsMenu(!showIconsMenu)} show={showIconsMenu}>
<Dropdown.Toggle
as={CustomIconsToggle}
@ -589,7 +566,7 @@ export function Workspace () {
}
</Dropdown.Menu>
</Dropdown>
</span>
</span>) : null}
</div>
<Dropdown id="workspacesSelect" data-id="workspacesSelect" onToggle={toggleDropdown} show={showDropdown}>
@ -627,7 +604,6 @@ export function Workspace () {
</Dropdown.Item>
))
}
<Dropdown.Item onClick={() => { switchWorkspace(LOCALHOST) }}>{currentWorkspace === LOCALHOST ? <span>&#10003; localhost </span> : <span className="pl-3"> { LOCALHOST } </span>}</Dropdown.Item>
{ ((global.fs.browser.workspaces.length <= 0) || currentWorkspace === NO_WORKSPACE) && <Dropdown.Item onClick={() => { switchWorkspace(NO_WORKSPACE) }}>{ <span className="pl-3">NO_WORKSPACE</span> }</Dropdown.Item> }
</Dropdown.Menu>
</Dropdown>
@ -721,52 +697,57 @@ export function Workspace () {
</div>
{
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="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.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-' }
</Dropdown.Toggle>
<Dropdown.Menu as={CustomMenu} className='custom-dropdown-items branches-dropdown' data-id="custom-dropdown-items">
<div className='d-flex text-dark' style={{ fontSize: 14, fontWeight: 'bold' }}>
<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>
<Dropdown.Menu as={CustomMenu} className='custom-dropdown-items branches-dropdown'>
<div data-id="custom-dropdown-menu">
<div className='d-flex text-dark' style={{ fontSize: 14, fontWeight: 'bold' }}>
<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 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) => {
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>
(selectedWorkspace.branches || []).length > 4 && <div className='text-center border-top pt-2'><a href='#' style={{ fontSize: 12 }} onClick={showAllBranches}>view all branches</a></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>
</div>

@ -83,7 +83,7 @@ export class FoundryClient extends PluginClient {
}
if (!this.warnlog) {
// @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
}
}
@ -167,6 +167,6 @@ export class FoundryClient extends PluginClient {
console.log('syncing from Foundry')
this.processArtifact()
// @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) {
// @ts-ignore
this.call('terminal', 'log', 'receiving compilation result from Hardhat')
this.call('terminal', 'log', { value: 'receiving compilation result from Hardhat', type: 'log'} )
this.warnLog = true
}
if (targetsSynced.length) {
console.log(`Processing artifacts for files: ${[...new Set(targetsSynced)].join(', ')}`)
// @ts-ignore
this.call('terminal', 'log', `synced with Hardhat: ${[...new Set(targetsSynced)].join(', ')}`)
this.call('terminal', 'log', { type: 'log', value: `synced with Hardhat: ${[...new Set(targetsSynced)].join(', ')}` })
}
}

@ -81,7 +81,7 @@ export class TruffleClient extends PluginClient {
}
if (!this.warnLog) {
// @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
}
}
@ -141,6 +141,6 @@ export class TruffleClient extends PluginClient {
console.log('syncing from Truffle')
this.processArtifact()
// @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
This document includes:
- how to release the remixd
- how to publish remix libs to NPM
- how to update remix.ethereum.org
- how to update remix-alpha.ethereum.org
- how to update remix-beta.ethereum.org
- how to release remix IDE
## 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
This document includes the release instructions for:
- Feature freeze phase
- Publishing `remixd` to NPM
- Publishing remix libraries to NPM
- Updating Remix's live version on remix.ethereum.org
- Updating Remix's alpha version on remix-alpha.ethereum.org
- Updating Remix's beta version on remix-beta.ethereum.org
## Remix IDE release Part 1. First push master to beta. Feature Freeze
- git co -b remix_beta origin/remix_beta
- git reset --hard -master-commit-hash-
- git push -f origin remix_beta
## Feature Freeze
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.
## Testing phase
## In case of fixing bugs push PR's also to beta to include in Release
- `git checkout remix_beta`
- `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
- 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
Once ready to run, it can be run using the Node.js: `node build-qa-doc.js`
## 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
- git checkout origin/master
- git checkout -b bumpDevVersion
- update package.json version: bump the version and add the tag `dev` if not already present.
- update version in yarn.lock
- create a PR and merge it to origin/master
- Bump version for remixd in `./libs/remixd/package.json`
- Run: `nx build remixd`
- Move to build directory: `cd ./dist/libs/remixd`
- Publish to NPM: `npm publish` (Make sure you are logged in to NPM)
- create bump PR to 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
- git reset --hard -master-commit-hash- (or remix_beta-commit-hash-)
- git push -f origin remix_live
## Bump master branch
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 checkout origin/master
- git checkout -b bumpVersion
- update package.json version to the new version "vx.x.x-beta.1"
- update version in yarn.lock
- merge PR
- git fetch 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
- 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