pull/4877/head
Your Name 4 months ago
commit 9f7c1c6d76
  1. 32
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 20
      .github/ISSUE_TEMPLATE/feature_request.md
  3. 1
      README.md
  4. 24
      apps/circuit-compiler/src/app/services/circomPluginClient.ts
  5. 2
      apps/remix-ide-e2e/nightwatch-chrome.ts
  6. 2
      apps/remix-ide-e2e/nightwatch-firefox.ts
  7. 2
      apps/remix-ide-e2e/src/commands/addFileSnekmate.ts
  8. 121
      apps/remix-ide-e2e/src/tests/fileExplorer.test.ts
  9. 44
      apps/remix-ide-e2e/src/tests/file_explorer_context_menu.test.ts
  10. 3
      apps/remix-ide-e2e/src/tests/file_explorer_dragdrop.test.ts
  11. 47
      apps/remix-ide-e2e/src/tests/homeTab.test.ts
  12. 314
      apps/remix-ide-e2e/src/tests/remixd.test.ts
  13. 51
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  14. 3
      apps/remix-ide-e2e/src/tests/url.test.ts
  15. 71
      apps/remix-ide-e2e/src/tests/vyper_api.test.ts
  16. 6
      apps/remix-ide-e2e/src/tests/workspace_git.test.ts
  17. 3
      apps/remix-ide/contracts/ballot.sol
  18. 398
      apps/remix-ide/contracts/foundry/cache/solidity-files-cache.json
  19. 6
      apps/remix-ide/contracts/foundry/foundry.toml
  20. 385
      apps/remix-ide/contracts/foundry/out/Counter.s.sol/CounterScript.json
  21. 377
      apps/remix-ide/contracts/foundry/out/Counter.sol/Counter.json
  22. 1865
      apps/remix-ide/contracts/foundry/out/Counter.t.sol/CounterTest.json
  23. 3729
      apps/remix-ide/contracts/foundry/out/Script.sol/Script.json
  24. 38186
      apps/remix-ide/contracts/foundry/out/Test.sol/Test.json
  25. 37620
      apps/remix-ide/contracts/foundry/out/Test.sol/stdError.json
  26. 37347
      apps/remix-ide/contracts/foundry/out/Test.sol/stdMath.json
  27. 37499
      apps/remix-ide/contracts/foundry/out/Test.sol/stdStorage.json
  28. 10800
      apps/remix-ide/contracts/foundry/out/Vm.sol/Vm.json
  29. 110867
      apps/remix-ide/contracts/foundry/out/console.sol/console.json
  30. 110866
      apps/remix-ide/contracts/foundry/out/console2.sol/console2.json
  31. 23713
      apps/remix-ide/contracts/foundry/out/test.sol/DSTest.json
  32. 14
      apps/remix-ide/contracts/foundry/src/Counter.sol
  33. 10
      apps/remix-ide/src/app.js
  34. 1
      apps/remix-ide/src/app/components/preload.tsx
  35. 2
      apps/remix-ide/src/app/components/side-panel.tsx
  36. 105
      apps/remix-ide/src/app/components/status-bar.tsx
  37. 1
      apps/remix-ide/src/app/panels/file-panel.js
  38. 9
      apps/remix-ide/src/app/panels/tab-proxy.js
  39. 1
      apps/remix-ide/src/app/plugins/compile-details.tsx
  40. 266
      apps/remix-ide/src/app/plugins/remixGuide.tsx
  41. 47
      apps/remix-ide/src/app/plugins/remixGuideData.json
  42. 12
      apps/remix-ide/src/app/plugins/solcoderAI.tsx
  43. 2
      apps/remix-ide/src/app/tabs/locales/en/editor.json
  44. 11
      apps/remix-ide/src/app/tabs/locales/en/home.json
  45. 13
      apps/remix-ide/src/app/tabs/locales/en/solidity.json
  46. 9
      apps/remix-ide/src/app/tabs/locales/en/udapp.json
  47. 1
      apps/remix-ide/src/app/tabs/settings-tab.tsx
  48. BIN
      apps/remix-ide/src/assets/img/solidityScanLogo.webp
  49. BIN
      apps/remix-ide/src/assets/img/staticAnalysisColorBlue.webp
  50. BIN
      apps/remix-ide/src/assets/img/swarmColor.webp
  51. 6
      apps/remix-ide/src/remixAppManager.js
  52. 19
      apps/remix-ide/src/types/index.d.ts
  53. 2
      apps/remix-ide/webpack.config.js
  54. 31
      apps/vyper/src/app/app.tsx
  55. 23
      apps/vyper/src/app/utils/compiler.tsx
  56. 19
      apps/vyper/src/app/utils/remix-client.tsx
  57. 56
      build-qa-doc.js
  58. 59
      libs/remix-simulator/src/methods/accounts.ts
  59. 12
      libs/remix-simulator/src/provider.ts
  60. 36
      libs/remix-simulator/test/accounts.ts
  61. 13
      libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx
  62. 5
      libs/remix-ui/app/src/lib/remix-app/remix-app.tsx
  63. 6
      libs/remix-ui/app/src/lib/remix-app/style/remix-app.css
  64. 37
      libs/remix-ui/editor/src/lib/providers/documentationProvider.ts
  65. 65
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  66. 9
      libs/remix-ui/grid-view/src/lib/components/customCheckbox.tsx
  67. 4
      libs/remix-ui/grid-view/src/lib/filtersContext.tsx
  68. 6
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.css
  69. 116
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.tsx
  70. 5
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.tsx
  71. 137
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.tsx
  72. 27
      libs/remix-ui/home-tab/src/lib/components/homeTabFeatured.tsx
  73. 2
      libs/remix-ui/home-tab/src/lib/components/homeTabFeaturedPlugins.tsx
  74. 173
      libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx
  75. 183
      libs/remix-ui/home-tab/src/lib/components/homeTabGetStarted.tsx
  76. 156
      libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx
  77. 6
      libs/remix-ui/home-tab/src/lib/components/homeTablangOptions.tsx
  78. 33
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.css
  79. 31
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx
  80. 4
      libs/remix-ui/panel/src/lib/plugins/panel.css
  81. 20
      libs/remix-ui/panel/src/lib/types/index.ts
  82. 11
      libs/remix-ui/run-tab/src/lib/actions/account.ts
  83. 4
      libs/remix-ui/run-tab/src/lib/actions/deploy.ts
  84. 46
      libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx
  85. 10
      libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx
  86. 6
      libs/remix-ui/run-tab/src/lib/components/environment.tsx
  87. 2
      libs/remix-ui/run-tab/src/lib/components/gasLimit.tsx
  88. 6
      libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx
  89. 6
      libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx
  90. 122
      libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx
  91. 3
      libs/remix-ui/run-tab/src/lib/css/run-tab.css
  92. 23
      libs/remix-ui/run-tab/src/lib/run-tab.tsx
  93. 14
      libs/remix-ui/run-tab/src/lib/types/index.ts
  94. 23
      libs/remix-ui/solidity-compile-details/src/lib/components/solidityCompile.tsx
  95. 2
      libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx
  96. 261
      libs/remix-ui/solidity-compiler/src/lib/contract-selection.tsx
  97. 2
      libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts
  98. 18
      libs/remix-ui/solidity-compiler/src/lib/solScanTable.tsx
  99. 2
      libs/remix-ui/static-analyser/src/lib/Button/StaticAnalyserButton.tsx
  100. 23
      libs/remix-ui/statusbar/src/css/statusbar.css
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,32 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Windows, Linux or MacOS]
- Browser [e.g. chrome, firefox]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

@ -32,7 +32,6 @@
![Remix screenshot](https://github.com/ethereum/remix-project/raw/master/apps/remix-ide/remix-screenshot-400h.png)
**VSCode extension**, see: [Ethereum-Remix](https://marketplace.visualstudio.com/items?itemName=RemixProject.ethereum-remix)
## Remix libraries
Remix libraries are essential for Remix IDE's native plugins. Read more about libraries [here](libs/README.md)

@ -10,6 +10,8 @@ import * as compilerV215 from 'circom_wasm/v2.1.5'
import { extractNameFromKey, extractParentFromKey } from '@remix-ui/helper'
import { CompilationConfig, CompilerReport, PrimeValue, ResolverOutput } from '../types'
// @ts-ignore
const _paq = (window._paq = window._paq || [])
export class CircomPluginClient extends PluginClient {
public internalEvents: EventManager
private _compilationConfig: CompilationConfig = {
@ -119,6 +121,7 @@ export class CircomPluginClient extends PluginClient {
} else {
// @ts-ignore
await this.call('editor', 'clearErrorMarkers', [path])
this.emit('statusChanged', { key: 'none' })
}
}
@ -130,6 +133,7 @@ export class CircomPluginClient extends PluginClient {
async compile(path: string, compilationConfig?: CompilationConfig): Promise<void> {
this.internalEvents.emit('circuit_compiling_start')
this.emit('statusChanged', { key: 'loading', title: 'Compiling...', type: 'info' })
// @ts-ignore
this.call('terminal', 'log', { type: 'log', value: 'Compiling ' + path })
const [parseErrors, filePathToId] = await this.parse(path)
@ -145,6 +149,7 @@ export class CircomPluginClient extends PluginClient {
}
} else {
this.internalEvents.emit('circuit_parsing_done', parseErrors, filePathToId)
this.emit('statusChanged', { key: 'succeed', title: 'circuit compiled successfully', type: 'success' })
}
if (compilationConfig) {
const { prime, version } = compilationConfig
@ -159,6 +164,7 @@ export class CircomPluginClient extends PluginClient {
const circuitErrors = circuitApi.report()
this.logCompilerReport(circuitErrors)
_paq.push(['trackEvent', 'circuit-compiler', 'compile', 'Compilation failed'])
throw new Error(circuitErrors)
} else {
this.lastCompiledFile = path
@ -178,6 +184,7 @@ export class CircomPluginClient extends PluginClient {
} else {
this.internalEvents.emit('circuit_compiling_done', [])
}
_paq.push(['trackEvent', 'circuit-compiler', 'compile', 'Compilation successful'])
circuitApi.log().map(log => {
log && this.call('terminal', 'log', { type: 'log', value: log })
})
@ -188,6 +195,7 @@ export class CircomPluginClient extends PluginClient {
async generateR1cs (path: string, compilationConfig?: CompilationConfig): Promise<void> {
this.internalEvents.emit('circuit_generating_r1cs_start')
this.emit('statusChanged', { key: 'loading', title: 'Generating...', type: 'info' })
// @ts-ignore
this.call('terminal', 'log', { type: 'log', value: 'Generating R1CS for ' + path })
const [parseErrors, filePathToId] = await this.parse(path)
@ -203,6 +211,7 @@ export class CircomPluginClient extends PluginClient {
}
} else {
this.internalEvents.emit('circuit_parsing_done', parseErrors, filePathToId)
this.emit('statusChanged', { key: 'succeed', title: 'r1cs generated successfully', type: 'success' })
}
if (compilationConfig) {
const { prime, version } = compilationConfig
@ -217,6 +226,7 @@ export class CircomPluginClient extends PluginClient {
const r1csErrors = r1csApi.report()
this.logCompilerReport(r1csErrors)
_paq.push(['trackEvent', 'circuit-compiler', 'generateR1cs', 'R1CS Generation failed'])
throw new Error(r1csErrors)
} else {
this.internalEvents.emit('circuit_generating_r1cs_done')
@ -225,6 +235,7 @@ export class CircomPluginClient extends PluginClient {
// @ts-ignore
await this.call('fileManager', 'writeFile', writePath, r1csProgram, true)
_paq.push(['trackEvent', 'circuit-compiler', 'generateR1cs', 'R1CS Generation successful'])
r1csApi.log().map(log => {
log && this.call('terminal', 'log', { type: 'log', value: log })
})
@ -235,6 +246,7 @@ export class CircomPluginClient extends PluginClient {
async computeWitness (input: string): Promise<void> {
this.internalEvents.emit('circuit_computing_witness_start')
this.emit('statusChanged', { key: 'loading', title: 'Computing...', type: 'info' })
const wasmPath = this.lastCompiledCircuitPath
if (!wasmPath) throw new Error('No wasm file found')
@ -244,7 +256,9 @@ export class CircomPluginClient extends PluginClient {
const witness = this.compiler ? await this.compiler.generate_witness(dataRead, input) : await generate_witness(dataRead, input)
// @ts-ignore
await this.call('fileManager', 'writeFile', wasmPath.replace('.wasm', '.wtn'), witness, true)
_paq.push(['trackEvent', 'circuit-compiler', 'computeWitness', 'Witness computing successful'])
this.internalEvents.emit('circuit_computing_witness_done')
this.emit('statusChanged', { key: 'succeed', title: 'witness computed successfully', type: 'success' })
}
async resolveDependencies(filePath: string, fileContent: string, output?: Record<string, string>, depPath: string = '', blackPath: string[] = []): Promise<Record<string, string>> {
@ -403,7 +417,13 @@ export class CircomPluginClient extends PluginClient {
async logCompilerReport (report: CompilerReport[]): Promise<void> {
this.call('terminal', 'log', { type: 'log', value: JSON.stringify(report, null, 2) })
if (report[0].type === 'Error') this.call('terminal', 'log', { type: 'error', value: 'previous errors were found' })
if (report[0].type === 'Warning') this.call('terminal', 'log', { type: 'log', value: 'previous warnings were found' })
if (report[0].type === 'Error') {
this.call('terminal', 'log', { type: 'error', value: 'previous errors were found' })
this.emit('statusChanged', { key: report.length, title: `You have ${report.length} problem${report.length === 1 ? '' : 's'}`, type: 'error' })
}
if (report[0].type === 'Warning') {
this.call('terminal', 'log', { type: 'log', value: 'previous warnings were found' })
this.emit('statusChanged', { key: report.length, title: `You have ${report.length} problem${report.length === 1 ? '' : 's'}`, type: 'warning' })
}
}
}

@ -21,7 +21,7 @@ module.exports = {
'default': {
globals: {
waitForConditionTimeout: 10000,
asyncHookTimeout: 100000
asyncHookTimeout: 10000000
},
screenshots: {
enabled: true,

@ -18,7 +18,7 @@ module.exports = {
'default': {
globals: {
waitForConditionTimeout: 10000,
asyncHookTimeout: 100000
asyncHookTimeout: 10000000
},
screenshots: {
enabled: true,

@ -60,7 +60,7 @@ function addFileSnekmate(browser: NightwatchBrowser, name: string, content: Nigh
})
.setEditorValue(content.content)
.getEditorValue((result) => {
if(result != content.content) {
if (result != content.content) {
browser.setEditorValue(content.content)
}
})

@ -117,69 +117,68 @@ module.exports = {
'Should add deep tree with buttons #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('div[data-id="remixIdeSidePanel"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="filePanelFileExplorerTree"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep1')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep1"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep2')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep1/deep2"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep3')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep1/deep2/deep3"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFile"]')
.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep4.sol')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep1/deep2/deep3/deep4.sol"]')
.waitForElementVisible('div[data-id="remixIdeSidePanel"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="filePanelFileExplorerTree"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep1')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep1"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep2')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep1/deep2"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep3')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep1/deep2/deep3"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFile"]')
.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep4.sol')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep1/deep2/deep3/deep4.sol"]')
// click on root to focus
.click('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep5')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep5"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep6')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep5/deep6"]')
.click('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep5')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep5"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep6')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep5/deep6"]')
// focus on contracts
.click('li[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep7')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/deep7"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep8')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/deep7/deep8"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFile"]')
.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep9.sol')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/deep7/deep8/deep9.sol"]')
.end()
.click('li[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep7')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/deep7"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep8')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/deep7/deep8"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFile"]')
.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep9.sol')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/deep7/deep8/deep9.sol"]')
.end()
}
}

@ -90,7 +90,7 @@ module.exports = {
.rightClick('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.waitForElementPresent('[data-id="contextMenuItemcopy')
.click('[data-id="contextMenuItemcopy"]')
.rightClick('*[data-id="treeViewLiMenu"]')
.rightClick('*[data-id="treeViewUltreeViewMenu"]')
.saveScreenshot('./reports/screenshot/file_explorer_context_menu.png')
.click('*[data-id="contextMenuItempaste"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemCopy_README.txt"]', 7000)
@ -105,25 +105,25 @@ module.exports = {
.click('*[data-id="contextMenuItempaste"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/Copy_README.txt"]', 7000)
},
// folder copy paste tests
'Should copy folder and paste in root with right click and it will contain a copied folder #group1 ': function (browser: NightwatchBrowser) {
browser
.rightClick('li[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementPresent('[data-id="contextMenuItemcopy')
.click('[data-id="contextMenuItemcopy"]')
.rightClick('*[data-id="treeViewLiMenu"]')
.click('*[data-id="contextMenuItempaste"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemCopy_contracts"]', 7000)
},
'Should copy folder and paste in contracts with right click and it will contain a copied folder #group1 ': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.waitForElementVisible('li[data-id="treeViewLitreeViewItemscripts"]')
.rightClick('li[data-id="treeViewLitreeViewItemscripts"]')
.waitForElementPresent('[data-id="contextMenuItemcopy')
.click('[data-id="contextMenuItemcopy"]')
.rightClick('*[data-id="treeViewLitreeViewItemcontracts"]')
.click('*[data-id="contextMenuItempaste"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/Copy_scripts"]', 7000)
}
// folder copy paste tests
'Should copy folder and paste in root with right click and it will contain a copied folder #group1 ': function (browser: NightwatchBrowser) {
browser
.rightClick('li[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementPresent('[data-id="contextMenuItemcopy')
.click('[data-id="contextMenuItemcopy"]')
.rightClick('*[data-id="treeViewUltreeViewMenu"]')
.click('*[data-id="contextMenuItempaste"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemCopy_contracts"]', 7000)
},
'Should copy folder and paste in contracts with right click and it will contain a copied folder #group1 ': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.waitForElementVisible('li[data-id="treeViewLitreeViewItemscripts"]')
.rightClick('li[data-id="treeViewLitreeViewItemscripts"]')
.waitForElementPresent('[data-id="contextMenuItemcopy')
.click('[data-id="contextMenuItemcopy"]')
.rightClick('*[data-id="treeViewLitreeViewItemcontracts"]')
.click('*[data-id="contextMenuItempaste"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/Copy_scripts"]', 7000)
}
}

@ -6,7 +6,6 @@ const checkBrowserIsChrome = function (browser: NightwatchBrowser) {
return browser.browserName.indexOf('chrome') > -1
}
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
@ -98,6 +97,4 @@ module.exports = {
}
}
}

@ -3,15 +3,52 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'Should start coding': function (browser: NightwatchBrowser) {
'Should start coding #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="homeTabGetStartedremixDefault"]')
.click('*[data-id="homeTabGetStartedremixDefault"]')
.waitForElementVisible('*[data-id="treeViewDivtreeViewItemcontracts/1_Storage.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"')
.waitForElementVisible('*[data-id="treeViewDivDraggableItemREADME.txt"')
.click('*[data-id="treeViewDivtreeViewItemcontracts/1_Storage.sol"]')
.waitForElementPresent({
selector: "//div[contains(@class, 'view-line') and contains(.//span, 'pragma')]",
locateStrategy: 'xpath'
})
.getEditorValue((editorContent) => {
browser.assert.ok(editorContent.indexOf(`pragma solidity`) !== -1, 'unexpected content encountered!')
})
},
'Should start with ERC20 workspace #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-path="home"')
.waitForElementVisible('*[data-id="homeTabGetStartedERC20"]')
.click('*[data-id="homeTabGetStartedERC20"')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
.waitForElementVisible('*[data-id="treeViewDivtreeViewItemtests/MyToken_test.sol"]')
.click('*[data-id="treeViewDivtreeViewItemtests/MyToken_test.sol"]')
.waitForElementPresent({
selector: "//div[contains(@class, 'view-line') and contains(.//span, 'pragma')]",
locateStrategy: 'xpath'
})
.getEditorValue((editorContent) => {
browser.assert.ok(editorContent.indexOf(`import "../contracts/MyToken.sol";`) !== -1, 'content encountered!')
})
},
'Should create a new file in the current workspace': '' +function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="homeTabStartCoding"]')
.click('*[data-id="homeTabStartCoding"]')
.waitForElementVisible('div[data-id="treeViewDivtreeViewItemcontracts/HelloWorld.sol"]')
.click('*[data-path="home"]')
.waitForElementVisible('*[data-id="homeTabNewFile"]')
.click('*[data-id="homeTabNewFile"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'HometabNewFile.txt')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemHometabNewFile.txt"]', 7000)
}
}

@ -3,13 +3,8 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
import { join } from 'path'
import { ChildProcess, spawn } from 'child_process'
import { writeFileSync } from 'fs'
import * as hardhatCompilation from '../helpers/hardhat_compilation_7839ba878952cc00ff316061405f273a.json'
import * as hardhat_compilation_Lock_dbg from '../helpers/hardhat_compilation_Lock.dbg.json'
import * as hardhat_compilation_Lock from '../helpers/hardhat_compilation_Lock.json'
import { homedir } from 'os'
import * as foundryCompilation from '../helpers/foundry_compilation.json'
import * as truffle_compilation from '../helpers/truffle_compilation.json'
import kill from 'tree-kill'
let remixd: ChildProcess
@ -71,9 +66,13 @@ module.exports = {
'@sources': function () {
return sources
},
'run Remixd tests #group4': function (browser) {
'run Remixd tests #group1': function (browser) {
browser.perform(async (done) => {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts'))
try {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts'))
} catch (err) {
console.error(err)
}
console.log('working directory', process.cwd())
connectRemixd(browser, done)
})
@ -81,10 +80,10 @@ module.exports = {
runTests(browser, done)
})
},
'Import from node_modules #group1': function (browser) {
'Import from node_modules #group2': function (browser) {
/*
when a relative import is used (i.e import "openzeppelin-solidity/contracts/math/SafeMath.sol")
remix (as well as truffle) try to resolve it against the node_modules and installed_contracts folder.
remix try to resolve it against the node_modules and installed_contracts folder.
*/
browser.perform(async (done) => {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts'))
@ -96,7 +95,7 @@ module.exports = {
.setSolidityCompilerVersion('soljson-v0.5.0+commit.1d4f565a.js')
.testContracts('test_import_node_modules.sol', sources[3]['test_import_node_modules.sol'], ['SafeMath'])
},
'Import from node_modules and reference a github import #group2': function (browser) {
'Import from node_modules and reference a github import #group3': function (browser) {
browser.perform(async (done) => {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts'))
console.log('working directory', process.cwd())
@ -107,77 +106,52 @@ module.exports = {
.setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.20
.testContracts('test_import_node_modules_with_github_import.sol', sources[4]['test_import_node_modules_with_github_import.sol'], ['ERC20', 'test11'])
},
'Static Analysis run with remixd #group3': '' + function (browser) {
browser.testContracts('test_static_analysis_with_remixd_and_hardhat.sol', sources[5]['test_static_analysis_with_remixd_and_hardhat.sol'], ['test5']).pause(2000)
.clickLaunchIcon('solidityStaticAnalysis')
/*
.click('#staticanalysisButton button').pause(4000)
.waitForElementPresent('#staticanalysisresult .warning', 2000, true, function () {
browser
.waitForElementVisible('[data-id="staticAnalysisModuleMiscellaneous1Button"]')
.click('[data-id="staticAnalysisModuleMiscellaneous1Button"]')
.waitForElementVisible('.highlightLine16', 60000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(
'function _sendLogPayload(bytes memory payload) private view {') !== -1,
'code has not been loaded')
})
})
*/
},
'Run git status': '' + function (browser) {
browser
.executeScriptInTerminal('git status')
.pause(3000)
.journalLastChildIncludes('On branch ')
},
'Close Remixd #group3': '' + function (browser) {
browser
.clickLaunchIcon('pluginManager')
.scrollAndClick('#pluginManager *[data-id="pluginManagerComponentDeactivateButtonremixd"]')
'Should setup a hardhat project #group4': function (browser: NightwatchBrowser) {
browser.perform(async (done) => {
await setupHardhatProject()
done()
})
},
'Should listen on compilation result from hardhat #group5': function (browser: NightwatchBrowser) {
'Should listen on compilation result from hardhat #group4': function (browser: NightwatchBrowser) {
browser.perform(async (done) => {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts/hardhat'))
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide/hardhat-boilerplate'))
console.log('working directory', process.cwd())
connectRemixd(browser, done)
})
.perform((done) => {
.perform(async (done) => {
console.log('generating compilation result')
writeFileSync('./apps/remix-ide/contracts/hardhat/artifacts/build-info/7839ba878952cc00ff316061405f273a.json', JSON.stringify(hardhatCompilation))
writeFileSync('./apps/remix-ide/contracts/hardhat/artifacts/contracts/Lock.sol/Lock.json', JSON.stringify(hardhat_compilation_Lock))
writeFileSync('./apps/remix-ide/contracts/hardhat/artifacts/contracts/Lock.sol/Lock.dbg.json', JSON.stringify(hardhat_compilation_Lock_dbg))
await compileHardhatProject()
done()
})
.expect.element('*[data-id="terminalJournal"]').text.to.contain('receiving compilation result from Hardhat').before(60000)
let addressRef
browser.clickLaunchIcon('filePanel')
.openFile('contracts')
.openFile('contracts/Lock.sol')
.openFile('contracts/Token.sol')
.clickLaunchIcon('udapp')
.selectContract('Lock')
.createContract('1')
.expect.element('*[data-id="terminalJournal"]').text.to.contain('Unlock time should be in the future').before(60000)
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c')
.selectContract('Token')
.createContract('')
.clickInstance(0)
.clickFunction('balanceOf - call', { types: 'address account', values: '0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c' })
.getAddressAtPosition(0, (address) => {
addressRef = address
})
.perform((done) => {
browser.verifyCallReturnValue(addressRef, ['0:uint256: 1000000'])
.perform(() => done())
})
},
'Should load compilation result from hardhat when remixd connects #group6': function (browser: NightwatchBrowser) {
// artifacts/build-info/c7062fdd360381a85af23eeef31c98f8.json has already been created
'Should load compilation result from hardhat when remixd connects #group4': function (browser: NightwatchBrowser) {
let addressRef
browser
.perform((done) => {
writeFileSync('./apps/remix-ide/contracts/hardhat/artifacts/contracts/Lock.sol/Lock.dbg.json', JSON.stringify(hardhat_compilation_Lock_dbg))
writeFileSync('./apps/remix-ide/contracts/hardhat/artifacts/contracts/Lock.sol/Lock.json', JSON.stringify(hardhat_compilation_Lock))
writeFileSync('./apps/remix-ide/contracts/hardhat/artifacts/build-info/7839ba878952cc00ff316061405f273a.json', JSON.stringify(hardhatCompilation))
done()
})
.refresh()
.perform(async (done) => {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts/hardhat'))
console.log('working directory', process.cwd())
connectRemixd(browser, done)
})
@ -185,24 +159,40 @@ module.exports = {
browser.clickLaunchIcon('filePanel')
.openFile('contracts')
.openFile('contracts/Lock.sol')
.openFile('contracts/Token.sol')
.clickLaunchIcon('udapp')
.selectContract('Lock')
.createContract('1')
.expect.element('*[data-id="terminalJournal"]').text.to.contain('Unlock time should be in the future').before(60000)
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c')
.selectContract('Token')
.createContract('')
.clickInstance(0)
.clickFunction('balanceOf - call', { types: 'address account', values: '0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c' })
.getAddressAtPosition(0, (address) => {
addressRef = address
})
.perform((done) => {
browser.verifyCallReturnValue(addressRef, ['0:uint256: 1000000'])
.perform(() => done())
})
},
'Should install foundry #group5': function (browser: NightwatchBrowser) {
browser.perform(async (done) => {
await downloadFoundry()
await installFoundry()
await initFoundryProject()
done()
})
},
'Should listen on compilation result from foundry #group7': function (browser: NightwatchBrowser) {
'Should listen on compilation result from foundry #group5': function (browser: NightwatchBrowser) {
browser.perform(async (done) => {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts/foundry'))
console.log('working directory', process.cwd())
console.log('working directory', homedir() + '/foundry_tmp/hello_foundry')
remixd = await spawnRemixd(join(homedir(), '/foundry_tmp/hello_foundry'))
connectRemixd(browser, done)
})
.perform((done) => {
writeFileSync('./apps/remix-ide/contracts/foundry/out/Counter.sol/Counter.json', JSON.stringify(foundryCompilation))
.perform(async (done) => {
await buildFoundryProject()
done()
})
.expect.element('*[data-id="terminalJournal"]').text.to.contain('receiving compilation result from Foundry').before(60000)
@ -225,36 +215,37 @@ module.exports = {
done()
})
})
},
'Should listen on compilation result from truffle #group8': function (browser: NightwatchBrowser) {
'Should load compilation result from hardhat when remixd connects #group5': function (browser: NightwatchBrowser) {
browser.perform(async (done) => {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts/truffle'))
console.log('working directory', process.cwd())
browser.refresh().perform(async (done) => {
console.log('working directory', homedir() + '/foundry_tmp/hello_foundry')
connectRemixd(browser, done)
})
.perform((done) => {
writeFileSync('./apps/remix-ide/contracts/truffle/build/contracts/Migrations.json', JSON.stringify(truffle_compilation))
done()
})
.expect.element('*[data-id="terminalJournal"]').text.to.contain('receiving compilation result from Truffle').before(60000)
.expect.element('*[data-id="terminalJournal"]').text.to.contain('receiving compilation result from Foundry').before(60000)
let contractAaddress
browser.clickLaunchIcon('filePanel')
.openFile('contracts')
.openFile('contracts/Migrations.sol')
.openFile('src')
.openFile('src/Counter.sol')
.clickLaunchIcon('udapp')
.selectContract('Migrations')
.selectContract('Counter')
.createContract('')
.testFunction('last',
{
status: '0x1 Transaction mined and execution succeed'
.getAddressAtPosition(0, (address) => {
console.log(contractAaddress)
contractAaddress = address
})
.clickInstance(0)
.clickFunction('increment - transact (not payable)')
.perform((done) => {
browser.testConstantFunction(contractAaddress, 'number - call', null, '0:\nuint256: 1').perform(() => {
done()
})
})
},
'Should disable git when running remixd #group9': function (browser: NightwatchBrowser) {
browser.perform(async (done) => {
@ -269,7 +260,7 @@ module.exports = {
.clickLaunchIcon('filePanel')
.switchWorkspace('default_workspace')
.click({
selector:'[data-path="connectionAlert-modal-footer-ok-react"]',
selector: '[data-path="connectionAlert-modal-footer-ok-react"]',
suppressNotFoundErrors: true,
timeout: 2000
})
@ -328,7 +319,30 @@ function testImportFromRemixd(browser: NightwatchBrowser, callback: VoidFunction
.perform(() => { callback() })
}
async function installRemixd(): Promise<void> {
console.log('installRemixd')
const remixd = spawn('yarn install', [], { cwd: process.cwd() + '/dist/libs/remixd', shell: true, detached: true })
return new Promise((resolve, reject) => {
remixd.stdout.on('data', function (data) {
console.log(data.toString())
if (
data.toString().includes('success Saved lockfile')
|| data.toString().includes('success Already up-to-date')
) {
resolve()
}
})
remixd.stderr.on('err', function (data) {
console.log(data.toString())
reject(data.toString())
})
})
}
async function spawnRemixd(path: string): Promise<ChildProcess> {
console.log('spawnRemixd', path)
await installRemixd()
const remixd = spawn('chmod +x dist/libs/remixd/src/bin/remixd.js && dist/libs/remixd/src/bin/remixd.js --remix-ide http://127.0.0.1:8080', [`-s ${path}`], { cwd: process.cwd(), shell: true, detached: true })
return new Promise((resolve, reject) => {
remixd.stdout.on('data', function (data) {
@ -341,6 +355,7 @@ async function spawnRemixd(path: string): Promise<ChildProcess> {
}
})
remixd.stderr.on('err', function (data) {
console.log(data.toString())
reject(data.toString())
})
})
@ -368,3 +383,120 @@ function connectRemixd(browser: NightwatchBrowser, done: any) {
.perform(() => done())
}
async function setupHardhatProject(): Promise<void> {
console.log(process.cwd())
try {
const server = spawn('git clone https://github.com/NomicFoundation/hardhat-boilerplate && cd hardhat-boilerplate && yarn install && yarn add "@typechain/ethers-v5@^10.1.0" && yarn add "@typechain/hardhat@^6.1.2" && yarn add "typechain@^8.1.0" && echo "END"', [], { cwd: process.cwd() + '/apps/remix-ide', shell: true, detached: true })
return new Promise((resolve, reject) => {
server.on('exit', function (exitCode) {
console.log("Child exited with code: " + exitCode);
console.log('end')
resolve()
})
})
} catch (e) {
console.log(e)
}
}
async function compileHardhatProject(): Promise<void> {
console.log(process.cwd())
try {
const server = spawn('npx hardhat compile', [], { cwd: process.cwd() + '/apps/remix-ide/hardhat-boilerplate', shell: true, detached: true })
return new Promise((resolve, reject) => {
server.on('exit', function (exitCode) {
console.log("Child exited with code: " + exitCode);
console.log('end')
resolve()
})
})
} catch (e) {
console.log(e)
}
}
async function downloadFoundry(): Promise<void> {
console.log('downloadFoundry', process.cwd())
try {
const server = spawn('curl -L https://foundry.paradigm.xyz | bash', [], { cwd: process.cwd(), shell: true, detached: true })
return new Promise((resolve, reject) => {
server.stdout.on('data', function (data) {
console.log(data.toString())
if (
data.toString().includes("simply run 'foundryup' to install Foundry")
|| data.toString().includes("foundryup: could not detect shell, manually add")
) {
console.log('resolving')
resolve()
}
})
server.stderr.on('err', function (data) {
console.log(data.toString())
reject(data.toString())
})
})
} catch (e) {
console.log(e)
}
}
async function installFoundry(): Promise<void> {
console.log('installFoundry', process.cwd())
try {
const server = spawn('export PATH="' + homedir() + '/.foundry/bin:$PATH" && foundryup', [], { cwd: process.cwd(), shell: true, detached: true })
return new Promise((resolve, reject) => {
server.stdout.on('data', function (data) {
console.log(data.toString())
if (
data.toString().includes("foundryup: done!")
) {
console.log('resolving')
resolve()
}
})
server.stderr.on('err', function (data) {
console.log(data.toString())
reject(data.toString())
})
})
} catch (e) {
console.log(e)
}
}
async function initFoundryProject(): Promise<void> {
console.log('initFoundryProject', homedir())
try {
spawn('git config --global user.email \"you@example.com\"', [], { cwd: homedir(), shell: true, detached: true })
spawn('git config --global user.name \"Your Name\"', [], { cwd: homedir(), shell: true, detached: true })
spawn('mkdir foundry_tmp', [], { cwd: homedir(), shell: true, detached: true })
const server = spawn('export PATH="' + homedir() + '/.foundry/bin:$PATH" && forge init hello_foundry', [], { cwd: homedir() + '/foundry_tmp', shell: true, detached: true })
server.stdout.pipe(process.stdout)
return new Promise((resolve, reject) => {
server.on('exit', function (exitCode) {
console.log("Child exited with code: " + exitCode);
console.log('end')
resolve()
})
})
} catch (e) {
console.log(e)
}
}
async function buildFoundryProject(): Promise<void> {
console.log('buildFoundryProject', homedir())
try {
const server = spawn('export PATH="' + homedir() + '/.foundry/bin:$PATH" && forge build', [], { cwd: homedir() + '/foundry_tmp/hello_foundry', shell: true, detached: true })
server.stdout.pipe(process.stdout)
return new Promise((resolve, reject) => {
server.on('exit', function (exitCode) {
console.log("Child exited with code: " + exitCode);
console.log('end')
resolve()
})
})
} catch (e) {
console.log(e)
}
}

@ -167,7 +167,7 @@ module.exports = {
},
'Should print hardhat logs #group4': function (browser: NightwatchBrowser) {
browser
.addFile('printHardhatlog.sol', { content: hardhatLog })
.addFile('printHardhatlog.sol', { content: hardhatLog })
.clickLaunchIcon('solidity')
.click('*[data-id="terminalClearConsole"]') // clear the terminal
.waitForElementVisible('[for="autoCompile"]')
@ -266,22 +266,22 @@ module.exports = {
if (Array.isArray(result.value) && result.value.length > 0) {
console.log('Found ' + result.value.length + ' transactions')
browser
.click({
selector: '[data-id="listenNetworkCheckInput"]',
})
.click({
selector: '*[data-id="terminalClearConsole"]',
})
.click({
selector: '*[data-id="compilerContainerCompileAndRunBtn"]',
})
.pause(10000)
.waitForElementNotPresent({
locateStrategy: 'xpath',
selector: "//*[@class='remix_ui_terminal_log' and contains(.,'to:') and contains(.,'from:')]",
timeout: 120000
})
.end()
.click({
selector: '[data-id="listenNetworkCheckInput"]',
})
.click({
selector: '*[data-id="terminalClearConsole"]',
})
.click({
selector: '*[data-id="compilerContainerCompileAndRunBtn"]',
})
.pause(10000)
.waitForElementNotPresent({
locateStrategy: 'xpath',
selector: "//*[@class='remix_ui_terminal_log' and contains(.,'to:') and contains(.,'from:')]",
timeout: 120000
})
.end()
} else {
browser
.assert.fail('No transaction found')
@ -309,7 +309,7 @@ module.exports = {
.switchEnvironment('vm-custom-fork')
.waitForElementVisible('[data-id="vm-custom-fork-modal-footer-ok-react"]')
.execute(() => {
(document.querySelector('*[data-id="vm-custom-forkModalDialogContainer-react"] input[data-id="CustomForkNodeUrl"]') as any).focus()
(document.querySelector('*[data-id="vm-custom-forkModalDialogContainer-react"] input[data-id="CustomForkNodeUrl"]') as any).focus()
}, [], () => { })
.clearValue('*[data-id="CustomForkNodeUrl"]').pause(1000).setValue('*[data-id="CustomForkNodeUrl"]', 'https://go.getblock.io/ee42d0a88f314707be11dd799b122cb9')
.execute(() => {
@ -371,9 +371,9 @@ module.exports = {
.perform(function () {
const actions = this.actions({ async: true });
return actions
.keyDown(this.Keys.SHIFT)
.keyDown(this.Keys.ALT)
.sendKeys('r')
.keyDown(this.Keys.SHIFT)
.keyDown(this.Keys.ALT)
.sendKeys('r')
})
.useCss()
.waitForElementContainsText('*[data-id="terminalJournal"]', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', 120000)
@ -398,18 +398,15 @@ module.exports = {
.perform(function () {
const actions = this.actions({ async: true });
return actions
.keyDown(this.Keys.SHIFT)
.keyDown(this.Keys.ALT)
.sendKeys('r')
.keyDown(this.Keys.SHIFT)
.keyDown(this.Keys.ALT)
.sendKeys('r')
})
.useCss()
.waitForElementContainsText('*[data-id="terminalJournal"]', 'test running free function', 120000)
}
}
const asyncAwait = `
var p = function () {
return new Promise(function (resolve, reject) {

@ -117,7 +117,7 @@ module.exports = {
})
},
'Should load Blockscout verified contracts from URL "address" and "blockscout" params (single source)': function (browser: NightwatchBrowser) {
'Should load Blockscout verified contracts from URL "address" and "blockscout" params (single source)': ''+function (browser: NightwatchBrowser) {
browser
.url('http://127.0.0.1:8080/#address=0xdAC17F958D2ee523a2206206994597C13D831ec7&blockscout=eth.blockscout.com')
.refreshPage()
@ -175,6 +175,7 @@ module.exports = {
'code has been loaded')
})
.url('http://127.0.0.1:8080') // refresh without loading the code sample
.pause(2000)
.currentWorkspaceIs('default_workspace')
.execute(() => {
return document.querySelector('[data-id="dropdown-item-code-sample"]') === null

@ -27,7 +27,7 @@ module.exports = {
.frameParent()
.clickLaunchIcon('filePanel')
.waitForElementVisible({
selector: "//*[@data-id='workspacesSelect' and contains(.,'snekmate')]",
selector: "//*[@data-id='workspacesSelect' and contains(.,'vyper-lang')]",
locateStrategy: 'xpath',
timeout: 120000
})
@ -38,15 +38,16 @@ module.exports = {
timeout: 1200000
})
},
// 'Add vyper file to run tests #group1': function (browser: NightwatchBrowser) {
// browser.addFile('TestBallot.sol', sources[0]['TestBallot.sol'])
// },
'@sources': () => sources,
// '@sources': () => sources,
// 'Context menu click to compile blind_auction should succeed #group1': function (browser: NightwatchBrowser) {
// browser
// .addFileSnekmate('blind_auction.vy', sources[0]['blindAuction'])
'Context menu click to compile blind_auction should succeed #group1': function (browser: NightwatchBrowser) {
browser
.addFileSnekmate('blind_auction.vy', sources[0]['blindAuction'])
.click('*[data-id="treeViewLitreeViewItemblind_auction.vy"]')
.rightClick('*[data-id="treeViewLitreeViewItemblind_auction.vy"]')
.click('*[data-id="treeViewDivtreeViewItemexamples/auctions/blind_auction.vy"]')
.rightClick('*[data-id="treeViewDivtreeViewItemexamples/auctions/blind_auction.vy"]')
.waitForElementPresent('[data-id="contextMenuItemvyper"]')
.click('[data-id="contextMenuItemvyper"]')
.clickLaunchIcon('vyper')
@ -145,32 +146,32 @@ module.exports = {
})
},
'Compile Ownable contract from snekmate #group1': function (browser: NightwatchBrowser) {
let contractAddress
browser
.frameParent()
.clickLaunchIcon('filePanel')
.switchWorkspace('snekmate')
.openFile('src')
.openFile('src/snekmate')
.openFile('src/snekmate/auth')
.openFile('src/snekmate/auth/Ownable.vy')
.rightClick('*[data-id="treeViewLitreeViewItemsrc/snekmate/auth/Ownable.vy"]')
.waitForElementVisible('*[data-id="contextMenuItemvyper"]')
.click('*[data-id="contextMenuItemvyper"]')
.clickLaunchIcon('vyper')
// @ts-ignore
.frame(0)
.click('[data-id="compile"]')
.waitForElementVisible({
selector:'[data-id="compilation-details"]',
timeout: 60000
})
.click('[data-id="compilation-details"]')
.frameParent()
.waitForElementVisible('[data-id="copy-abi"]')
.end()
}
// 'Compile Ownable contract from snekmate #group1': function (browser: NightwatchBrowser) {
// let contractAddress
// browser
// .frameParent()
// .clickLaunchIcon('filePanel')
// .switchWorkspace('snekmate')
// .openFile('src')
// .openFile('src/snekmate')
// .openFile('src/snekmate/auth')
// .openFile('src/snekmate/auth/Ownable.vy')
// .rightClick('*[data-id="treeViewLitreeViewItemsrc/snekmate/auth/Ownable.vy"]')
// .waitForElementVisible('*[data-id="contextMenuItemvyper"]')
// .click('*[data-id="contextMenuItemvyper"]')
// .clickLaunchIcon('vyper')
// // @ts-ignore
// .frame(0)
// .click('[data-id="compile"]')
// .waitForElementVisible({
// selector:'[data-id="compilation-details"]',
// timeout: 60000
// })
// .click('[data-id="compilation-details"]')
// .frameParent()
// .waitForElementVisible('[data-id="copy-abi"]')
// .end()
// }
}
const testContract = `
@ -384,6 +385,6 @@ def auctionEnd():
# Transfer funds to beneficiary
send(self.beneficiary, self.highestBid)
`}
` }
}
]

@ -174,11 +174,13 @@ module.exports = {
.setValue('[data-id="modalDialogCustomPromptTextClone"]', 'https://github.com/ioedeveloper/test-branch-change')
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.waitForElementPresent('.fa-spinner')
.pause(5000)
.pause(7000)
.waitForElementNotPresent('.fa-spinner')
.waitForElementContainsText('[data-id="workspacesSelect"]', 'test-branch-change')
.waitForElementVisible('[data-id="workspaceGitPanel"]')
.waitForElementVisible('[data-id="workspaceGitBranchesDropdown"]')
.click('[data-id="workspaceGitBranchesDropdown"]')
.pause()
.waitForElementVisible('[data-id="custom-dropdown-menu"]')
.waitForElementContainsText('[data-id="custom-dropdown-items"]', 'origin/dev')
.waitForElementContainsText('[data-id="custom-dropdown-items"]', 'origin/production')
@ -502,11 +504,9 @@ module.exports = {
// GIT WORKSPACE E2E ENDS
tearDown: sauce,
}
const gitmodules = `[submodule "subdemo3"]
path = subdemo3
url = https://github.com/bunsenstraat/empty3

@ -60,7 +60,7 @@ contract Ballot {
!voters[voter].voted,
"The voter already voted."
);
require(voters[voter].weight == 0);
require(voters[voter].weight == 0, "Voter already has the right to vote.");
voters[voter].weight = 1;
}
@ -101,6 +101,7 @@ contract Ballot {
Voter storage sender = voters[msg.sender];
require(sender.weight != 0, "Has no right to vote");
require(!sender.voted, "Already voted.");
require(proposal < proposals.length, "Invalid proposal index.");
sender.voted = true;
sender.vote = proposal;

@ -1,398 +0,0 @@
{
"_format": "ethers-rs-sol-cache-3",
"paths": {
"artifacts": "out",
"build_infos": "out/build-info",
"sources": "src",
"tests": "test",
"scripts": "script",
"libraries": [
"lib"
]
},
"files": {
"lib/forge-std/lib/ds-test/src/test.sol": {
"lastModificationDate": 1661541843388,
"contentHash": "962996f0e05d5218857a538a62d7c47e",
"sourceName": "lib/forge-std/lib/ds-test/src/test.sol",
"solcConfig": {
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"bytecodeHash": "ipfs"
},
"outputSelection": {
"*": {
"": [
"ast"
],
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata"
]
}
},
"evmVersion": "london",
"libraries": {}
}
},
"imports": [],
"versionRequirement": ">=0.5.0",
"artifacts": {
"DSTest": {
"0.8.16+commit.07a7930e.Linux.gcc": "test.sol/DSTest.json"
}
}
},
"lib/forge-std/src/Script.sol": {
"lastModificationDate": 1661541842048,
"contentHash": "b313d0193442f5a12848be9c422a0064",
"sourceName": "lib/forge-std/src/Script.sol",
"solcConfig": {
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"bytecodeHash": "ipfs"
},
"outputSelection": {
"*": {
"": [
"ast"
],
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata"
]
}
},
"evmVersion": "london",
"libraries": {}
}
},
"imports": [
"lib/forge-std/src/Vm.sol",
"lib/forge-std/src/console.sol",
"lib/forge-std/src/console2.sol"
],
"versionRequirement": ">=0.6.0, <0.9.0",
"artifacts": {
"Script": {
"0.8.16+commit.07a7930e.Linux.gcc": "Script.sol/Script.json"
}
}
},
"lib/forge-std/src/Test.sol": {
"lastModificationDate": 1661541842048,
"contentHash": "8e1ae731c7bb8023f36077d86d18693f",
"sourceName": "lib/forge-std/src/Test.sol",
"solcConfig": {
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"bytecodeHash": "ipfs"
},
"outputSelection": {
"*": {
"": [
"ast"
],
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata"
]
}
},
"evmVersion": "london",
"libraries": {}
}
},
"imports": [
"lib/forge-std/lib/ds-test/src/test.sol",
"lib/forge-std/src/Script.sol",
"lib/forge-std/src/Vm.sol",
"lib/forge-std/src/console.sol",
"lib/forge-std/src/console2.sol"
],
"versionRequirement": ">=0.6.0, <0.9.0",
"artifacts": {
"Test": {
"0.8.16+commit.07a7930e.Linux.gcc": "Test.sol/Test.json"
},
"stdError": {
"0.8.16+commit.07a7930e.Linux.gcc": "Test.sol/stdError.json"
},
"stdMath": {
"0.8.16+commit.07a7930e.Linux.gcc": "Test.sol/stdMath.json"
},
"stdStorage": {
"0.8.16+commit.07a7930e.Linux.gcc": "Test.sol/stdStorage.json"
}
}
},
"lib/forge-std/src/Vm.sol": {
"lastModificationDate": 1661541842048,
"contentHash": "225040109969e43ff90255e34aaecc99",
"sourceName": "lib/forge-std/src/Vm.sol",
"solcConfig": {
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"bytecodeHash": "ipfs"
},
"outputSelection": {
"*": {
"": [
"ast"
],
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata"
]
}
},
"evmVersion": "london",
"libraries": {}
}
},
"imports": [],
"versionRequirement": ">=0.6.0, <0.9.0",
"artifacts": {
"Vm": {
"0.8.16+commit.07a7930e.Linux.gcc": "Vm.sol/Vm.json"
}
}
},
"lib/forge-std/src/console.sol": {
"lastModificationDate": 1663196945880,
"contentHash": "100b8a33b917da1147740d7ab8b0ded3",
"sourceName": "lib/forge-std/src/console.sol",
"solcConfig": {
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"bytecodeHash": "ipfs"
},
"outputSelection": {
"*": {
"": [
"ast"
],
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata"
]
}
},
"evmVersion": "london",
"libraries": {}
}
},
"imports": [],
"versionRequirement": ">=0.4.22, <0.9.0",
"artifacts": {
"console": {
"0.8.16+commit.07a7930e.Linux.gcc": "console.sol/console.json"
}
}
},
"lib/forge-std/src/console2.sol": {
"lastModificationDate": 1661541842052,
"contentHash": "5df91f8e93efbfcccf68973dc1b74a70",
"sourceName": "lib/forge-std/src/console2.sol",
"solcConfig": {
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"bytecodeHash": "ipfs"
},
"outputSelection": {
"*": {
"": [
"ast"
],
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata"
]
}
},
"evmVersion": "london",
"libraries": {}
}
},
"imports": [],
"versionRequirement": ">=0.4.22, <0.9.0",
"artifacts": {
"console2": {
"0.8.16+commit.07a7930e.Linux.gcc": "console2.sol/console2.json"
}
}
},
"script/Counter.s.sol": {
"lastModificationDate": 1661541840908,
"contentHash": "0705c52104730a78aef4aa6694175c81",
"sourceName": "script/Counter.s.sol",
"solcConfig": {
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"bytecodeHash": "ipfs"
},
"outputSelection": {
"*": {
"": [
"ast"
],
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata"
]
}
},
"evmVersion": "london",
"libraries": {}
}
},
"imports": [
"lib/forge-std/src/Script.sol",
"lib/forge-std/src/Vm.sol",
"lib/forge-std/src/console.sol",
"lib/forge-std/src/console2.sol"
],
"versionRequirement": "^0.8.13",
"artifacts": {
"CounterScript": {
"0.8.16+commit.07a7930e.Linux.gcc": "Counter.s.sol/CounterScript.json"
}
}
},
"src/Counter.sol": {
"lastModificationDate": 1664875932853,
"contentHash": "ae6c800a2b4c57768024d6e9423d39e8",
"sourceName": "src/Counter.sol",
"solcConfig": {
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"bytecodeHash": "ipfs"
},
"outputSelection": {
"*": {
"": [
"ast"
],
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata"
]
}
},
"evmVersion": "london",
"libraries": {}
}
},
"imports": [],
"versionRequirement": "^0.8.13",
"artifacts": {
"Counter": {
"0.8.16+commit.07a7930e.Linux.gcc": "Counter.sol/Counter.json"
}
}
},
"test/Counter.t.sol": {
"lastModificationDate": 1661541840908,
"contentHash": "5122f4f87ee8fbf9a2468a4c9c780b6a",
"sourceName": "test/Counter.t.sol",
"solcConfig": {
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"bytecodeHash": "ipfs"
},
"outputSelection": {
"*": {
"": [
"ast"
],
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata"
]
}
},
"evmVersion": "london",
"libraries": {}
}
},
"imports": [
"lib/forge-std/lib/ds-test/src/test.sol",
"lib/forge-std/src/Script.sol",
"lib/forge-std/src/Test.sol",
"lib/forge-std/src/Vm.sol",
"lib/forge-std/src/console.sol",
"lib/forge-std/src/console2.sol",
"src/Counter.sol"
],
"versionRequirement": "^0.8.13",
"artifacts": {
"CounterTest": {
"0.8.16+commit.07a7930e.Linux.gcc": "Counter.t.sol/CounterTest.json"
}
}
}
}
}

@ -1,6 +0,0 @@
[profile.default]
src = 'src'
out = 'out'
libs = ['lib']
# See more config options https://github.com/foundry-rs/foundry/tree/master/config

@ -1,385 +0,0 @@
{
"abi": [
{
"inputs": [],
"name": "IS_SCRIPT",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "run",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "setUp",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "vm",
"outputs": [
{
"internalType": "contract Vm",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
],
"bytecode": {
"object": "0x60806040526000805460ff1916600117905534801561001d57600080fd5b5061014c8061002d6000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80630a9254e4146100515780633a76846314610053578063c04062261461008b578063f8ccbf4714610093575b600080fd5b005b61006e737109709ecfa91a80626ff3989d68f67f5b1dd12d81565b6040516001600160a01b0390911681526020015b60405180910390f35b6100516100b0565b6000546100a09060ff1681565b6040519015158152602001610082565b604080516302bf260160e61b81529051737109709ecfa91a80626ff3989d68f67f5b1dd12d9163afc9804091600480830192600092919082900301818387803b1580156100fc57600080fd5b505af1158015610110573d6000803e3d6000fd5b5050505056fea26469706673582212203a39488c6d5e73072e1dd0c6593caff56e39d0849abc1557f1c6e25cf7dedc2e64736f6c63430008100033",
"sourceMap": "97:126:6:-:0;;;165:28:1;;;-1:-1:-1;;165:28:1;189:4;165:28;;;97:126:6;;;;;;;;;;;;;;;;",
"linkReferences": {}
},
"deployedBytecode": {
"object": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80630a9254e4146100515780633a76846314610053578063c04062261461008b578063f8ccbf4714610093575b600080fd5b005b61006e737109709ecfa91a80626ff3989d68f67f5b1dd12d81565b6040516001600160a01b0390911681526020015b60405180910390f35b6100516100b0565b6000546100a09060ff1681565b6040519015158152602001610082565b604080516302bf260160e61b81529051737109709ecfa91a80626ff3989d68f67f5b1dd12d9163afc9804091600480830192600092919082900301818387803b1580156100fc57600080fd5b505af1158015610110573d6000803e3d6000fd5b5050505056fea26469706673582212203a39488c6d5e73072e1dd0c6593caff56e39d0849abc1557f1c6e25cf7dedc2e64736f6c63430008100033",
"sourceMap": "97:126:6:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;136:26;;316:38:1;;245:64;316:38;;;;;-1:-1:-1;;;;;189:32:9;;;171:51;;159:2;144:18;316:38:1;;;;;;;;168:53:6;;;:::i;165:28:1:-;;;;;;;;;;;;398:14:9;;391:22;373:41;;361:2;346:18;165:28:1;233:187:9;168:53:6;200:14;;;-1:-1:-1;;;200:14:6;;;;245:64:1;;200:12:6;;:14;;;;;269:37:1;;200:14:6;;;;;;;269:37:1;245:64;200:14:6;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;168:53::o",
"linkReferences": {}
},
"methodIdentifiers": {
"IS_SCRIPT()": "f8ccbf47",
"run()": "c0406226",
"setUp()": "0a9254e4",
"vm()": "3a768463"
},
"rawMetadata": "{\"compiler\":{\"version\":\"0.8.16+commit.07a7930e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"IS_SCRIPT\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"run\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"setUp\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vm\",\"outputs\":[{\"internalType\":\"contract Vm\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"script/Counter.s.sol\":\"CounterScript\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":forge-std/=lib/forge-std/src/\"]},\"sources\":{\"lib/forge-std/src/Script.sol\":{\"keccak256\":\"0x4424dbcb8f5b741475445726f87408fcd89951fad973bec2ca442ee157f910e7\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://5b0b9f6dfb69245d8f888558ae82bf1d2cdeace46201444fe4b2e6a5283f944a\",\"dweb:/ipfs/QmWFSKeFEZngNcwNn7A84EF7pASo5qe6r5oK24r9Kwca7Z\"]},\"lib/forge-std/src/Vm.sol\":{\"keccak256\":\"0xa0ede8e0d3dc3246912530aed6cacbc4703e4430c4b4acd91963ccea709755ea\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a28e7d00aab57ad5159247b0f0f268eda4c6980b29eee7f903578254a2be677f\",\"dweb:/ipfs/QmZrM8gY5BpW8o1QckmPNCYbBP5Q7k5DkcHdaVULKVntxp\"]},\"lib/forge-std/src/console.sol\":{\"keccak256\":\"0x91d5413c2434ca58fd278b6e1e79fd98d10c83931cc2596a6038eee4daeb34ba\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://91ccea707361e48b9b7a161fe81f496b9932bc471e9c4e4e1e9c283f2453cc70\",\"dweb:/ipfs/QmcB66sZhQ6Kz7MUHcLE78YXRUZxoZnnxZjN6yATsbB2ec\"]},\"lib/forge-std/src/console2.sol\":{\"keccak256\":\"0xbeb823fcdb356244a83aaccdf828ad019ecc1ffaa3dff18e624fc6d5714ea671\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://4cbe9400340e5f9ec55e2aff3bad1c15fa3afbbe37e80800e6f3fed2ad26854f\",\"dweb:/ipfs/QmdJBABsuXkvWxVzEyGXsTE3vyfBPXDdw5xvvtUz3JeoYW\"]},\"script/Counter.s.sol\":{\"keccak256\":\"0x01edaa1835b1a5bd3f4f66f73451488b8441d30642d3bf1f5fa2c5bf7c005bee\",\"license\":\"UNLICENSED\",\"urls\":[\"bzz-raw://3c6a0f19216ceeebf4ec16f8f2662a3bebbe18d4037d1399adf2e3e4ccbb57a2\",\"dweb:/ipfs/Qmc8NknjPkSgbXLg6zZQ8uKT6kAWBvBXz5JrDvZfa88UNT\"]}},\"version\":1}",
"metadata": {
"compiler": {
"version": "0.8.16+commit.07a7930e"
},
"language": "Solidity",
"output": {
"abi": [
{
"inputs": [],
"stateMutability": "view",
"type": "function",
"name": "IS_SCRIPT",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
]
},
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "function",
"name": "run"
},
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "function",
"name": "setUp"
},
{
"inputs": [],
"stateMutability": "view",
"type": "function",
"name": "vm",
"outputs": [
{
"internalType": "contract Vm",
"name": "",
"type": "address"
}
]
}
],
"devdoc": {
"kind": "dev",
"methods": {},
"version": 1
},
"userdoc": {
"kind": "user",
"methods": {},
"version": 1
}
},
"settings": {
"remappings": [
":ds-test/=lib/forge-std/lib/ds-test/src/",
":forge-std/=lib/forge-std/src/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"bytecodeHash": "ipfs"
},
"compilationTarget": {
"script/Counter.s.sol": "CounterScript"
},
"libraries": {}
},
"sources": {
"lib/forge-std/src/Script.sol": {
"keccak256": "0x4424dbcb8f5b741475445726f87408fcd89951fad973bec2ca442ee157f910e7",
"urls": [
"bzz-raw://5b0b9f6dfb69245d8f888558ae82bf1d2cdeace46201444fe4b2e6a5283f944a",
"dweb:/ipfs/QmWFSKeFEZngNcwNn7A84EF7pASo5qe6r5oK24r9Kwca7Z"
],
"license": "MIT"
},
"lib/forge-std/src/Vm.sol": {
"keccak256": "0xa0ede8e0d3dc3246912530aed6cacbc4703e4430c4b4acd91963ccea709755ea",
"urls": [
"bzz-raw://a28e7d00aab57ad5159247b0f0f268eda4c6980b29eee7f903578254a2be677f",
"dweb:/ipfs/QmZrM8gY5BpW8o1QckmPNCYbBP5Q7k5DkcHdaVULKVntxp"
],
"license": "MIT"
},
"lib/forge-std/src/console.sol": {
"keccak256": "0x91d5413c2434ca58fd278b6e1e79fd98d10c83931cc2596a6038eee4daeb34ba",
"urls": [
"bzz-raw://91ccea707361e48b9b7a161fe81f496b9932bc471e9c4e4e1e9c283f2453cc70",
"dweb:/ipfs/QmcB66sZhQ6Kz7MUHcLE78YXRUZxoZnnxZjN6yATsbB2ec"
],
"license": "MIT"
},
"lib/forge-std/src/console2.sol": {
"keccak256": "0xbeb823fcdb356244a83aaccdf828ad019ecc1ffaa3dff18e624fc6d5714ea671",
"urls": [
"bzz-raw://4cbe9400340e5f9ec55e2aff3bad1c15fa3afbbe37e80800e6f3fed2ad26854f",
"dweb:/ipfs/QmdJBABsuXkvWxVzEyGXsTE3vyfBPXDdw5xvvtUz3JeoYW"
],
"license": "MIT"
},
"script/Counter.s.sol": {
"keccak256": "0x01edaa1835b1a5bd3f4f66f73451488b8441d30642d3bf1f5fa2c5bf7c005bee",
"urls": [
"bzz-raw://3c6a0f19216ceeebf4ec16f8f2662a3bebbe18d4037d1399adf2e3e4ccbb57a2",
"dweb:/ipfs/Qmc8NknjPkSgbXLg6zZQ8uKT6kAWBvBXz5JrDvZfa88UNT"
],
"license": "UNLICENSED"
}
},
"version": 1
},
"ast": {
"absolutePath": "script/Counter.s.sol",
"id": 21582,
"exportedSymbols": {
"CounterScript": [
21581
],
"Script": [
2022
],
"Vm": [
5434
],
"console": [
13498
],
"console2": [
21562
]
},
"nodeType": "SourceUnit",
"src": "39:185:6",
"nodes": [
{
"id": 21564,
"nodeType": "PragmaDirective",
"src": "39:24:6",
"literals": [
"solidity",
"^",
"0.8",
".13"
]
},
{
"id": 21565,
"nodeType": "ImportDirective",
"src": "65:30:6",
"absolutePath": "lib/forge-std/src/Script.sol",
"file": "forge-std/Script.sol",
"nameLocation": "-1:-1:-1",
"scope": 21582,
"sourceUnit": 2023,
"symbolAliases": [],
"unitAlias": ""
},
{
"id": 21581,
"nodeType": "ContractDefinition",
"src": "97:126:6",
"nodes": [
{
"id": 21571,
"nodeType": "FunctionDefinition",
"src": "136:26:6",
"body": {
"id": 21570,
"nodeType": "Block",
"src": "160:2:6",
"statements": []
},
"functionSelector": "0a9254e4",
"implemented": true,
"kind": "function",
"modifiers": [],
"name": "setUp",
"nameLocation": "145:5:6",
"parameters": {
"id": 21568,
"nodeType": "ParameterList",
"parameters": [],
"src": "150:2:6"
},
"returnParameters": {
"id": 21569,
"nodeType": "ParameterList",
"parameters": [],
"src": "160:0:6"
},
"scope": 21581,
"stateMutability": "nonpayable",
"virtual": false,
"visibility": "public"
},
{
"id": 21580,
"nodeType": "FunctionDefinition",
"src": "168:53:6",
"body": {
"id": 21579,
"nodeType": "Block",
"src": "190:31:6",
"statements": [
{
"expression": {
"arguments": [],
"expression": {
"argumentTypes": [],
"expression": {
"id": 21574,
"name": "vm",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 1817,
"src": "200:2:6",
"typeDescriptions": {
"typeIdentifier": "t_contract$_Vm_$5434",
"typeString": "contract Vm"
}
},
"id": 21576,
"isConstant": false,
"isLValue": false,
"isPure": false,
"lValueRequested": false,
"memberLocation": "203:9:6",
"memberName": "broadcast",
"nodeType": "MemberAccess",
"referencedDeclaration": 5173,
"src": "200:12:6",
"typeDescriptions": {
"typeIdentifier": "t_function_external_nonpayable$__$returns$__$",
"typeString": "function () external"
}
},
"id": 21577,
"isConstant": false,
"isLValue": false,
"isPure": false,
"kind": "functionCall",
"lValueRequested": false,
"nameLocations": [],
"names": [],
"nodeType": "FunctionCall",
"src": "200:14:6",
"tryCall": false,
"typeDescriptions": {
"typeIdentifier": "t_tuple$__$",
"typeString": "tuple()"
}
},
"id": 21578,
"nodeType": "ExpressionStatement",
"src": "200:14:6"
}
]
},
"functionSelector": "c0406226",
"implemented": true,
"kind": "function",
"modifiers": [],
"name": "run",
"nameLocation": "177:3:6",
"parameters": {
"id": 21572,
"nodeType": "ParameterList",
"parameters": [],
"src": "180:2:6"
},
"returnParameters": {
"id": 21573,
"nodeType": "ParameterList",
"parameters": [],
"src": "190:0:6"
},
"scope": 21581,
"stateMutability": "nonpayable",
"virtual": false,
"visibility": "public"
}
],
"abstract": false,
"baseContracts": [
{
"baseName": {
"id": 21566,
"name": "Script",
"nameLocations": [
"123:6:6"
],
"nodeType": "IdentifierPath",
"referencedDeclaration": 2022,
"src": "123:6:6"
},
"id": 21567,
"nodeType": "InheritanceSpecifier",
"src": "123:6:6"
}
],
"canonicalName": "CounterScript",
"contractDependencies": [],
"contractKind": "contract",
"fullyImplemented": true,
"linearizedBaseContracts": [
21581,
2022
],
"name": "CounterScript",
"nameLocation": "106:13:6",
"scope": 21582,
"usedErrors": []
}
],
"license": "UNLICENSED"
},
"id": 6
}

@ -1,377 +0,0 @@
{
"abi": [
{
"inputs": [],
"name": "increment",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "number",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "newNumber",
"type": "uint256"
}
],
"name": "setNumber",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": {
"object": "0x608060405234801561001057600080fd5b5060f78061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220f4a9b22e7a2d64c24355b4e7a6f8c62115ca728f26fc2a1e98e364ee91f794fa64736f6c63430008100033",
"sourceMap": "65:192:7:-:0;;;;;;;;;;;;;;;;;;;",
"linkReferences": {}
},
"deployedBytecode": {
"object": "0x6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220f4a9b22e7a2d64c24355b4e7a6f8c62115ca728f26fc2a1e98e364ee91f794fa64736f6c63430008100033",
"sourceMap": "65:192:7:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;116:80;;;;;;:::i;:::-;171:6;:18;116:80;;;88:21;;;;;;;;;345:25:9;;;333:2;318:18;88:21:7;;;;;;;202:53;;240:6;:8;;;:6;:8;;;:::i;:::-;;;;;;202:53::o;14:180:9:-;73:6;126:2;114:9;105:7;101:23;97:32;94:52;;;142:1;139;132:12;94:52;-1:-1:-1;165:23:9;;14:180;-1:-1:-1;14:180:9:o;381:232::-;420:3;441:17;;;438:140;;500:10;495:3;491:20;488:1;481:31;535:4;532:1;525:15;563:4;560:1;553:15;438:140;-1:-1:-1;605:1:9;594:13;;381:232::o",
"linkReferences": {}
},
"methodIdentifiers": {
"increment()": "d09de08a",
"number()": "8381f58a",
"setNumber(uint256)": "3fb5c1cb"
},
"rawMetadata": "{\"compiler\":{\"version\":\"0.8.16+commit.07a7930e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"increment\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"number\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newNumber\",\"type\":\"uint256\"}],\"name\":\"setNumber\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/Counter.sol\":\"Counter\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":forge-std/=lib/forge-std/src/\"]},\"sources\":{\"src/Counter.sol\":{\"keccak256\":\"0x09277f949d59a9521708c870dc39c2c434ad8f86a5472efda6a732ef728c0053\",\"license\":\"UNLICENSED\",\"urls\":[\"bzz-raw://94cd5258357da018bf911aeda60ed9f5b130dce27445669ee200313cd3389200\",\"dweb:/ipfs/QmNbEfWAqXCtfQpk6u7TpGa8sTHXFLpUz7uebz2FVbchSC\"]}},\"version\":1}",
"metadata": {
"compiler": {
"version": "0.8.16+commit.07a7930e"
},
"language": "Solidity",
"output": {
"abi": [
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "function",
"name": "increment"
},
{
"inputs": [],
"stateMutability": "view",
"type": "function",
"name": "number",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
]
},
{
"inputs": [
{
"internalType": "uint256",
"name": "newNumber",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function",
"name": "setNumber"
}
],
"devdoc": {
"kind": "dev",
"methods": {},
"version": 1
},
"userdoc": {
"kind": "user",
"methods": {},
"version": 1
}
},
"settings": {
"remappings": [
":ds-test/=lib/forge-std/lib/ds-test/src/",
":forge-std/=lib/forge-std/src/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"bytecodeHash": "ipfs"
},
"compilationTarget": {
"src/Counter.sol": "Counter"
},
"libraries": {}
},
"sources": {
"src/Counter.sol": {
"keccak256": "0x09277f949d59a9521708c870dc39c2c434ad8f86a5472efda6a732ef728c0053",
"urls": [
"bzz-raw://94cd5258357da018bf911aeda60ed9f5b130dce27445669ee200313cd3389200",
"dweb:/ipfs/QmNbEfWAqXCtfQpk6u7TpGa8sTHXFLpUz7uebz2FVbchSC"
],
"license": "UNLICENSED"
}
},
"version": 1
},
"ast": {
"absolutePath": "src/Counter.sol",
"id": 21604,
"exportedSymbols": {
"Counter": [
21603
]
},
"nodeType": "SourceUnit",
"src": "39:219:7",
"nodes": [
{
"id": 21583,
"nodeType": "PragmaDirective",
"src": "39:24:7",
"literals": [
"solidity",
"^",
"0.8",
".13"
]
},
{
"id": 21603,
"nodeType": "ContractDefinition",
"src": "65:192:7",
"nodes": [
{
"id": 21585,
"nodeType": "VariableDeclaration",
"src": "88:21:7",
"constant": false,
"functionSelector": "8381f58a",
"mutability": "mutable",
"name": "number",
"nameLocation": "103:6:7",
"scope": 21603,
"stateVariable": true,
"storageLocation": "default",
"typeDescriptions": {
"typeIdentifier": "t_uint256",
"typeString": "uint256"
},
"typeName": {
"id": 21584,
"name": "uint256",
"nodeType": "ElementaryTypeName",
"src": "88:7:7",
"typeDescriptions": {
"typeIdentifier": "t_uint256",
"typeString": "uint256"
}
},
"visibility": "public"
},
{
"id": 21595,
"nodeType": "FunctionDefinition",
"src": "116:80:7",
"body": {
"id": 21594,
"nodeType": "Block",
"src": "161:35:7",
"statements": [
{
"expression": {
"id": 21592,
"isConstant": false,
"isLValue": false,
"isPure": false,
"lValueRequested": false,
"leftHandSide": {
"id": 21590,
"name": "number",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 21585,
"src": "171:6:7",
"typeDescriptions": {
"typeIdentifier": "t_uint256",
"typeString": "uint256"
}
},
"nodeType": "Assignment",
"operator": "=",
"rightHandSide": {
"id": 21591,
"name": "newNumber",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 21587,
"src": "180:9:7",
"typeDescriptions": {
"typeIdentifier": "t_uint256",
"typeString": "uint256"
}
},
"src": "171:18:7",
"typeDescriptions": {
"typeIdentifier": "t_uint256",
"typeString": "uint256"
}
},
"id": 21593,
"nodeType": "ExpressionStatement",
"src": "171:18:7"
}
]
},
"functionSelector": "3fb5c1cb",
"implemented": true,
"kind": "function",
"modifiers": [],
"name": "setNumber",
"nameLocation": "125:9:7",
"parameters": {
"id": 21588,
"nodeType": "ParameterList",
"parameters": [
{
"constant": false,
"id": 21587,
"mutability": "mutable",
"name": "newNumber",
"nameLocation": "143:9:7",
"nodeType": "VariableDeclaration",
"scope": 21595,
"src": "135:17:7",
"stateVariable": false,
"storageLocation": "default",
"typeDescriptions": {
"typeIdentifier": "t_uint256",
"typeString": "uint256"
},
"typeName": {
"id": 21586,
"name": "uint256",
"nodeType": "ElementaryTypeName",
"src": "135:7:7",
"typeDescriptions": {
"typeIdentifier": "t_uint256",
"typeString": "uint256"
}
},
"visibility": "internal"
}
],
"src": "134:19:7"
},
"returnParameters": {
"id": 21589,
"nodeType": "ParameterList",
"parameters": [],
"src": "161:0:7"
},
"scope": 21603,
"stateMutability": "nonpayable",
"virtual": false,
"visibility": "public"
},
{
"id": 21602,
"nodeType": "FunctionDefinition",
"src": "202:53:7",
"body": {
"id": 21601,
"nodeType": "Block",
"src": "230:25:7",
"statements": [
{
"expression": {
"id": 21599,
"isConstant": false,
"isLValue": false,
"isPure": false,
"lValueRequested": false,
"nodeType": "UnaryOperation",
"operator": "++",
"prefix": false,
"src": "240:8:7",
"subExpression": {
"id": 21598,
"name": "number",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 21585,
"src": "240:6:7",
"typeDescriptions": {
"typeIdentifier": "t_uint256",
"typeString": "uint256"
}
},
"typeDescriptions": {
"typeIdentifier": "t_uint256",
"typeString": "uint256"
}
},
"id": 21600,
"nodeType": "ExpressionStatement",
"src": "240:8:7"
}
]
},
"functionSelector": "d09de08a",
"implemented": true,
"kind": "function",
"modifiers": [],
"name": "increment",
"nameLocation": "211:9:7",
"parameters": {
"id": 21596,
"nodeType": "ParameterList",
"parameters": [],
"src": "220:2:7"
},
"returnParameters": {
"id": 21597,
"nodeType": "ParameterList",
"parameters": [],
"src": "230:0:7"
},
"scope": 21603,
"stateMutability": "nonpayable",
"virtual": false,
"visibility": "public"
}
],
"abstract": false,
"baseContracts": [],
"canonicalName": "Counter",
"contractDependencies": [],
"contractKind": "contract",
"fullyImplemented": true,
"linearizedBaseContracts": [
21603
],
"name": "Counter",
"nameLocation": "74:7:7",
"scope": 21604,
"usedErrors": []
}
],
"license": "UNLICENSED"
},
"id": 7
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,14 +0,0 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
contract Counter {
uint256 public number;
function setNumber(uint256 newNumber) public {
number = newNumber;
}
function increment() public {
number++;
}
}

@ -9,6 +9,7 @@ import {Web3ProviderModule} from './app/tabs/web3-provider'
import {CompileAndRun} from './app/tabs/compile-and-run'
import {PluginStateLogger} from './app/tabs/state-logger'
import {SidePanel} from './app/components/side-panel'
import {StatusBar} from './app/components/status-bar'
import {HiddenPanel} from './app/components/hidden-panel'
import {PinnedPanel} from './app/components/pinned-panel'
import {VerticalIcons} from './app/components/vertical-icons'
@ -350,7 +351,7 @@ class AppComponent {
solidityumlgen,
compilationDetails,
vyperCompilationDetails,
// remixGuide,
remixGuide,
contractFlattener,
solidityScript,
templates,
@ -392,10 +393,11 @@ class AppComponent {
const pluginManagerComponent = new PluginManagerComponent(appManager, this.engine)
const filePanel = new FilePanel(appManager)
this.statusBar = new StatusBar(filePanel, this.menuicons)
const landingPage = new LandingPage(appManager, this.menuicons, fileManager, filePanel, contentImport)
this.settings = new SettingsTab(Registry.getInstance().get('config').api, editor, appManager)
this.engine.register([this.menuicons, landingPage, this.hiddenPanel, this.sidePanel, filePanel, pluginManagerComponent, this.settings, this.pinnedPanel])
this.engine.register([this.menuicons, landingPage, this.hiddenPanel, this.sidePanel, this.statusBar, filePanel, pluginManagerComponent, this.settings, this.pinnedPanel])
// CONTENT VIEWS & DEFAULT PLUGINS
const openZeppelinProxy = new OpenZeppelinProxy(blockchain)
@ -477,6 +479,7 @@ class AppComponent {
'pluginStateLogger'
])
await this.appManager.activatePlugin(['mainPanel', 'menuicons', 'tabs'])
await this.appManager.activatePlugin(['statusBar'])
await this.appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await this.appManager.activatePlugin(['pinnedPanel'])
await this.appManager.activatePlugin(['home'])
@ -516,9 +519,6 @@ class AppComponent {
)
await this.appManager.activatePlugin(['solidity-script'])
await this.appManager.activatePlugin(['solcoder'])
await this.appManager.activatePlugin(['filePanel'])
// Set workspace after initial activation

@ -1,7 +1,6 @@
import { RemixApp } from '@remix-ui/app'
import axios from 'axios'
import React, { useEffect, useRef, useState } from 'react'
import { createRoot } from 'react-dom/client'
import * as packageJson from '../../../../../package.json'
import { fileSystem, fileSystems } from '../files/fileSystem'
import { indexedDBFileSystem } from '../files/filesystems/indexedDB'

@ -12,7 +12,7 @@ const sidePanel = {
displayName: 'Side Panel',
description: 'Remix IDE side panel',
version: packageJson.version,
methods: ['addView', 'removeView', 'currentFocus', 'pinView', 'unPinView']
methods: ['addView', 'removeView', 'currentFocus', 'pinView', 'unPinView', 'focus']
}
export class SidePanel extends AbstractPanel {

@ -0,0 +1,105 @@
import React from 'react'
import { EventEmitter } from 'events'
import { Plugin } from '@remixproject/engine'
import packageJson from '../../../../../package.json'
import { PluginViewWrapper } from '@remix-ui/helper'
import { PluginProfile, StatusBarInterface } from '../../types'
import { RemixUIStatusBar } from '@remix-ui/statusbar'
import { FilePanelType } from '@remix-ui/workspace'
import { VerticalIcons } from './vertical-icons'
const statusBarProfile: PluginProfile = {
name: 'statusBar',
displayName: 'Status Bar',
description: 'Remix IDE status bar panel',
methods: ['isAIActive'],
version: packageJson.version,
}
export class StatusBar extends Plugin implements StatusBarInterface {
htmlElement: HTMLDivElement
events: EventEmitter
filePanelPlugin: FilePanelType
verticalIcons: VerticalIcons
dispatch: React.Dispatch<any> = () => {}
currentWorkspaceName: string = ''
isGitRepo: boolean = false
isAiActive: boolean = false
constructor(filePanel: FilePanelType, veritcalIcons: VerticalIcons) {
super(statusBarProfile)
this.filePanelPlugin = filePanel
this.verticalIcons = veritcalIcons
this.events = new EventEmitter()
this.htmlElement = document.createElement('div')
this.htmlElement.setAttribute('id', 'status-bar')
this.filePanelPlugin
}
async isWorkspaceAGitRepo() {
const isGit = await this.call('fileManager', 'isGitRepo')
if (!isGit) return
this.isGitRepo = true
this.renderComponent()
}
async setCurrentGitWorkspaceName() {
if (!this.isGitRepo) return
const workspaceName = localStorage.getItem('currentWorkspace')
workspaceName && workspaceName.length > 0 ? this.currentWorkspaceName = workspaceName : this.currentWorkspaceName = 'unknown'
this.renderComponent()
}
async isAIActive() {
let aiActive
this.on('settings', 'copilotChoiceUpdated', async (isChecked) => {
aiActive = isChecked
this.isAiActive = isChecked
})
this.renderComponent()
return aiActive
}
onActivation(): void {
this.on('filePanel', 'workspaceInitializationCompleted', async () => {
const isGit = await this.call('fileManager', 'isGitRepo')
if (!isGit) return
const workspaceName = localStorage.getItem('currentWorkspace')
workspaceName && workspaceName.length > 0 ? this.currentWorkspaceName = workspaceName : this.currentWorkspaceName = ''
})
this.on('filePanel', 'switchToWorkspace', async (workspace: string) => {
await this.isWorkspaceAGitRepo()
if (!this.isGitRepo) {
this.currentWorkspaceName = 'Not a git repo'
return
}
const workspaceName = localStorage.getItem('currentWorkspace')
workspaceName && workspaceName.length > 0 ? this.currentWorkspaceName = workspaceName : this.currentWorkspaceName = 'error'
})
this.on('settings', 'copilotChoiceChanged', (isAiActive) => {
this.isAiActive = isAiActive
})
this.renderComponent()
}
setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
}
renderComponent() {
this.dispatch({
plugins: this,
})
}
updateComponent(state: any) {
return <RemixUIStatusBar statusBarPlugin={state.plugins} />
}
render() {
return (
<div data-id="status-bar-container">
<PluginViewWrapper plugin={this} />
</div>
)
}
}

@ -34,6 +34,7 @@ const profile = {
methods: [
'createNewFile',
'uploadFile',
'echoCall',
'getCurrentWorkspace',
'getAvailableWorkspaceName',
'getWorkspaces',

@ -257,6 +257,11 @@ export class TabProxy extends Plugin {
if ((name.endsWith('.vy') && icon === undefined) || title.includes('Vyper')) {
icon = 'assets/img/vyperLogo2.webp'
}
if (title === 'Solidity Compile Details') {
icon = 'assets/img/solidity.webp'
}
var slash = name.split('/')
const tabPath = slash.reverse()
@ -374,7 +379,9 @@ export class TabProxy extends Plugin {
const onZoomIn = () => this.editor.editorFontSize(1)
const onZoomOut = () => this.editor.editorFontSize(-1)
const onReady = (api) => { this.tabsApi = api }
const onReady = (api) => {
this.tabsApi = api
}
this.dispatch({
plugin: this,

@ -45,7 +45,6 @@ export class CompilationDetailsPlugin extends ViewPlugin {
async showDetails(sentPayload: any) {
await this.call('tabs', 'focus', 'compilationDetails')
setTimeout(() => {
// TODO: use the react API to render when the tab is focused and the plugin in the view.
this.payload = sentPayload
this.renderComponent()
}, 2000)

@ -1,12 +1,12 @@
import React from 'react'
import React, {useState} from 'react' // eslint-disable-line
import { ViewPlugin } from '@remixproject/engine-web'
import { PluginViewWrapper } from '@remix-ui/helper'
import { RemixAppManager } from '../../remixAppManager'
import { RemixUIGridView } from '@remix-ui/remix-ui-grid-view'
import { RemixUIGridSection } from '@remix-ui/remix-ui-grid-section'
import { RemixUIGridCell } from '@remix-ui/remix-ui-grid-cell'
import { ThemeKeys, ThemeObject } from '@microlink/react-json-view'
import * as Data from './remixGuideData.json'
//@ts-ignore
const _paq = (window._paq = window._paq || [])
@ -15,7 +15,6 @@ const profile = {
displayName: 'Remix Guide',
description: 'Learn remix with videos',
location: 'mainPanel',
methods: ['showDetails'],
events: []
}
@ -24,13 +23,20 @@ export class RemixGuidePlugin extends ViewPlugin {
appManager: RemixAppManager
element: HTMLDivElement
payload: any
themeStyle: any
theme: ThemeKeys | ThemeObject
showVideo: boolean
videoID: string
handleKeyDown: any
handleEscape: any
constructor(appManager: RemixAppManager) {
super(profile)
this.appManager = appManager
this.element = document.createElement('div')
this.element.setAttribute('id', 'remixGuideEl')
this.payload = {
sectionToExpandedCell: [['', '']],
data: {}
}
}
async onActivation() {
@ -38,24 +44,23 @@ export class RemixGuidePlugin extends ViewPlugin {
await this.call('tabs', 'focus', 'remixGuide')
this.renderComponent()
_paq.push(['trackEvent', 'plugin', 'activated', 'remixGuide'])
// Read the data
this.payload.data = Data
this.handleKeyDown = (event) => {
if (event.key === 'Escape') {
this.showVideo = false
this.renderComponent()
}
}
document.addEventListener('keydown', this.handleKeyDown);
}
onDeactivation(): void {
}
async showDetails(sentPayload: any) {
const contractName = Object.entries(sentPayload).find(([key, value]) => key)
await this.call('tabs', 'focus', 'remixGuide')
this.profile.displayName = `${contractName[0]}`
this.payload = sentPayload
const active = await this.call('theme', 'currentTheme')
this.renderComponent()
document.removeEventListener('keydown', this.handleKeyDown);
}
private handleThemeChange() {
this.on('theme', 'themeChanged', (theme: any) => {
this.renderComponent()
})
}
@ -64,6 +69,7 @@ export class RemixGuidePlugin extends ViewPlugin {
this.dispatch = dispatch
this.renderComponent()
}
render() {
return (
<div className="bg-dark" id="remixGuide">
@ -76,168 +82,88 @@ export class RemixGuidePlugin extends ViewPlugin {
this.dispatch({
...this,
...this.payload,
themeStyle: this.themeStyle,
theme: this.theme
showVideo: this.showVideo,
videoID: this.videoID
})
}
updateComponent(state: any) {
return (
<RemixUIGridView
plugin={this}
styleList={""}
logo='/assets/img/YouTubeLogo.webp'
enableFilter={true}
showUntagged={true}
showPin={true}
tagList={[
['beginner', 'danger'],
['advanced', 'warning'],
['AI', 'success'],
['plugins', 'secondary'],
['solidity', 'primary'],
['vyper', 'info'],
['L2', 'danger']
]}
title='Remix Guide'
description="Streamlined access to categorized video tutorials for mastering Remix IDE. From fundamentals to advanced techniques, level up your development skills with ease."
//themeStyle={state.themeStyle}
>
<RemixUIGridSection
<div className='d-flex'>
<RemixUIGridView
plugin={this}
title='Basics'
hScrollable= {true}
styleList={""}
logo='/assets/img/YouTubeLogo.webp'
enableFilter={true}
showUntagged={true}
showPin={false}
tagList={[
['beginner', 'danger'],
['advanced', 'warning'],
['AI', 'success'],
['plugins', 'secondary'],
['solidity', 'primary'],
['vyper', 'info'],
['L2', 'danger']
]}
title={Data.title}
description={Data.description}
>
<RemixUIGridCell
plugin={this}
title="first item"
tagList={['L2', 'AI']}
logo='/assets/img/soliditySurvey2023.webp'
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="next"
pinned={true}
tagList={['L2', 'plugins']}
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell> <RemixUIGridCell
plugin={this}
title="something"
pinned={false}
tagList={['solidity', 'plugins']}
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
tagList={['solidity']}
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell> <RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="Something very very long"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell> <RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
</RemixUIGridSection>
<RemixUIGridSection
plugin={this}
title='Basics not scrollable'
hScrollable= {false}
{ Data.sections.map(section => {
return <RemixUIGridSection
plugin={this}
title={section.title}
hScrollable= {true}
>
{ section.cells.map(cell => {
return <RemixUIGridCell
plugin={this}
title={cell.title}
tagList={cell.tagList}
expandViewEl={
cell.expandViewElement
}
handleExpand={() => {
this.showVideo = true
this.videoID = cell.expandViewElement.videoID
this.renderComponent()
}}
logo={cell.expandViewElement.logo}
>
<a href={"https://www.youtube.com/@" + cell.authorURL} target="__blank">
<img src={"//img.youtube.com/vi/" + this.videoID + "/0.jpg"} style={{ height: '70px', width: '70px' }}></img>
</a>
</RemixUIGridCell>
})}
</RemixUIGridSection>
})}
</RemixUIGridView>
{ state.showVideo && <div
data-id={`EnterModalDialogContainer-react`}
data-backdrop="static"
data-keyboard="false"
className={"modal d-flex"}
role="dialog"
style={{ justifyContent: "center" }}
>
<RemixUIGridCell
plugin={this}
title="first item"
logo='/assets/img/soliditySurvey2023.webp'
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="next"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell> <RemixUIGridCell
plugin={this}
title="something"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell> <RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell> <RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
</RemixUIGridSection>
</RemixUIGridView>
<div className="align-self-center pb-4" role="document">
<div
tabIndex={-1}
className={'modal-content remixModalContent mb-4'}
>
<div className="text-break remixModalBody d-flex flex-column p-3 justify-content-between" data-id={`EnterModalDialogModalBody-react`}>
<iframe style={{ minHeight: "500px", minWidth: "1000px" }} width="1000" height="500" src={"https://www.youtube.com/embed/" + this.videoID + "?si=ZdckOaSPR7VsLj_2"} allowFullScreen></iframe>
</div>
<div className="modal-footer d-flex flex-column">
<button onClick={() => {
this.showVideo = false
this.renderComponent()
}}>Close</button>
</div>
</div>
</div>
</div>}
</div>
)
}

@ -0,0 +1,47 @@
{
"logo": "/assets/img/YouTubeLogo.webp",
"title": "Remix Guide",
"description": "Streamlined access to categorized video tutorials for mastering Remix IDE. From fundamentals to advanced techniques, level up your development skills with ease.",
"sections": [
{
"title": "Basics",
"hScrollable": "true",
"cells": [
{
"title": "first item",
"tagList": [
"L2",
"AI"
],
"authorURL": "EatTheBlocks",
"expandViewElement": {
"videoID": "vH8T3In6ZkE",
"logo": "https://yt3.ggpht.com/9NFZbC9mkA152sSWJJgNBls6GlBdknsF-9gi6ZVk_xsHjmc82j3q1Pd5a--GCnOKUrP-YtNbHls=s48-c-k-c0x00ffffff-no-rj"
}
},
{
"title": "second item",
"tagList": [
"solidity",
"AI"
],
"expandViewElement": {
"videoID": "vH8T3In6ZkE",
"logo": "https://yt3.ggpht.com/9NFZbC9mkA152sSWJJgNBls6GlBdknsF-9gi6ZVk_xsHjmc82j3q1Pd5a--GCnOKUrP-YtNbHls=s48-c-k-c0x00ffffff-no-rj"
}
},
{
"title": "third item",
"tagList": [
"vyper",
"AI"
],
"expandViewElement": {
"videoID": "vH8T3In6ZkE",
"logo": "https://yt3.ggpht.com/9NFZbC9mkA152sSWJJgNBls6GlBdknsF-9gi6ZVk_xsHjmc82j3q1Pd5a--GCnOKUrP-YtNbHls=s48-c-k-c0x00ffffff-no-rj"
}
}
]
}
]
}

@ -47,6 +47,12 @@ export class SolCoder extends Plugin {
this.solgpt_chat_history = []
}
pushChatHistory(prompt, result){
const chat:ChatEntry = [prompt, result.data[0]]
this.solgpt_chat_history.push(chat)
if (this.solgpt_chat_history.length > this.max_history){this.solgpt_chat_history.shift()}
}
async code_generation(prompt): Promise<any> {
this.emit("aiInfering")
this.call('layout', 'maximizeTerminal')
@ -105,9 +111,7 @@ export class SolCoder extends Plugin {
}
if (result) {
this.call('terminal', 'log', { type: 'aitypewriterwarning', value: result.data[0] })
const chat:ChatEntry = [prompt, result.data[0]]
this.solgpt_chat_history.push(chat)
if (this.solgpt_chat_history.length >this.max_history){this.solgpt_chat_history.shift()}
this.pushChatHistory(prompt, result)
} else if (result.error) {
this.call('terminal', 'log', { type: 'aitypewriterwarning', value: "Error on request" })
}
@ -134,6 +138,7 @@ export class SolCoder extends Plugin {
).json()
if (result) {
this.call('terminal', 'log', { type: 'aitypewriterwarning', value: result.data[0] })
this.pushChatHistory(prompt, result)
}
return result.data[0]
} catch (e) {
@ -250,6 +255,7 @@ export class SolCoder extends Plugin {
).json()
if (result) {
this.call('terminal', 'log', { type: 'aitypewriterwarning', value: result.data[0] })
this.pushChatHistory(prompt, result)
}
return result.data[0]
} catch (e) {

@ -21,7 +21,7 @@
"editor.formatCode": "Format Code",
"editor.generateDocumentation": "Generate documentation for this function",
"editor.generateDocumentation2": "Generate documentation for the function \"{name}\"",
"editor.generateDocumentationByAI": "solidity code: {content}\n Generate the documentation for the function {currentFunction} using the Doxygen style syntax",
"editor.generateDocumentationByAI": "solidity code: {content}\n Generate the natspec documentation for the function {currentFunction} using the docstring style syntax. Only use docstring supported tags",
"editor.explainFunction": "Explain this function",
"editor.explainFunctionSol": "Explain this code",
"editor.explainFunction2": "Explain the function \"{name}\"",

@ -29,7 +29,7 @@
"home.dgitPluginDesc": "Add source control to your projects.",
"home.oneClickDappDesc": "Quickly generate smart contract interfaces",
"home.getStarted": "Get Started",
"home.projectTemplates": "Project Templates",
"home.projectTemplates": "Explore. Prototype. Create.",
"home.blankTemplateDesc": "Create an empty workspace.",
"home.remixDefaultTemplateDesc": "Create a workspace with sample files.",
"home.ozerc20TemplateDesc": "Create an ERC20 token by importing OpenZeppelin library.",
@ -39,7 +39,7 @@
"home.zeroxErc20TemplateDesc": "Create an ERC20 token by importing 0xProject contract.",
"home.learn": "Learn",
"home.learnEth1": "Remix Basics",
"home.learnEth1Desc":"An introduction to Remix's interface and basic operations.",
"home.learnEth1Desc": "An introduction to Remix's interface and basic operations.",
"home.learnEth2": "Intro to Solidity",
"home.learnEth2Desc": "Interactively learn Solidity beginner concepts.",
"home.remixAdvanced": "Deploying with Libraries",
@ -56,15 +56,16 @@
"home.remixDesktop": "Remix Desktop",
"home.searchDocumentation": "Search Documentation",
"home.files": "Files",
"home.newFile": "New File",
"home.newFile": "New",
"home.startCoding": "Start Coding",
"home.startCodingPlayground": "Open a playground for prototyping and simple learning",
"home.openFile": "Open File",
"home.openFile": "Open",
"home.openFileTooltip": "Open a File from your File System",
"home.accessFileSystem": "Access File System",
"home.accessFileSystem": "Connect to Local Filesystem",
"home.loadFrom": "Load from",
"home.resources": "Resources",
"home.connectToLocalhost": "Connect to Localhost",
"home.seeAllTutorials": "See all tutorials",
"home.maintainedByRemix": "Maintained by Remix"
}

@ -52,6 +52,19 @@
"solidity._comment_contract-selection.tsx": "libs/remix-ui/solidity-compiler/src/lib/contract-selection.tsx",
"solidity.publishOn": "Publish on",
"solidity.runStaticAnalysis": "Run Remix Analysis",
"solidity.runStaticAnalysis.iconTooltip": "Click to analyze this contract for vulnerabilities using Remix, Solhint and Slither analyzers",
"solidity.runSolidityScan": "Run SolidityScan",
"solidity.solScan.iconTooltip": "Click to scan this contract for vulnerabilities using SolidityScan, a third-party provider [BETA]",
"solidity.solScan.modalTitle": "Permission to share code",
"solidity.solScan.modalMessage": "To scan and analyze the contract for risks and vulnerabilities, its code will be shared with SolidityScan, a third-party provider. ",
"solidity.solScan.likeToContinue": "Would you like to continue?",
"solidity.solScan.modalOkLabel": "Continue",
"solidity.solScan.modalCancelLabel": "Cancel",
"solidity.solScan.errModalTitle": "Scan error",
"solidity.solScan.successModalTitle": "Scan result",
"solidity.flatten": "Flatten contracts before UML generation.",
"solidity.generateUML": "Generate a UML diagram of your contract.",
"solidity.flattenLabel": "Flatten",

@ -80,15 +80,6 @@
"udapp.pinnedAt": "Pinned at",
"udapp.filePath": "File path",
"udapp.solScan.iconTooltip": "Click to scan this contract for vulnerabilities using third-party SolidityScan [BETA]",
"udapp.solScan.modalTitle": "Permission to share code",
"udapp.solScan.modalMessage": "To scan the contract for vulnerabilities & possible risks, smart contract code will be shared to third-party SolidityScan (https://solidityscan.com/).\n\n Would you like to continue?",
"udapp.solScan.modalOkLabel": "Continue",
"udapp.solScan.modalCancelLabel": "Cancel",
"udapp.solScan.errModalTitle": "Scan error",
"udapp.solScan.successModalTitle": "Scan result",
"udapp._comment_recorderCardUI.tsx": "libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx",
"udapp.transactionsRecorded": "Transactions recorded",
"udapp.transactionsCountTooltip": "The number of recorded transactions",

@ -97,6 +97,7 @@ module.exports = class SettingsTab extends ViewPlugin {
updateCopilotChoice(isChecked) {
this.config.set('settings/copilot/suggest/activate', isChecked)
this.useCopilot = isChecked
this.emit('copilotChoiceUpdated', isChecked)
this.dispatch({
...this
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

@ -31,6 +31,7 @@ let requiredModules = [ // services + layout views + system views
'menuicons',
'filePanel',
'terminal',
'statusBar',
'settings',
'pluginManager',
'tabs',
@ -79,7 +80,8 @@ let requiredModules = [ // services + layout views + system views
'solhint',
'dgit',
'pinnedPanel',
'pluginStateLogger'
'pluginStateLogger',
'remixGuide'
]
@ -118,6 +120,7 @@ export function isNative(name) {
'solhint',
'solidityUnitTesting',
'layout',
'statusBar',
'notification',
'hardhat-provider',
'ganache-provider',
@ -158,7 +161,6 @@ export class RemixAppManager extends PluginManager {
if (Registry.getInstance().get('platform').api.isDesktop()) {
requiredModules = [...requiredModules, 'fs', 'electronTemplates', 'isogit', 'remix-templates', 'electronconfig', 'xterm', 'compilerloader', 'ripgrep']
}
}
async canActivatePlugin(from, to) {

@ -0,0 +1,19 @@
export interface PluginProfile {
name: string
displayName: string
description: string
keywords?: string[]
icon?: string
url?: string
methods?: string[]
events?: string[]
version?: string
}
export interface StatusBarInterface {
htmlElement: HTMLDivElement
events: EventEmitter
filePanelPlugin: FilePanelType
dispatch: React.Dispatch<any>
setDispatch(dispatch: React.Dispatch<any>): void
}

@ -170,7 +170,7 @@ class CopyFileAfterBuild {
apply(compiler) {
const onEnd = async () => {
try {
console.log('runnning CopyFileAfterBuild')
console.log('running CopyFileAfterBuild')
// This copy the raw-loader files used by the etherscan plugin to the remix-ide root folder.
// This is needed because by default the etherscan resources are served from the /plugins/etherscan/ folder,
// but the raw-loader try to access the resources from the root folder.

@ -1,23 +1,19 @@
import React, {useState, useEffect, useRef} from 'react'
import { useState, useEffect, useRef } from 'react'
import {remixClient} from './utils'
import {CompilationResult} from '@remixproject/plugin-api'
import { remixClient } from './utils'
import { CompilationResult } from '@remixproject/plugin-api'
// Components
import CompilerButton from './components/CompilerButton'
import WarnRemote from './components/WarnRemote'
import VyperResult from './components/VyperResult'
import LocalUrlInput from './components/LocalUrl'
import ToggleButtonGroup from 'react-bootstrap/ToggleButtonGroup'
import ToggleButton from 'react-bootstrap/ToggleButton'
import Button from 'react-bootstrap/Button'
import Accordion from 'react-bootstrap/Accordion'
import Card from 'react-bootstrap/Card'
import './app.css'
import {CustomTooltip} from '@remix-ui/helper'
import {Form} from 'react-bootstrap'
import {CompileErrorCard} from './components/CompileErrorCard'
import { CustomTooltip } from '@remix-ui/helper'
import { Form } from 'react-bootstrap'
import { CompileErrorCard } from './components/CompileErrorCard'
import CustomAccordionToggle from './components/CustomAccordionToggle'
interface AppState {
@ -89,11 +85,11 @@ const App = () => {
/** Update the environment state value */
function setEnvironment(environment: 'local' | 'remote') {
setState({...state, environment})
setState({ ...state, environment })
}
function setLocalUrl(url: string) {
setState({...state, localUrl: url})
setState({ ...state, localUrl: url })
}
function compilerUrl() {
@ -111,14 +107,19 @@ const App = () => {
spinnerIcon.current.classList.add('remixui_spinningIcon')
}
const [toggleAccordion, setToggleAccordion] = useState(false)
const [cloneCount, setCloneCount] = useState(0)
return (
<main id="vyper-plugin">
<section>
<div className="px-3 pt-3 mb-3 w-100">
<CustomTooltip placement="bottom" tooltipText="Clone a repo of Vyper examples. Switch to the File Explorer to see the examples.">
<Button data-id="add-repository" className="w-100 btn btn-secondary" onClick={() => remixClient.cloneVyperRepo()}>
<Button data-id="add-repository" className="w-100 btn btn-secondary" onClick={() => {
{cloneCount === 0 ? remixClient.cloneVyperRepo() : remixClient.cloneVyperRepo(cloneCount)}
setCloneCount((prev) => {
return ++prev
})
}}>
Clone a repo of Vyper examples
</Button>
</CustomTooltip>
@ -162,7 +163,7 @@ const App = () => {
in the .vy file.
</span>
<div className="px-3 w-100 mb-3 mt-1" id="compile-btn">
<CompilerButton compilerUrl={compilerUrl()} contract={contract} setOutput={(name, update) => setOutput({...output, [name]: update})} resetCompilerState={resetCompilerResultState} output={output} remixClient={remixClient}/>
<CompilerButton compilerUrl={compilerUrl()} contract={contract} setOutput={(name, update) => setOutput({ ...output, [name]: update })} resetCompilerState={resetCompilerResultState} output={output} remixClient={remixClient}/>
</div>
<article id="result" className="p-2 mx-3 border-top mt-2">

@ -1,9 +1,8 @@
import { ABIDescription} from '@remixproject/plugin-api'
import { ABIDescription } from '@remixproject/plugin-api'
import axios from 'axios'
import { remixClient } from './remix-client'
import _ from 'lodash'
export interface Contract {
name: string
content: string
@ -72,8 +71,8 @@ const buildError = (output) => {
const line = output.line
if (line) {
const lineColumnPos = {
start: {line: line - 1, column: 10},
end: {line: line - 1, column: 10}
start: { line: line - 1, column: 10 },
end: { line: line - 1, column: 10 }
}
// remixClient.highlight(lineColumnPos as any, _contract.name, '#e0b4b4')
} else {
@ -92,8 +91,8 @@ const buildError = (output) => {
}
if (location?.length > 0) {
const lineColumnPos = {
start: {line: parseInt(location[0]) - 1, column: 10},
end: {line: parseInt(location[0]) - 1, column: 10}
start: { line: parseInt(location[0]) - 1, column: 10 },
end: { line: parseInt(location[0]) - 1, column: 10 }
}
// remixClient.highlight(lineColumnPos as any, _contract.name, message)
}
@ -175,13 +174,11 @@ export async function compile(url: string, contract: Contract): Promise<any> {
throw new Error('Use extension .vy for Vyper.')
}
let contractName = contract['name']
const compilePackage = {
manifest: 'ethpm/3',
sources: {
[contractName] : {content : fixContractContent(contract.content)}
[contractName] : { content : fixContractContent(contract.content) }
}
}
let response = await axios.post(`${url}compile`, compilePackage )
@ -266,7 +263,6 @@ export function toStandardOutput(fileName: string, compilationResult: any): any
}
}
export async function compileContract(contract: string, compilerUrl: string, setOutput?: any, setLoadingSpinnerState?: React.Dispatch<React.SetStateAction<boolean>>, spinner?: boolean) {
remixClient.eventEmitter.emit('resetCompilerState', {})
spinner && spinner === true ? setLoadingSpinnerState && setLoadingSpinnerState(true) : null
@ -301,7 +297,7 @@ export async function compileContract(contract: string, compilerUrl: string, set
})
setLoadingSpinnerState && setLoadingSpinnerState(false)
remixClient.eventEmitter.emit('setOutput', {status: 'failed', message: output.message, title: 'Error compiling...', line: output.line, column: output.column, key: 1 })
remixClient.eventEmitter.emit('setOutput', { status: 'failed', message: output.message, title: 'Error compiling...', line: output.line, column: output.column, key: 1 })
output = null
return
}
@ -332,13 +328,10 @@ export async function compileContract(contract: string, compilerUrl: string, set
})
setLoadingSpinnerState && setLoadingSpinnerState(false)
remixClient.eventEmitter.emit('setOutput', {status: 'failed', message: err.message})
remixClient.eventEmitter.emit('setOutput', { status: 'failed', message: err.message })
}
}
export type StandardOutput = {
sources: {
[fileName: string]: {

@ -1,9 +1,9 @@
import {HighlightPosition, CompilationResult, RemixApi, customAction} from '@remixproject/plugin-api'
import {Api, Status} from '@remixproject/plugin-utils'
import {createClient} from '@remixproject/plugin-webview'
import {PluginClient} from '@remixproject/plugin'
import {Contract, compileContract} from './compiler'
import {ExampleContract} from '../components/VyperResult'
import { HighlightPosition, CompilationResult, RemixApi, customAction } from '@remixproject/plugin-api'
import { Api, Status } from '@remixproject/plugin-utils'
import { createClient } from '@remixproject/plugin-webview'
import { PluginClient } from '@remixproject/plugin'
import { Contract, compileContract } from './compiler'
import { ExampleContract } from '../components/VyperResult'
import EventEmitter from 'events'
import { Plugin } from "@remixproject/engine";
import { CustomRemixApi } from '@remix-api'
@ -51,7 +51,7 @@ export class RemixClient extends PluginClient<any, CustomRemixApi> {
}
/** Load Ballot contract example into the file manager */
async loadContract({name, address}: ExampleContract) {
async loadContract({ name, address }: ExampleContract) {
try {
const content = await this.client.call('contentImport', 'resolve', address)
await this.client.call('fileManager', 'setFile', content.cleanUrl, content.content)
@ -79,7 +79,8 @@ export class RemixClient extends PluginClient<any, CustomRemixApi> {
}
}
async cloneVyperRepo() {
async cloneVyperRepo(count?: number) {
try {
// @ts-ignore
this.call('notification', 'toast', 'cloning Snekmate Vyper repository...')
@ -103,7 +104,7 @@ export class RemixClient extends PluginClient<any, CustomRemixApi> {
// @ts-ignore
'notification',
'toast',
'Snekmate Vyper repository cloned, the workspace snekmate has been created.'
'Vyper repository cloned, the workspace Vyper has been created.'
)
} catch (e) {
// @ts-ignore

@ -2,12 +2,12 @@ const fs = require('fs')
let value = fs.readFileSync('./done.json')
value = JSON.parse(value)
const inDone = value.data.search.edges[0].node.project.columns.edges[0].node.cards.edges
const inDone = value.data.repository.projectV2.items.edges
let data = ''
console.log(inDone.length, 'issues/Prs\n')
data = inDone.length + ' issues/Prs\n'
console.log(inDone.length, 'PRs\n')
data = inDone.length + ' PRs\n'
for (let card of inDone) {
if (card.node.content.url && card.node.content.merged !== false) {
if (card.node.content.url && card.node.content.merged && card.node.content.merged !== false) {
data += `${card.node.content.title} - ${card.node.content.url}\n`
}
}
@ -24,50 +24,24 @@ console.log('done.txt updated')
- get the result in the file done.txt
/*
{
search(type: REPOSITORY, query: "remix-project", first: 1) {
edges {
node {
__typename
... on Repository {
owner {
id
}
name
project(number: 31) {
number
name
columns(last: 1) {
edges {
node {
name
cards(first: 100) {
edges {
cursor
node {
id
note
state
content {
... on Issue {
url
id
number
title
}
... on PullRequest {
repository(owner: "ethereum", name: "remix-project") {
name
projectV2(number: 52) {
url
items(first: 100) {
totalCount
edges {
node {
content {
... on PullRequest {
url
id
number
title
merged
}
}
}
}
}
}
}
}
}
}

@ -1,11 +1,17 @@
import { signTypedData, SignTypedDataVersion, TypedMessage, MessageTypes } from '@metamask/eth-sig-util'
import { privateToAddress, toChecksumAddress, isValidPrivate, Address, toBytes, bytesToHex, Account } from '@ethereumjs/util'
import { privateKeyToAccount } from 'web3-eth-accounts'
import { toBigInt } from 'web3-utils'
import * as crypto from 'crypto'
type AccountType = {
nonce: number,
privateKey: Uint8Array
}
export class Web3Accounts {
accounts: Record<string, unknown>
accountsKeys: Record<string, unknown>
accounts: Record<string, AccountType>
accountsKeys: Record<string, string>
vmContext
constructor (vmContext) {
@ -73,7 +79,9 @@ export class Web3Accounts {
eth_accounts: this.eth_accounts.bind(this),
eth_getBalance: this.eth_getBalance.bind(this),
eth_sign: this.eth_sign.bind(this),
eth_chainId: this.eth_chainId.bind(this)
eth_chainId: this.eth_chainId.bind(this),
eth_signTypedData: this.eth_signTypedData_v4.bind(this), // default call is using V4
eth_signTypedData_v4: this.eth_signTypedData_v4.bind(this)
}
}
@ -108,4 +116,49 @@ export class Web3Accounts {
eth_chainId (_payload, cb) {
return cb(null, '0x539') // 0x539 is hex of 1337
}
eth_signTypedData_v4 (payload, cb) {
const address: string = payload.params[0]
const typedData: TypedMessage<MessageTypes> = payload.params[1]
try {
if (this.accounts[toChecksumAddress(address)] == null) {
throw new Error("cannot sign data; no private key");
}
if (typeof typedData === "string") {
throw new Error("cannot sign data; string sent, expected object");
}
if (!typedData.types) {
throw new Error("cannot sign data; types missing");
}
if (!typedData.types.EIP712Domain) {
throw new Error("cannot sign data; EIP712Domain definition missing");
}
if (!typedData.domain) {
throw new Error("cannot sign data; domain missing");
}
if (!typedData.primaryType) {
throw new Error("cannot sign data; primaryType missing");
}
if (!typedData.message) {
throw new Error("cannot sign data; message missing");
}
const ret = signTypedData({
privateKey: Buffer.from(this.accounts[toChecksumAddress(address)].privateKey),
data: typedData,
version: SignTypedDataVersion.V4
})
cb(null, ret)
} catch (e) {
cb(e.message)
}
}
}

@ -11,7 +11,6 @@ import { Transactions } from './methods/transactions'
import { Debug } from './methods/debug'
import { VMContext } from './vm-context'
import { Web3PluginBase } from 'web3'
import { Block } from '@ethereumjs/block'
export interface JSONRPCRequestPayload {
params: any[];
@ -109,11 +108,11 @@ export class Provider {
callback(new Error('unknown method ' + payload.method))
}
sendAsync (payload: JSONRPCRequestPayload, callback: (err: Error, result?: JSONRPCResponsePayload) => void) {
async sendAsync (payload: JSONRPCRequestPayload, callback?: (err: Error, result?: JSONRPCResponsePayload) => void) : Promise<JSONRPCResponsePayload> {
return new Promise((resolve,reject)=>{
const cb = (err, result) => {
if (typeof callback==='function'){
callback(err,result)
return callback(err, result)
}
if (err){
return reject(err)
@ -125,7 +124,12 @@ export class Provider {
}
send (payload, callback) {
return this.sendAsync(payload,callback)
this.sendAsync(payload, callback)
}
async request (payload: JSONRPCRequestPayload) : Promise<any> {
const ret = await this.sendAsync(payload)
return ret.result
}
isConnected () {

@ -39,4 +39,40 @@ describe('Accounts', () => {
assert.deepEqual(typeof signature === 'string' ? signature.length : signature.signature.length, 132)
})
})
describe('eth_signTypedData', () => {
it('should sign typed data', async () => {
const accounts: string[] = await web3.eth.getAccounts()
const typedData = {
domain: {
chainId: 1,
name: "Example App",
verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
version: "1",
},
message: {
prompt: "Welcome! In order to authenticate to this website, sign this request and your public address will be sent to the server in a verifiable way.",
createdAt: 1718570375196,
},
primaryType: 'AuthRequest',
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
AuthRequest: [
{ name: 'prompt', type: 'string' },
{ name: 'createdAt', type: 'uint256' },
],
},
};
const result = await web3.currentProvider.request({
method: 'eth_signTypedData',
params: [accounts[0], typedData]
})
assert.equal(result, '0x248d23de0e23231370db8aa21ad5908ca90c33ae2b8c611b906674bda6b1a8b85813f945c2ea896316e240089029619ab3d801a1b098c199bd462dd8026349da1c')
})
})
})

@ -23,7 +23,18 @@ const DragBar = (props: IRemixDragBarUi) => {
if (props.hidden) {
setDragBarPosX(offset)
} else if (props.layoutPosition === 'left') {
setDragBarPosX(offset + props.refObject.current.offsetWidth)
const checkResolution = () => {
const width = window.innerWidth
const height = window.innerHeight
if (height <= 781 && width <= 1150) {
setDragBarPosX(props.minWidth - 50)
} else {
setDragBarPosX(props.minWidth + 50)
}
}
checkResolution()
props.refObject.current.style.width = props.minWidth + 'px'
} else if (props.layoutPosition === 'right') {
setDragBarPosX(offset)
}

@ -217,8 +217,11 @@ const RemixApp = (props: IRemixAppUi) => {
layoutPosition='right'
></DragBar>
}
<div>{props.app.hiddenPanel.render()}</div>
<div className="statusBar fixed-bottom">
{props.app.statusBar.render()}
</div>
</div>
<div>{props.app.hiddenPanel.render()}</div>
<AppDialogs></AppDialogs>
<DialogViewPlugin></DialogViewPlugin>
</AppProvider>

@ -20,6 +20,7 @@ pre {
overflow : hidden;
flex : 1;
min-width : 320px;
padding-bottom : 1.4rem;
}
.iconpanel {
display : flex;
@ -27,12 +28,17 @@ pre {
overflow : hidden;
width : 50px;
user-select : none;
padding-bottom : 1.4rem;
}
.sidepanel {
display : flex;
flex-direction : row-reverse;
width : 320px;
transition : width 0.25s;
padding-bottom : 1.4rem;
}
.statusBar {
}
.pinnedpanel {
width : 320px;

@ -0,0 +1,37 @@
/* eslint-disable no-control-regex */
import { EditorUIProps, monacoTypes } from '@remix-ui/editor';
export class RemixSolidityDocumentationProvider implements monacoTypes.languages.InlineCompletionsProvider{
props:EditorUIProps
monaco:any
completion:string
constructor(completion: any){
this.completion = completion
}
async provideInlineCompletions(model: monacoTypes.editor.ITextModel, position: monacoTypes.Position, context: monacoTypes.languages.InlineCompletionContext, token: monacoTypes.CancellationToken): Promise<monacoTypes.languages.InlineCompletions<monacoTypes.languages.InlineCompletion>> {
const item: monacoTypes.languages.InlineCompletion = {
insertText: this.completion
};
return {
items: [item],
enableForwardStability: true
}
}
handleItemDidShow?(completions: monacoTypes.languages.InlineCompletions<monacoTypes.languages.InlineCompletion>, item: monacoTypes.languages.InlineCompletion, updatedInsertText: string): void {
}
handlePartialAccept?(completions: monacoTypes.languages.InlineCompletions<monacoTypes.languages.InlineCompletion>, item: monacoTypes.languages.InlineCompletion, acceptedCharacters: number): void {
}
freeInlineCompletions(completions: monacoTypes.languages.InlineCompletions<monacoTypes.languages.InlineCompletion>): void {
}
groupId?: string;
yieldsToGroupIds?: string[];
toString?(): string {
throw new Error('Method not implemented.');
}
}

@ -16,6 +16,7 @@ import { retrieveNodesAtPosition } from './helpers/retrieveNodesAtPosition'
import { RemixHoverProvider } from './providers/hoverProvider'
import { RemixReferenceProvider } from './providers/referenceProvider'
import { RemixCompletionProvider } from './providers/completionProvider'
import { RemixSolidityDocumentationProvider } from './providers/documentationProvider'
import { RemixHighLightProvider } from './providers/highlightProvider'
import { RemixDefinitionProvider } from './providers/definitionProvider'
import { RemixCodeActionProvider } from './providers/codeActionProvider'
@ -23,6 +24,7 @@ import './remix-ui-editor.css'
import { circomLanguageConfig, circomTokensProvider } from './syntaxes/circom'
import { IPosition } from 'monaco-editor'
import { RemixInLineCompletionProvider } from './providers/inlineCompletionProvider'
import { providers } from 'ethers'
const _paq = (window._paq = window._paq || [])
enum MarkerSeverity {
@ -180,6 +182,8 @@ export const EditorUI = (props: EditorUIProps) => {
const currentFunction = useRef('')
const currentFileRef = useRef('')
const currentUrlRef = useRef('')
let currenFunctionNode = useRef('')
// const currentDecorations = useRef({ sourceAnnotationsPerFile: {}, markerPerFile: {} }) // decorations that are currently in use by the editor
// const registeredDecorations = useRef({}) // registered decorations
@ -739,17 +743,62 @@ export const EditorUI = (props: EditorUIProps) => {
}
let gptGenerateDocumentationAction
const extractNatspecComments = (codeString: string): string => {
const natspecCommentRegex = /\/\*\*[\s\S]*?\*\//g;
const comments = codeString.match(natspecCommentRegex);
return comments ? comments[0] : "";
}
const executeGptGenerateDocumentationAction = {
id: 'generateDocumentation',
label: intl.formatMessage({ id: 'editor.generateDocumentation' }),
contextMenuOrder: 0, // choose the order
contextMenuGroupId: 'gtp', // create a new grouping
keybindings: [],
keybindings: [
// Keybinding for Ctrl + D
monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.KeyD
],
run: async () => {
const unsupportedDocTags = ['@title'] // these tags are not supported by the current docstring parser
const file = await props.plugin.call('fileManager', 'getCurrentFile')
const content = await props.plugin.call('fileManager', 'readFile', file)
const message = intl.formatMessage({ id: 'editor.generateDocumentationByAI' }, { content, currentFunction: currentFunction.current })
await props.plugin.call('solcoder', 'code_explaining', message)
const cm = await props.plugin.call('solcoder', 'code_explaining', message)
const natSpecCom = "\n" + extractNatspecComments(cm)
const cln = await props.plugin.call('codeParser', "getLineColumnOfNode", currenFunctionNode)
const range = new monacoRef.current.Range(cln.start.line, cln.start.column, cln.start.line, cln.start.column)
const lines = natSpecCom.split('\n')
const newNatSpecCom = []
for (let i = 0; i < lines.length; i++) {
let cont = false
for (let j = 0; j < unsupportedDocTags.length; j++) {
if (lines[i].includes(unsupportedDocTags[j])) {
cont = true
break
}
}
if (cont) {continue}
if (i <= 1) { newNatSpecCom.push(' '.repeat(cln.start.column) + lines[i].trimStart()) }
else { newNatSpecCom.push(' '.repeat(cln.start.column + 1) + lines[i].trimStart()) }
}
// TODO: activate the provider to let the user accept the documentation suggestion
// const provider = new RemixSolidityDocumentationProvider(natspecCom)
// monacoRef.current.languages.registerInlineCompletionsProvider('solidity', provider)
editor.executeEdits('clipboard', [
{
range: range,
text: newNatSpecCom.join('\n'),
forceMoveMarkers: true,
},
]);
_paq.push(['trackEvent', 'ai', 'solcoder', 'generateDocumentation'])
},
}
@ -760,7 +809,10 @@ export const EditorUI = (props: EditorUIProps) => {
label: intl.formatMessage({ id: 'editor.explainFunction' }),
contextMenuOrder: 1, // choose the order
contextMenuGroupId: 'gtp', // create a new grouping
keybindings: [],
keybindings: [
// Keybinding for Ctrl + Shift + E
monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyMod.Shift | monacoRef.current.KeyCode.KeyE
],
run: async () => {
const file = await props.plugin.call('fileManager', 'getCurrentFile')
const content = await props.plugin.call('fileManager', 'readFile', file)
@ -776,7 +828,10 @@ export const EditorUI = (props: EditorUIProps) => {
label: intl.formatMessage({ id: 'editor.explainFunctionSol' }),
contextMenuOrder: 1, // choose the order
contextMenuGroupId: 'sol-gtp', // create a new grouping
keybindings: [],
keybindings: [
// Keybinding for Ctrl + E
monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.KeyE
],
run: async () => {
const file = await props.plugin.call('fileManager', 'getCurrentFile')
const content = await props.plugin.call('fileManager', 'readFile', file)
@ -862,6 +917,8 @@ export const EditorUI = (props: EditorUIProps) => {
const functionImpl = nodesAtPosition.find((node) => node.kind === 'function')
if (functionImpl) {
currentFunction.current = functionImpl.name
currenFunctionNode = functionImpl
executeGptGenerateDocumentationAction.label = intl.formatMessage({ id: 'editor.generateDocumentation2' }, { name: functionImpl.name })
gptGenerateDocumentationAction = editor.addAction(executeGptGenerateDocumentationAction)
executegptExplainFunctionAction.label = intl.formatMessage({ id: 'editor.explainFunction2' }, { name: functionImpl.name })

@ -16,15 +16,16 @@ export const CustomCheckbox = (props: CustomCheckboxProps) => {
if (!textColor || textColor == '') textColor = filterCon.keyValueMap[props.label].color
return (
<div id={textColor + props.label} className="h-80 mx-1 align-items-center custom-control custom-checkbox" style={{ minWidth: '4rem' }}>
<div id={textColor + props.label}
className="h-80 mx-1 align-items-center custom-control custom-checkbox"
style={{ minWidth: '4rem' }}
>
<input
className="custom-control-input"
id={"GVCheckbox" + props.label}
defaultChecked={defChecked}
onChange={e => {
if (props.label == 'no tag')
filterCon.showUntagged = ! filterCon.showUntagged
else filterCon.updateValue(props.label, e.target.checked, textColor)}}
filterCon.updateValue(props.label, e.target.checked, textColor)}}
type="checkbox"
/>
<label

@ -3,9 +3,10 @@ import React, { createContext, useState, useContext } from 'react';
interface FilterContextType {
showUntagged: boolean
showPin: boolean
keyValueMap: Record<string, { enabled: boolean; color: string; }>;
keyValueMap: Record<string, { enabled: boolean; color: string; }>
updateValue: (key: string, enabled: boolean, color: string) => void
addValue: (key: string, enabled: boolean, color: string) => void
filter: string
}
const FiltersContext = createContext<FilterContextType>({
showUntagged: false,
@ -13,6 +14,7 @@ const FiltersContext = createContext<FilterContextType>({
keyValueMap: {},
updateValue: () => {},
addValue: () => {},
filter: ""
});
export default FiltersContext

@ -38,6 +38,12 @@
top: 0.1rem;
}
.remixui_grid_cell_tags_no_pin {
position: relative;
right: 0rem;
top: 0.1rem;
}
.remixui_grid_cell_tag {
font-size: x-small;
font-weight: bolder;

@ -1,4 +1,4 @@
import React, {useState, useEffect, useContext, useRef, ReactNode} from 'react' // eslint-disable-line
import React, {useState, useEffect, useContext, useRef, ReactNode, ReactHTMLElement} from 'react' // eslint-disable-line
import './remix-ui-grid-cell.css'
import FiltersContext from "./filtersContext"
@ -16,67 +16,97 @@ interface RemixUIGridCellProps {
pinned?: boolean
pinStateCallback?: any
logo?: string
title?: string
title: string
tagList?: string[] // max 8, others will be ignored
classList?: string
styleList?: any
children?: ReactNode
expandViewEl?: any
handleExpand?: any
}
export const RemixUIGridCell = (props: RemixUIGridCellProps) => {
const filterCon = useContext(FiltersContext)
const [anyEnabled, setAnyEnabled] = useState(false)
const [expand, setExpand] = useState(false)
const [pinned, setPinned] = useState<boolean>(props.pinned)
useEffect(() => {
if (props.tagList) setAnyEnabled(props.tagList.some((key) => filterCon.keyValueMap[key]?.enabled))
else setAnyEnabled(filterCon.showUntagged)
else setAnyEnabled(filterCon?.keyValueMap['no tag']?.enabled)
if (filterCon.filter != '') setAnyEnabled(anyEnabled && props.title.toLowerCase().includes(filterCon.filter))
console.log("pin ", pinned)
}, [filterCon, props.tagList])
/*const listenOnExpand = (key) => {
if (key === props.key) setExpand(props.toggleExpandView)
console.log('expand ----> ', key)
}
// The expanded widged should go to the grid-segment and be updated based on the expandedItem state variable of the plugin.
// The state var will work like theme dispattching is working.
useEffect(() => {
// TODO should be refactored to update based on state of plugin.
props.plugin.on(props.plugin.name, 'expandGridCell', listenOnExpand)
}, [])
*/
return (
<div className='mr-2 mt-3'>
{ anyEnabled && <div className='d-flex flex-grid'>
<div className={"d-flex mx-0 p-2 bg-light border border-secondary remixui_grid_cell_container " + props.classList || ''} data-id={"remixUIGS" + props.title}>
<div className="d-flex remixui_grid_cell flex-column">
<div className='d-flex flex-row pb-1 align-items-end' style={{ width: '8rem', height: '1rem' }}>
{ props.logo && <img className='remixui_grid_view_logo mr-1' src={props.logo} style={{ width: '1rem', height: '1rem' }}/> }
{ props.title && <label
className='m-0 p-0 align-items-left'
style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontSize: 'xx-small' }}
>
{ props.title }
</label> }
<div className='mr-2 mt-3' onClick={() => {
if (props.expandViewEl)
props.handleExpand(!expand)
else return
}}>
{ anyEnabled && <div className='d-flex flex-column'>
<div className='d-flex flex-grid'>
<div className={"d-flex mx-0 p-2 bg-light border border-secondary remixui_grid_cell_container " + props.classList || ''} data-id={"remixUIGS" + props.title}>
<div className="d-flex remixui_grid_cell flex-column">
<div className='d-flex flex-row pb-1 align-items-end' style={{ width: '8rem', height: '1rem' }}>
{ props.logo && <img className='remixui_grid_view_logo mr-1' src={props.logo} style={{ width: '1rem', height: '1rem' }}/> }
{ props.title && <label
className='m-0 p-0 align-items-left'
style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontSize: 'xx-small' }}
>
{ props.title }
</label> }
</div>
{ props.children }
</div>
{ props.children }
</div>
</div>
{ filterCon.showPin && <button
className={`${pinned ? 'fa-duotone' : 'fa-light'}` + ` fa-map-pin text-info border-0 mb-0 remixui_grid_cell_pin`}
onClick={() => {
setPinned(!pinned)
props.pinStateCallback()
}}
></button>}
{ props.tagList && <div className='d-flex flex-column align-items-begin remixui_grid_cell_tags'>
{ Object.keys(props.tagList).map((key) => (
filterCon.keyValueMap[props.tagList[key]].enabled && (
<CustomTooltip
placement="right"
tooltipId="pluginManagerInactiveTitleLinkToDoc"
tooltipClasses="text-nowrap"
tooltipText={props.tagList[key]}
>
<span key={props.tagList[key]}
className={'remixui_grid_cell_tag bg-' + filterCon.keyValueMap[props.tagList[key]].color}
{ filterCon.showPin && <button
className={`${pinned ? 'fa-duotone' : 'fa-light'}` + ` fa-map-pin text-info border-0 mb-0 remixui_grid_cell_pin`}
onClick={() => {
setPinned(!pinned)
props.pinStateCallback()
}}
></button>}
{ props.tagList && <div className={`d-flex flex-column align-items-begin ` +`${filterCon.showPin ? 'remixui_grid_cell_tags' : 'remixui_grid_cell_tags_no_pin'}`}>
{ Object.keys(props.tagList).map((key) => (
filterCon.keyValueMap[props.tagList[key]].enabled && (
<CustomTooltip
placement="right"
tooltipId="pluginManagerInactiveTitleLinkToDoc"
tooltipClasses="text-nowrap"
tooltipText={props.tagList[key]}
>
</span>
</CustomTooltip>
)
)) }
</div> }
{ !props.tagList && <span
className={'remixui_grid_cell_tags'}>
</span> }
<span key={props.tagList[key]}
className={'remixui_grid_cell_tag bg-' + filterCon.keyValueMap[props.tagList[key]].color}
>
</span>
</CustomTooltip>
)
)) }
</div> }
{ !props.tagList && <span
className={'remixui_grid_cell_tags'}>
</span> }
</div>
{ expand && <div>
{ props.expandViewEl }
</div>
}
</div> }
</div>
)

@ -16,6 +16,7 @@ interface RemixUIGridSectionProps {
classList?: string
styleList?: any
children?: ReactNode
expandedCell?: any
}
export const RemixUIGridSection = (props: RemixUIGridSectionProps) => {
@ -30,6 +31,10 @@ export const RemixUIGridSection = (props: RemixUIGridSectionProps) => {
<div className={(props.hScrollable) ? `d-flex flex-row pb-2 overflow-auto` : `d-flex flex-wrap`}>
{ props.children }
</div>
{ props.expandedCell && <div>
{ props.expandedCell }
</div>
}
</div>
</div>
)

@ -1,7 +1,6 @@
import React, {useState, useEffect, useContext, useRef, ReactNode} from 'react' // eslint-disable-line
import './remix-ui-grid-view.css'
import { ThemeContext, themes } from './themeContext'
import CustomCheckbox from './components/customCheckbox'
import FiltersContext from "./filtersContext"
@ -28,7 +27,7 @@ interface RemixUIGridViewProps {
export const RemixUIGridView = (props: RemixUIGridViewProps) => {
const [keyValueMap, setKeyValueMap] = useState<Record<string, { enabled: boolean; color: string; }>>({});
const [filter, setFilter] = useState("")
const showUntagged = props.showUntagged || false
const showPin = props.showPin || false
const updateValue = (key: string, enabled: boolean, color?: string) => {
@ -39,6 +38,30 @@ export const RemixUIGridView = (props: RemixUIGridViewProps) => {
}))
}
const [state, setState] = useState<{
searchDisable: boolean
}>({
searchDisable: true
})
const searchInputRef = useRef(null)
const handleSearchKeyDown = (e: KeyboardEvent) => {
if (e.target !== searchInputRef.current) return
if (e.key === 'Enter') {
searchInputRef.current.value = ''
} else {
setState((prevState) => {
console.log("update filter", searchInputRef.current.value)
return {
...prevState,
searchDisable: searchInputRef.current.value === '',
filter: searchInputRef.current.value
}
})
setFilter(searchInputRef.current.value)
}
}
const addValue = (key: string, enabled: boolean, color: string) => {
// Check if the key already exists, if so, do not add
if (key in keyValueMap) {
@ -52,17 +75,10 @@ export const RemixUIGridView = (props: RemixUIGridViewProps) => {
}))
}
const { plugin } = props.plugin
const searchInputRef = useRef(null)
const [state, setState] = useState<{
themeQuality: {filter: string; name: string}
}>({
themeQuality: themes.light
})
// Initialize filters context with data from props
useEffect(() => {
document.addEventListener('keyup', (e) => handleSearchKeyDown(e))
if (props.tagList && Array.isArray(props.tagList)) {
const initialKeyValueMap: Record<string, { enabled: boolean; color: string; }> = {};
@ -74,71 +90,52 @@ export const RemixUIGridView = (props: RemixUIGridViewProps) => {
if (showUntagged) initialKeyValueMap['no tag'] = { enabled: true, color: 'primary' }
setKeyValueMap(initialKeyValueMap)
}
return () => {
document.removeEventListener('keyup', handleSearchKeyDown)
}
}, [])
useEffect(() => {
plugin?.call('theme', 'currentTheme').then((theme) => {
// update theme quality. To be used for for images
setState((prevState) => {
return {
...prevState,
themeQuality: theme.quality === 'dark' ? themes.dark : themes.light
}
})
})
plugin?.on('theme', 'themeChanged', (theme) => {
// update theme quality. To be used for for images
setState((prevState) => {
return {
...prevState,
themeQuality: theme.quality === 'dark' ? themes.dark : themes.light
}
})
})
}, [plugin])
return (
<FiltersContext.Provider value={{ showUntagged, showPin, keyValueMap, updateValue, addValue }}>
<FiltersContext.Provider value={{ showUntagged, showPin, keyValueMap, updateValue, addValue, filter }}>
<div className={"d-flex flex-column bg-dark w-100 h-100 remixui_grid_view_container " + props.classList || ''} data-id="remixUIGV">
<ThemeContext.Provider value={state.themeQuality}>
<div className="d-flex flex-column w-100 remixui_grid_view">
<div className='d-flex p-4 bg-light flex-column remixui_grid_view_titlebar'>
<div className='d-flex flex-row align-items-center mb-2'>
{ props.logo && <img className='remixui_grid_view_logo mr-2' src={props.logo} /> }
{ props.title && <h3 className='mb-0'>{ props.title }</h3> }
</div>
{ props.description && <div className='pb-3 remixui_grid_view_title'>{ props.description }</div> }
{ props.enableFilter && <div className='d-flex flex-row'>
<div className="d-flex flex-row pr-2 pb-1 align-items-center justify-content-between">
<div className='d-flex' id="GVFilter">
<button
className="remixui_grid_view_btn text-secondary form-control bg-light border d-flex align-items-center p-2 justify-content-center fas fa-filter bg-light"
onClick={(e) => {
_paq.push(['trackEvent', 'GridView' + props.title ? props.title : '', 'filter', searchInputRef.current.value])
//setstate
}}
></button>
<input
ref={searchInputRef}
type="text"
style={{ minWidth: '100px' }}
className="border form-control border-right-0 mr-4"
id="GVFilterInput"
placeholder={"Filter the list"}
data-id="RemixGVFilterInput"
/>
</div>
<div className='d-flex flex-row'>
{ Object.keys(keyValueMap).map((key) => (
<CustomCheckbox label={key} />
)) }
</div>
</div>
</div> }
<div className="d-flex flex-column w-100 remixui_grid_view">
<div className='d-flex p-4 bg-light flex-column remixui_grid_view_titlebar'>
<div className='d-flex flex-row align-items-center mb-2'>
{ props.logo && <img className='remixui_grid_view_logo mr-2' src={props.logo} /> }
{ props.title && <h3 className='mb-0'>{ props.title }</h3> }
</div>
{ props.children }
{ props.description && <div className='pb-3 remixui_grid_view_title'>{ props.description }</div> }
{ props.enableFilter && <div className='d-flex flex-row'>
<div className="d-flex flex-row pr-2 pb-1 align-items-center justify-content-between">
<div className='d-flex' id="GVFilter">
<button
disabled={state.searchDisable}
className="remixui_grid_view_btn text-secondary form-control bg-light border d-flex align-items-center p-2 justify-content-center fas fa-filter bg-light"
onClick={(e) => {
setFilter(searchInputRef.current.value)
_paq.push(['trackEvent', 'GridView' + props.title ? props.title : '', 'filter', searchInputRef.current.value])
}}
></button>
<input
ref={searchInputRef}
type="text"
style={{ minWidth: '100px' }}
className="border form-control border-right-0 mr-4"
id="GVFilterInput"
placeholder={"Filter the list"}
data-id="RemixGVFilterInput"
/>
</div>
<div className='d-flex flex-row'>
{ Object.keys(keyValueMap).map((key) => (
<CustomCheckbox label={key} />
)) }
</div>
</div>
</div> }
</div>
</ThemeContext.Provider>
{ props.children }
</div>
</div>
</FiltersContext.Provider>
)

@ -12,12 +12,9 @@ function HomeTabFeatured() {
const themeFilter = useContext(ThemeContext)
return (
<div className="pt-3 pl-2" id="hTFeaturedeSection">
<label style={{ fontSize: '1.2rem' }}>
<FormattedMessage id="home.featured" />
</label>
<div className="mb-2">
<div className="w-100 d-flex flex-column" style={{ height: '200px' }}>
<div className="pt-1 pl-2" id="hTFeaturedeSection">
<div className="mb-2 remix_ui-carousel-container">
<div className="w-100 d-flex flex-column rounded-3 remix_ui-carouselbox">
<ThemeContext.Provider value={themeFilter}>
<Carousel
arrows={false}
@ -34,16 +31,16 @@ function HomeTabFeatured() {
centerMode={false}
autoPlay={true}
keyBoardControl={true}
containerClass="border w-full carousel-container"
containerClass="border w-full carousel-container d-flex align-items-center"
sliderClass="h-100 justify-content-between"
deviceType={'desktop'}
itemClass=""
autoPlaySpeed={10000}
dotListClass="position-relative mt-2"
>
<div className="mr-1 pr-1 d-flex">
<div className="mr-1 pr-1 d-flex align-items-center justify-content-center h-100">
<a href={releaseDetails.moreLink} target="__blank">
<img src={'assets/img/remi_drums_whatsnew.webp'} style={{ flex: '1', height: '170px', maxWidth: '170px' }} alt=""></img>
<img src={'assets/img/remi_drums_whatsnew.webp'} className="remixui_carouselImage" alt=""></img>
</a>
<div className="h6 w-50 p-2 pl-4 align-self-center" style={{ flex: '1' }}>
<h5>{releaseDetails.version} {releaseDetails.title}</h5>
@ -65,9 +62,9 @@ function HomeTabFeatured() {
</a>
</div>
</div>
<div className="mr-1 pr-1 d-flex">
<div className="mr-1 pr-1 d-flex align-items-center justify-content-center h-100">
<a href="https://remix-project.org" target="__blank">
<img src={'assets/img/bgRemi_small.webp'} style={{ flex: '1', height: '170px', maxWidth: '170px' }} alt=""></img>
<img src={'assets/img/bgRemi_small.webp'} className="remixui_carouselImage" alt=""></img>
</a>
<div className="h6 w-50 p-2 pl-4 align-self-center" style={{ flex: '1' }}>
<h5>
@ -86,9 +83,9 @@ function HomeTabFeatured() {
</a>
</div>
</div>
<div className="mr-1 pr-1 d-flex">
<div className="mr-1 pr-1 d-flex align-items-center justify-content-center h-100">
<a href="https://www.youtube.com/@EthereumRemix/videos" target="__blank">
<img src={'/assets/img/YouTubeLogo.webp'} style={{ flex: '1', height: '170px', maxWidth: '170px' }} alt=""></img>
<img src={'/assets/img/YouTubeLogo.webp'} className="remixui_carouselImage" alt=""></img>
</a>
<div className="h6 w-50 p-2 pl-4 align-self-center" style={{ flex: '1' }}>
<h5>
@ -110,9 +107,9 @@ function HomeTabFeatured() {
</a>
</div>
</div>
<div className="mr-1 pr-1 d-flex">
<div className="mr-1 pr-1 d-flex align-items-center justify-content-center h-100">
<a href="https://docs.google.com/forms/d/e/1FAIpQLSd0WsJnKbeJo-BGrnf7WijxAdmE4PnC_Z4M0IApbBfHLHZdsQ/viewform" target="__blank">
<img src={'/assets/img/remixRewardBetaTester_small.webp'} style={{ flex: '1', height: '170px', maxWidth: '170px' }} alt=""></img>
<img src={'/assets/img/remixRewardBetaTester_small.webp'} className="remixui_carouselImage" alt=""></img>
</a>
<div className="h6 w-50 p-2 pl-4 align-self-center" style={{ flex: '1' }}>
<h5>

@ -84,7 +84,7 @@ function HomeTabFeaturedPlugins({ plugin }: HomeTabFeaturedPluginsProps) {
}
return (
<div className="pl-2 w-100" id="hTFeaturedPlugins">
<div className="pl-2 w-100 align-items-end remixui_featuredplugins_container" id="hTFeaturedPlugins">
<label className="" style={{ fontSize: '1.2rem' }}>
<FormattedMessage id="home.featuredPlugins" />
</label>

@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useState, useRef, useReducer, useEffect } from 'react'
import { FormattedMessage } from 'react-intl'
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
import {ModalDialog} from '@remix-ui/modal-dialog' // eslint-disable-line
import {Toaster} from '@remix-ui/toaster' // eslint-disable-line
const _paq = (window._paq = window._paq || []) // eslint-disable-line
import { CustomTooltip } from '@remix-ui/helper'
import { TEMPLATE_NAMES } from '@remix-ui/workspace'
@ -14,7 +14,7 @@ interface HomeTabFileProps {
const loadingInitialState = {
tooltip: '',
showModalDialog: false,
importSource: ''
importSource: '',
}
const loadingReducer = (state = loadingInitialState, action) => {
@ -22,7 +22,7 @@ const loadingReducer = (state = loadingInitialState, action) => {
...state,
tooltip: action.tooltip,
showModalDialog: false,
importSource: ''
importSource: '',
}
}
@ -45,7 +45,7 @@ function HomeTabFile({ plugin }: HomeTabFileProps) {
modalInfo: { title: '', loadItem: '', examples: [], prefix: '' },
importSource: '',
toasterMsg: '',
recentWorkspaces: []
recentWorkspaces: [],
})
const [, dispatch] = useReducer(loadingReducer, loadingInitialState)
@ -71,7 +71,9 @@ function HomeTabFile({ plugin }: HomeTabFileProps) {
if (!recents) {
newRecents = []
} else {
newRecents = recents.filter((el) => { return el !== name})
newRecents = recents.filter((el) => {
return el !== name
})
localStorage.setItem('recentWorkspaces', JSON.stringify(newRecents))
}
setState((prevState) => {
@ -85,8 +87,7 @@ function HomeTabFile({ plugin }: HomeTabFileProps) {
try {
plugin.off('filePanel', 'setWorkspace')
plugin.off('filePanel', 'workspaceDeleted')
} catch (e) {
}
} catch (e) {}
}
}, [plugin])
@ -162,7 +163,6 @@ contract HelloWorld {
} else {
await plugin.call('fileManager', 'open', '/contracts/HelloWorld.sol')
}
}
const uploadFile = async (target) => {
@ -216,14 +216,7 @@ contract HelloWorld {
return (
<>
<ModalDialog
id="homeTab"
title={'Import from ' + state.modalInfo.title}
okLabel="Import"
hide={!state.showModalDialog}
handleHide={() => hideFullMessage()}
okFn={() => processLoading(state.modalInfo.title)}
>
<ModalDialog id="homeTab" title={'Import from ' + state.modalInfo.title} okLabel="Import" hide={!state.showModalDialog} handleHide={() => hideFullMessage()} okFn={() => processLoading(state.modalInfo.title)}>
<div className="p-2 user-select-auto">
{state.modalInfo.loadItem !== '' && <span>Enter the {state.modalInfo.loadItem} you would like to load.</span>}
{state.modalInfo.examples.length !== 0 && (
@ -236,9 +229,9 @@ contract HelloWorld {
{state.modalInfo.prefix && <span className="text-nowrap align-self-center mr-2">ipfs://</span>}
<input
ref={inputValue}
type='text'
name='prompt_text'
id='inputPrompt_text'
type="text"
name="prompt_text"
id="inputPrompt_text"
className="w-100 mt-1 form-control"
data-id="homeTabModalDialogCustomPromptText"
value={state.importSource}
@ -253,62 +246,11 @@ contract HelloWorld {
</ModalDialog>
<Toaster message={state.toasterMsg} />
<div className="justify-content-start mt-1 p-2 d-flex flex-column" id="hTFileSection">
<label style={{ fontSize: '1.2rem' }}>
<FormattedMessage id="home.files" />
</label>
<div className="d-flex flex-column">
<div className="d-flex flex-row">
<CustomTooltip
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id='home.startCodingPlayground' />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button className="btn btn-primary text-nowrap p-2 mr-2 border my-1" data-id="homeTabStartCoding" style={{ width: 'fit-content' }} onClick={() => startCoding()}>
<FormattedMessage id="home.startCoding" />
</button>
</CustomTooltip>
<CustomTooltip
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id='home.openFileTooltip' />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<span>
<label className="btn text-nowrap p-2 mr-2 border my-1" style={{ width: 'fit-content', cursor: 'pointer' }} htmlFor="openFileInput">
<FormattedMessage id="home.openFile" />
</label>
<input
title="open file"
type="file"
id="openFileInput"
onChange={(event) => {
event.stopPropagation()
plugin.verticalIcons.select('filePanel')
uploadFile(event.target)
}}
multiple
/>
</span>
</CustomTooltip>
<CustomTooltip
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id="home.connectToLocalhost" />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button className="btn text-nowrap p-2 border my-1" style={{ width: 'fit-content' }} onClick={() => connectToLocalhost()}>
<FormattedMessage id="home.accessFileSystem" />
</button>
</CustomTooltip>
</div>
<div className="mb-3">
{(state.recentWorkspaces[0] || state.recentWorkspaces[1] || state.recentWorkspaces[2]) && (
<div className="d-flex flex-column">
<div className="d-flex flex-column mb-5 remixui_recentworkspace">
<label style={{ fontSize: '0.8rem' }} className="mt-3">
Recent workspaces
Recent Workspaces
</label>
{state.recentWorkspaces[0] && state.recentWorkspaces[0] !== '' && (
<a className="cursor-pointer mb-1 ml-2" href="#" onClick={(e) => handleSwichToRecentWorkspace(e, state.recentWorkspaces[0])}>
@ -328,36 +270,63 @@ contract HelloWorld {
</div>
)}
</div>
<label style={{ fontSize: '0.8rem' }} className="pt-3">
<FormattedMessage id="home.loadFrom" />
</label>
<div className="d-flex">
<button
className="btn p-2 border mr-2"
data-id="landingPageImportFromGitHubButton"
onClick={() =>
showFullMessage('GitHub', 'github URL', [
'https://github.com/0xcert/ethereum-erc721/src/contracts/tokens/nf-token-metadata.sol',
'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/67bca857eedf99bf44a4b6a0fc5b5ed553135316/contracts/access/Roles.sol',
])
}
>
GitHub
</button>
<button className="btn p-2 border mr-2" data-id="landingPageImportFromGistButton" onClick={() => importFromGist()}>
<div className="d-flex flex-column flex-nowrap pt-3">
<label style={{ fontSize: '1.2rem' }}>
<FormattedMessage id="home.files" />
</label>
<div className="d-flex flex-column">
<div className="d-flex flex-row">
<CustomTooltip placement={'top'} tooltipId="overlay-tooltip" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="home.startCodingPlayground" />} tooltipTextClasses="border bg-light text-dark p-1 pr-3">
<button className="btn btn-primary text-nowrap p-2 mr-2 border my-1" data-id="homeTabNewFile" style={{ width: 'fit-content' }} onClick={async () => await plugin.call('filePanel', 'createNewFile')}>
<FormattedMessage id="home.newFile" />
</button>
</CustomTooltip>
<CustomTooltip placement={'top'} tooltipId="overlay-tooltip" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="home.openFileTooltip" />} tooltipTextClasses="border bg-light text-dark p-1 pr-3">
<span>
<label className="btn text-nowrap p-2 mr-2 border my-1" style={{ width: 'fit-content', cursor: 'pointer' }} htmlFor="openFileInput">
<FormattedMessage id="home.openFile" />
</label>
<input
title="open file"
type="file"
id="openFileInput"
onChange={(event) => {
event.stopPropagation()
plugin.verticalIcons.select('filePanel')
uploadFile(event.target)
}}
multiple
/>
</span>
</CustomTooltip>
<button className="btn text-nowrap p-2 mr-2 border my-1" onClick={() => showFullMessage('Ipfs', 'ipfs hash', ['ipfs://QmQQfBMkpDgmxKzYaoAtqfaybzfgGm9b2LWYyT56Chv6xH'], 'ipfs://')}>
IPFS
</button>
<button className="btn text-nowrap p-2 mr-2 border my-1" data-id="landingPageImportFromGitHubButton" onClick={() => showFullMessage('GitHub', 'github URL', ['https://github.com/0xcert/ethereum-erc721/src/contracts/tokens/nf-token-metadata.sol', 'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/67bca857eedf99bf44a4b6a0fc5b5ed553135316/contracts/access/Roles.sol'])}>
Git Clone
</button>
<button className="btn text-nowrap p-2 mr-2 border my-1" data-id="landingPageImportFromGistButton" onClick={() => importFromGist()}>
Gist
</button>
<button className="btn p-2 border mr-2" onClick={() => showFullMessage('Ipfs', 'ipfs hash', ['ipfs://QmQQfBMkpDgmxKzYaoAtqfaybzfgGm9b2LWYyT56Chv6xH'], 'ipfs://')}>
IPFS
</button>
<button
className="btn p-2 border"
onClick={() =>
showFullMessage('Https', 'http/https raw content', ['https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol'])
}
>
</button>
<button
className="btn text-nowrap p-2 mr-2 border my-1"
onClick={() =>
showFullMessage('Https', 'http/https raw content', ['https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol'])
}
>
HTTPS
</button>
</button>
</div>
</div>
<div className="d-flex mt-2 align-items-end w-100">
<CustomTooltip placement={'top'} tooltipId="overlay-tooltip" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="home.connectToLocalhost" />} tooltipTextClasses="border bg-light text-dark p-1 pr-3">
<button className="btn btn-block text-nowrap p-2 border my-1" onClick={() => connectToLocalhost()}>
<i className="fa-regular fa-desktop pr-2"></i>
<FormattedMessage id="home.accessFileSystem" />
</button>
</CustomTooltip>
</div>
</div>
</div>
</>

@ -1,15 +1,15 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useEffect, useRef, useContext } from 'react'
import React, { useEffect, useRef, useContext, SyntheticEvent, useState } from 'react'
import { useIntl, FormattedMessage } from 'react-intl'
import { TEMPLATE_NAMES,TEMPLATE_METADATA } from '@remix-ui/workspace'
import { TEMPLATE_NAMES, TEMPLATE_METADATA } from '@remix-ui/workspace'
import { ThemeContext } from '../themeContext'
import Carousel from 'react-multi-carousel'
import WorkspaceTemplate from './workspaceTemplate'
import 'react-multi-carousel/lib/styles.css'
import CustomNavButtons from './customNavButtons'
import { appPlatformTypes, platformContext } from '@remix-ui/app'
import { Plugin } from "@remixproject/engine";
import { CustomRemixApi } from '@remix-api'
import { CustomTooltip } from '@remix-ui/helper'
declare global {
interface Window {
_paq: any
@ -20,12 +20,65 @@ interface HomeTabGetStartedProps {
plugin: any
}
type WorkspaceTemplate = {
gsID: string
workspaceTitle: string
description: string
projectLogo: string
templateName: string
}
const workspaceTemplates: WorkspaceTemplate[] = [
{
gsID: 'sUTLogo',
workspaceTitle: 'Start Coding',
description: 'Create a new project using this template.',
projectLogo: 'assets/img/remixverticaltextLogo.png',
templateName: 'remixDefault',
},
{
gsID: 'sUTLogo',
workspaceTitle: 'Circom',
description: 'Create a new ZK Project with Circom using this template.',
projectLogo: 'assets/img/circom.webp',
templateName: 'semaphore',
},
{
gsID: 'sUTLogo',
workspaceTitle: 'Uniswap',
description: 'Create a new MultiSig wallet using this template.',
projectLogo: 'assets/img/gnosissafeLogo.png',
templateName: 'uniswapV4Template',
},
{
gsID: 'sUTLogo',
workspaceTitle: 'ERC20',
description: 'Create a new ERC20 token using this template.',
projectLogo: 'assets/img/oxprojectLogo.png',
templateName: 'ozerc20',
},
{
gsID: 'sUTLogo',
workspaceTitle: 'NFT / ERC721',
description: 'Create a new ERC721 token using this template.',
projectLogo: 'assets/img/openzeppelinLogo.png',
templateName: 'ozerc721',
},
{
gsID: 'sUTLogo',
workspaceTitle: 'MultiSig',
description: 'Create a new MultiSig wallet using this template.',
projectLogo: 'assets/img/gnosissafeLogo.png',
templateName: 'gnosisSafeMultisig',
},
]
function HomeTabGetStarted({ plugin }: HomeTabGetStartedProps) {
const platform = useContext(platformContext)
const themeFilter = useContext(ThemeContext)
const intl = useIntl()
const carouselRef = useRef<any>({})
const carouselRefDiv = useRef(null)
const intl = useIntl()
useEffect(() => {
document.addEventListener('wheel', handleScroll)
@ -62,8 +115,7 @@ function HomeTabGetStarted({ plugin }: HomeTabGetStartedProps) {
}
const createWorkspace = async (templateName) => {
if (platform === appPlatformTypes.desktop){
if (platform === appPlatformTypes.desktop) {
await plugin.call('remix-templates', 'loadTemplateInNewWindow', templateName)
return
}
@ -99,90 +151,45 @@ function HomeTabGetStarted({ plugin }: HomeTabGetStartedProps) {
return (
<div className="pl-2" id="hTGetStartedSection">
<label style={{ fontSize: '1.2rem' }}>
<label className="pt-3" style={{ fontSize: '1.2rem' }}>
<FormattedMessage id="home.projectTemplates" />
</label>
<div ref={carouselRefDiv} className="w-100 d-flex flex-column">
<div ref={carouselRefDiv} className="w-100 d-flex flex-column pt-1">
<ThemeContext.Provider value={themeFilter}>
<Carousel
ref={carouselRef}
focusOnSelect={true}
customButtonGroup={<CustomNavButtons next={undefined} previous={undefined} goToSlide={undefined} parent={carouselRef} />}
arrows={false}
swipeable={false}
draggable={true}
showDots={false}
responsive={{
superLargeDesktop: {
breakpoint: { max: 4000, min: 3000 },
items: 5
},
desktop: {
breakpoint: { max: 3000, min: 1024 },
items: 5,
partialVisibilityGutter: 0
}
}}
renderButtonGroupOutside={true}
ssr={true} // means to render carousel on server-side.
keyBoardControl={true}
containerClass="carousel-container"
deviceType={'desktop'}
itemClass="w-100"
>
<WorkspaceTemplate
gsID="sUTLogo"
workspaceTitle="MultiSig"
description={
intl.formatMessage({ id: 'home.gnosisSafeMultisigTemplateDesc' })
}
projectLogo="assets/img/gnosissafeLogo.png"
callback={() => createWorkspace("gnosisSafeMultisig")}
/>
<WorkspaceTemplate
gsID="sUTLogo"
workspaceTitle="ERC20"
description={
intl.formatMessage({ id: 'home.zeroxErc20TemplateDesc' })
}
projectLogo="assets/img/oxprojectLogo.png"
callback={() => createWorkspace("zeroxErc20")}
/>
<WorkspaceTemplate
gsID="sourcifyLogo"
workspaceTitle="ERC20"
description={intl.formatMessage({ id: 'home.ozerc20TemplateDesc' })}
projectLogo="assets/img/openzeppelinLogo.png"
callback={() => createWorkspace('ozerc20')}
/>
<WorkspaceTemplate
gsID="sUTLogo"
workspaceTitle="ERC721"
description={intl.formatMessage({
id: 'home.ozerc721TemplateDesc'
})}
projectLogo="assets/img/openzeppelinLogo.png"
callback={() => createWorkspace("ozerc721")}
/>
<WorkspaceTemplate
gsID="sUTLogo"
workspaceTitle="ERC1155"
description={intl.formatMessage({
id: 'home.ozerc1155TemplateDesc'
})}
projectLogo="assets/img/openzeppelinLogo.png"
callback={() => createWorkspace("ozerc1155")}
/>
<WorkspaceTemplate
gsID="solhintLogo"
workspaceTitle="Basic"
description={intl.formatMessage({
id: 'home.remixDefaultTemplateDesc'
})}
projectLogo="assets/img/remixverticaltextLogo.png"
callback={() => createWorkspace("remixDefault")}
/>
</Carousel>
<div className="pt-3">
<div className="d-flex flex-row align-items-center mb-3 flex-nowrap">
{workspaceTemplates.slice(0, 3).map((template, index) => (
<CustomTooltip tooltipText={template.description} tooltipId={template.gsID} tooltipClasses="text-nowrap" tooltipTextClasses="border bg-light text-dark p-1 pr-3" placement="top-start" key={`${template.gsID}-${template.workspaceTitle}-${index}`}>
<button
key={index}
className={index === 0 ? 'btn btn-primary border p-2 text-nowrap mr-3' : index === workspaceTemplates.length - 1 ? 'btn border p-2 text-nowrap mr-2' : 'btn border p-2 text-nowrap mr-3'}
onClick={(e) => {
createWorkspace(template.templateName)
}}
data-id={`homeTabGetStarted${template.templateName}`}
>
{template.workspaceTitle}
</button>
</CustomTooltip>
))}
</div>
<div className="d-flex flex-row align-items-center mb-2 flex-nowrap">
{workspaceTemplates.slice(3, workspaceTemplates.length).map((template, index) => (
<CustomTooltip tooltipText={template.description} tooltipId={template.gsID} tooltipClasses="text-nowrap" tooltipTextClasses="border bg-light text-dark p-1 pr-3" placement="bottom-start" key={`${template.gsID}-${template.workspaceTitle}-${index}`}>
<button
key={index}
className={'btn border p-2 text-nowrap mr-3'}
onClick={() => {
createWorkspace(template.templateName)
}}
data-id={`homeTabGetStarted${template.workspaceTitle}`}
>
{template.workspaceTitle}
</button>
</CustomTooltip>
))}
</div>
</div>
</ThemeContext.Provider>
</div>
</div>

@ -3,8 +3,55 @@
import React, { useEffect, useState, useRef, useContext } from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { CustomTooltip } from '@remix-ui/helper'
import { Placement } from 'react-bootstrap/esm/Overlay'
const _paq = (window._paq = window._paq || []) // eslint-disable-line
type HometabIconSection = {
textToolip: JSX.Element
urlLink: string
iconClass: 'fa-youtube'|'fa-x-twitter'|'fa-linkedin'|'fa-medium'|'fa-discord'
placement: Placement
matomoTrackingEntry: string[]
}
const iconButtons: HometabIconSection[] = [
{
textToolip: <FormattedMessage id="home.remixYoutubePlaylist" />,
matomoTrackingEntry: ['trackEvent', 'hometab', 'socialMedia', 'youtube'],
urlLink: 'https://www.youtube.com/channel/UCjTUPyFEr2xDGN6Cg8nKDaA',
iconClass: 'fa-youtube',
placement: 'top'
},
{
textToolip: <FormattedMessage id="home.remixTwitterProfile" />,
matomoTrackingEntry: ['trackEvent', 'hometab', 'socialMedia', 'twitter'],
urlLink: 'https://twitter.com/EthereumRemix',
iconClass: 'fa-x-twitter',
placement: 'top'
},
{
textToolip: <FormattedMessage id="home.remixLinkedinProfile" />,
matomoTrackingEntry: ['trackEvent', 'hometab', 'socialmedia', 'linkedin'],
urlLink: 'https://www.linkedin.com/company/ethereum-remix/',
iconClass: 'fa-linkedin',
placement: 'top'
},
{
textToolip: <FormattedMessage id="home.remixMediumPosts" />,
matomoTrackingEntry: ['trackEvent', 'hometab', 'socialmedia', 'medium'],
urlLink: 'https://medium.com/remix-ide',
iconClass: 'fa-medium',
placement: 'top'
},
{
textToolip: <FormattedMessage id="home.joinUsOnDiscord" />,
matomoTrackingEntry: ['trackEvent', 'hometab', 'socialmedia', 'discord'],
urlLink: 'https://discord.gg/mh9hFCKkEq',
iconClass: 'fa-discord',
placement: 'top'
}
]
function HomeTabTitle() {
useEffect(() => {
document.addEventListener('keyup', (e) => handleSearchKeyDown(e))
@ -64,82 +111,25 @@ function HomeTabTitle() {
</div>
</div>
<span className="d-flex flex-nowrap align-self-end">
<CustomTooltip
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id="home.remixYoutubePlaylist" />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button
onClick={() => {
openLink('https://www.youtube.com/channel/UCjTUPyFEr2xDGN6Cg8nKDaA')
_paq.push(['trackEvent', 'hometab', 'socialMedia', 'youtube'])
}}
className="border-0 px-1 h-100 btn fab fa-youtube"
></button>
</CustomTooltip>
<CustomTooltip
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id="home.remixTwitterProfile" />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button
onClick={() => {
openLink('https://twitter.com/EthereumRemix')
_paq.push(['trackEvent', 'hometab', 'socialMedia', 'twitter'])
}}
className="border-0 px-1 h-100 btn fab fa-x-twitter"
></button>
</CustomTooltip>
<CustomTooltip
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id="home.remixLinkedinProfile" />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button
onClick={() => {
openLink('https://www.linkedin.com/company/ethereum-remix/')
_paq.push(['trackEvent', 'hometab', 'socialmedia', 'linkedin'])
}}
className="border-0 px-1 h-100 btn fab fa-linkedin"
></button>
</CustomTooltip>
<CustomTooltip
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id="home.remixMediumPosts" />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button
onClick={() => {
openLink('https://medium.com/remix-ide')
_paq.push(['trackEvent', 'hometab', 'socialmedia', 'medium'])
}}
className="border-0 h-100 px-1 btn fab fa-medium"
></button>
</CustomTooltip>
<CustomTooltip
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id="home.joinUsOnDiscord" />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button
onClick={() => {
openLink('https://discord.gg/mh9hFCKkEq')
_paq.push(['trackEvent', 'hometab', 'socialmedia', 'discord'])
}}
className="border-0 h-100 pl-1 pr-0 btn fab fa-discord"
></button>
</CustomTooltip>
{iconButtons.map((button, index) => (
<CustomTooltip
key={index}
placement={button.placement}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText={button.textToolip}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button
key={index}
onClick={() => {
openLink(button.urlLink)
_paq.push(button.matomoTrackingEntry)
}}
className={`border-0 h-100 pl-1 pr-0 btn fab ${button.iconClass}`}
></button>
</CustomTooltip>
))}
</span>
</div>
<b className="py-1 text-dark" style={{ fontStyle: 'italic' }}>
@ -149,27 +139,19 @@ function HomeTabTitle() {
<a className="remixui_home_text" onClick={() => _paq.push(['trackEvent', 'hometab', 'header', 'webSite'])} target="__blank" href="https://remix-project.org">
<FormattedMessage id="home.website" />
</a>
<a
{/* <a
className="pl-2 remixui_home_text"
onClick={() => _paq.push(['trackEvent', 'hometab', 'header', 'documentation'])}
target="__blank"
href="https://remix-ide.readthedocs.io/en/latest"
>
<FormattedMessage id="home.documentation" />
</a>
<a
className="pl-2 remixui_home_text"
onClick={() => _paq.push(['trackEvent', 'hometab', 'header', 'remixPlugin'])}
target="__blank"
href="https://remix-plugin-docs.readthedocs.io/en/latest/"
>
<FormattedMessage id="home.remixPlugin" />
</a>
</a> */}
<a
className="pl-2 remixui_home_text"
onClick={() => _paq.push(['trackEvent', 'hometab', 'header', 'remixDesktop'])}
target="__blank"
href="https://github.com/ethereum/remix-desktop/releases"
href="https://github.com/remix-project-org/remix-desktop-insiders"
>
<FormattedMessage id="home.remixDesktop" />
</a>

@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'
import { Dropdown, DropdownButton } from 'react-bootstrap'
import DropdownItem from 'react-bootstrap/DropdownItem'
import { localeLang } from './types/carouselTypes'
import { FormattedMessage } from 'react-intl'
export function LanguageOptions({ plugin }: { plugin: any }) {
const [langOptions, setLangOptions] = useState<string>()
@ -24,7 +25,10 @@ export function LanguageOptions({ plugin }: { plugin: any }) {
return (
<>
<div style={{ position: 'absolute', right: "1rem", paddingTop: "0.4rem" }}>
<div className="d-flex justify-content-between w-100 align-items-center pt-4">
<label style={{ fontSize: '1.2rem' }} className="ml-2 pb-0 mb-0">
<FormattedMessage id="home.featured" />
</label>
<Dropdown>
<Dropdown.Toggle title={langOptions} id="languagedropdown" size="sm" style={{ backgroundColor: 'var(--secondary)', color: 'var(--text)' }}>
{langOptions}

@ -114,3 +114,36 @@
background-color: var(--body-bg);
color: var(--text);
}
.remixui_recentworkspace {
height: 2.4rem;
}
.remixui_carouselImage {
flex: 1;
height: 20rem;
width: 20rem;
}
.remixui_carouselbox {
min-height: 25.12rem;
}
.remix_ui-carousel-container {
container: remix_ui-carousel-container / inline-size;
}
@container remix_ui-carousel-container (inline-size < 700px) {
.remix_ui-carouselbox {
min-height: 20rem;
margin-right: 10rem;
}
.remixui_carouselImage {
height: 12.5rem;
width: 12.5rem;
}
.remixui_recentworkspace {
height: 0.2rem;
}
}

@ -32,6 +32,7 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
}>({
themeQuality: themes.light
})
const [carouselWidth, setCarouselWidth] = useState(65)
useEffect(() => {
plugin.call('theme', 'currentTheme').then((theme) => {
@ -54,23 +55,39 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
})
}, [])
useEffect(() => {
const checkResolution = () => {
const width = window.innerWidth
const height = window.innerHeight
if (height < 781 && width < 1150) {
setCarouselWidth(75)
}
}
checkResolution()
return () => {
checkResolution()
}
}, [])
// border-right
return (
<div className="d-flex flex-column w-100" data-id="remixUIHTAll">
<div className="d-flex flex-column w-100 h-100" data-id="remixUIHTAll">
<ThemeContext.Provider value={state.themeQuality}>
<div className="d-flex flex-row w-100 custom_home_bg">
<div className="px-2 pl-3 justify-content-start d-flex border-right flex-column" id="remixUIHTLeft" style={{ width: 'inherit' }}>
<div className="d-flex flex-row w-100 h-100 custom_home_bg">
<div className="px-2 pl-3 justify-content-start border-right d-flex flex-column" id="remixUIHTLeft" style={{ width: `${100 - carouselWidth}%` }}>
<HomeTabTitle />
<HomeTabGetStarted plugin={plugin}></HomeTabGetStarted>
{!(platform === appPlatformTypes.desktop) ?
<HomeTabFile plugin={plugin} />:
<HomeTabFileElectron plugin={plugin}></HomeTabFileElectron>}
<HomeTabLearn plugin={plugin} />
{/* <HomeTabLearn plugin={plugin} /> */}
</div>
<div className="pl-2 pr-3 justify-content-start d-flex flex-column" style={{ width: '65%' }} id="remixUIHTRight">
<div className="pl-2 pr-3 justify-content-start d-flex flex-column" style={{ width: `${carouselWidth}%` }} id="remixUIHTRight">
<LanguageOptions plugin={plugin}/>
<HomeTabFeatured></HomeTabFeatured>
<HomeTabGetStarted plugin={plugin}></HomeTabGetStarted>
<HomeTabFeaturedPlugins plugin={plugin}></HomeTabFeaturedPlugins>
<HomeTabScamAlert></HomeTabScamAlert>
</div>
</div>
</ThemeContext.Provider>

@ -110,3 +110,7 @@ iframe {
.highlight {
animation: highlight 2s forwards;
}
.remixui_height {
height: 97vh;
}

@ -1,4 +1,5 @@
import { Profile } from '@remixproject/plugin-utils'
import EventEmitter from 'events'
export type PluginRecord = {
profile: Profile
@ -8,3 +9,22 @@ export type PluginRecord = {
class?: string
minimized?: boolean
}
export interface PluginProfile {
name: string
displayName: string
description: string
keywords?: string[]
icon?: string
url?: string
methods?: string[]
events?: string[]
version?: string
}
export interface StatusBarInterface extends Plugin {
htmlElement: HTMLDivElement
events: EventEmitter
dispatch: React.Dispatch<any>
setDispatch(dispatch: React.Dispatch<any>): void
}

@ -1,7 +1,7 @@
import { shortenAddress } from "@remix-ui/helper"
import { RunTab } from "../types/run-tab"
import { clearInstances, setAccount, setExecEnv } from "./actions"
import { displayNotification, displayPopUp, fetchAccountsListFailed, fetchAccountsListRequest, fetchAccountsListSuccess, setMatchPassphrase, setPassphrase } from "./payload"
import { displayNotification, fetchAccountsListFailed, fetchAccountsListRequest, fetchAccountsListSuccess, setMatchPassphrase, setPassphrase } from "./payload"
import { toChecksumAddress } from '@ethereumjs/util'
export const updateAccountBalances = async (plugin: RunTab, dispatch: React.Dispatch<any>) => {
@ -39,7 +39,7 @@ export const fillAccountsList = async (plugin: RunTab, dispatch: React.Dispatch<
dispatch(fetchAccountsListFailed(e.message))
}
} catch (e) {
dispatch(displayPopUp(`Cannot get account list: ${e}`))
plugin.call('notification', 'toast', `Cannot get account list: ${e}`)
}
}
@ -78,9 +78,9 @@ export const createNewBlockchainAccount = async (plugin: RunTab, dispatch: React
},
async (error, address) => {
if (error) {
return dispatch(displayPopUp('Cannot create an account: ' + error))
return plugin.call('notification', 'toast', 'Cannot create an account: ' + error)
}
dispatch(displayPopUp(`account ${address} created`))
plugin.call('notification', 'toast', `account ${address} created`)
await fillAccountsList(plugin, dispatch)
}
)
@ -89,7 +89,8 @@ export const createNewBlockchainAccount = async (plugin: RunTab, dispatch: React
export const signMessageWithAddress = (plugin: RunTab, dispatch: React.Dispatch<any>, account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => {
plugin.blockchain.signMessage(message, account, passphrase, (err, msgHash, signedData) => {
if (err) {
return displayPopUp(err)
console.error(err)
return plugin.call('notification', 'toast', typeof err === 'string' ? err : err.message)
}
dispatch(displayNotification('Signed Message', modalContent(msgHash, signedData), 'OK', null, () => {}, null))
})

@ -6,7 +6,7 @@ import { SolcInput, SolcOutput } from "@openzeppelin/upgrades-core"
// Used direct path to UpgradeableContract class to fix cyclic dependency error from @openzeppelin/upgrades-core library
import { UpgradeableContract } from '../../../../../../node_modules/@openzeppelin/upgrades-core/dist/standalone'
import { DeployMode, MainnetPrompt } from "../types"
import { displayNotification, displayPopUp, fetchProxyDeploymentsSuccess, setDecodedResponse, updateInstancesBalance } from "./payload"
import { displayNotification, fetchProxyDeploymentsSuccess, setDecodedResponse, updateInstancesBalance } from "./payload"
import { addInstance } from "./actions"
import { addressToString, logBuilder } from "@remix-ui/helper"
import Web3 from "web3"
@ -256,7 +256,7 @@ export const loadAddress = (plugin: RunTab, dispatch: React.Dispatch<any>, contr
const contractData = { name: '<at address>', abi, contract: { file: plugin.REACT_API.contracts.currentFile } } as ContractData
return addInstance(dispatch, { contractData, address, name: '<at address>' })
} else if (loadType === 'instance') {
if (!contract) return dispatch(displayPopUp('No compiled contracts found.'))
if (!contract) return plugin.call('notification', 'toast', 'No compiled contracts found.')
const currentFile = plugin.REACT_API.contracts.currentFile
const compiler = plugin.REACT_API.contracts.contractList[currentFile].find(item => item.alias === contract.name)
const contractData = getSelectedContract(contract.name, compiler.compiler)

@ -391,32 +391,27 @@ export function ContractDropdownUI(props: ContractDropdownProps) {
/>
</label>
)}
</div>
{props.remixdActivated ? (
<CustomTooltip
placement={'right'}
tooltipClasses="text-wrap text-left"
tooltipId="info-sync-compiled-contract"
tooltipText={
<span className="text-left">
<FormattedMessage id="udapp.infoSyncCompiledContractTooltip" values={{ br: <br /> }} />
</span>
}
>
<button
className="btn d-flex py-0"
onClick={(_) => {
{props.remixdActivated ? (
<CustomTooltip
placement={'right'}
tooltipClasses="text-wrap text-left"
tooltipId="info-sync-compiled-contract"
tooltipText={
<span className="text-left">
<FormattedMessage id="udapp.infoSyncCompiledContractTooltip" values={{ br: <br /> }} />
</span>
}
>
<i style={{ cursor: 'pointer' }} onClick={(_) => {
props.syncContracts()
_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>
</CustomTooltip>
) : null}
}} className="udapp_syncFramework udapp_icon fa fa-refresh" aria-hidden="true"></i>
</CustomTooltip>
) : null}
</div>
</div>
<div className="udapp_subcontainer">
<CustomTooltip placement={'right'} tooltipClasses="text-nowrap text-left" tooltipId="remixUdappContractNamesTooltip" tooltipText={contractOptions.title}>
<CustomTooltip placement={'auto-end'} tooltipClasses="text-nowrap text-left" tooltipId="remixUdappContractNamesTooltip" tooltipText={contractOptions.title}>
<select
ref={contractsRef}
value={currentContract}
@ -447,7 +442,7 @@ export function ContractDropdownUI(props: ContractDropdownProps) {
</div>
{evmVersion && loadedContractData && (
<CustomTooltip
placement={'right'}
placement={'auto-end'}
tooltipClasses="text-wrap text-left"
tooltipId="info-evm-version-warn"
tooltipText={
@ -483,6 +478,9 @@ export function ContractDropdownUI(props: ContractDropdownProps) {
isValidProxyUpgrade={isValidProxyUpgrade}
modal={props.modal}
disabled={props.selectedAccount === ''}
solcVersion={props.solCompilerVersion}
setSolcVersion={props.setCompilerVersion}
getVersion={props.getCompilerVersion}
/>
<div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input
@ -494,7 +492,7 @@ export function ContractDropdownUI(props: ContractDropdownProps) {
checked={props.ipfsCheckedState}
/>
<CustomTooltip
placement={'right'}
placement={'auto-end'}
tooltipClasses="text-wrap text-left"
tooltipId="remixIpfsUdappTooltip"
tooltipText={

@ -32,7 +32,6 @@ export function ContractGUI(props: ContractGUIProps) {
const initializeFields = useRef<Array<HTMLInputElement | null>>([])
const basicInputRef = useRef<HTMLInputElement>()
const intl = useIntl()
useEffect(() => {
if (props.deployOption && Array.isArray(props.deployOption)) {
if (props.deployOption[0] && props.deployOption[0].title === 'Deploy with Proxy' && props.deployOption[0].active) handleDeployProxySelect(true)
@ -173,6 +172,7 @@ export function ContractGUI(props: ContractGUIProps) {
}
const handleActionClick = async () => {
props.getVersion()
if (deployState.deploy) {
const proxyInitializeString = getMultiValsString(initializeFields.current)
props.clickCallBack(props.initializerOptions.inputs.inputs, proxyInitializeString, ['Deploy with Proxy'])
@ -285,7 +285,7 @@ export function ContractGUI(props: ContractGUIProps) {
<div className="udapp_contractActionsContainerSingle pt-2" style={{ display: toggleContainer ? 'none' : 'flex' }}>
<CustomTooltip
delay={0}
placement={'right'}
placement={'auto-end'}
tooltipClasses="text-wrap"
tooltipId="remixUdappInstanceButtonTooltip"
tooltipText={
@ -299,8 +299,8 @@ export function ContractGUI(props: ContractGUIProps) {
<div className="d-flex p-0 wrapperElement" onClick={handleActionClick} data-id={buttonOptions.dataId} data-title={buttonOptions.title}>
<button
className={`udapp_instanceButton text-nowrap overflow-hidden text-truncate ${props.widthClass} btn btn-sm ${buttonOptions.classList}`}
data-id={buttonOptions.dataId}
data-title={buttonOptions.title}
data-id={`${buttonOptions.dataId}`}
data-title={`${buttonOptions.title}`}
disabled={(toggleUpgradeImp && !proxyAddress) || props.disabled || (props.inputs !== '' && basicInput === '')}
>
{title}
@ -368,7 +368,7 @@ export function ContractGUI(props: ContractGUIProps) {
</label>
</button>
</CopyToClipboard>
<CustomTooltip placement={'right'} tooltipClasses="text-nowrap" tooltipId="remixUdappInstanceButtonTooltip" tooltipText={buttonOptions.title}>
<CustomTooltip placement={'auto-end'} tooltipClasses="text-nowrap" tooltipId="remixUdappInstanceButtonTooltip" tooltipText={buttonOptions.title}>
<div onClick={handleExpandMultiClick}>
<button
type="button"

@ -24,12 +24,12 @@ export function EnvironmentUI(props: EnvironmentProps) {
<label id="selectExEnv" className="udapp_settingsLabel">
<FormattedMessage id="udapp.environment" />
<CustomTooltip placement={'right'} tooltipClasses="text-nowrap" tooltipId="info-recorder" tooltipText={<FormattedMessage id="udapp.tooltipText2" />}>
<CustomTooltip placement={'auto-end'} tooltipClasses="text-nowrap" tooltipId="info-recorder" tooltipText={<FormattedMessage id="udapp.tooltipText2" />}>
<a href="https://chainlist.org/" target="_blank">
<i className={'ml-2 fas fa-plug'} aria-hidden="true"></i>
</a>
</CustomTooltip>
<CustomTooltip placement={'right'} tooltipClasses="text-wrap" tooltipId="runAndDeployAddresstooltip" tooltipText={<FormattedMessage id="udapp.environmentDocs" />}>
<CustomTooltip placement={'auto-end'} tooltipClasses="text-wrap" tooltipId="runAndDeployAddresstooltip" tooltipText={<FormattedMessage id="udapp.environmentDocs" />}>
<a href="https://remix-ide.readthedocs.io/en/latest/run.html#environment" target="_blank" rel="noreferrer">
<i className="udapp_infoDeployAction ml-2 fas fa-info-circle"></i>
</a>
@ -41,7 +41,7 @@ export function EnvironmentUI(props: EnvironmentProps) {
{isL2(currentProvider && currentProvider.displayName)}
{currentProvider && currentProvider.displayName}
{currentProvider && bridges[currentProvider.displayName] && (
<CustomTooltip placement={'right'} tooltipClasses="text-nowrap" tooltipId="info-recorder" tooltipText={<FormattedMessage id="udapp.tooltipText3" />}>
<CustomTooltip placement={'auto-end'} tooltipClasses="text-nowrap" tooltipId="info-recorder" tooltipText={<FormattedMessage id="udapp.tooltipText3" />}>
<i
style={{ fontSize: 'medium' }}
className={'ml-2 fa fa-rocket-launch'}

@ -64,7 +64,7 @@ export function GasLimitUI(props: GasPriceProps) {
<label className="mb-1 w-50 form-check-label custom-control-label" htmlFor="glManualConfig" data-id="glManualConfiguration">
<FormattedMessage id="udapp.gasLimitManual" />
</label>
<CustomTooltip placement={'right'} tooltipClasses="text-nowrap" tooltipId="remixGasPriceTooltip" tooltipText={<FormattedMessage id="udapp.tooltipText4" />}>
<CustomTooltip placement={'auto-end'} tooltipClasses="text-nowrap" tooltipId="remixGasPriceTooltip" tooltipText={<FormattedMessage id="udapp.tooltipText4" />}>
<input
type="number"
ref={inputComponent}

@ -44,6 +44,8 @@ export function InstanceContainerUI(props: InstanceContainerProps) {
plugin={props.plugin}
exEnvironment={props.exEnvironment}
editInstance={props.editInstance}
solcVersion={props.solcVersion}
getVersion={props.getVersion}
/>
)
})}
@ -62,7 +64,7 @@ export function InstanceContainerUI(props: InstanceContainerProps) {
</CustomTooltip>
{instanceList.length > 0 ? (
<CustomTooltip
placement="right"
placement={'auto-end'}
tooltipClasses="text-nowrap"
tooltipId="deployAndRunClearInstancesTooltip"
tooltipText={<FormattedMessage id="udapp.deployAndRunClearInstances" />}
@ -92,6 +94,8 @@ export function InstanceContainerUI(props: InstanceContainerProps) {
plugin={props.plugin}
exEnvironment={props.exEnvironment}
editInstance={props.editInstance}
solcVersion={props.solcVersion}
getVersion={props.getVersion}
/>
)
})}

@ -41,7 +41,7 @@ export function RecorderUI(props: RecorderProps) {
<FormattedMessage id="udapp.transactionsRecorded" />
</label>
<CustomTooltip
placement={'right'}
placement={'auto-end'}
tooltipClasses="text-nowrap"
tooltipId="recordedTransactionsCounttooltip"
tooltipText={<FormattedMessage id="udapp.transactionsCountTooltip" />}
@ -51,7 +51,7 @@ export function RecorderUI(props: RecorderProps) {
</div>
</CustomTooltip>
<CustomTooltip
placement={'right'}
placement={'auto-end'}
tooltipClasses="text-nowrap"
tooltipId="recordedTransactionsWalkthroughtooltip"
tooltipText={<FormattedMessage id="udapp.transactionsWalkthroughTooltip" />}
@ -77,7 +77,7 @@ export function RecorderUI(props: RecorderProps) {
<div className="mb-1 mt-1 custom-control custom-checkbox mb-1" id='udappRecorderUseLatest'>
<input ref={inputLive} type="checkbox" id="livemode-recorder" className="custom-control-input custom-select" name="input-livemode" />
<CustomTooltip
placement={'right'}
placement={'auto-end'}
tooltipClasses="text-wrap"
tooltipId="tooltip-livemode-recorder"
tooltipText={

@ -6,10 +6,7 @@ import { FuncABI } from '@remix-project/core-plugin'
import { CopyToClipboard } from '@remix-ui/clipboard'
import * as remixLib from '@remix-project/remix-lib'
import * as ethJSUtil from '@ethereumjs/util'
import axios from 'axios'
import { AppModal } from '@remix-ui/app'
import { ContractGUI } from './contractGUI'
import { SolScanTable } from './solScanTable'
import { TreeView, TreeViewItem } from '@remix-ui/tree-view'
import { BN } from 'bn.js'
import { CustomTooltip, is0XPrefixed, isHexadecimal, isNumeric, shortenAddress } from '@remix-ui/helper'
@ -28,8 +25,6 @@ export function UniversalDappUI(props: UdappProps) {
const [evmBC, setEvmBC] = useState(null)
const [instanceBalance, setInstanceBalance] = useState(0)
const getVersion = () => window.location.href.split('=')[5].split('+')[0].split('-')[1]
useEffect(() => {
if (!props.instance.abi) {
const abi = txHelper.sortAbiFunction(props.instance.contractData.abi)
@ -220,100 +215,6 @@ export function UniversalDappUI(props: UdappProps) {
setCalldataValue(value)
}
const handleScanContinue = async () => {
await props.plugin.call('notification', 'toast', 'Processing data to scan...')
_paq.push(['trackEvent', 'udapp', 'solidityScan', 'initiateScan'])
const workspace = await props.plugin.call('filePanel', 'getCurrentWorkspace')
const fileName = props.instance.filePath || `${workspace.name}/${props.instance.contractData.contract.file}`
const filePath = `.workspaces/${fileName}`
const file = await props.plugin.call('fileManager', 'readFile', filePath)
const urlResponse = await axios.post(`https://solidityscan.remixproject.org/uploadFile`, { file, fileName })
if (urlResponse.data.status === 'success') {
const ws = new WebSocket('wss://solidityscan.remixproject.org/solidityscan')
ws.addEventListener('error', console.error);
ws.addEventListener('open', async (event) => {
await props.plugin.call('notification', 'toast', 'Initiating scan...')
})
ws.addEventListener('message', async (event) => {
const data = JSON.parse(event.data)
if (data.type === "auth_token_register" && data.payload.message === "Auth token registered.") {
// Message on Bearer token successful registration
const reqToInitScan = {
"action": "message",
"payload": {
"type": "private_project_scan_initiate",
"body": {
"file_urls": [
urlResponse.data.result.url
],
"project_name": "RemixProject",
"project_type": "new"
}
}
}
ws.send(JSON.stringify(reqToInitScan))
} else if (data.type === "scan_status" && data.payload.scan_status === "download_failed") {
// Message on failed scan
_paq.push(['trackEvent', 'udapp', 'solidityScan', 'scanFailed'])
const modal: AppModal = {
id: 'SolidityScanError',
title: <FormattedMessage id="udapp.solScan.errModalTitle" />,
message: data.payload.scan_status_err_message,
okLabel: 'Close'
}
await props.plugin.call('notification', 'modal', modal)
} else if (data.type === "scan_status" && data.payload.scan_status === "scan_done") {
// Message on successful scan
_paq.push(['trackEvent', 'udapp', 'solidityScan', 'scanSuccess'])
const url = data.payload.scan_details.link
const { data: scanData } = await axios.post('https://solidityscan.remixproject.org/downloadResult', { url })
const scanDetails: Record<string, any>[] = scanData.scan_report.multi_file_scan_details
let modal: AppModal
if (scanDetails && scanDetails.length) {
modal = {
id: 'SolidityScanSuccess',
title: <FormattedMessage id="udapp.solScan.successModalTitle" />,
message: <SolScanTable scanDetails={scanDetails} fileName={fileName}/>,
okLabel: 'Close',
modalParentClass: 'modal-xl'
}
} else {
modal = {
id: 'SolidityScanError',
title: <FormattedMessage id="udapp.solScan.errModalTitle" />,
message: "Some error occurred! Please try again",
okLabel: 'Close'
}
}
await props.plugin.call('notification', 'modal', modal)
}
})
}
}
const askPermissionToScan = async () => {
_paq.push(['trackEvent', 'udapp', 'solidityScan', 'askPermissionToScan'])
const modal: AppModal = {
id: 'SolidityScanPermissionHandler',
title: <FormattedMessage id="udapp.solScan.modalTitle" />,
message: <FormattedMessage id="udapp.solScan.modalMessage" />,
okLabel: <FormattedMessage id="udapp.solScan.modalOkLabel" />,
okFn: handleScanContinue,
cancelLabel: <FormattedMessage id="udapp.solScan.modalCancelLabel" />
}
await props.plugin.call('notification', 'modal', modal)
}
const label = (key: string | number, value: string) => {
return (
<div className="d-flex mt-2 flex-row label_item">
@ -406,9 +307,6 @@ export function UniversalDappUI(props: UdappProps) {
></i>
</CustomTooltip>
)}
<CustomTooltip placement="top" tooltipClasses="text-nowrap" tooltipId="udapp_udappSolScanTooltip" tooltipText={<FormattedMessage id="udapp.solScan.iconTooltip" />}>
<i className="fas fa-qrcode p-0" onClick={askPermissionToScan}></i>
</CustomTooltip>
</div>
</div>
{ props.isPinnedContract && props.instance.pinnedAt ? (
@ -435,6 +333,7 @@ export function UniversalDappUI(props: UdappProps) {
return (
<div key={index}>
<ContractGUI
getVersion={props.getVersion}
funcABI={funcABI}
clickCallBack={(valArray: {name: string; type: string}[], inputsValues: string) => {
runTransaction(lookupOnly, funcABI, valArray, inputsValues, index)
@ -469,10 +368,21 @@ export function UniversalDappUI(props: UdappProps) {
<div className="py-2 border-top d-flex justify-content-start flex-grow-1">
<FormattedMessage id="udapp.lowLevelInteractions" />
</div>
<CustomTooltip placement={'bottom-end'} tooltipClasses="text-wrap" tooltipId="receiveEthDocstoolTip" tooltipText={<FormattedMessage id="udapp.tooltipText8" />}>
<a href={`https://solidity.readthedocs.io/en/${getVersion()}/contracts.html#receive-ether-function`} target="_blank" rel="noreferrer">
<i aria-hidden="true" className="fas fa-info my-2 mr-1"></i>
</a>
<CustomTooltip
placement={'bottom-end'}
tooltipClasses="text-wrap"
tooltipId="receiveEthDocstoolTip"
tooltipText={<FormattedMessage id="udapp.tooltipText8" />}
>
{ // receive method added to solidity v0.6.x. use this as diff.
props.solcVersion.canReceive === false ? (
<a href={`https://solidity.readthedocs.io/en/v${props.solcVersion.version}/contracts.html`} target="_blank" rel="noreferrer">
<i aria-hidden="true" className="fas fa-info my-2 mr-1"></i>
</a>
) :<a href={`https://solidity.readthedocs.io/en/v${props.solcVersion.version}/contracts.html#receive-ether-function`} target="_blank" rel="noreferrer">
<i aria-hidden="true" className="fas fa-info my-2 mr-1"></i>
</a>
}
</CustomTooltip>
</div>
<div className="d-flex flex-column align-items-start">

@ -25,6 +25,9 @@
.udapp_settingsCompiledBy {
margin-bottom: 4px;
}
.udapp_syncFramework {
margin-bottom: 4px;
}
.udapp_environment {
display: flex;
align-items: center;

@ -1,5 +1,6 @@
// eslint-disable-next-line no-use-before-define
import React, { Fragment, useEffect, useReducer, useState } from 'react'
import semver from 'semver'
import { FormattedMessage } from 'react-intl'
import { ModalDialog } from '@remix-ui/modal-dialog'
// eslint-disable-next-line no-unused-vars
@ -80,6 +81,23 @@ export function RunTabUI(props: RunTabProps) {
const [runTab, dispatch] = useReducer(runTabReducer, initialState)
const REACT_API = { runTab }
const currentfile = plugin.config.get('currentFile')
const [solcVersion, setSolcVersion] = useState<{version: string, canReceive: boolean}>({ version: '', canReceive: true })
const getVersion = () => {
let version = '0.8.25'
try {
const regVersion = window.location.href.match(/soljson-v(.*)\+commit/g)
if (regVersion && regVersion[1]) version = regVersion[1]
if (semver.lt(version, '0.6.0')) {
setSolcVersion({ version: version, canReceive: false })
} else {
setSolcVersion({ version: version, canReceive: true })
}
} catch (e) {
setSolcVersion({ version, canReceive: true })
console.log(e)
}
}
useEffect(() => {
if (!props.initialState) {
@ -306,6 +324,9 @@ export function RunTabUI(props: RunTabProps) {
isValidProxyAddress={isValidProxyAddress}
isValidProxyUpgrade={isValidProxyUpgrade}
proxy={runTab.proxy}
solCompilerVersion={solcVersion}
setCompilerVersion={setSolcVersion}
getCompilerVersion={getVersion}
/>
<RecorderUI
plugin={plugin}
@ -330,6 +351,8 @@ export function RunTabUI(props: RunTabProps) {
mainnetPrompt={mainnetPrompt}
runTransactions={executeTransactions}
sendValue={runTab.sendValue}
solcVersion={solcVersion}
getVersion={getVersion}
getFuncABIInputs={getFuncABIValues}
exEnvironment={runTab.selectExEnv}
editInstance={(instance) => {

@ -279,6 +279,11 @@ export interface ContractDropdownProps {
isValidProxyAddress?: (address: string) => Promise<boolean>,
isValidProxyUpgrade?: (proxyAddress: string, contractName: string, solcInput: SolcInput, solcOuput: SolcOutput, solcVersion: string) => Promise<LayoutCompatibilityReport | { ok: boolean, pass: boolean, warning: boolean }>,
proxy: { deployments: { address: string, date: string, contractName: string }[] }
solCompilerVersion: { version: string, canReceive: boolean }
setCompilerVersion: React.Dispatch<React.SetStateAction<{
version: string;
canReceive: boolean;}>>
getCompilerVersion: () => void
}
export interface RecorderProps {
@ -343,6 +348,8 @@ export interface InstanceContainerProps {
exEnvironment: string
editInstance: (instance) => void
plugin: RunTab
solcVersion: { version: string, canReceive: boolean }
getVersion: any
}
export interface Modal {
@ -397,6 +404,11 @@ export interface ContractGUIProps {
isValidProxyAddress?: (address: string) => Promise<boolean>,
isValidProxyUpgrade?: (proxyAddress: string) => Promise<LayoutCompatibilityReport | { ok: boolean, pass: boolean, warning: boolean }>,
modal?: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void, okBtnClass?: string, cancelBtnClass?: string) => void
solcVersion?: { version: string, canReceive: boolean }
setSolcVersion?: React.Dispatch<React.SetStateAction<{
version: string;
canReceive: boolean;}>>
getVersion: () => void
}
export interface MainnetProps {
network: Network,
@ -452,6 +464,8 @@ export interface UdappProps {
exEnvironment: string
editInstance: (instance) => void
plugin: RunTab
solcVersion: { version: string, canReceive: boolean }
getVersion: () => string
}
export interface DeployButtonProps {

@ -29,16 +29,19 @@ export default function SolidityCompile({ contractProperties, selectedContract,
</span>
)
const questionMark = (
<span className="remixui_questionMark">
<i
title={intl.formatMessage({
id: `solidity.${propertyName}`,
defaultMessage: help[propertyName]
})}
className="fas fa-question-circle"
aria-hidden="true"
></i>
</span>
<CustomTooltip
tooltipText={intl.formatMessage({
id: `solidity.${propertyName}`,
defaultMessage: help[propertyName]
})}
>
<span className="remixui_questionMark">
<i
className="fas fa-info-circle"
aria-hidden="true"
></i>
</span>
</CustomTooltip>
)
return (

@ -1127,7 +1127,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
disabled={(configFilePath === '' && state.useFileConfiguration) || disableCompileButton}
>
<CustomTooltip
placement="right"
placement={'auto-end'}
tooltipId="overlay-tooltip-compile-run"
tooltipText={
<div className="text-left">

@ -5,6 +5,9 @@ import {PublishToStorage} from '@remix-ui/publish-to-storage' // eslint-disable-
import {TreeView, TreeViewItem} from '@remix-ui/tree-view' // eslint-disable-line
import {CopyToClipboard} from '@remix-ui/clipboard' // eslint-disable-line
import { saveAs } from 'file-saver'
import { AppModal } from '@remix-ui/app'
import { SolScanTable } from './solScanTable'
import axios from 'axios'
import './css/style.css'
import { CustomTooltip } from '@remix-ui/helper'
@ -77,7 +80,7 @@ export const ContractSelection = (props: ContractSelectionProps) => {
key={keyPath}
label={
<div className="d-flex mt-2 flex-row remixui_label_item">
<label className="small font-weight-bold pr-1 remixui_label_key">{key}:</label>
<label className="font-weight-bold pr-1 remixui_label_key">{key}:</label>
<label className="m-0 remixui_label_value">{typeof data.self === 'boolean' ? `${data.self}` : data.self}</label>
</div>
}
@ -94,7 +97,7 @@ export const ContractSelection = (props: ContractSelectionProps) => {
key={keyPath}
label={
<div className="d-flex mt-2 flex-row remixui_label_item">
<label className="small font-weight-bold pr-1 remixui_label_key">{key}:</label>
<label className="font-weight-bold pr-1 remixui_label_key">{key}:</label>
<label className="m-0 remixui_label_value">{typeof data.self === 'boolean' ? `${data.self}` : data.self}</label>
</div>
}
@ -245,6 +248,113 @@ export const ContractSelection = (props: ContractSelectionProps) => {
return bytecodeObj.object
}
const runStaticAnalysis = async () => {
_paq.push(['trackEvent', 'solidityCompiler', 'runStaticAnalysis', 'initiate'])
const plugin = api as any
const isStaticAnalyzersActive = await plugin.call('manager', 'isActive', 'solidityStaticAnalysis')
if (!isStaticAnalyzersActive) {
await plugin.call('manager', 'activatePlugin', 'solidityStaticAnalysis')
}
plugin.call('menuicons', 'select', 'solidityStaticAnalysis')
}
const handleScanContinue = async () => {
const plugin = api as any
await plugin.call('notification', 'toast', 'Processing data to scan...')
_paq.push(['trackEvent', 'solidityCompiler', 'solidityScan', 'initiateScan'])
const workspace = await plugin.call('filePanel', 'getCurrentWorkspace')
const fileName = `${workspace.name}/${props.compiledFileName}`
const filePath = `.workspaces/${fileName}`
const file = await plugin.call('fileManager', 'readFile', filePath)
const urlResponse = await axios.post(`https://solidityscan.remixproject.org/uploadFile`, { file, fileName })
if (urlResponse.data.status === 'success') {
const ws = new WebSocket('wss://solidityscan.remixproject.org/solidityscan')
ws.addEventListener('error', console.error);
ws.addEventListener('open', async (event) => {
await plugin.call('notification', 'toast', 'Loading scan result in Remix terminal...')
})
ws.addEventListener('message', async (event) => {
const data = JSON.parse(event.data)
if (data.type === "auth_token_register" && data.payload.message === "Auth token registered.") {
// Message on Bearer token successful registration
const reqToInitScan = {
"action": "message",
"payload": {
"type": "private_project_scan_initiate",
"body": {
"file_urls": [
urlResponse.data.result.url
],
"project_name": "RemixProject",
"project_type": "new"
}
}
}
ws.send(JSON.stringify(reqToInitScan))
} else if (data.type === "scan_status" && data.payload.scan_status === "download_failed") {
// Message on failed scan
_paq.push(['trackEvent', 'solidityCompiler', 'solidityScan', 'scanFailed'])
const modal: AppModal = {
id: 'SolidityScanError',
title: <FormattedMessage id="solidity.solScan.errModalTitle" />,
message: data.payload.scan_status_err_message,
okLabel: 'Close'
}
await plugin.call('notification', 'modal', modal)
} else if (data.type === "scan_status" && data.payload.scan_status === "scan_done") {
// Message on successful scan
_paq.push(['trackEvent', 'solidityCompiler', 'solidityScan', 'scanSuccess'])
const url = data.payload.scan_details.link
const { data: scanData } = await axios.post('https://solidityscan.remixproject.org/downloadResult', { url })
const scanDetails: Record<string, any>[] = scanData.scan_report.multi_file_scan_details
if (scanDetails && scanDetails.length) {
await plugin.call('terminal', 'logHtml', <SolScanTable scanDetails={scanDetails} fileName={fileName}/>)
} else {
const modal: AppModal = {
id: 'SolidityScanError',
title: <FormattedMessage id="solidity.solScan.errModalTitle" />,
message: "Some error occurred! Please try again",
okLabel: 'Close'
}
await plugin.call('notification', 'modal', modal)
}
}
})
}
}
const runSolidityScan = async () => {
_paq.push(['trackEvent', 'solidityCompiler', 'solidityScan', 'askPermissionToScan'])
const modal: AppModal = {
id: 'SolidityScanPermissionHandler',
title: <FormattedMessage id="solidity.solScan.modalTitle" />,
message: <div className='d-flex flex-column'>
<span><FormattedMessage id="solidity.solScan.modalMessage" />
<a href={'https://solidityscan.com'}
target="_blank"
onClick={() => _paq.push(['trackEvent', 'solidityCompiler', 'solidityScan', 'learnMore'])}>
Learn more
</a>
</span>
<br/>
<FormattedMessage id="solidity.solScan.likeToContinue" />
</div>,
okLabel: <FormattedMessage id="solidity.solScan.modalOkLabel" />,
okFn: handleScanContinue,
cancelLabel: <FormattedMessage id="solidity.solScan.modalCancelLabel" />
}
await (api as any).call('notification', 'modal', modal)
}
return (
// define swarm logo
<>
@ -264,72 +374,121 @@ export const ContractSelection = (props: ContractSelectionProps) => {
</select>
</div>
<article className="mt-2 pb-0">
<button
id="publishOnIpfs"
className="btn btn-secondary btn-block"
onClick={() => {
handlePublishToStorage('ipfs')
}}
<CustomTooltip
placement={'auto-end'}
tooltipId="runStaticAnalysisTooltip"
tooltipClasses="text-nowrap"
tooltipText={`${intl.formatMessage({
id: 'solidity.runStaticAnalysis.iconTooltip'
})}`}
>
<button
id="runStaticAnalysis"
className="btn border btn-block"
onClick={() => {
runStaticAnalysis()
}}
>
<span>
<img id="ssaLogo" className="remixui_storageLogo mr-2" src="assets/img/staticAnalysisColorBlue.webp" />
<span>
<FormattedMessage id="solidity.runStaticAnalysis" />
</span>
</span>
</button>
</CustomTooltip>
<CustomTooltip
placement={'auto-end'}
tooltipId="runSolidityScanTooltip"
tooltipClasses="text-nowrap"
tooltipText={`${intl.formatMessage({
id: 'solidity.solScan.iconTooltip'
})}`}
>
<CustomTooltip
placement="right"
tooltipId="publishOnIpfsTooltip"
tooltipClasses="text-nowrap"
tooltipText={`${intl.formatMessage({
id: 'solidity.publishOn'
})} Ipfs`}
<button
id="runSolidityScan"
className="btn border btn-block"
onClick={() => {
runSolidityScan()
}}
>
<span>
<img id="solscanLogo" className="remixui_storageLogo mr-2" src="assets/img/solidityScanLogo.webp" />
<span>
<FormattedMessage id="solidity.publishOn" /> Ipfs
<FormattedMessage id="solidity.runSolidityScan" />
</span>
<img id="ipfsLogo" className="remixui_storageLogo ml-2" src="assets/img/ipfs.webp" />
</span>
</CustomTooltip>
</button>
<button
id="publishOnSwarm"
className="btn btn-secondary btn-block"
onClick={() => {
handlePublishToStorage('swarm')
}}
</button>
</CustomTooltip>
<CustomTooltip
placement={'auto-end'}
tooltipId="publishOnIpfsTooltip"
tooltipClasses="text-nowrap"
tooltipText={`${intl.formatMessage({
id: 'solidity.publishOn'
})} Ipfs`}
>
<CustomTooltip
placement="right"
tooltipId="publishOnSwarmTooltip"
tooltipClasses="text-nowrap"
tooltipText={`${intl.formatMessage({
id: 'solidity.publishOn'
})} Swarm`}
<button
id="publishOnIpfs"
className="btn border btn-block"
onClick={() => {
handlePublishToStorage('ipfs')
}}
>
<span>
<img id="ipfsLogo" className="remixui_storageLogo mr-2" src="assets/img/ipfs.webp" />
<span>
<FormattedMessage id="solidity.publishOn" /> IPFS
</span>
</span>
</button>
</CustomTooltip>
<CustomTooltip
placement={'auto-end'}
tooltipId="publishOnSwarmTooltip"
tooltipClasses="text-nowrap"
tooltipText={`${intl.formatMessage({
id: 'solidity.publishOn'
})} Swarm`}
>
<button
id="publishOnSwarm"
className="btn border btn-block"
onClick={() => {
handlePublishToStorage('swarm')
}}
>
<span>
<img id="swarmLogo" className="remixui_storageLogo mr-2" src="assets/img/swarmColor.webp" />
<span>
<FormattedMessage id="solidity.publishOn" /> Swarm
</span>
<img id="swarmLogo" className="remixui_storageLogo ml-2" src="assets/img/swarm.webp" />
</span>
</CustomTooltip>
</button>
<button
data-id="compilation-details"
className="btn btn-secondary btn-block"
onClick={async () => {
details()
await (api as any).call('compilationDetails', 'showDetails', payload)
}}
</button>
</CustomTooltip>
<CustomTooltip
placement={'auto-end'}
tooltipId="CompilationDetailsTooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id="solidity.displayContractDetails" />}
>
<CustomTooltip
placement="right"
tooltipId="CompilationDetailsTooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id="solidity.displayContractDetails" />}
<button
data-id="compilation-details"
className="btn border btn-block"
onClick={async () => {
details()
await (api as any).call('compilationDetails', 'showDetails', payload)
}}
>
<span>
<FormattedMessage id="solidity.compilationDetails" />
<i className="fa-regular fa-memo-pad mr-2 text-primary"></i>
<span>
<FormattedMessage id="solidity.compilationDetails" />
</span>
</span>
</CustomTooltip>
</button>
</button>
</CustomTooltip>
{/* Copy to Clipboard */}
<div className="remixui_contractHelperButtons">
<div className="input-group">

@ -104,7 +104,7 @@ export class CompileTabLogic {
* @param {string} target the path to the file to compile
*/
compileFile (target) {
if (!target) throw new Error('No target provided for compiliation')
if (!target) throw new Error('No target provided for compilation')
return new Promise((resolve, reject) => {
this.api.readFile(target).then(async(content) => {
const sources = { [target]: { content } }

@ -1,6 +1,7 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import parse from 'html-react-parser';
import parse from 'html-react-parser'
const _paq = (window._paq = window._paq || [])
interface SolScanTableProps {
scanDetails: Record<string, any>[],
@ -12,11 +13,19 @@ export function SolScanTable(props: SolScanTableProps) {
return (
<>
<p>Scanning successful! <b>{scanDetails.length} warnings </b> found for file: <b>{fileName}</b></p>
<p>See the warning details below. For more details, <a href="https://solidityscan.com/signup" target='blank'>Go to SolidityScan</a></p>
<br/>
<h6>SolidityScan result for <b>{fileName}</b>:</h6>
<p className='text-success'><b>{scanDetails.length} warnings </b> found. See the warning details below. For more details,&nbsp;
<a href="https://solidityscan.com/signup"
target='_blank'
onClick={() => _paq.push(['trackEvent', 'solidityCompiler', 'solidityScan', 'goToSolidityScan'])}>
go to SolidityScan.
</a>
</p>
<table className="table table-bordered table-hover">
<thead>
<tr>
<td scope="col" style={{ wordBreak: "keep-all" }}>#</td>
<td scope="col" style={{ wordBreak: "keep-all" }}>NAME</td>
<td scope="col" style={{ wordBreak: "keep-all" }}>SEVERITY</td>
<td scope="col" style={{ wordBreak: "keep-all" }}>CONFIDENCE</td>
@ -26,9 +35,10 @@ export function SolScanTable(props: SolScanTableProps) {
</thead>
<tbody>
{
Array.from(scanDetails, (template) => {
Array.from(scanDetails, (template, index) => {
return (
<tr key={template.template_details.issue_id}>
<td scope="col">{index + 1}.</td>
<td scope="col">{template.template_details.issue_name}</td>
<td scope="col">{template.template_details.issue_severity}</td>
<td scope="col">{template.template_details.issue_confidence}</td>

@ -27,7 +27,7 @@ const StaticAnalyserButton = ({ onClick, buttonText, disabled, title, classList
)
const buttonWithTooltip = () => (
<CustomTooltip placement="right" tooltipId="ssaRunButtonTooltip" tooltipClasses="text-nowrap" tooltipText={title}>
<CustomTooltip placement={'auto-end'} tooltipId="ssaRunButtonTooltip" tooltipClasses="text-nowrap" tooltipText={title}>
<div id="staticAnalysisWrapper" className={`${newclassList} p-0`}>
<button id="staticAnalysisRunBtn" className={newclassList} disabled={disabled} onClick={onClick} style={{ pointerEvents: 'none', color: 'white' }}>
<span className="pl-3 pr-4">{buttonText}</span>

@ -0,0 +1,23 @@
remixui_statusbar:hover {
cursor: pointer;
}
.remixui_statusbar_gitstatus
.remixui_statusbar_gitstatus:hover {
cursor: pointer;
}
/**
* approximately same height with vscode statusbar
**/
.remixui_statusbar_height {
height: 21px;
}
.remixui_statusbar_activelink {
text-decoration: none;
}
.remixui_statusbar_activelink:active {
color: var(--danger);
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save