pull/4877/head
Your Name 5 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. 46
      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. 49
      apps/remix-ide-e2e/src/tests/homeTab.test.ts
  12. 314
      apps/remix-ide-e2e/src/tests/remixd.test.ts
  13. 159
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  14. 13
      apps/remix-ide-e2e/src/tests/url.test.ts
  15. 71
      apps/remix-ide-e2e/src/tests/vyper_api.test.ts
  16. 2
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  17. 6
      apps/remix-ide-e2e/src/tests/workspace_git.test.ts
  18. 5
      apps/remix-ide/contracts/ballot.sol
  19. 398
      apps/remix-ide/contracts/foundry/cache/solidity-files-cache.json
  20. 6
      apps/remix-ide/contracts/foundry/foundry.toml
  21. 385
      apps/remix-ide/contracts/foundry/out/Counter.s.sol/CounterScript.json
  22. 377
      apps/remix-ide/contracts/foundry/out/Counter.sol/Counter.json
  23. 1865
      apps/remix-ide/contracts/foundry/out/Counter.t.sol/CounterTest.json
  24. 3729
      apps/remix-ide/contracts/foundry/out/Script.sol/Script.json
  25. 38186
      apps/remix-ide/contracts/foundry/out/Test.sol/Test.json
  26. 37620
      apps/remix-ide/contracts/foundry/out/Test.sol/stdError.json
  27. 37347
      apps/remix-ide/contracts/foundry/out/Test.sol/stdMath.json
  28. 37499
      apps/remix-ide/contracts/foundry/out/Test.sol/stdStorage.json
  29. 10800
      apps/remix-ide/contracts/foundry/out/Vm.sol/Vm.json
  30. 110867
      apps/remix-ide/contracts/foundry/out/console.sol/console.json
  31. 110866
      apps/remix-ide/contracts/foundry/out/console2.sol/console2.json
  32. 23713
      apps/remix-ide/contracts/foundry/out/test.sol/DSTest.json
  33. 14
      apps/remix-ide/contracts/foundry/src/Counter.sol
  34. 14
      apps/remix-ide/src/app.js
  35. 1
      apps/remix-ide/src/app/components/preload.tsx
  36. 2
      apps/remix-ide/src/app/components/side-panel.tsx
  37. 105
      apps/remix-ide/src/app/components/status-bar.tsx
  38. 7
      apps/remix-ide/src/app/panels/file-panel.js
  39. 11
      apps/remix-ide/src/app/panels/tab-proxy.js
  40. 1
      apps/remix-ide/src/app/plugins/compile-details.tsx
  41. 266
      apps/remix-ide/src/app/plugins/remixGuide.tsx
  42. 47
      apps/remix-ide/src/app/plugins/remixGuideData.json
  43. 12
      apps/remix-ide/src/app/plugins/solcoderAI.tsx
  44. 2
      apps/remix-ide/src/app/tabs/locales/en/editor.json
  45. 11
      apps/remix-ide/src/app/tabs/locales/en/home.json
  46. 13
      apps/remix-ide/src/app/tabs/locales/en/solidity.json
  47. 9
      apps/remix-ide/src/app/tabs/locales/en/udapp.json
  48. 1
      apps/remix-ide/src/app/tabs/settings-tab.tsx
  49. BIN
      apps/remix-ide/src/assets/img/solidityScanLogo.webp
  50. BIN
      apps/remix-ide/src/assets/img/staticAnalysisColorBlue.webp
  51. BIN
      apps/remix-ide/src/assets/img/swarmColor.webp
  52. 8
      apps/remix-ide/src/remixAppManager.js
  53. 19
      apps/remix-ide/src/types/index.d.ts
  54. 2
      apps/remix-ide/webpack.config.js
  55. 31
      apps/vyper/src/app/app.tsx
  56. 23
      apps/vyper/src/app/utils/compiler.tsx
  57. 19
      apps/vyper/src/app/utils/remix-client.tsx
  58. 56
      build-qa-doc.js
  59. 59
      libs/remix-simulator/src/methods/accounts.ts
  60. 12
      libs/remix-simulator/src/provider.ts
  61. 36
      libs/remix-simulator/test/accounts.ts
  62. 13
      libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx
  63. 5
      libs/remix-ui/app/src/lib/remix-app/remix-app.tsx
  64. 8
      libs/remix-ui/app/src/lib/remix-app/style/remix-app.css
  65. 37
      libs/remix-ui/editor/src/lib/providers/documentationProvider.ts
  66. 71
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  67. 9
      libs/remix-ui/grid-view/src/lib/components/customCheckbox.tsx
  68. 4
      libs/remix-ui/grid-view/src/lib/filtersContext.tsx
  69. 6
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.css
  70. 116
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.tsx
  71. 5
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.tsx
  72. 137
      libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.tsx
  73. 27
      libs/remix-ui/home-tab/src/lib/components/homeTabFeatured.tsx
  74. 2
      libs/remix-ui/home-tab/src/lib/components/homeTabFeaturedPlugins.tsx
  75. 173
      libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx
  76. 183
      libs/remix-ui/home-tab/src/lib/components/homeTabGetStarted.tsx
  77. 156
      libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx
  78. 6
      libs/remix-ui/home-tab/src/lib/components/homeTablangOptions.tsx
  79. 35
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.css
  80. 31
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx
  81. 2
      libs/remix-ui/panel/src/index.ts
  82. 4
      libs/remix-ui/panel/src/lib/plugins/panel.css
  83. 20
      libs/remix-ui/panel/src/lib/types/index.ts
  84. 11
      libs/remix-ui/run-tab/src/lib/actions/account.ts
  85. 4
      libs/remix-ui/run-tab/src/lib/actions/deploy.ts
  86. 46
      libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx
  87. 10
      libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx
  88. 6
      libs/remix-ui/run-tab/src/lib/components/environment.tsx
  89. 2
      libs/remix-ui/run-tab/src/lib/components/gasLimit.tsx
  90. 6
      libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx
  91. 6
      libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx
  92. 122
      libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx
  93. 3
      libs/remix-ui/run-tab/src/lib/css/run-tab.css
  94. 23
      libs/remix-ui/run-tab/src/lib/run-tab.tsx
  95. 14
      libs/remix-ui/run-tab/src/lib/types/index.ts
  96. 23
      libs/remix-ui/solidity-compile-details/src/lib/components/solidityCompile.tsx
  97. 2
      libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx
  98. 261
      libs/remix-ui/solidity-compiler/src/lib/contract-selection.tsx
  99. 2
      libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts
  100. 18
      libs/remix-ui/solidity-compiler/src/lib/solScanTable.tsx
  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) ![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
Remix libraries are essential for Remix IDE's native plugins. Read more about libraries [here](libs/README.md) 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 { extractNameFromKey, extractParentFromKey } from '@remix-ui/helper'
import { CompilationConfig, CompilerReport, PrimeValue, ResolverOutput } from '../types' import { CompilationConfig, CompilerReport, PrimeValue, ResolverOutput } from '../types'
// @ts-ignore
const _paq = (window._paq = window._paq || [])
export class CircomPluginClient extends PluginClient { export class CircomPluginClient extends PluginClient {
public internalEvents: EventManager public internalEvents: EventManager
private _compilationConfig: CompilationConfig = { private _compilationConfig: CompilationConfig = {
@ -119,6 +121,7 @@ export class CircomPluginClient extends PluginClient {
} else { } else {
// @ts-ignore // @ts-ignore
await this.call('editor', 'clearErrorMarkers', [path]) 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> { async compile(path: string, compilationConfig?: CompilationConfig): Promise<void> {
this.internalEvents.emit('circuit_compiling_start') this.internalEvents.emit('circuit_compiling_start')
this.emit('statusChanged', { key: 'loading', title: 'Compiling...', type: 'info' })
// @ts-ignore // @ts-ignore
this.call('terminal', 'log', { type: 'log', value: 'Compiling ' + path }) this.call('terminal', 'log', { type: 'log', value: 'Compiling ' + path })
const [parseErrors, filePathToId] = await this.parse(path) const [parseErrors, filePathToId] = await this.parse(path)
@ -145,6 +149,7 @@ export class CircomPluginClient extends PluginClient {
} }
} else { } else {
this.internalEvents.emit('circuit_parsing_done', parseErrors, filePathToId) this.internalEvents.emit('circuit_parsing_done', parseErrors, filePathToId)
this.emit('statusChanged', { key: 'succeed', title: 'circuit compiled successfully', type: 'success' })
} }
if (compilationConfig) { if (compilationConfig) {
const { prime, version } = compilationConfig const { prime, version } = compilationConfig
@ -159,6 +164,7 @@ export class CircomPluginClient extends PluginClient {
const circuitErrors = circuitApi.report() const circuitErrors = circuitApi.report()
this.logCompilerReport(circuitErrors) this.logCompilerReport(circuitErrors)
_paq.push(['trackEvent', 'circuit-compiler', 'compile', 'Compilation failed'])
throw new Error(circuitErrors) throw new Error(circuitErrors)
} else { } else {
this.lastCompiledFile = path this.lastCompiledFile = path
@ -178,6 +184,7 @@ export class CircomPluginClient extends PluginClient {
} else { } else {
this.internalEvents.emit('circuit_compiling_done', []) this.internalEvents.emit('circuit_compiling_done', [])
} }
_paq.push(['trackEvent', 'circuit-compiler', 'compile', 'Compilation successful'])
circuitApi.log().map(log => { circuitApi.log().map(log => {
log && this.call('terminal', 'log', { type: 'log', value: 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> { async generateR1cs (path: string, compilationConfig?: CompilationConfig): Promise<void> {
this.internalEvents.emit('circuit_generating_r1cs_start') this.internalEvents.emit('circuit_generating_r1cs_start')
this.emit('statusChanged', { key: 'loading', title: 'Generating...', type: 'info' })
// @ts-ignore // @ts-ignore
this.call('terminal', 'log', { type: 'log', value: 'Generating R1CS for ' + path }) this.call('terminal', 'log', { type: 'log', value: 'Generating R1CS for ' + path })
const [parseErrors, filePathToId] = await this.parse(path) const [parseErrors, filePathToId] = await this.parse(path)
@ -203,6 +211,7 @@ export class CircomPluginClient extends PluginClient {
} }
} else { } else {
this.internalEvents.emit('circuit_parsing_done', parseErrors, filePathToId) this.internalEvents.emit('circuit_parsing_done', parseErrors, filePathToId)
this.emit('statusChanged', { key: 'succeed', title: 'r1cs generated successfully', type: 'success' })
} }
if (compilationConfig) { if (compilationConfig) {
const { prime, version } = compilationConfig const { prime, version } = compilationConfig
@ -217,6 +226,7 @@ export class CircomPluginClient extends PluginClient {
const r1csErrors = r1csApi.report() const r1csErrors = r1csApi.report()
this.logCompilerReport(r1csErrors) this.logCompilerReport(r1csErrors)
_paq.push(['trackEvent', 'circuit-compiler', 'generateR1cs', 'R1CS Generation failed'])
throw new Error(r1csErrors) throw new Error(r1csErrors)
} else { } else {
this.internalEvents.emit('circuit_generating_r1cs_done') this.internalEvents.emit('circuit_generating_r1cs_done')
@ -225,6 +235,7 @@ export class CircomPluginClient extends PluginClient {
// @ts-ignore // @ts-ignore
await this.call('fileManager', 'writeFile', writePath, r1csProgram, true) await this.call('fileManager', 'writeFile', writePath, r1csProgram, true)
_paq.push(['trackEvent', 'circuit-compiler', 'generateR1cs', 'R1CS Generation successful'])
r1csApi.log().map(log => { r1csApi.log().map(log => {
log && this.call('terminal', 'log', { type: 'log', value: 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> { async computeWitness (input: string): Promise<void> {
this.internalEvents.emit('circuit_computing_witness_start') this.internalEvents.emit('circuit_computing_witness_start')
this.emit('statusChanged', { key: 'loading', title: 'Computing...', type: 'info' })
const wasmPath = this.lastCompiledCircuitPath const wasmPath = this.lastCompiledCircuitPath
if (!wasmPath) throw new Error('No wasm file found') 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) const witness = this.compiler ? await this.compiler.generate_witness(dataRead, input) : await generate_witness(dataRead, input)
// @ts-ignore // @ts-ignore
await this.call('fileManager', 'writeFile', wasmPath.replace('.wasm', '.wtn'), witness, true) 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.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>> { 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> { async logCompilerReport (report: CompilerReport[]): Promise<void> {
this.call('terminal', 'log', { type: 'log', value: JSON.stringify(report, null, 2) }) 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 === 'Error') {
if (report[0].type === 'Warning') this.call('terminal', 'log', { type: 'log', value: 'previous warnings were found' }) 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': { 'default': {
globals: { globals: {
waitForConditionTimeout: 10000, waitForConditionTimeout: 10000,
asyncHookTimeout: 100000 asyncHookTimeout: 10000000
}, },
screenshots: { screenshots: {
enabled: true, enabled: true,

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

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

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

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

@ -6,7 +6,6 @@ const checkBrowserIsChrome = function (browser: NightwatchBrowser) {
return browser.browserName.indexOf('chrome') > -1 return browser.browserName.indexOf('chrome') > -1
} }
module.exports = { module.exports = {
'@disabled': true, '@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) { 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' import init from '../helpers/init'
module.exports = { module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) { before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done) 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 browser
.waitForElementVisible('*[data-id="homeTabStartCoding"]') .click('*[data-path="home"]')
.click('*[data-id="homeTabStartCoding"]') .waitForElementVisible('*[data-id="homeTabNewFile"]')
.waitForElementVisible('div[data-id="treeViewDivtreeViewItemcontracts/HelloWorld.sol"]') .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 init from '../helpers/init'
import { join } from 'path' import { join } from 'path'
import { ChildProcess, spawn } from 'child_process' import { ChildProcess, spawn } from 'child_process'
import { writeFileSync } from 'fs' import { homedir } from 'os'
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 * as foundryCompilation from '../helpers/foundry_compilation.json'
import * as truffle_compilation from '../helpers/truffle_compilation.json'
import kill from 'tree-kill' import kill from 'tree-kill'
let remixd: ChildProcess let remixd: ChildProcess
@ -71,9 +66,13 @@ module.exports = {
'@sources': function () { '@sources': function () {
return sources return sources
}, },
'run Remixd tests #group4': function (browser) { 'run Remixd tests #group1': function (browser) {
browser.perform(async (done) => { 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()) console.log('working directory', process.cwd())
connectRemixd(browser, done) connectRemixd(browser, done)
}) })
@ -81,10 +80,10 @@ module.exports = {
runTests(browser, done) 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") 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) => { browser.perform(async (done) => {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts')) remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts'))
@ -96,7 +95,7 @@ module.exports = {
.setSolidityCompilerVersion('soljson-v0.5.0+commit.1d4f565a.js') .setSolidityCompilerVersion('soljson-v0.5.0+commit.1d4f565a.js')
.testContracts('test_import_node_modules.sol', sources[3]['test_import_node_modules.sol'], ['SafeMath']) .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) => { browser.perform(async (done) => {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts')) remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts'))
console.log('working directory', process.cwd()) 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 .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']) .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) { 'Should setup a hardhat project #group4': function (browser: NightwatchBrowser) {
browser browser.perform(async (done) => {
.executeScriptInTerminal('git status') await setupHardhatProject()
.pause(3000) done()
.journalLastChildIncludes('On branch ') })
},
'Close Remixd #group3': '' + function (browser) {
browser
.clickLaunchIcon('pluginManager')
.scrollAndClick('#pluginManager *[data-id="pluginManagerComponentDeactivateButtonremixd"]')
}, },
'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) => { 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()) console.log('working directory', process.cwd())
connectRemixd(browser, done) connectRemixd(browser, done)
}) })
.perform((done) => { .perform(async (done) => {
console.log('generating compilation result') console.log('generating compilation result')
writeFileSync('./apps/remix-ide/contracts/hardhat/artifacts/build-info/7839ba878952cc00ff316061405f273a.json', JSON.stringify(hardhatCompilation)) await compileHardhatProject()
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))
done() done()
}) })
.expect.element('*[data-id="terminalJournal"]').text.to.contain('receiving compilation result from Hardhat').before(60000) .expect.element('*[data-id="terminalJournal"]').text.to.contain('receiving compilation result from Hardhat').before(60000)
let addressRef
browser.clickLaunchIcon('filePanel') browser.clickLaunchIcon('filePanel')
.openFile('contracts') .openFile('contracts')
.openFile('contracts/Lock.sol') .openFile('contracts/Token.sol')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectContract('Lock') .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c')
.createContract('1') .selectContract('Token')
.expect.element('*[data-id="terminalJournal"]').text.to.contain('Unlock time should be in the future').before(60000) .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) { 'Should load compilation result from hardhat when remixd connects #group4': function (browser: NightwatchBrowser) {
// artifacts/build-info/c7062fdd360381a85af23eeef31c98f8.json has already been created let addressRef
browser browser
.perform((done) => { .refresh()
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()
})
.perform(async (done) => { .perform(async (done) => {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts/hardhat'))
console.log('working directory', process.cwd()) console.log('working directory', process.cwd())
connectRemixd(browser, done) connectRemixd(browser, done)
}) })
@ -185,24 +159,40 @@ module.exports = {
browser.clickLaunchIcon('filePanel') browser.clickLaunchIcon('filePanel')
.openFile('contracts') .openFile('contracts')
.openFile('contracts/Lock.sol') .openFile('contracts/Token.sol')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectContract('Lock') .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c')
.createContract('1') .selectContract('Token')
.expect.element('*[data-id="terminalJournal"]').text.to.contain('Unlock time should be in the future').before(60000) .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) => { browser.perform(async (done) => {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts/foundry')) console.log('working directory', homedir() + '/foundry_tmp/hello_foundry')
console.log('working directory', process.cwd()) remixd = await spawnRemixd(join(homedir(), '/foundry_tmp/hello_foundry'))
connectRemixd(browser, done) connectRemixd(browser, done)
}) })
.perform((done) => { .perform(async (done) => {
writeFileSync('./apps/remix-ide/contracts/foundry/out/Counter.sol/Counter.json', JSON.stringify(foundryCompilation)) await buildFoundryProject()
done() done()
}) })
.expect.element('*[data-id="terminalJournal"]').text.to.contain('receiving compilation result from Foundry').before(60000) .expect.element('*[data-id="terminalJournal"]').text.to.contain('receiving compilation result from Foundry').before(60000)
@ -225,36 +215,37 @@ module.exports = {
done() 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) => { browser.refresh().perform(async (done) => {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts/truffle')) console.log('working directory', homedir() + '/foundry_tmp/hello_foundry')
console.log('working directory', process.cwd())
connectRemixd(browser, done) connectRemixd(browser, done)
}) })
.perform((done) => { .expect.element('*[data-id="terminalJournal"]').text.to.contain('receiving compilation result from Foundry').before(60000)
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)
let contractAaddress
browser.clickLaunchIcon('filePanel') browser.clickLaunchIcon('filePanel')
.openFile('contracts') .openFile('src')
.openFile('contracts/Migrations.sol') .openFile('src/Counter.sol')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectContract('Migrations') .selectContract('Counter')
.createContract('') .createContract('')
.testFunction('last', .getAddressAtPosition(0, (address) => {
{ console.log(contractAaddress)
status: '0x1 Transaction mined and execution succeed' 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) { 'Should disable git when running remixd #group9': function (browser: NightwatchBrowser) {
browser.perform(async (done) => { browser.perform(async (done) => {
@ -269,7 +260,7 @@ module.exports = {
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.switchWorkspace('default_workspace') .switchWorkspace('default_workspace')
.click({ .click({
selector:'[data-path="connectionAlert-modal-footer-ok-react"]', selector: '[data-path="connectionAlert-modal-footer-ok-react"]',
suppressNotFoundErrors: true, suppressNotFoundErrors: true,
timeout: 2000 timeout: 2000
}) })
@ -328,7 +319,30 @@ function testImportFromRemixd(browser: NightwatchBrowser, callback: VoidFunction
.perform(() => { callback() }) .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> { 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 }) 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) => { return new Promise((resolve, reject) => {
remixd.stdout.on('data', function (data) { remixd.stdout.on('data', function (data) {
@ -341,6 +355,7 @@ async function spawnRemixd(path: string): Promise<ChildProcess> {
} }
}) })
remixd.stderr.on('err', function (data) { remixd.stderr.on('err', function (data) {
console.log(data.toString())
reject(data.toString()) reject(data.toString())
}) })
}) })
@ -368,3 +383,120 @@ function connectRemixd(browser: NightwatchBrowser, done: any) {
.perform(() => done()) .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) { 'Should print hardhat logs #group4': function (browser: NightwatchBrowser) {
browser browser
.addFile('printHardhatlog.sol', { content: hardhatLog }) .addFile('printHardhatlog.sol', { content: hardhatLog })
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.click('*[data-id="terminalClearConsole"]') // clear the terminal .click('*[data-id="terminalClearConsole"]') // clear the terminal
.waitForElementVisible('[for="autoCompile"]') .waitForElementVisible('[for="autoCompile"]')
@ -266,22 +266,22 @@ module.exports = {
if (Array.isArray(result.value) && result.value.length > 0) { if (Array.isArray(result.value) && result.value.length > 0) {
console.log('Found ' + result.value.length + ' transactions') console.log('Found ' + result.value.length + ' transactions')
browser browser
.click({ .click({
selector: '[data-id="listenNetworkCheckInput"]', selector: '[data-id="listenNetworkCheckInput"]',
}) })
.click({ .click({
selector: '*[data-id="terminalClearConsole"]', selector: '*[data-id="terminalClearConsole"]',
}) })
.click({ .click({
selector: '*[data-id="compilerContainerCompileAndRunBtn"]', selector: '*[data-id="compilerContainerCompileAndRunBtn"]',
}) })
.pause(10000) .pause(10000)
.waitForElementNotPresent({ .waitForElementNotPresent({
locateStrategy: 'xpath', locateStrategy: 'xpath',
selector: "//*[@class='remix_ui_terminal_log' and contains(.,'to:') and contains(.,'from:')]", selector: "//*[@class='remix_ui_terminal_log' and contains(.,'to:') and contains(.,'from:')]",
timeout: 120000 timeout: 120000
}) })
.end() .end()
} else { } else {
browser browser
.assert.fail('No transaction found') .assert.fail('No transaction found')
@ -309,7 +309,7 @@ module.exports = {
.switchEnvironment('vm-custom-fork') .switchEnvironment('vm-custom-fork')
.waitForElementVisible('[data-id="vm-custom-fork-modal-footer-ok-react"]') .waitForElementVisible('[data-id="vm-custom-fork-modal-footer-ok-react"]')
.execute(() => { .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') .clearValue('*[data-id="CustomForkNodeUrl"]').pause(1000).setValue('*[data-id="CustomForkNodeUrl"]', 'https://go.getblock.io/ee42d0a88f314707be11dd799b122cb9')
.execute(() => { .execute(() => {
@ -331,7 +331,7 @@ module.exports = {
.executeScriptInTerminal(`web3.eth.getCode('0x75F509A4eDA030470272DfBAf99A47D587E76709')`) // sepolia contract .executeScriptInTerminal(`web3.eth.getCode('0x75F509A4eDA030470272DfBAf99A47D587E76709')`) // sepolia contract
.waitForElementContainsText('*[data-id="terminalJournal"]', byteCodeInSepolia, 120000) .waitForElementContainsText('*[data-id="terminalJournal"]', byteCodeInSepolia, 120000)
.click('*[data-id="terminalClearConsole"]') .click('*[data-id="terminalClearConsole"]')
}, },
'Should run a free function while being connected to mainnet #group9': function (browser: NightwatchBrowser) { 'Should run a free function while being connected to mainnet #group9': function (browser: NightwatchBrowser) {
const script = ` const script = `
@ -359,10 +359,10 @@ module.exports = {
.switchEnvironment('vm-mainnet-fork') .switchEnvironment('vm-mainnet-fork')
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.addFile('test_mainnet.sol', { content: script }) .addFile('test_mainnet.sol', { content: script })
const path = "//*[@class='view-line' and contains(.,'resolveENS') and contains(.,'view')]//span//span[contains(.,'(')]" const path = "//*[@class='view-line' and contains(.,'resolveENS') and contains(.,'view')]//span//span[contains(.,'(')]"
const pathRunFunction = `//li//*[@aria-label='Run the free function "resolveENS"']` const pathRunFunction = `//li//*[@aria-label='Run the free function "resolveENS"']`
browser.waitForElementVisible('#editorView') browser.waitForElementVisible('#editorView')
//.waitForElementPresent(pathRunFunction) //.waitForElementPresent(pathRunFunction)
.pause(10000) // the parser need to parse the code .pause(10000) // the parser need to parse the code
.useXpath() .useXpath()
@ -371,25 +371,25 @@ module.exports = {
.perform(function () { .perform(function () {
const actions = this.actions({ async: true }); const actions = this.actions({ async: true });
return actions return actions
.keyDown(this.Keys.SHIFT) .keyDown(this.Keys.SHIFT)
.keyDown(this.Keys.ALT) .keyDown(this.Keys.ALT)
.sendKeys('r') .sendKeys('r')
}) })
.useCss() .useCss()
.waitForElementContainsText('*[data-id="terminalJournal"]', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', 120000) .waitForElementContainsText('*[data-id="terminalJournal"]', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', 120000)
}, },
'Should run free function which logs in the terminal #group10': function (browser: NightwatchBrowser) { 'Should run free function which logs in the terminal #group10': function (browser: NightwatchBrowser) {
const script = `import "hardhat/console.sol"; const script = `import "hardhat/console.sol";
function runSomething () view { function runSomething () view {
console.log("test running free function"); console.log("test running free function");
} }
` `
browser browser
.addFile('test.sol', { content: script }) .addFile('test.sol', { content: script })
.scrollToLine(3) .scrollToLine(3)
const path = "//*[@class='view-line' and contains(.,'runSomething') and contains(.,'view')]//span//span[contains(.,'(')]" const path = "//*[@class='view-line' and contains(.,'runSomething') and contains(.,'view')]//span//span[contains(.,'(')]"
const pathRunFunction = `//li//*[@aria-label='Run the free function "runSomething"']` const pathRunFunction = `//li//*[@aria-label='Run the free function "runSomething"']`
browser.waitForElementVisible('#editorView') browser.waitForElementVisible('#editorView')
.useXpath() .useXpath()
@ -398,18 +398,15 @@ module.exports = {
.perform(function () { .perform(function () {
const actions = this.actions({ async: true }); const actions = this.actions({ async: true });
return actions return actions
.keyDown(this.Keys.SHIFT) .keyDown(this.Keys.SHIFT)
.keyDown(this.Keys.ALT) .keyDown(this.Keys.ALT)
.sendKeys('r') .sendKeys('r')
}) })
.useCss() .useCss()
.waitForElementContainsText('*[data-id="terminalJournal"]', 'test running free function', 120000) .waitForElementContainsText('*[data-id="terminalJournal"]', 'test running free function', 120000)
} }
} }
const asyncAwait = ` const asyncAwait = `
var p = function () { var p = function () {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
@ -417,7 +414,7 @@ const asyncAwait = `
resolve("Promise Resolved") resolve("Promise Resolved")
}, 5000) }, 5000)
}) })
} }
var run = async () => { var run = async () => {
console.log('Waiting Promise') console.log('Waiting Promise')
@ -455,7 +452,7 @@ const resolveExternalUrlAndSave = `
} catch (e) { } catch (e) {
console.log(e.message) console.log(e.message)
} }
})() })()
` `
const resolveExternalUrlAndSaveToaPath = ` const resolveExternalUrlAndSaveToaPath = `
@ -466,7 +463,7 @@ const resolveExternalUrlAndSaveToaPath = `
} catch (e) { } catch (e) {
console.log(e.message) console.log(e.message)
} }
})() })()
` `
const resolveUrl = ` const resolveUrl = `
@ -477,7 +474,7 @@ const resolveUrl = `
} catch (e) { } catch (e) {
console.log(e.message) console.log(e.message)
} }
})() })()
` `
const deployWithEthersJs = ` const deployWithEthersJs = `
@ -485,32 +482,32 @@ const deployWithEthersJs = `
(async () => { (async () => {
try { try {
console.log('Running deployWithEthers script...') console.log('Running deployWithEthers script...')
const constructorArgs = [] // Put constructor args (if any) here for your contract const constructorArgs = [] // Put constructor args (if any) here for your contract
// Note that the script needs the ABI which is generated from the compilation artifact. // Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated // Make sure contract is compiled and artifacts are generated
const artifactsPath = 'contracts/artifacts/Owner.json' // Change this for different path const artifactsPath = 'contracts/artifacts/Owner.json' // Change this for different path
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath))
// 'web3Provider' is a remix global variable object // 'web3Provider' is a remix global variable object
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner() const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
let factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) let factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
let contract = await factory.deploy(...constructorArgs); let contract = await factory.deploy(...constructorArgs);
console.log('Contract Address: ', contract.address); console.log('Contract Address: ', contract.address);
// The contract is NOT deployed yet; we must wait until it is mined // The contract is NOT deployed yet; we must wait until it is mined
await contract.deployed() await contract.deployed()
console.log('Deployment successful.') console.log('Deployment successful.')
contract.on('OwnerSet', (previousOwner, newOwner) => { contract.on('OwnerSet', (previousOwner, newOwner) => {
console.log('previousOwner' , previousOwner) console.log('previousOwner' , previousOwner)
console.log('newOwner' , newOwner) console.log('newOwner' , newOwner)
}) })
console.log('ok') console.log('ok')
} catch (e) { } catch (e) {
console.log(e.message) console.log(e.message)
@ -577,12 +574,12 @@ contract StorageWithLib {
* @dev Store valrue in variable * @dev Store valrue in variable
* @param num value to store * @param num value to store
*/ */
function store(uint256 num) public { function store(uint256 num) public {
number = num; number = num;
} }
/** /**
* @dev Return value * @dev Return value
* @return value of 'number' * @return value of 'number'
*/ */
function retrieve() public view returns (uint256){ function retrieve() public view returns (uint256){
@ -629,11 +626,11 @@ describe("Storage", function () {
deployedLinkReferences: metadata.data.deployedBytecode.linkReferences, deployedLinkReferences: metadata.data.deployedBytecode.linkReferences,
} }
const options = { const options = {
libraries: { libraries: {
'Lib': lib.address 'Lib': lib.address
} }
} }
const factory = await ethers.getContractFactoryFromArtifact(artifact, options) const factory = await ethers.getContractFactoryFromArtifact(artifact, options)
const storage = await factory.deploy(); const storage = await factory.deploy();
await storage.deployed() await storage.deployed()
@ -657,10 +654,10 @@ import "hardhat/console.sol";
contract OwnerTest { contract OwnerTest {
address private owner; address private owner;
// event for EVM logging // event for EVM logging
event OwnerSet(address indexed oldOwner, address indexed newOwner); event OwnerSet(address indexed oldOwner, address indexed newOwner);
// modifier to check if caller is owner // modifier to check if caller is owner
modifier isOwner() { modifier isOwner() {
// If the first argument of 'require' evaluates to 'false', execution terminates and all // If the first argument of 'require' evaluates to 'false', execution terminates and all
@ -671,7 +668,7 @@ contract OwnerTest {
require(msg.sender == owner, "Caller is not owner"); require(msg.sender == owner, "Caller is not owner");
_; _;
} }
/** /**
* @dev Set contract deployer as owner * @dev Set contract deployer as owner
*/ */
@ -692,7 +689,7 @@ contract OwnerTest {
} }
/** /**
* @dev Return owner address * @dev Return owner address
* @return address of owner * @return address of owner
*/ */
function getOwner() external view returns (address) { function getOwner() external view returns (address) {
@ -705,39 +702,39 @@ const scriptAutoExec = {
contract: `// SPDX-License-Identifier: GPL-3.0 contract: `// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0; pragma solidity >=0.8.2 <0.9.0;
library lib { library lib {
function test () public view returns (uint) { function test () public view returns (uint) {
return 147; return 147;
} }
} }
/** /**
* @title Storage * @title Storage
* @dev Store & retrieve value inr a variable * @dev Store & retrieve value inr a variable
* @custom:dev-run-script ./scripts/deploy_storage.js * @custom:dev-run-script ./scripts/deploy_storage.js
*/ */
contract Storage { contract Storage {
uint256 number; uint256 number;
/** /**
* @dev Store valrue in variable * @dev Store valrue in variable
* @param num value to store * @param num value to store
*/ */
function store(uint256 num) public { function store(uint256 num) public {
number = num; number = num;
} }
/** /**
* @dev Return value * @dev Return value
* @return value of 'number' * @return value of 'number'
*/ */
function retrieve() public view returns (uint256){ function retrieve() public view returns (uint256){
return number; return number;
} }
function getFromLib() public view returns (uint) { function getFromLib() public view returns (uint) {
return lib.test(); return lib.test();
} }
@ -747,16 +744,16 @@ const scriptAutoExec = {
// Right click on the script name and hit "Run" to execute // Right click on the script name and hit "Run" to execute
const { expect } = require("chai"); const { expect } = require("chai");
const { ethers } = require("hardhat"); const { ethers } = require("hardhat");
(async () => { (async () => {
try { try {
// function getContractFactoryFromArtifact(artifact: Artifact, signer?: ethers.Signer): Promise<ethers.ContractFactory>; // function getContractFactoryFromArtifact(artifact: Artifact, signer?: ethers.Signer): Promise<ethers.ContractFactory>;
// function getContractFactoryFromArtifact(artifact: Artifact, factoryOptions: FactoryOptions): Promise<ethers.ContractFactory>; // function getContractFactoryFromArtifact(artifact: Artifact, factoryOptions: FactoryOptions): Promise<ethers.ContractFactory>;
const metadataLib = JSON.parse(await remix.call('fileManager', 'readFile', 'contracts/artifacts/lib.json')) const metadataLib = JSON.parse(await remix.call('fileManager', 'readFile', 'contracts/artifacts/lib.json'))
console.log('deploying lib:') console.log('deploying lib:')
const artifactLib = { const artifactLib = {
contractName: 'Lib', contractName: 'Lib',
sourceName: 'contracts/1_Storage.sol', sourceName: 'contracts/1_Storage.sol',
@ -767,15 +764,15 @@ const scriptAutoExec = {
deployedLinkReferences: metadataLib.data.deployedBytecode.linkReferences, deployedLinkReferences: metadataLib.data.deployedBytecode.linkReferences,
} }
const optionsLib = {} const optionsLib = {}
const factoryLib = await ethers.getContractFactoryFromArtifact(artifactLib, optionsLib) const factoryLib = await ethers.getContractFactoryFromArtifact(artifactLib, optionsLib)
const lib = await factoryLib.deploy(); const lib = await factoryLib.deploy();
await lib.deployed() await lib.deployed()
console.log('lib deployed', lib.address) console.log('lib deployed', lib.address)
const metadata = JSON.parse(await remix.call('fileManager', 'readFile', 'contracts/artifacts/Storage.json')) const metadata = JSON.parse(await remix.call('fileManager', 'readFile', 'contracts/artifacts/Storage.json'))
const artifact = { const artifact = {
contractName: 'Storage', contractName: 'Storage',
@ -787,25 +784,25 @@ const scriptAutoExec = {
deployedLinkReferences: metadata.data.deployedBytecode.linkReferences, deployedLinkReferences: metadata.data.deployedBytecode.linkReferences,
} }
const options = { const options = {
libraries: { libraries: {
'lib': lib.address 'lib': lib.address
} }
} }
const factory = await ethers.getContractFactoryFromArtifact(artifact, options) const factory = await ethers.getContractFactoryFromArtifact(artifact, options)
const storage = await factory.deploy(); const storage = await factory.deploy();
await storage.deployed() await storage.deployed()
const storeValue = await storage.store(333); const storeValue = await storage.store(333);
// wait until the transaction is mined // wait until the transaction is mined
await storeValue.wait(); await storeValue.wait();
console.log((await storage.getFromLib()).toString()) console.log((await storage.getFromLib()).toString())
// expect((await storage.getFromLib()).toString()).to.equal('34'); // expect((await storage.getFromLib()).toString()).to.equal('34');
} catch (e) { } catch (e) {
console.error(e.message) console.error(e.message)
} }

@ -11,31 +11,31 @@ const sources = [
content: ` content: `
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20; pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable { contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor /// @custom:oz-upgrades-unsafe-allow constructor
constructor() { constructor() {
_disableInitializers(); _disableInitializers();
} }
function initialize(address initialOwner) initializer public { function initialize(address initialOwner) initializer public {
__ERC721_init("MyToken", "MTK"); __ERC721_init("MyToken", "MTK");
__Ownable_init(initialOwner); __Ownable_init(initialOwner);
__UUPSUpgradeable_init(); __UUPSUpgradeable_init();
} }
function _authorizeUpgrade(address newImplementation) function _authorizeUpgrade(address newImplementation)
internal internal
onlyOwner onlyOwner
override override
{} {}
} }
` `
} }
} }
@ -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 browser
.url('http://127.0.0.1:8080/#address=0xdAC17F958D2ee523a2206206994597C13D831ec7&blockscout=eth.blockscout.com') .url('http://127.0.0.1:8080/#address=0xdAC17F958D2ee523a2206206994597C13D831ec7&blockscout=eth.blockscout.com')
.refreshPage() .refreshPage()
@ -175,6 +175,7 @@ module.exports = {
'code has been loaded') 'code has been loaded')
}) })
.url('http://127.0.0.1:8080') // refresh without loading the code sample .url('http://127.0.0.1:8080') // refresh without loading the code sample
.pause(2000)
.currentWorkspaceIs('default_workspace') .currentWorkspaceIs('default_workspace')
.execute(() => { .execute(() => {
return document.querySelector('[data-id="dropdown-item-code-sample"]') === null return document.querySelector('[data-id="dropdown-item-code-sample"]') === null

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

@ -577,7 +577,7 @@ module.exports = {
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.indexOf(`contract Create2FactoryAssembly {`) !== -1, browser.assert.ok(content.indexOf(`contract Create2FactoryAssembly {`) !== -1,
'current displayed content is not Create2FactoryAssembly') 'current displayed content is not Create2FactoryAssembly')
}) })
}, },
tearDown: sauce tearDown: sauce

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

@ -60,7 +60,7 @@ contract Ballot {
!voters[voter].voted, !voters[voter].voted,
"The voter already 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; voters[voter].weight = 1;
} }
@ -101,6 +101,7 @@ contract Ballot {
Voter storage sender = voters[msg.sender]; Voter storage sender = voters[msg.sender];
require(sender.weight != 0, "Has no right to vote"); require(sender.weight != 0, "Has no right to vote");
require(!sender.voted, "Already voted."); require(!sender.voted, "Already voted.");
require(proposal < proposals.length, "Invalid proposal index.");
sender.voted = true; sender.voted = true;
sender.vote = proposal; sender.vote = proposal;
@ -135,4 +136,4 @@ contract Ballot {
{ {
winnerName_ = proposals[winningProposal()].name; winnerName_ = proposals[winningProposal()].name;
} }
} }

@ -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 {CompileAndRun} from './app/tabs/compile-and-run'
import {PluginStateLogger} from './app/tabs/state-logger' import {PluginStateLogger} from './app/tabs/state-logger'
import {SidePanel} from './app/components/side-panel' import {SidePanel} from './app/components/side-panel'
import {StatusBar} from './app/components/status-bar'
import {HiddenPanel} from './app/components/hidden-panel' import {HiddenPanel} from './app/components/hidden-panel'
import {PinnedPanel} from './app/components/pinned-panel' import {PinnedPanel} from './app/components/pinned-panel'
import {VerticalIcons} from './app/components/vertical-icons' import {VerticalIcons} from './app/components/vertical-icons'
@ -306,7 +307,7 @@ class AppComponent {
const permissionHandler = new PermissionHandlerPlugin() const permissionHandler = new PermissionHandlerPlugin()
// ----------------- run script after each compilation results ----------- // ----------------- run script after each compilation results -----------
const pluginStateLogger = new PluginStateLogger() const pluginStateLogger = new PluginStateLogger()
this.engine.register([ this.engine.register([
permissionHandler, permissionHandler,
this.layout, this.layout,
@ -344,13 +345,13 @@ class AppComponent {
hardhatProvider, hardhatProvider,
ganacheProvider, ganacheProvider,
foundryProvider, foundryProvider,
externalHttpProvider, externalHttpProvider,
this.walkthroughService, this.walkthroughService,
search, search,
solidityumlgen, solidityumlgen,
compilationDetails, compilationDetails,
vyperCompilationDetails, vyperCompilationDetails,
// remixGuide, remixGuide,
contractFlattener, contractFlattener,
solidityScript, solidityScript,
templates, templates,
@ -392,10 +393,11 @@ class AppComponent {
const pluginManagerComponent = new PluginManagerComponent(appManager, this.engine) const pluginManagerComponent = new PluginManagerComponent(appManager, this.engine)
const filePanel = new FilePanel(appManager) const filePanel = new FilePanel(appManager)
this.statusBar = new StatusBar(filePanel, this.menuicons)
const landingPage = new LandingPage(appManager, this.menuicons, fileManager, filePanel, contentImport) const landingPage = new LandingPage(appManager, this.menuicons, fileManager, filePanel, contentImport)
this.settings = new SettingsTab(Registry.getInstance().get('config').api, editor, appManager) 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 // CONTENT VIEWS & DEFAULT PLUGINS
const openZeppelinProxy = new OpenZeppelinProxy(blockchain) const openZeppelinProxy = new OpenZeppelinProxy(blockchain)
@ -477,6 +479,7 @@ class AppComponent {
'pluginStateLogger' 'pluginStateLogger'
]) ])
await this.appManager.activatePlugin(['mainPanel', 'menuicons', 'tabs']) 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(['sidePanel']) // activating host plugin separately
await this.appManager.activatePlugin(['pinnedPanel']) await this.appManager.activatePlugin(['pinnedPanel'])
await this.appManager.activatePlugin(['home']) await this.appManager.activatePlugin(['home'])
@ -516,9 +519,6 @@ class AppComponent {
) )
await this.appManager.activatePlugin(['solidity-script']) await this.appManager.activatePlugin(['solidity-script'])
await this.appManager.activatePlugin(['solcoder']) await this.appManager.activatePlugin(['solcoder'])
await this.appManager.activatePlugin(['filePanel']) await this.appManager.activatePlugin(['filePanel'])
// Set workspace after initial activation // Set workspace after initial activation

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

@ -12,7 +12,7 @@ const sidePanel = {
displayName: 'Side Panel', displayName: 'Side Panel',
description: 'Remix IDE side panel', description: 'Remix IDE side panel',
version: packageJson.version, version: packageJson.version,
methods: ['addView', 'removeView', 'currentFocus', 'pinView', 'unPinView'] methods: ['addView', 'removeView', 'currentFocus', 'pinView', 'unPinView', 'focus']
} }
export class SidePanel extends AbstractPanel { 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: [ methods: [
'createNewFile', 'createNewFile',
'uploadFile', 'uploadFile',
'echoCall',
'getCurrentWorkspace', 'getCurrentWorkspace',
'getAvailableWorkspaceName', 'getAvailableWorkspaceName',
'getWorkspaces', 'getWorkspaces',
@ -43,7 +44,7 @@ const profile = {
'registerContextMenuItem', 'registerContextMenuItem',
'renameWorkspace', 'renameWorkspace',
'deleteWorkspace', 'deleteWorkspace',
'loadTemplate', 'loadTemplate',
'clone', 'clone',
'isExpanded', 'isExpanded',
'isGist' 'isGist'
@ -154,10 +155,10 @@ module.exports = class Filepanel extends ViewPlugin {
return this.workspaces return this.workspaces
} }
getAvailableWorkspaceName(name) { getAvailableWorkspaceName(name) {
if (!this.workspaces) return name if (!this.workspaces) return name
let index = 1 let index = 1
let workspace = this.workspaces.find((workspace) => workspace.name === name + ' - ' + index) let workspace = this.workspaces.find((workspace) => workspace.name === name + ' - ' + index)
while (workspace) { while (workspace) {
index++ index++
workspace = this.workspaces.find((workspace) => workspace.name === name + ' - ' + index) workspace = this.workspaces.find((workspace) => workspace.name === name + ' - ' + index)

@ -191,7 +191,7 @@ export class TabProxy extends Plugin {
this.on('fileDecorator', 'fileDecoratorsChanged', async (items) => { this.on('fileDecorator', 'fileDecoratorsChanged', async (items) => {
this.tabsApi.setFileDecorations(items) this.tabsApi.setFileDecorations(items)
}) })
try { try {
this.themeQuality = (await this.call('theme', 'currentTheme') ).quality this.themeQuality = (await this.call('theme', 'currentTheme') ).quality
} catch (e) { } catch (e) {
@ -257,6 +257,11 @@ export class TabProxy extends Plugin {
if ((name.endsWith('.vy') && icon === undefined) || title.includes('Vyper')) { if ((name.endsWith('.vy') && icon === undefined) || title.includes('Vyper')) {
icon = 'assets/img/vyperLogo2.webp' icon = 'assets/img/vyperLogo2.webp'
} }
if (title === 'Solidity Compile Details') {
icon = 'assets/img/solidity.webp'
}
var slash = name.split('/') var slash = name.split('/')
const tabPath = slash.reverse() const tabPath = slash.reverse()
@ -374,7 +379,9 @@ export class TabProxy extends Plugin {
const onZoomIn = () => this.editor.editorFontSize(1) const onZoomIn = () => this.editor.editorFontSize(1)
const onZoomOut = () => this.editor.editorFontSize(-1) const onZoomOut = () => this.editor.editorFontSize(-1)
const onReady = (api) => { this.tabsApi = api } const onReady = (api) => {
this.tabsApi = api
}
this.dispatch({ this.dispatch({
plugin: this, plugin: this,

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

@ -1,12 +1,12 @@
import React from 'react' import React, {useState} from 'react' // eslint-disable-line
import { ViewPlugin } from '@remixproject/engine-web' import { ViewPlugin } from '@remixproject/engine-web'
import { PluginViewWrapper } from '@remix-ui/helper' import { PluginViewWrapper } from '@remix-ui/helper'
import { RemixAppManager } from '../../remixAppManager' import { RemixAppManager } from '../../remixAppManager'
import { RemixUIGridView } from '@remix-ui/remix-ui-grid-view' import { RemixUIGridView } from '@remix-ui/remix-ui-grid-view'
import { RemixUIGridSection } from '@remix-ui/remix-ui-grid-section' import { RemixUIGridSection } from '@remix-ui/remix-ui-grid-section'
import { RemixUIGridCell } from '@remix-ui/remix-ui-grid-cell' 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 //@ts-ignore
const _paq = (window._paq = window._paq || []) const _paq = (window._paq = window._paq || [])
@ -15,7 +15,6 @@ const profile = {
displayName: 'Remix Guide', displayName: 'Remix Guide',
description: 'Learn remix with videos', description: 'Learn remix with videos',
location: 'mainPanel', location: 'mainPanel',
methods: ['showDetails'],
events: [] events: []
} }
@ -24,13 +23,20 @@ export class RemixGuidePlugin extends ViewPlugin {
appManager: RemixAppManager appManager: RemixAppManager
element: HTMLDivElement element: HTMLDivElement
payload: any payload: any
themeStyle: any showVideo: boolean
theme: ThemeKeys | ThemeObject videoID: string
handleKeyDown: any
handleEscape: any
constructor(appManager: RemixAppManager) { constructor(appManager: RemixAppManager) {
super(profile) super(profile)
this.appManager = appManager this.appManager = appManager
this.element = document.createElement('div') this.element = document.createElement('div')
this.element.setAttribute('id', 'remixGuideEl') this.element.setAttribute('id', 'remixGuideEl')
this.payload = {
sectionToExpandedCell: [['', '']],
data: {}
}
} }
async onActivation() { async onActivation() {
@ -38,24 +44,23 @@ export class RemixGuidePlugin extends ViewPlugin {
await this.call('tabs', 'focus', 'remixGuide') await this.call('tabs', 'focus', 'remixGuide')
this.renderComponent() this.renderComponent()
_paq.push(['trackEvent', 'plugin', 'activated', 'remixGuide']) _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 { onDeactivation(): void {
} document.removeEventListener('keydown', this.handleKeyDown);
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()
} }
private handleThemeChange() { private handleThemeChange() {
this.on('theme', 'themeChanged', (theme: any) => { this.on('theme', 'themeChanged', (theme: any) => {
this.renderComponent() this.renderComponent()
}) })
} }
@ -64,6 +69,7 @@ export class RemixGuidePlugin extends ViewPlugin {
this.dispatch = dispatch this.dispatch = dispatch
this.renderComponent() this.renderComponent()
} }
render() { render() {
return ( return (
<div className="bg-dark" id="remixGuide"> <div className="bg-dark" id="remixGuide">
@ -76,168 +82,88 @@ export class RemixGuidePlugin extends ViewPlugin {
this.dispatch({ this.dispatch({
...this, ...this,
...this.payload, ...this.payload,
themeStyle: this.themeStyle, showVideo: this.showVideo,
theme: this.theme videoID: this.videoID
}) })
} }
updateComponent(state: any) { updateComponent(state: any) {
return ( return (
<RemixUIGridView <div className='d-flex'>
plugin={this} <RemixUIGridView
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
plugin={this} plugin={this}
title='Basics' styleList={""}
hScrollable= {true} 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 { Data.sections.map(section => {
plugin={this} return <RemixUIGridSection
title="first item" plugin={this}
tagList={['L2', 'AI']} title={section.title}
logo='/assets/img/soliditySurvey2023.webp' hScrollable= {true}
> >
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img> { section.cells.map(cell => {
</RemixUIGridCell> return <RemixUIGridCell
<RemixUIGridCell plugin={this}
plugin={this} title={cell.title}
title="next" tagList={cell.tagList}
pinned={true} expandViewEl={
tagList={['L2', 'plugins']} cell.expandViewElement
> }
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img> handleExpand={() => {
</RemixUIGridCell> <RemixUIGridCell this.showVideo = true
plugin={this} this.videoID = cell.expandViewElement.videoID
title="something" this.renderComponent()
pinned={false} }}
tagList={['solidity', 'plugins']} logo={cell.expandViewElement.logo}
> >
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img> <a href={"https://www.youtube.com/@" + cell.authorURL} target="__blank">
</RemixUIGridCell> <img src={"//img.youtube.com/vi/" + this.videoID + "/0.jpg"} style={{ height: '70px', width: '70px' }}></img>
<RemixUIGridCell </a>
plugin={this} </RemixUIGridCell>
title="1" })}
tagList={['solidity']} </RemixUIGridSection>
> })}
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img> </RemixUIGridView>
</RemixUIGridCell> <RemixUIGridCell { state.showVideo && <div
plugin={this} data-id={`EnterModalDialogContainer-react`}
title="1" data-backdrop="static"
> data-keyboard="false"
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img> className={"modal d-flex"}
</RemixUIGridCell> role="dialog"
<RemixUIGridCell style={{ justifyContent: "center" }}
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}
> >
<RemixUIGridCell <div className="align-self-center pb-4" role="document">
plugin={this} <div
title="first item" tabIndex={-1}
logo='/assets/img/soliditySurvey2023.webp' className={'modal-content remixModalContent mb-4'}
> >
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img> <div className="text-break remixModalBody d-flex flex-column p-3 justify-content-between" data-id={`EnterModalDialogModalBody-react`}>
</RemixUIGridCell> <iframe style={{ minHeight: "500px", minWidth: "1000px" }} width="1000" height="500" src={"https://www.youtube.com/embed/" + this.videoID + "?si=ZdckOaSPR7VsLj_2"} allowFullScreen></iframe>
<RemixUIGridCell </div>
plugin={this} <div className="modal-footer d-flex flex-column">
title="next" <button onClick={() => {
> this.showVideo = false
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img> this.renderComponent()
</RemixUIGridCell> <RemixUIGridCell }}>Close</button>
plugin={this} </div>
title="something" </div>
> </div>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img> </div>}
</RemixUIGridCell> </div>
<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>
) )
} }

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

@ -21,7 +21,7 @@
"editor.formatCode": "Format Code", "editor.formatCode": "Format Code",
"editor.generateDocumentation": "Generate documentation for this function", "editor.generateDocumentation": "Generate documentation for this function",
"editor.generateDocumentation2": "Generate documentation for the function \"{name}\"", "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.explainFunction": "Explain this function",
"editor.explainFunctionSol": "Explain this code", "editor.explainFunctionSol": "Explain this code",
"editor.explainFunction2": "Explain the function \"{name}\"", "editor.explainFunction2": "Explain the function \"{name}\"",

@ -29,7 +29,7 @@
"home.dgitPluginDesc": "Add source control to your projects.", "home.dgitPluginDesc": "Add source control to your projects.",
"home.oneClickDappDesc": "Quickly generate smart contract interfaces", "home.oneClickDappDesc": "Quickly generate smart contract interfaces",
"home.getStarted": "Get Started", "home.getStarted": "Get Started",
"home.projectTemplates": "Project Templates", "home.projectTemplates": "Explore. Prototype. Create.",
"home.blankTemplateDesc": "Create an empty workspace.", "home.blankTemplateDesc": "Create an empty workspace.",
"home.remixDefaultTemplateDesc": "Create a workspace with sample files.", "home.remixDefaultTemplateDesc": "Create a workspace with sample files.",
"home.ozerc20TemplateDesc": "Create an ERC20 token by importing OpenZeppelin library.", "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.zeroxErc20TemplateDesc": "Create an ERC20 token by importing 0xProject contract.",
"home.learn": "Learn", "home.learn": "Learn",
"home.learnEth1": "Remix Basics", "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.learnEth2": "Intro to Solidity",
"home.learnEth2Desc": "Interactively learn Solidity beginner concepts.", "home.learnEth2Desc": "Interactively learn Solidity beginner concepts.",
"home.remixAdvanced": "Deploying with Libraries", "home.remixAdvanced": "Deploying with Libraries",
@ -56,15 +56,16 @@
"home.remixDesktop": "Remix Desktop", "home.remixDesktop": "Remix Desktop",
"home.searchDocumentation": "Search Documentation", "home.searchDocumentation": "Search Documentation",
"home.files": "Files", "home.files": "Files",
"home.newFile": "New File", "home.newFile": "New",
"home.startCoding": "Start Coding", "home.startCoding": "Start Coding",
"home.startCodingPlayground": "Open a playground for prototyping and simple learning", "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.openFileTooltip": "Open a File from your File System",
"home.accessFileSystem": "Access File System", "home.accessFileSystem": "Connect to Local Filesystem",
"home.loadFrom": "Load from", "home.loadFrom": "Load from",
"home.resources": "Resources", "home.resources": "Resources",
"home.connectToLocalhost": "Connect to Localhost", "home.connectToLocalhost": "Connect to Localhost",
"home.seeAllTutorials": "See all tutorials", "home.seeAllTutorials": "See all tutorials",
"home.maintainedByRemix": "Maintained by Remix" "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._comment_contract-selection.tsx": "libs/remix-ui/solidity-compiler/src/lib/contract-selection.tsx",
"solidity.publishOn": "Publish on", "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.flatten": "Flatten contracts before UML generation.",
"solidity.generateUML": "Generate a UML diagram of your contract.", "solidity.generateUML": "Generate a UML diagram of your contract.",
"solidity.flattenLabel": "Flatten", "solidity.flattenLabel": "Flatten",

@ -80,15 +80,6 @@
"udapp.pinnedAt": "Pinned at", "udapp.pinnedAt": "Pinned at",
"udapp.filePath": "File path", "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._comment_recorderCardUI.tsx": "libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx",
"udapp.transactionsRecorded": "Transactions recorded", "udapp.transactionsRecorded": "Transactions recorded",
"udapp.transactionsCountTooltip": "The number of recorded transactions", "udapp.transactionsCountTooltip": "The number of recorded transactions",

@ -97,6 +97,7 @@ module.exports = class SettingsTab extends ViewPlugin {
updateCopilotChoice(isChecked) { updateCopilotChoice(isChecked) {
this.config.set('settings/copilot/suggest/activate', isChecked) this.config.set('settings/copilot/suggest/activate', isChecked)
this.useCopilot = isChecked this.useCopilot = isChecked
this.emit('copilotChoiceUpdated', isChecked)
this.dispatch({ this.dispatch({
...this ...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', 'menuicons',
'filePanel', 'filePanel',
'terminal', 'terminal',
'statusBar',
'settings', 'settings',
'pluginManager', 'pluginManager',
'tabs', 'tabs',
@ -79,7 +80,8 @@ let requiredModules = [ // services + layout views + system views
'solhint', 'solhint',
'dgit', 'dgit',
'pinnedPanel', 'pinnedPanel',
'pluginStateLogger' 'pluginStateLogger',
'remixGuide'
] ]
@ -104,7 +106,7 @@ const isVM = (name) => {
} }
export function isNative(name) { export function isNative(name) {
// nativePlugin allows to bypass the permission request // nativePlugin allows to bypass the permission request
const nativePlugins = [ const nativePlugins = [
'vyper', 'vyper',
@ -118,6 +120,7 @@ export function isNative(name) {
'solhint', 'solhint',
'solidityUnitTesting', 'solidityUnitTesting',
'layout', 'layout',
'statusBar',
'notification', 'notification',
'hardhat-provider', 'hardhat-provider',
'ganache-provider', 'ganache-provider',
@ -158,7 +161,6 @@ export class RemixAppManager extends PluginManager {
if (Registry.getInstance().get('platform').api.isDesktop()) { if (Registry.getInstance().get('platform').api.isDesktop()) {
requiredModules = [...requiredModules, 'fs', 'electronTemplates', 'isogit', 'remix-templates', 'electronconfig', 'xterm', 'compilerloader', 'ripgrep'] requiredModules = [...requiredModules, 'fs', 'electronTemplates', 'isogit', 'remix-templates', 'electronconfig', 'xterm', 'compilerloader', 'ripgrep']
} }
} }
async canActivatePlugin(from, to) { 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) { apply(compiler) {
const onEnd = async () => { const onEnd = async () => {
try { 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 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, // 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. // 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 { remixClient } from './utils'
import {CompilationResult} from '@remixproject/plugin-api' import { CompilationResult } from '@remixproject/plugin-api'
// Components // Components
import CompilerButton from './components/CompilerButton' import CompilerButton from './components/CompilerButton'
import WarnRemote from './components/WarnRemote'
import VyperResult from './components/VyperResult' import VyperResult from './components/VyperResult'
import LocalUrlInput from './components/LocalUrl' import LocalUrlInput from './components/LocalUrl'
import ToggleButtonGroup from 'react-bootstrap/ToggleButtonGroup'
import ToggleButton from 'react-bootstrap/ToggleButton'
import Button from 'react-bootstrap/Button' import Button from 'react-bootstrap/Button'
import Accordion from 'react-bootstrap/Accordion' import Accordion from 'react-bootstrap/Accordion'
import Card from 'react-bootstrap/Card'
import './app.css' import './app.css'
import {CustomTooltip} from '@remix-ui/helper' import { CustomTooltip } from '@remix-ui/helper'
import {Form} from 'react-bootstrap' import { Form } from 'react-bootstrap'
import {CompileErrorCard} from './components/CompileErrorCard' import { CompileErrorCard } from './components/CompileErrorCard'
import CustomAccordionToggle from './components/CustomAccordionToggle' import CustomAccordionToggle from './components/CustomAccordionToggle'
interface AppState { interface AppState {
@ -89,11 +85,11 @@ const App = () => {
/** Update the environment state value */ /** Update the environment state value */
function setEnvironment(environment: 'local' | 'remote') { function setEnvironment(environment: 'local' | 'remote') {
setState({...state, environment}) setState({ ...state, environment })
} }
function setLocalUrl(url: string) { function setLocalUrl(url: string) {
setState({...state, localUrl: url}) setState({ ...state, localUrl: url })
} }
function compilerUrl() { function compilerUrl() {
@ -111,14 +107,19 @@ const App = () => {
spinnerIcon.current.classList.add('remixui_spinningIcon') spinnerIcon.current.classList.add('remixui_spinningIcon')
} }
const [toggleAccordion, setToggleAccordion] = useState(false) const [cloneCount, setCloneCount] = useState(0)
return ( return (
<main id="vyper-plugin"> <main id="vyper-plugin">
<section> <section>
<div className="px-3 pt-3 mb-3 w-100"> <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."> <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 Clone a repo of Vyper examples
</Button> </Button>
</CustomTooltip> </CustomTooltip>
@ -162,7 +163,7 @@ const App = () => {
in the .vy file. in the .vy file.
</span> </span>
<div className="px-3 w-100 mb-3 mt-1" id="compile-btn"> <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> </div>
<article id="result" className="p-2 mx-3 border-top mt-2"> <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 axios from 'axios'
import { remixClient } from './remix-client' import { remixClient } from './remix-client'
import _ from 'lodash' import _ from 'lodash'
export interface Contract { export interface Contract {
name: string name: string
content: string content: string
@ -72,8 +71,8 @@ const buildError = (output) => {
const line = output.line const line = output.line
if (line) { if (line) {
const lineColumnPos = { const lineColumnPos = {
start: {line: line - 1, column: 10}, start: { line: line - 1, column: 10 },
end: {line: line - 1, column: 10} end: { line: line - 1, column: 10 }
} }
// remixClient.highlight(lineColumnPos as any, _contract.name, '#e0b4b4') // remixClient.highlight(lineColumnPos as any, _contract.name, '#e0b4b4')
} else { } else {
@ -92,8 +91,8 @@ const buildError = (output) => {
} }
if (location?.length > 0) { if (location?.length > 0) {
const lineColumnPos = { const lineColumnPos = {
start: {line: parseInt(location[0]) - 1, column: 10}, start: { line: parseInt(location[0]) - 1, column: 10 },
end: {line: parseInt(location[0]) - 1, column: 10} end: { line: parseInt(location[0]) - 1, column: 10 }
} }
// remixClient.highlight(lineColumnPos as any, _contract.name, message) // 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.') throw new Error('Use extension .vy for Vyper.')
} }
let contractName = contract['name'] let contractName = contract['name']
const compilePackage = { const compilePackage = {
manifest: 'ethpm/3', manifest: 'ethpm/3',
sources: { sources: {
[contractName] : {content : fixContractContent(contract.content)} [contractName] : { content : fixContractContent(contract.content) }
} }
} }
let response = await axios.post(`${url}compile`, compilePackage ) 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) { export async function compileContract(contract: string, compilerUrl: string, setOutput?: any, setLoadingSpinnerState?: React.Dispatch<React.SetStateAction<boolean>>, spinner?: boolean) {
remixClient.eventEmitter.emit('resetCompilerState', {}) remixClient.eventEmitter.emit('resetCompilerState', {})
spinner && spinner === true ? setLoadingSpinnerState && setLoadingSpinnerState(true) : null spinner && spinner === true ? setLoadingSpinnerState && setLoadingSpinnerState(true) : null
@ -301,7 +297,7 @@ export async function compileContract(contract: string, compilerUrl: string, set
}) })
setLoadingSpinnerState && setLoadingSpinnerState(false) 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 output = null
return return
} }
@ -332,13 +328,10 @@ export async function compileContract(contract: string, compilerUrl: string, set
}) })
setLoadingSpinnerState && setLoadingSpinnerState(false) setLoadingSpinnerState && setLoadingSpinnerState(false)
remixClient.eventEmitter.emit('setOutput', {status: 'failed', message: err.message}) remixClient.eventEmitter.emit('setOutput', { status: 'failed', message: err.message })
} }
} }
export type StandardOutput = { export type StandardOutput = {
sources: { sources: {
[fileName: string]: { [fileName: string]: {

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

@ -2,12 +2,12 @@ const fs = require('fs')
let value = fs.readFileSync('./done.json') let value = fs.readFileSync('./done.json')
value = JSON.parse(value) 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 = '' let data = ''
console.log(inDone.length, 'issues/Prs\n') console.log(inDone.length, 'PRs\n')
data = inDone.length + ' issues/Prs\n' data = inDone.length + ' PRs\n'
for (let card of inDone) { 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` 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 - get the result in the file done.txt
/* /*
{ {
search(type: REPOSITORY, query: "remix-project", first: 1) { repository(owner: "ethereum", name: "remix-project") {
edges { name
node { projectV2(number: 52) {
__typename url
... on Repository { items(first: 100) {
owner { totalCount
id edges {
} node {
name content {
project(number: 31) { ... on PullRequest {
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 {
url url
id id
number number
title title
merged 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 { privateToAddress, toChecksumAddress, isValidPrivate, Address, toBytes, bytesToHex, Account } from '@ethereumjs/util'
import { privateKeyToAccount } from 'web3-eth-accounts' import { privateKeyToAccount } from 'web3-eth-accounts'
import { toBigInt } from 'web3-utils' import { toBigInt } from 'web3-utils'
import * as crypto from 'crypto' import * as crypto from 'crypto'
type AccountType = {
nonce: number,
privateKey: Uint8Array
}
export class Web3Accounts { export class Web3Accounts {
accounts: Record<string, unknown> accounts: Record<string, AccountType>
accountsKeys: Record<string, unknown> accountsKeys: Record<string, string>
vmContext vmContext
constructor (vmContext) { constructor (vmContext) {
@ -73,7 +79,9 @@ export class Web3Accounts {
eth_accounts: this.eth_accounts.bind(this), eth_accounts: this.eth_accounts.bind(this),
eth_getBalance: this.eth_getBalance.bind(this), eth_getBalance: this.eth_getBalance.bind(this),
eth_sign: this.eth_sign.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) { eth_chainId (_payload, cb) {
return cb(null, '0x539') // 0x539 is hex of 1337 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 { Debug } from './methods/debug'
import { VMContext } from './vm-context' import { VMContext } from './vm-context'
import { Web3PluginBase } from 'web3' import { Web3PluginBase } from 'web3'
import { Block } from '@ethereumjs/block'
export interface JSONRPCRequestPayload { export interface JSONRPCRequestPayload {
params: any[]; params: any[];
@ -109,11 +108,11 @@ export class Provider {
callback(new Error('unknown method ' + payload.method)) 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)=>{ return new Promise((resolve,reject)=>{
const cb = (err, result) => { const cb = (err, result) => {
if (typeof callback==='function'){ if (typeof callback==='function'){
callback(err,result) return callback(err, result)
} }
if (err){ if (err){
return reject(err) return reject(err)
@ -125,7 +124,12 @@ export class Provider {
} }
send (payload, callback) { 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 () { isConnected () {

@ -39,4 +39,40 @@ describe('Accounts', () => {
assert.deepEqual(typeof signature === 'string' ? signature.length : signature.signature.length, 132) 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) { if (props.hidden) {
setDragBarPosX(offset) setDragBarPosX(offset)
} else if (props.layoutPosition === 'left') { } 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') { } else if (props.layoutPosition === 'right') {
setDragBarPosX(offset) setDragBarPosX(offset)
} }

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

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

@ -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 { RemixHoverProvider } from './providers/hoverProvider'
import { RemixReferenceProvider } from './providers/referenceProvider' import { RemixReferenceProvider } from './providers/referenceProvider'
import { RemixCompletionProvider } from './providers/completionProvider' import { RemixCompletionProvider } from './providers/completionProvider'
import { RemixSolidityDocumentationProvider } from './providers/documentationProvider'
import { RemixHighLightProvider } from './providers/highlightProvider' import { RemixHighLightProvider } from './providers/highlightProvider'
import { RemixDefinitionProvider } from './providers/definitionProvider' import { RemixDefinitionProvider } from './providers/definitionProvider'
import { RemixCodeActionProvider } from './providers/codeActionProvider' import { RemixCodeActionProvider } from './providers/codeActionProvider'
@ -23,6 +24,7 @@ import './remix-ui-editor.css'
import { circomLanguageConfig, circomTokensProvider } from './syntaxes/circom' import { circomLanguageConfig, circomTokensProvider } from './syntaxes/circom'
import { IPosition } from 'monaco-editor' import { IPosition } from 'monaco-editor'
import { RemixInLineCompletionProvider } from './providers/inlineCompletionProvider' import { RemixInLineCompletionProvider } from './providers/inlineCompletionProvider'
import { providers } from 'ethers'
const _paq = (window._paq = window._paq || []) const _paq = (window._paq = window._paq || [])
enum MarkerSeverity { enum MarkerSeverity {
@ -152,10 +154,10 @@ export const EditorUI = (props: EditorUIProps) => {
const [isDiff, setIsDiff] = useState(false) const [isDiff, setIsDiff] = useState(false)
const [isSplit, setIsSplit] = useState(true) const [isSplit, setIsSplit] = useState(true)
const defaultEditorValue = ` const defaultEditorValue = `
\t\t\t\t\t\t\t ____ _____ __ __ ___ __ __ ___ ____ _____ \t\t\t\t\t\t\t ____ _____ __ __ ___ __ __ ___ ____ _____
\t\t\t\t\t\t\t| _ \\ | ____| | \\/ | |_ _| \\ \\/ / |_ _| | _ \\ | ____| \t\t\t\t\t\t\t| _ \\ | ____| | \\/ | |_ _| \\ \\/ / |_ _| | _ \\ | ____|
\t\t\t\t\t\t\t| |_) | | _| | |\\/| | | | \\ / | | | | | | | _| \t\t\t\t\t\t\t| |_) | | _| | |\\/| | | | \\ / | | | | | | | _|
\t\t\t\t\t\t\t| _ < | |___ | | | | | | / \\ | | | |_| | | |___ \t\t\t\t\t\t\t| _ < | |___ | | | | | | / \\ | | | |_| | | |___
\t\t\t\t\t\t\t|_| \\_\\ |_____| |_| |_| |___| /_/\\_\\ |___| |____/ |_____|\n\n \t\t\t\t\t\t\t|_| \\_\\ |_____| |_| |_| |___| /_/\\_\\ |___| |____/ |_____|\n\n
\t\t\t\t\t\t\t${intl.formatMessage({ id: 'editor.keyboardShortcuts' })}:\n \t\t\t\t\t\t\t${intl.formatMessage({ id: 'editor.keyboardShortcuts' })}:\n
\t\t\t\t\t\t\t\tCTRL + S: ${intl.formatMessage({ id: 'editor.keyboardShortcuts.text1' })}\n \t\t\t\t\t\t\t\tCTRL + S: ${intl.formatMessage({ id: 'editor.keyboardShortcuts.text1' })}\n
@ -180,6 +182,8 @@ export const EditorUI = (props: EditorUIProps) => {
const currentFunction = useRef('') const currentFunction = useRef('')
const currentFileRef = useRef('') const currentFileRef = useRef('')
const currentUrlRef = useRef('') const currentUrlRef = useRef('')
let currenFunctionNode = useRef('')
// const currentDecorations = useRef({ sourceAnnotationsPerFile: {}, markerPerFile: {} }) // decorations that are currently in use by the editor // const currentDecorations = useRef({ sourceAnnotationsPerFile: {}, markerPerFile: {} }) // decorations that are currently in use by the editor
// const registeredDecorations = useRef({}) // registered decorations // const registeredDecorations = useRef({}) // registered decorations
@ -739,17 +743,62 @@ export const EditorUI = (props: EditorUIProps) => {
} }
let gptGenerateDocumentationAction let gptGenerateDocumentationAction
const extractNatspecComments = (codeString: string): string => {
const natspecCommentRegex = /\/\*\*[\s\S]*?\*\//g;
const comments = codeString.match(natspecCommentRegex);
return comments ? comments[0] : "";
}
const executeGptGenerateDocumentationAction = { const executeGptGenerateDocumentationAction = {
id: 'generateDocumentation', id: 'generateDocumentation',
label: intl.formatMessage({ id: 'editor.generateDocumentation' }), label: intl.formatMessage({ id: 'editor.generateDocumentation' }),
contextMenuOrder: 0, // choose the order contextMenuOrder: 0, // choose the order
contextMenuGroupId: 'gtp', // create a new grouping contextMenuGroupId: 'gtp', // create a new grouping
keybindings: [], keybindings: [
// Keybinding for Ctrl + D
monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.KeyD
],
run: async () => { run: async () => {
const unsupportedDocTags = ['@title'] // these tags are not supported by the current docstring parser
const file = await props.plugin.call('fileManager', 'getCurrentFile') const file = await props.plugin.call('fileManager', 'getCurrentFile')
const content = await props.plugin.call('fileManager', 'readFile', file) const content = await props.plugin.call('fileManager', 'readFile', file)
const message = intl.formatMessage({ id: 'editor.generateDocumentationByAI' }, { content, currentFunction: currentFunction.current }) 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']) _paq.push(['trackEvent', 'ai', 'solcoder', 'generateDocumentation'])
}, },
} }
@ -760,7 +809,10 @@ export const EditorUI = (props: EditorUIProps) => {
label: intl.formatMessage({ id: 'editor.explainFunction' }), label: intl.formatMessage({ id: 'editor.explainFunction' }),
contextMenuOrder: 1, // choose the order contextMenuOrder: 1, // choose the order
contextMenuGroupId: 'gtp', // create a new grouping 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 () => { run: async () => {
const file = await props.plugin.call('fileManager', 'getCurrentFile') const file = await props.plugin.call('fileManager', 'getCurrentFile')
const content = await props.plugin.call('fileManager', 'readFile', file) const content = await props.plugin.call('fileManager', 'readFile', file)
@ -776,7 +828,10 @@ export const EditorUI = (props: EditorUIProps) => {
label: intl.formatMessage({ id: 'editor.explainFunctionSol' }), label: intl.formatMessage({ id: 'editor.explainFunctionSol' }),
contextMenuOrder: 1, // choose the order contextMenuOrder: 1, // choose the order
contextMenuGroupId: 'sol-gtp', // create a new grouping contextMenuGroupId: 'sol-gtp', // create a new grouping
keybindings: [], keybindings: [
// Keybinding for Ctrl + E
monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.KeyE
],
run: async () => { run: async () => {
const file = await props.plugin.call('fileManager', 'getCurrentFile') const file = await props.plugin.call('fileManager', 'getCurrentFile')
const content = await props.plugin.call('fileManager', 'readFile', file) 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') const functionImpl = nodesAtPosition.find((node) => node.kind === 'function')
if (functionImpl) { if (functionImpl) {
currentFunction.current = functionImpl.name currentFunction.current = functionImpl.name
currenFunctionNode = functionImpl
executeGptGenerateDocumentationAction.label = intl.formatMessage({ id: 'editor.generateDocumentation2' }, { name: functionImpl.name }) executeGptGenerateDocumentationAction.label = intl.formatMessage({ id: 'editor.generateDocumentation2' }, { name: functionImpl.name })
gptGenerateDocumentationAction = editor.addAction(executeGptGenerateDocumentationAction) gptGenerateDocumentationAction = editor.addAction(executeGptGenerateDocumentationAction)
executegptExplainFunctionAction.label = intl.formatMessage({ id: 'editor.explainFunction2' }, { name: functionImpl.name }) 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 if (!textColor || textColor == '') textColor = filterCon.keyValueMap[props.label].color
return ( 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 <input
className="custom-control-input" className="custom-control-input"
id={"GVCheckbox" + props.label} id={"GVCheckbox" + props.label}
defaultChecked={defChecked} defaultChecked={defChecked}
onChange={e => { onChange={e => {
if (props.label == 'no tag') filterCon.updateValue(props.label, e.target.checked, textColor)}}
filterCon.showUntagged = ! filterCon.showUntagged
else filterCon.updateValue(props.label, e.target.checked, textColor)}}
type="checkbox" type="checkbox"
/> />
<label <label

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

@ -38,6 +38,12 @@
top: 0.1rem; top: 0.1rem;
} }
.remixui_grid_cell_tags_no_pin {
position: relative;
right: 0rem;
top: 0.1rem;
}
.remixui_grid_cell_tag { .remixui_grid_cell_tag {
font-size: x-small; font-size: x-small;
font-weight: bolder; 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 './remix-ui-grid-cell.css'
import FiltersContext from "./filtersContext" import FiltersContext from "./filtersContext"
@ -16,67 +16,97 @@ interface RemixUIGridCellProps {
pinned?: boolean pinned?: boolean
pinStateCallback?: any pinStateCallback?: any
logo?: string logo?: string
title?: string title: string
tagList?: string[] // max 8, others will be ignored tagList?: string[] // max 8, others will be ignored
classList?: string classList?: string
styleList?: any styleList?: any
children?: ReactNode children?: ReactNode
expandViewEl?: any
handleExpand?: any
} }
export const RemixUIGridCell = (props: RemixUIGridCellProps) => { export const RemixUIGridCell = (props: RemixUIGridCellProps) => {
const filterCon = useContext(FiltersContext) const filterCon = useContext(FiltersContext)
const [anyEnabled, setAnyEnabled] = useState(false) const [anyEnabled, setAnyEnabled] = useState(false)
const [expand, setExpand] = useState(false)
const [pinned, setPinned] = useState<boolean>(props.pinned) const [pinned, setPinned] = useState<boolean>(props.pinned)
useEffect(() => { useEffect(() => {
if (props.tagList) setAnyEnabled(props.tagList.some((key) => filterCon.keyValueMap[key]?.enabled)) 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]) }, [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 ( return (
<div className='mr-2 mt-3'> <div className='mr-2 mt-3' onClick={() => {
{ anyEnabled && <div className='d-flex flex-grid'> if (props.expandViewEl)
<div className={"d-flex mx-0 p-2 bg-light border border-secondary remixui_grid_cell_container " + props.classList || ''} data-id={"remixUIGS" + props.title}> props.handleExpand(!expand)
<div className="d-flex remixui_grid_cell flex-column"> else return
<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' }}/> } { anyEnabled && <div className='d-flex flex-column'>
{ props.title && <label <div className='d-flex flex-grid'>
className='m-0 p-0 align-items-left' <div className={"d-flex mx-0 p-2 bg-light border border-secondary remixui_grid_cell_container " + props.classList || ''} data-id={"remixUIGS" + props.title}>
style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontSize: 'xx-small' }} <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.title } { props.logo && <img className='remixui_grid_view_logo mr-1' src={props.logo} style={{ width: '1rem', height: '1rem' }}/> }
</label> } { 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> </div>
{ props.children }
</div> </div>
</div> { filterCon.showPin && <button
{ filterCon.showPin && <button className={`${pinned ? 'fa-duotone' : 'fa-light'}` + ` fa-map-pin text-info border-0 mb-0 remixui_grid_cell_pin`}
className={`${pinned ? 'fa-duotone' : 'fa-light'}` + ` fa-map-pin text-info border-0 mb-0 remixui_grid_cell_pin`} onClick={() => {
onClick={() => { setPinned(!pinned)
setPinned(!pinned) props.pinStateCallback()
props.pinStateCallback() }}
}} ></button>}
></button>} { props.tagList && <div className={`d-flex flex-column align-items-begin ` +`${filterCon.showPin ? 'remixui_grid_cell_tags' : 'remixui_grid_cell_tags_no_pin'}`}>
{ props.tagList && <div className='d-flex flex-column align-items-begin remixui_grid_cell_tags'> { Object.keys(props.tagList).map((key) => (
{ Object.keys(props.tagList).map((key) => ( filterCon.keyValueMap[props.tagList[key]].enabled && (
filterCon.keyValueMap[props.tagList[key]].enabled && ( <CustomTooltip
<CustomTooltip placement="right"
placement="right" tooltipId="pluginManagerInactiveTitleLinkToDoc"
tooltipId="pluginManagerInactiveTitleLinkToDoc" tooltipClasses="text-nowrap"
tooltipClasses="text-nowrap" tooltipText={props.tagList[key]}
tooltipText={props.tagList[key]}
>
<span key={props.tagList[key]}
className={'remixui_grid_cell_tag bg-' + filterCon.keyValueMap[props.tagList[key]].color}
> >
</span> <span key={props.tagList[key]}
</CustomTooltip> className={'remixui_grid_cell_tag bg-' + filterCon.keyValueMap[props.tagList[key]].color}
) >
)) } </span>
</div> } </CustomTooltip>
{ !props.tagList && <span )
className={'remixui_grid_cell_tags'}> )) }
</span> } </div> }
{ !props.tagList && <span
className={'remixui_grid_cell_tags'}>
</span> }
</div>
{ expand && <div>
{ props.expandViewEl }
</div>
}
</div> } </div> }
</div> </div>
) )

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

@ -1,7 +1,6 @@
import React, {useState, useEffect, useContext, useRef, ReactNode} from 'react' // eslint-disable-line import React, {useState, useEffect, useContext, useRef, ReactNode} from 'react' // eslint-disable-line
import './remix-ui-grid-view.css' import './remix-ui-grid-view.css'
import { ThemeContext, themes } from './themeContext'
import CustomCheckbox from './components/customCheckbox' import CustomCheckbox from './components/customCheckbox'
import FiltersContext from "./filtersContext" import FiltersContext from "./filtersContext"
@ -28,7 +27,7 @@ interface RemixUIGridViewProps {
export const RemixUIGridView = (props: RemixUIGridViewProps) => { export const RemixUIGridView = (props: RemixUIGridViewProps) => {
const [keyValueMap, setKeyValueMap] = useState<Record<string, { enabled: boolean; color: string; }>>({}); const [keyValueMap, setKeyValueMap] = useState<Record<string, { enabled: boolean; color: string; }>>({});
const [filter, setFilter] = useState("")
const showUntagged = props.showUntagged || false const showUntagged = props.showUntagged || false
const showPin = props.showPin || false const showPin = props.showPin || false
const updateValue = (key: string, enabled: boolean, color?: string) => { 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) => { const addValue = (key: string, enabled: boolean, color: string) => {
// Check if the key already exists, if so, do not add // Check if the key already exists, if so, do not add
if (key in keyValueMap) { 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 // Initialize filters context with data from props
useEffect(() => { useEffect(() => {
document.addEventListener('keyup', (e) => handleSearchKeyDown(e))
if (props.tagList && Array.isArray(props.tagList)) { if (props.tagList && Array.isArray(props.tagList)) {
const initialKeyValueMap: Record<string, { enabled: boolean; color: string; }> = {}; 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' } if (showUntagged) initialKeyValueMap['no tag'] = { enabled: true, color: 'primary' }
setKeyValueMap(initialKeyValueMap) 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 ( 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"> <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 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 p-4 bg-light flex-column remixui_grid_view_titlebar'> <div className='d-flex flex-row align-items-center mb-2'>
<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.logo && <img className='remixui_grid_view_logo mr-2' src={props.logo} /> } { props.title && <h3 className='mb-0'>{ props.title }</h3> }
{ 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> </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> </div>
</ThemeContext.Provider> { props.children }
</div>
</div> </div>
</FiltersContext.Provider> </FiltersContext.Provider>
) )

@ -12,12 +12,9 @@ function HomeTabFeatured() {
const themeFilter = useContext(ThemeContext) const themeFilter = useContext(ThemeContext)
return ( return (
<div className="pt-3 pl-2" id="hTFeaturedeSection"> <div className="pt-1 pl-2" id="hTFeaturedeSection">
<label style={{ fontSize: '1.2rem' }}> <div className="mb-2 remix_ui-carousel-container">
<FormattedMessage id="home.featured" /> <div className="w-100 d-flex flex-column rounded-3 remix_ui-carouselbox">
</label>
<div className="mb-2">
<div className="w-100 d-flex flex-column" style={{ height: '200px' }}>
<ThemeContext.Provider value={themeFilter}> <ThemeContext.Provider value={themeFilter}>
<Carousel <Carousel
arrows={false} arrows={false}
@ -34,16 +31,16 @@ function HomeTabFeatured() {
centerMode={false} centerMode={false}
autoPlay={true} autoPlay={true}
keyBoardControl={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" sliderClass="h-100 justify-content-between"
deviceType={'desktop'} deviceType={'desktop'}
itemClass="" itemClass=""
autoPlaySpeed={10000} autoPlaySpeed={10000}
dotListClass="position-relative mt-2" 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"> <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> </a>
<div className="h6 w-50 p-2 pl-4 align-self-center" style={{ flex: '1' }}> <div className="h6 w-50 p-2 pl-4 align-self-center" style={{ flex: '1' }}>
<h5>{releaseDetails.version} {releaseDetails.title}</h5> <h5>{releaseDetails.version} {releaseDetails.title}</h5>
@ -65,9 +62,9 @@ function HomeTabFeatured() {
</a> </a>
</div> </div>
</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"> <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> </a>
<div className="h6 w-50 p-2 pl-4 align-self-center" style={{ flex: '1' }}> <div className="h6 w-50 p-2 pl-4 align-self-center" style={{ flex: '1' }}>
<h5> <h5>
@ -86,9 +83,9 @@ function HomeTabFeatured() {
</a> </a>
</div> </div>
</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"> <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> </a>
<div className="h6 w-50 p-2 pl-4 align-self-center" style={{ flex: '1' }}> <div className="h6 w-50 p-2 pl-4 align-self-center" style={{ flex: '1' }}>
<h5> <h5>
@ -110,9 +107,9 @@ function HomeTabFeatured() {
</a> </a>
</div> </div>
</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"> <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> </a>
<div className="h6 w-50 p-2 pl-4 align-self-center" style={{ flex: '1' }}> <div className="h6 w-50 p-2 pl-4 align-self-center" style={{ flex: '1' }}>
<h5> <h5>

@ -84,7 +84,7 @@ function HomeTabFeaturedPlugins({ plugin }: HomeTabFeaturedPluginsProps) {
} }
return ( 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' }}> <label className="" style={{ fontSize: '1.2rem' }}>
<FormattedMessage id="home.featuredPlugins" /> <FormattedMessage id="home.featuredPlugins" />
</label> </label>

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

@ -1,15 +1,15 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* 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 { 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 { ThemeContext } from '../themeContext'
import Carousel from 'react-multi-carousel'
import WorkspaceTemplate from './workspaceTemplate' import WorkspaceTemplate from './workspaceTemplate'
import 'react-multi-carousel/lib/styles.css' import 'react-multi-carousel/lib/styles.css'
import CustomNavButtons from './customNavButtons'
import { appPlatformTypes, platformContext } from '@remix-ui/app' import { appPlatformTypes, platformContext } from '@remix-ui/app'
import { Plugin } from "@remixproject/engine"; import { Plugin } from "@remixproject/engine";
import { CustomRemixApi } from '@remix-api' import { CustomRemixApi } from '@remix-api'
import { CustomTooltip } from '@remix-ui/helper'
declare global { declare global {
interface Window { interface Window {
_paq: any _paq: any
@ -20,12 +20,65 @@ interface HomeTabGetStartedProps {
plugin: any 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) { function HomeTabGetStarted({ plugin }: HomeTabGetStartedProps) {
const platform = useContext(platformContext) const platform = useContext(platformContext)
const themeFilter = useContext(ThemeContext) const themeFilter = useContext(ThemeContext)
const intl = useIntl()
const carouselRef = useRef<any>({}) const carouselRef = useRef<any>({})
const carouselRefDiv = useRef(null) const carouselRefDiv = useRef(null)
const intl = useIntl()
useEffect(() => { useEffect(() => {
document.addEventListener('wheel', handleScroll) document.addEventListener('wheel', handleScroll)
@ -62,8 +115,7 @@ function HomeTabGetStarted({ plugin }: HomeTabGetStartedProps) {
} }
const createWorkspace = async (templateName) => { const createWorkspace = async (templateName) => {
if (platform === appPlatformTypes.desktop) {
if (platform === appPlatformTypes.desktop){
await plugin.call('remix-templates', 'loadTemplateInNewWindow', templateName) await plugin.call('remix-templates', 'loadTemplateInNewWindow', templateName)
return return
} }
@ -99,90 +151,45 @@ function HomeTabGetStarted({ plugin }: HomeTabGetStartedProps) {
return ( return (
<div className="pl-2" id="hTGetStartedSection"> <div className="pl-2" id="hTGetStartedSection">
<label style={{ fontSize: '1.2rem' }}> <label className="pt-3" style={{ fontSize: '1.2rem' }}>
<FormattedMessage id="home.projectTemplates" /> <FormattedMessage id="home.projectTemplates" />
</label> </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}> <ThemeContext.Provider value={themeFilter}>
<Carousel <div className="pt-3">
ref={carouselRef} <div className="d-flex flex-row align-items-center mb-3 flex-nowrap">
focusOnSelect={true} {workspaceTemplates.slice(0, 3).map((template, index) => (
customButtonGroup={<CustomNavButtons next={undefined} previous={undefined} goToSlide={undefined} parent={carouselRef} />} <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}`}>
arrows={false} <button
swipeable={false} key={index}
draggable={true} 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'}
showDots={false} onClick={(e) => {
responsive={{ createWorkspace(template.templateName)
superLargeDesktop: { }}
breakpoint: { max: 4000, min: 3000 }, data-id={`homeTabGetStarted${template.templateName}`}
items: 5 >
}, {template.workspaceTitle}
desktop: { </button>
breakpoint: { max: 3000, min: 1024 }, </CustomTooltip>
items: 5, ))}
partialVisibilityGutter: 0 </div>
} <div className="d-flex flex-row align-items-center mb-2 flex-nowrap">
}} {workspaceTemplates.slice(3, workspaceTemplates.length).map((template, index) => (
renderButtonGroupOutside={true} <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}`}>
ssr={true} // means to render carousel on server-side. <button
keyBoardControl={true} key={index}
containerClass="carousel-container" className={'btn border p-2 text-nowrap mr-3'}
deviceType={'desktop'} onClick={() => {
itemClass="w-100" createWorkspace(template.templateName)
> }}
<WorkspaceTemplate data-id={`homeTabGetStarted${template.workspaceTitle}`}
gsID="sUTLogo" >
workspaceTitle="MultiSig" {template.workspaceTitle}
description={ </button>
intl.formatMessage({ id: 'home.gnosisSafeMultisigTemplateDesc' }) </CustomTooltip>
} ))}
projectLogo="assets/img/gnosissafeLogo.png" </div>
callback={() => createWorkspace("gnosisSafeMultisig")} </div>
/>
<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>
</ThemeContext.Provider> </ThemeContext.Provider>
</div> </div>
</div> </div>

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

@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'
import { Dropdown, DropdownButton } from 'react-bootstrap' import { Dropdown, DropdownButton } from 'react-bootstrap'
import DropdownItem from 'react-bootstrap/DropdownItem' import DropdownItem from 'react-bootstrap/DropdownItem'
import { localeLang } from './types/carouselTypes' import { localeLang } from './types/carouselTypes'
import { FormattedMessage } from 'react-intl'
export function LanguageOptions({ plugin }: { plugin: any }) { export function LanguageOptions({ plugin }: { plugin: any }) {
const [langOptions, setLangOptions] = useState<string>() const [langOptions, setLangOptions] = useState<string>()
@ -24,7 +25,10 @@ export function LanguageOptions({ plugin }: { plugin: any }) {
return ( 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>
<Dropdown.Toggle title={langOptions} id="languagedropdown" size="sm" style={{ backgroundColor: 'var(--secondary)', color: 'var(--text)' }}> <Dropdown.Toggle title={langOptions} id="languagedropdown" size="sm" style={{ backgroundColor: 'var(--secondary)', color: 'var(--text)' }}>
{langOptions} {langOptions}

@ -76,7 +76,7 @@
text-align: left; text-align: left;
} }
.remixui_home_cursorStyle { .remixui_home_cursorStyle {
cursor: pointer; cursor: pointer;
font-weight: 900; font-weight: 900;
} }
.remixui_home_envButton { .remixui_home_envButton {
@ -114,3 +114,36 @@
background-color: var(--body-bg); background-color: var(--body-bg);
color: var(--text); 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 themeQuality: themes.light
}) })
const [carouselWidth, setCarouselWidth] = useState(65)
useEffect(() => { useEffect(() => {
plugin.call('theme', 'currentTheme').then((theme) => { 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 ( 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}> <ThemeContext.Provider value={state.themeQuality}>
<div className="d-flex flex-row w-100 custom_home_bg"> <div className="d-flex flex-row w-100 h-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="px-2 pl-3 justify-content-start border-right d-flex flex-column" id="remixUIHTLeft" style={{ width: `${100 - carouselWidth}%` }}>
<HomeTabTitle /> <HomeTabTitle />
<HomeTabGetStarted plugin={plugin}></HomeTabGetStarted>
{!(platform === appPlatformTypes.desktop) ? {!(platform === appPlatformTypes.desktop) ?
<HomeTabFile plugin={plugin} />: <HomeTabFile plugin={plugin} />:
<HomeTabFileElectron plugin={plugin}></HomeTabFileElectron>} <HomeTabFileElectron plugin={plugin}></HomeTabFileElectron>}
<HomeTabLearn plugin={plugin} /> {/* <HomeTabLearn plugin={plugin} /> */}
</div> </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}/> <LanguageOptions plugin={plugin}/>
<HomeTabFeatured></HomeTabFeatured> <HomeTabFeatured></HomeTabFeatured>
<HomeTabGetStarted plugin={plugin}></HomeTabGetStarted>
<HomeTabFeaturedPlugins plugin={plugin}></HomeTabFeaturedPlugins> <HomeTabFeaturedPlugins plugin={plugin}></HomeTabFeaturedPlugins>
<HomeTabScamAlert></HomeTabScamAlert>
</div> </div>
</div> </div>
</ThemeContext.Provider> </ThemeContext.Provider>

@ -1,4 +1,4 @@
export { default as RemixPluginPanel } from './lib/plugins/remix-ui-panel' export { default as RemixPluginPanel } from './lib/plugins/remix-ui-panel'
export { default as RemixUIMainPanel } from './lib/main/main-panel' export { default as RemixUIMainPanel } from './lib/main/main-panel'
export { PluginRecord } from './lib/types' export { PluginRecord } from './lib/types'
export { default as RemixUIPanelHeader } from './lib/plugins/panel-header' export { default as RemixUIPanelHeader } from './lib/plugins/panel-header'

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

@ -1,4 +1,5 @@
import { Profile } from '@remixproject/plugin-utils' import { Profile } from '@remixproject/plugin-utils'
import EventEmitter from 'events'
export type PluginRecord = { export type PluginRecord = {
profile: Profile profile: Profile
@ -8,3 +9,22 @@ export type PluginRecord = {
class?: string class?: string
minimized?: boolean 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 { shortenAddress } from "@remix-ui/helper"
import { RunTab } from "../types/run-tab" import { RunTab } from "../types/run-tab"
import { clearInstances, setAccount, setExecEnv } from "./actions" 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' import { toChecksumAddress } from '@ethereumjs/util'
export const updateAccountBalances = async (plugin: RunTab, dispatch: React.Dispatch<any>) => { 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)) dispatch(fetchAccountsListFailed(e.message))
} }
} catch (e) { } 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) => { async (error, address) => {
if (error) { 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) 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) => { 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) => { plugin.blockchain.signMessage(message, account, passphrase, (err, msgHash, signedData) => {
if (err) { 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)) 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 // 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 { UpgradeableContract } from '../../../../../../node_modules/@openzeppelin/upgrades-core/dist/standalone'
import { DeployMode, MainnetPrompt } from "../types" 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 { addInstance } from "./actions"
import { addressToString, logBuilder } from "@remix-ui/helper" import { addressToString, logBuilder } from "@remix-ui/helper"
import Web3 from "web3" 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 const contractData = { name: '<at address>', abi, contract: { file: plugin.REACT_API.contracts.currentFile } } as ContractData
return addInstance(dispatch, { contractData, address, name: '<at address>' }) return addInstance(dispatch, { contractData, address, name: '<at address>' })
} else if (loadType === 'instance') { } 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 currentFile = plugin.REACT_API.contracts.currentFile
const compiler = plugin.REACT_API.contracts.contractList[currentFile].find(item => item.alias === contract.name) const compiler = plugin.REACT_API.contracts.contractList[currentFile].find(item => item.alias === contract.name)
const contractData = getSelectedContract(contract.name, compiler.compiler) const contractData = getSelectedContract(contract.name, compiler.compiler)

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

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

@ -24,12 +24,12 @@ export function EnvironmentUI(props: EnvironmentProps) {
<label id="selectExEnv" className="udapp_settingsLabel"> <label id="selectExEnv" className="udapp_settingsLabel">
<FormattedMessage id="udapp.environment" /> <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"> <a href="https://chainlist.org/" target="_blank">
<i className={'ml-2 fas fa-plug'} aria-hidden="true"></i> <i className={'ml-2 fas fa-plug'} aria-hidden="true"></i>
</a> </a>
</CustomTooltip> </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"> <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> <i className="udapp_infoDeployAction ml-2 fas fa-info-circle"></i>
</a> </a>
@ -41,7 +41,7 @@ export function EnvironmentUI(props: EnvironmentProps) {
{isL2(currentProvider && currentProvider.displayName)} {isL2(currentProvider && currentProvider.displayName)}
{currentProvider && currentProvider.displayName} {currentProvider && currentProvider.displayName}
{currentProvider && bridges[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 <i
style={{ fontSize: 'medium' }} style={{ fontSize: 'medium' }}
className={'ml-2 fa fa-rocket-launch'} 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"> <label className="mb-1 w-50 form-check-label custom-control-label" htmlFor="glManualConfig" data-id="glManualConfiguration">
<FormattedMessage id="udapp.gasLimitManual" /> <FormattedMessage id="udapp.gasLimitManual" />
</label> </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 <input
type="number" type="number"
ref={inputComponent} ref={inputComponent}

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

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

@ -6,10 +6,7 @@ import { FuncABI } from '@remix-project/core-plugin'
import { CopyToClipboard } from '@remix-ui/clipboard' import { CopyToClipboard } from '@remix-ui/clipboard'
import * as remixLib from '@remix-project/remix-lib' import * as remixLib from '@remix-project/remix-lib'
import * as ethJSUtil from '@ethereumjs/util' import * as ethJSUtil from '@ethereumjs/util'
import axios from 'axios'
import { AppModal } from '@remix-ui/app'
import { ContractGUI } from './contractGUI' import { ContractGUI } from './contractGUI'
import { SolScanTable } from './solScanTable'
import { TreeView, TreeViewItem } from '@remix-ui/tree-view' import { TreeView, TreeViewItem } from '@remix-ui/tree-view'
import { BN } from 'bn.js' import { BN } from 'bn.js'
import { CustomTooltip, is0XPrefixed, isHexadecimal, isNumeric, shortenAddress } from '@remix-ui/helper' 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 [evmBC, setEvmBC] = useState(null)
const [instanceBalance, setInstanceBalance] = useState(0) const [instanceBalance, setInstanceBalance] = useState(0)
const getVersion = () => window.location.href.split('=')[5].split('+')[0].split('-')[1]
useEffect(() => { useEffect(() => {
if (!props.instance.abi) { if (!props.instance.abi) {
const abi = txHelper.sortAbiFunction(props.instance.contractData.abi) const abi = txHelper.sortAbiFunction(props.instance.contractData.abi)
@ -220,100 +215,6 @@ export function UniversalDappUI(props: UdappProps) {
setCalldataValue(value) 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) => { const label = (key: string | number, value: string) => {
return ( return (
<div className="d-flex mt-2 flex-row label_item"> <div className="d-flex mt-2 flex-row label_item">
@ -406,9 +307,6 @@ export function UniversalDappUI(props: UdappProps) {
></i> ></i>
</CustomTooltip> </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>
</div> </div>
{ props.isPinnedContract && props.instance.pinnedAt ? ( { props.isPinnedContract && props.instance.pinnedAt ? (
@ -435,6 +333,7 @@ export function UniversalDappUI(props: UdappProps) {
return ( return (
<div key={index}> <div key={index}>
<ContractGUI <ContractGUI
getVersion={props.getVersion}
funcABI={funcABI} funcABI={funcABI}
clickCallBack={(valArray: {name: string; type: string}[], inputsValues: string) => { clickCallBack={(valArray: {name: string; type: string}[], inputsValues: string) => {
runTransaction(lookupOnly, funcABI, valArray, inputsValues, index) 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"> <div className="py-2 border-top d-flex justify-content-start flex-grow-1">
<FormattedMessage id="udapp.lowLevelInteractions" /> <FormattedMessage id="udapp.lowLevelInteractions" />
</div> </div>
<CustomTooltip placement={'bottom-end'} tooltipClasses="text-wrap" tooltipId="receiveEthDocstoolTip" tooltipText={<FormattedMessage id="udapp.tooltipText8" />}> <CustomTooltip
<a href={`https://solidity.readthedocs.io/en/${getVersion()}/contracts.html#receive-ether-function`} target="_blank" rel="noreferrer"> placement={'bottom-end'}
<i aria-hidden="true" className="fas fa-info my-2 mr-1"></i> tooltipClasses="text-wrap"
</a> 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> </CustomTooltip>
</div> </div>
<div className="d-flex flex-column align-items-start"> <div className="d-flex flex-column align-items-start">

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

@ -1,5 +1,6 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React, { Fragment, useEffect, useReducer, useState } from 'react' import React, { Fragment, useEffect, useReducer, useState } from 'react'
import semver from 'semver'
import { FormattedMessage } from 'react-intl' import { FormattedMessage } from 'react-intl'
import { ModalDialog } from '@remix-ui/modal-dialog' import { ModalDialog } from '@remix-ui/modal-dialog'
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
@ -80,6 +81,23 @@ export function RunTabUI(props: RunTabProps) {
const [runTab, dispatch] = useReducer(runTabReducer, initialState) const [runTab, dispatch] = useReducer(runTabReducer, initialState)
const REACT_API = { runTab } const REACT_API = { runTab }
const currentfile = plugin.config.get('currentFile') 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(() => { useEffect(() => {
if (!props.initialState) { if (!props.initialState) {
@ -306,6 +324,9 @@ export function RunTabUI(props: RunTabProps) {
isValidProxyAddress={isValidProxyAddress} isValidProxyAddress={isValidProxyAddress}
isValidProxyUpgrade={isValidProxyUpgrade} isValidProxyUpgrade={isValidProxyUpgrade}
proxy={runTab.proxy} proxy={runTab.proxy}
solCompilerVersion={solcVersion}
setCompilerVersion={setSolcVersion}
getCompilerVersion={getVersion}
/> />
<RecorderUI <RecorderUI
plugin={plugin} plugin={plugin}
@ -330,6 +351,8 @@ export function RunTabUI(props: RunTabProps) {
mainnetPrompt={mainnetPrompt} mainnetPrompt={mainnetPrompt}
runTransactions={executeTransactions} runTransactions={executeTransactions}
sendValue={runTab.sendValue} sendValue={runTab.sendValue}
solcVersion={solcVersion}
getVersion={getVersion}
getFuncABIInputs={getFuncABIValues} getFuncABIInputs={getFuncABIValues}
exEnvironment={runTab.selectExEnv} exEnvironment={runTab.selectExEnv}
editInstance={(instance) => { editInstance={(instance) => {

@ -279,6 +279,11 @@ export interface ContractDropdownProps {
isValidProxyAddress?: (address: string) => Promise<boolean>, isValidProxyAddress?: (address: string) => Promise<boolean>,
isValidProxyUpgrade?: (proxyAddress: string, contractName: string, solcInput: SolcInput, solcOuput: SolcOutput, solcVersion: string) => Promise<LayoutCompatibilityReport | { ok: boolean, pass: boolean, warning: 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 }[] } 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 { export interface RecorderProps {
@ -343,6 +348,8 @@ export interface InstanceContainerProps {
exEnvironment: string exEnvironment: string
editInstance: (instance) => void editInstance: (instance) => void
plugin: RunTab plugin: RunTab
solcVersion: { version: string, canReceive: boolean }
getVersion: any
} }
export interface Modal { export interface Modal {
@ -397,6 +404,11 @@ export interface ContractGUIProps {
isValidProxyAddress?: (address: string) => Promise<boolean>, isValidProxyAddress?: (address: string) => Promise<boolean>,
isValidProxyUpgrade?: (proxyAddress: string) => Promise<LayoutCompatibilityReport | { ok: boolean, pass: boolean, warning: 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 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 { export interface MainnetProps {
network: Network, network: Network,
@ -452,6 +464,8 @@ export interface UdappProps {
exEnvironment: string exEnvironment: string
editInstance: (instance) => void editInstance: (instance) => void
plugin: RunTab plugin: RunTab
solcVersion: { version: string, canReceive: boolean }
getVersion: () => string
} }
export interface DeployButtonProps { export interface DeployButtonProps {

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

@ -1127,7 +1127,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
disabled={(configFilePath === '' && state.useFileConfiguration) || disableCompileButton} disabled={(configFilePath === '' && state.useFileConfiguration) || disableCompileButton}
> >
<CustomTooltip <CustomTooltip
placement="right" placement={'auto-end'}
tooltipId="overlay-tooltip-compile-run" tooltipId="overlay-tooltip-compile-run"
tooltipText={ tooltipText={
<div className="text-left"> <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 {TreeView, TreeViewItem} from '@remix-ui/tree-view' // eslint-disable-line
import {CopyToClipboard} from '@remix-ui/clipboard' // eslint-disable-line import {CopyToClipboard} from '@remix-ui/clipboard' // eslint-disable-line
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
import { AppModal } from '@remix-ui/app'
import { SolScanTable } from './solScanTable'
import axios from 'axios'
import './css/style.css' import './css/style.css'
import { CustomTooltip } from '@remix-ui/helper' import { CustomTooltip } from '@remix-ui/helper'
@ -77,7 +80,7 @@ export const ContractSelection = (props: ContractSelectionProps) => {
key={keyPath} key={keyPath}
label={ label={
<div className="d-flex mt-2 flex-row remixui_label_item"> <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> <label className="m-0 remixui_label_value">{typeof data.self === 'boolean' ? `${data.self}` : data.self}</label>
</div> </div>
} }
@ -94,7 +97,7 @@ export const ContractSelection = (props: ContractSelectionProps) => {
key={keyPath} key={keyPath}
label={ label={
<div className="d-flex mt-2 flex-row remixui_label_item"> <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> <label className="m-0 remixui_label_value">{typeof data.self === 'boolean' ? `${data.self}` : data.self}</label>
</div> </div>
} }
@ -245,6 +248,113 @@ export const ContractSelection = (props: ContractSelectionProps) => {
return bytecodeObj.object 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 ( return (
// define swarm logo // define swarm logo
<> <>
@ -264,72 +374,121 @@ export const ContractSelection = (props: ContractSelectionProps) => {
</select> </select>
</div> </div>
<article className="mt-2 pb-0"> <article className="mt-2 pb-0">
<button <CustomTooltip
id="publishOnIpfs" placement={'auto-end'}
className="btn btn-secondary btn-block" tooltipId="runStaticAnalysisTooltip"
onClick={() => { tooltipClasses="text-nowrap"
handlePublishToStorage('ipfs') 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 <button
placement="right" id="runSolidityScan"
tooltipId="publishOnIpfsTooltip" className="btn border btn-block"
tooltipClasses="text-nowrap" onClick={() => {
tooltipText={`${intl.formatMessage({ runSolidityScan()
id: 'solidity.publishOn' }}
})} Ipfs`}
> >
<span> <span>
<img id="solscanLogo" className="remixui_storageLogo mr-2" src="assets/img/solidityScanLogo.webp" />
<span> <span>
<FormattedMessage id="solidity.publishOn" /> Ipfs <FormattedMessage id="solidity.runSolidityScan" />
</span> </span>
<img id="ipfsLogo" className="remixui_storageLogo ml-2" src="assets/img/ipfs.webp" />
</span> </span>
</CustomTooltip> </button>
</button> </CustomTooltip>
<button <CustomTooltip
id="publishOnSwarm" placement={'auto-end'}
className="btn btn-secondary btn-block" tooltipId="publishOnIpfsTooltip"
onClick={() => { tooltipClasses="text-nowrap"
handlePublishToStorage('swarm') tooltipText={`${intl.formatMessage({
}} id: 'solidity.publishOn'
})} Ipfs`}
> >
<CustomTooltip <button
placement="right" id="publishOnIpfs"
tooltipId="publishOnSwarmTooltip" className="btn border btn-block"
tooltipClasses="text-nowrap" onClick={() => {
tooltipText={`${intl.formatMessage({ handlePublishToStorage('ipfs')
id: 'solidity.publishOn' }}
})} Swarm`}
> >
<span> <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> <span>
<FormattedMessage id="solidity.publishOn" /> Swarm <FormattedMessage id="solidity.publishOn" /> Swarm
</span> </span>
<img id="swarmLogo" className="remixui_storageLogo ml-2" src="assets/img/swarm.webp" />
</span> </span>
</CustomTooltip> </button>
</button> </CustomTooltip>
<CustomTooltip
<button placement={'auto-end'}
data-id="compilation-details" tooltipId="CompilationDetailsTooltip"
className="btn btn-secondary btn-block" tooltipClasses="text-nowrap"
onClick={async () => { tooltipText={<FormattedMessage id="solidity.displayContractDetails" />}
details()
await (api as any).call('compilationDetails', 'showDetails', payload)
}}
> >
<CustomTooltip <button
placement="right" data-id="compilation-details"
tooltipId="CompilationDetailsTooltip" className="btn border btn-block"
tooltipClasses="text-nowrap" onClick={async () => {
tooltipText={<FormattedMessage id="solidity.displayContractDetails" />} details()
await (api as any).call('compilationDetails', 'showDetails', payload)
}}
> >
<span> <span>
<FormattedMessage id="solidity.compilationDetails" /> <i className="fa-regular fa-memo-pad mr-2 text-primary"></i>
<span>
<FormattedMessage id="solidity.compilationDetails" />
</span>
</span> </span>
</CustomTooltip> </button>
</button> </CustomTooltip>
{/* Copy to Clipboard */} {/* Copy to Clipboard */}
<div className="remixui_contractHelperButtons"> <div className="remixui_contractHelperButtons">
<div className="input-group"> <div className="input-group">

@ -104,7 +104,7 @@ export class CompileTabLogic {
* @param {string} target the path to the file to compile * @param {string} target the path to the file to compile
*/ */
compileFile (target) { 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) => { return new Promise((resolve, reject) => {
this.api.readFile(target).then(async(content) => { this.api.readFile(target).then(async(content) => {
const sources = { [target]: { content } } const sources = { [target]: { content } }

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

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

Loading…
Cancel
Save