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

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

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

@ -32,7 +32,6 @@
![Remix screenshot](https://github.com/ethereum/remix-project/raw/master/apps/remix-ide/remix-screenshot-400h.png) ![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)
} }
}) })

@ -181,5 +181,4 @@ module.exports = {
.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)
@ -111,7 +111,7 @@ module.exports = {
.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)
}, },

@ -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) => {
try {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts')) 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)
}
}

@ -407,9 +407,6 @@ module.exports = {
} }
} }
const asyncAwait = ` const asyncAwait = `
var p = function () { var p = function () {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {

@ -117,7 +117,7 @@ module.exports = {
}) })
}, },
'Should load Blockscout verified contracts from URL "address" and "blockscout" params (single source)': function (browser: NightwatchBrowser) { 'Should load Blockscout verified contracts from URL "address" and "blockscout" params (single source)': ''+function (browser: NightwatchBrowser) {
browser 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)
`} ` }
} }
] ]

@ -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;

@ -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'
@ -350,7 +351,7 @@ class AppComponent {
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',

@ -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()
} }
}
onDeactivation(): void { document.addEventListener('keydown', this.handleKeyDown);
} }
async showDetails(sentPayload: any) { onDeactivation(): void {
const contractName = Object.entries(sentPayload).find(([key, value]) => key) document.removeEventListener('keydown', this.handleKeyDown);
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,20 +82,21 @@ 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 (
<div className='d-flex'>
<RemixUIGridView <RemixUIGridView
plugin={this} plugin={this}
styleList={""} styleList={""}
logo='/assets/img/YouTubeLogo.webp' logo='/assets/img/YouTubeLogo.webp'
enableFilter={true} enableFilter={true}
showUntagged={true} showUntagged={true}
showPin={true} showPin={false}
tagList={[ tagList={[
['beginner', 'danger'], ['beginner', 'danger'],
['advanced', 'warning'], ['advanced', 'warning'],
@ -99,145 +106,64 @@ export class RemixGuidePlugin extends ViewPlugin {
['vyper', 'info'], ['vyper', 'info'],
['L2', 'danger'] ['L2', 'danger']
]} ]}
title='Remix Guide' title={Data.title}
description="Streamlined access to categorized video tutorials for mastering Remix IDE. From fundamentals to advanced techniques, level up your development skills with ease." description={Data.description}
//themeStyle={state.themeStyle}
> >
<RemixUIGridSection { Data.sections.map(section => {
return <RemixUIGridSection
plugin={this} plugin={this}
title='Basics' title={section.title}
hScrollable= {true} hScrollable= {true}
> >
<RemixUIGridCell { section.cells.map(cell => {
plugin={this} return <RemixUIGridCell
title="first item"
tagList={['L2', 'AI']}
logo='/assets/img/soliditySurvey2023.webp'
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="next"
pinned={true}
tagList={['L2', 'plugins']}
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell> <RemixUIGridCell
plugin={this}
title="something"
pinned={false}
tagList={['solidity', 'plugins']}
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
tagList={['solidity']}
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell> <RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="Something very very long"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell> <RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="1"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
</RemixUIGridSection>
<RemixUIGridSection
plugin={this}
title='Basics not scrollable'
hScrollable= {false}
>
<RemixUIGridCell
plugin={this}
title="first item"
logo='/assets/img/soliditySurvey2023.webp'
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell>
<RemixUIGridCell
plugin={this}
title="next"
>
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img>
</RemixUIGridCell> <RemixUIGridCell
plugin={this} plugin={this}
title="something" title={cell.title}
> tagList={cell.tagList}
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img> expandViewEl={
</RemixUIGridCell> cell.expandViewElement
<RemixUIGridCell }
plugin={this} handleExpand={() => {
title="1" this.showVideo = true
> this.videoID = cell.expandViewElement.videoID
<img src={'/assets/img/soliditySurvey2023.webp'} style={{ height: '70px', width: '70px' }} alt=""></img> this.renderComponent()
</RemixUIGridCell> <RemixUIGridCell }}
plugin={this} logo={cell.expandViewElement.logo}
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> <a href={"https://www.youtube.com/@" + cell.authorURL} target="__blank">
<img src={"//img.youtube.com/vi/" + this.videoID + "/0.jpg"} style={{ height: '70px', width: '70px' }}></img>
</a>
</RemixUIGridCell> </RemixUIGridCell>
})}
</RemixUIGridSection> </RemixUIGridSection>
})}
</RemixUIGridView> </RemixUIGridView>
{ state.showVideo && <div
data-id={`EnterModalDialogContainer-react`}
data-backdrop="static"
data-keyboard="false"
className={"modal d-flex"}
role="dialog"
style={{ justifyContent: "center" }}
>
<div className="align-self-center pb-4" role="document">
<div
tabIndex={-1}
className={'modal-content remixModalContent mb-4'}
>
<div className="text-break remixModalBody d-flex flex-column p-3 justify-content-between" data-id={`EnterModalDialogModalBody-react`}>
<iframe style={{ minHeight: "500px", minWidth: "1000px" }} width="1000" height="500" src={"https://www.youtube.com/embed/" + this.videoID + "?si=ZdckOaSPR7VsLj_2"} allowFullScreen></iframe>
</div>
<div className="modal-footer d-flex flex-column">
<button onClick={() => {
this.showVideo = false
this.renderComponent()
}}>Close</button>
</div>
</div>
</div>
</div>}
</div>
) )
} }

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

@ -47,6 +47,12 @@ export class SolCoder extends Plugin {
this.solgpt_chat_history = [] 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'
] ]
@ -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,37 +24,17 @@ 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 {
node {
__typename
... on Repository {
owner {
id
}
name
project(number: 31) {
number
name
columns(last: 1) {
edges {
node {
name name
cards(first: 100) { projectV2(number: 52) {
url
items(first: 100) {
totalCount
edges { edges {
cursor
node { node {
id
note
state
content { content {
... on Issue {
url
id
number
title
}
... on PullRequest { ... on PullRequest {
url url
id id
@ -68,11 +48,5 @@ console.log('done.txt updated')
} }
} }
} }
}
}
}
}
}
}
} }
*/ */

@ -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>
<div>{props.app.hiddenPanel.render()}</div> <div>{props.app.hiddenPanel.render()}</div>
<div className="statusBar fixed-bottom">
{props.app.statusBar.render()}
</div>
</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;

@ -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 {
@ -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,26 +16,51 @@ 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)
props.handleExpand(!expand)
else return
}}>
{ anyEnabled && <div className='d-flex flex-column'>
<div className='d-flex flex-grid'>
<div className={"d-flex mx-0 p-2 bg-light border border-secondary remixui_grid_cell_container " + props.classList || ''} data-id={"remixUIGS" + props.title}> <div className={"d-flex mx-0 p-2 bg-light border border-secondary remixui_grid_cell_container " + props.classList || ''} data-id={"remixUIGS" + props.title}>
<div className="d-flex remixui_grid_cell flex-column"> <div className="d-flex remixui_grid_cell flex-column">
<div className='d-flex flex-row pb-1 align-items-end' style={{ width: '8rem', height: '1rem' }}> <div className='d-flex flex-row pb-1 align-items-end' style={{ width: '8rem', height: '1rem' }}>
@ -57,7 +82,7 @@ export const RemixUIGridCell = (props: RemixUIGridCellProps) => {
props.pinStateCallback() props.pinStateCallback()
}} }}
></button>} ></button>}
{ props.tagList && <div className='d-flex flex-column align-items-begin remixui_grid_cell_tags'> { props.tagList && <div className={`d-flex flex-column align-items-begin ` +`${filterCon.showPin ? 'remixui_grid_cell_tags' : 'remixui_grid_cell_tags_no_pin'}`}>
{ Object.keys(props.tagList).map((key) => ( { Object.keys(props.tagList).map((key) => (
filterCon.keyValueMap[props.tagList[key]].enabled && ( filterCon.keyValueMap[props.tagList[key]].enabled && (
<CustomTooltip <CustomTooltip
@ -77,6 +102,11 @@ export const RemixUIGridCell = (props: RemixUIGridCellProps) => {
{ !props.tagList && <span { !props.tagList && <span
className={'remixui_grid_cell_tags'}> className={'remixui_grid_cell_tags'}>
</span> } </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,33 +90,14 @@ 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'>
@ -112,10 +109,11 @@ export const RemixUIGridView = (props: RemixUIGridViewProps) => {
<div className="d-flex flex-row pr-2 pb-1 align-items-center justify-content-between"> <div className="d-flex flex-row pr-2 pb-1 align-items-center justify-content-between">
<div className='d-flex' id="GVFilter"> <div className='d-flex' id="GVFilter">
<button <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" 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) => { onClick={(e) => {
setFilter(searchInputRef.current.value)
_paq.push(['trackEvent', 'GridView' + props.title ? props.title : '', 'filter', searchInputRef.current.value]) _paq.push(['trackEvent', 'GridView' + props.title ? props.title : '', 'filter', searchInputRef.current.value])
//setstate
}} }}
></button> ></button>
<input <input
@ -138,7 +136,6 @@ export const RemixUIGridView = (props: RemixUIGridViewProps) => {
</div> </div>
{ props.children } { props.children }
</div> </div>
</ThemeContext.Provider>
</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,29 +246,42 @@ 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">
<div className="mb-3">
{(state.recentWorkspaces[0] || state.recentWorkspaces[1] || state.recentWorkspaces[2]) && (
<div className="d-flex flex-column mb-5 remixui_recentworkspace">
<label style={{ fontSize: '0.8rem' }} className="mt-3">
Recent Workspaces
</label>
{state.recentWorkspaces[0] && state.recentWorkspaces[0] !== '' && (
<a className="cursor-pointer mb-1 ml-2" href="#" onClick={(e) => handleSwichToRecentWorkspace(e, state.recentWorkspaces[0])}>
{state.recentWorkspaces[0]}
</a>
)}
{state.recentWorkspaces[1] && state.recentWorkspaces[1] !== '' && (
<a className="cursor-pointer mb-1 ml-2" href="#" onClick={(e) => handleSwichToRecentWorkspace(e, state.recentWorkspaces[1])}>
{state.recentWorkspaces[1]}
</a>
)}
{state.recentWorkspaces[2] && state.recentWorkspaces[2] !== '' && (
<a className="cursor-pointer ml-2" href="#" onClick={(e) => handleSwichToRecentWorkspace(e, state.recentWorkspaces[2])}>
{state.recentWorkspaces[2]}
</a>
)}
</div>
)}
</div>
<div className="d-flex flex-column flex-nowrap pt-3">
<label style={{ fontSize: '1.2rem' }}> <label style={{ fontSize: '1.2rem' }}>
<FormattedMessage id="home.files" /> <FormattedMessage id="home.files" />
</label> </label>
<div className="d-flex flex-column"> <div className="d-flex flex-column">
<div className="d-flex flex-row"> <div className="d-flex flex-row">
<CustomTooltip <CustomTooltip placement={'top'} tooltipId="overlay-tooltip" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="home.startCodingPlayground" />} tooltipTextClasses="border bg-light text-dark p-1 pr-3">
placement={'top'} <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')}>
tooltipId="overlay-tooltip" <FormattedMessage id="home.newFile" />
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> </button>
</CustomTooltip> </CustomTooltip>
<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">
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id='home.openFileTooltip' />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<span> <span>
<label className="btn text-nowrap p-2 mr-2 border my-1" style={{ width: 'fit-content', cursor: 'pointer' }} htmlFor="openFileInput"> <label className="btn text-nowrap p-2 mr-2 border my-1" style={{ width: 'fit-content', cursor: 'pointer' }} htmlFor="openFileInput">
<FormattedMessage id="home.openFile" /> <FormattedMessage id="home.openFile" />
@ -293,65 +299,18 @@ contract HelloWorld {
/> />
</span> </span>
</CustomTooltip> </CustomTooltip>
<CustomTooltip <button className="btn text-nowrap p-2 mr-2 border my-1" onClick={() => showFullMessage('Ipfs', 'ipfs hash', ['ipfs://QmQQfBMkpDgmxKzYaoAtqfaybzfgGm9b2LWYyT56Chv6xH'], 'ipfs://')}>
placement={'top'} IPFS
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> </button>
</CustomTooltip> <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'])}>
</div> Git Clone
{(state.recentWorkspaces[0] || state.recentWorkspaces[1] || state.recentWorkspaces[2]) && (
<div className="d-flex flex-column">
<label style={{ fontSize: '0.8rem' }} className="mt-3">
Recent workspaces
</label>
{state.recentWorkspaces[0] && state.recentWorkspaces[0] !== '' && (
<a className="cursor-pointer mb-1 ml-2" href="#" onClick={(e) => handleSwichToRecentWorkspace(e, state.recentWorkspaces[0])}>
{state.recentWorkspaces[0]}
</a>
)}
{state.recentWorkspaces[1] && state.recentWorkspaces[1] !== '' && (
<a className="cursor-pointer mb-1 ml-2" href="#" onClick={(e) => handleSwichToRecentWorkspace(e, state.recentWorkspaces[1])}>
{state.recentWorkspaces[1]}
</a>
)}
{state.recentWorkspaces[2] && state.recentWorkspaces[2] !== '' && (
<a className="cursor-pointer ml-2" href="#" onClick={(e) => handleSwichToRecentWorkspace(e, state.recentWorkspaces[2])}>
{state.recentWorkspaces[2]}
</a>
)}
</div>
)}
</div>
<label style={{ fontSize: '0.8rem' }} className="pt-3">
<FormattedMessage id="home.loadFrom" />
</label>
<div className="d-flex">
<button
className="btn p-2 border mr-2"
data-id="landingPageImportFromGitHubButton"
onClick={() =>
showFullMessage('GitHub', 'github URL', [
'https://github.com/0xcert/ethereum-erc721/src/contracts/tokens/nf-token-metadata.sol',
'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/67bca857eedf99bf44a4b6a0fc5b5ed553135316/contracts/access/Roles.sol',
])
}
>
GitHub
</button> </button>
<button className="btn p-2 border mr-2" data-id="landingPageImportFromGistButton" onClick={() => importFromGist()}> <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 <button
className="btn p-2 border" className="btn text-nowrap p-2 mr-2 border my-1"
onClick={() => onClick={() =>
showFullMessage('Https', 'http/https raw content', ['https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol']) showFullMessage('Https', 'http/https raw content', ['https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol'])
} }
@ -360,6 +319,16 @@ contract HelloWorld {
</button> </button>
</div> </div>
</div> </div>
<div className="d-flex mt-2 align-items-end w-100">
<CustomTooltip placement={'top'} tooltipId="overlay-tooltip" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="home.connectToLocalhost" />} tooltipTextClasses="border bg-light text-dark p-1 pr-3">
<button className="btn btn-block text-nowrap p-2 border my-1" onClick={() => connectToLocalhost()}>
<i className="fa-regular fa-desktop pr-2"></i>
<FormattedMessage id="home.accessFileSystem" />
</button>
</CustomTooltip>
</div>
</div>
</div>
</> </>
) )
} }

@ -1,15 +1,15 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* 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 },
items: 5
},
desktop: {
breakpoint: { max: 3000, min: 1024 },
items: 5,
partialVisibilityGutter: 0
}
}} }}
renderButtonGroupOutside={true} data-id={`homeTabGetStarted${template.templateName}`}
ssr={true} // means to render carousel on server-side.
keyBoardControl={true}
containerClass="carousel-container"
deviceType={'desktop'}
itemClass="w-100"
> >
<WorkspaceTemplate {template.workspaceTitle}
gsID="sUTLogo" </button>
workspaceTitle="MultiSig" </CustomTooltip>
description={ ))}
intl.formatMessage({ id: 'home.gnosisSafeMultisigTemplateDesc' }) </div>
} <div className="d-flex flex-row align-items-center mb-2 flex-nowrap">
projectLogo="assets/img/gnosissafeLogo.png" {workspaceTemplates.slice(3, workspaceTemplates.length).map((template, index) => (
callback={() => createWorkspace("gnosisSafeMultisig")} <CustomTooltip tooltipText={template.description} tooltipId={template.gsID} tooltipClasses="text-nowrap" tooltipTextClasses="border bg-light text-dark p-1 pr-3" placement="bottom-start" key={`${template.gsID}-${template.workspaceTitle}-${index}`}>
/> <button
<WorkspaceTemplate key={index}
gsID="sUTLogo" className={'btn border p-2 text-nowrap mr-3'}
workspaceTitle="ERC20" onClick={() => {
description={ createWorkspace(template.templateName)
intl.formatMessage({ id: 'home.zeroxErc20TemplateDesc' }) }}
} data-id={`homeTabGetStarted${template.workspaceTitle}`}
projectLogo="assets/img/oxprojectLogo.png" >
callback={() => createWorkspace("zeroxErc20")} {template.workspaceTitle}
/> </button>
<WorkspaceTemplate </CustomTooltip>
gsID="sourcifyLogo" ))}
workspaceTitle="ERC20" </div>
description={intl.formatMessage({ id: 'home.ozerc20TemplateDesc' })} </div>
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">
{iconButtons.map((button, index) => (
<CustomTooltip <CustomTooltip
placement={'top'} key={index}
tooltipId="overlay-tooltip" placement={button.placement}
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id="home.remixYoutubePlaylist" />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button
onClick={() => {
openLink('https://www.youtube.com/channel/UCjTUPyFEr2xDGN6Cg8nKDaA')
_paq.push(['trackEvent', 'hometab', 'socialMedia', 'youtube'])
}}
className="border-0 px-1 h-100 btn fab fa-youtube"
></button>
</CustomTooltip>
<CustomTooltip
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id="home.remixTwitterProfile" />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button
onClick={() => {
openLink('https://twitter.com/EthereumRemix')
_paq.push(['trackEvent', 'hometab', 'socialMedia', 'twitter'])
}}
className="border-0 px-1 h-100 btn fab fa-x-twitter"
></button>
</CustomTooltip>
<CustomTooltip
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id="home.remixLinkedinProfile" />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button
onClick={() => {
openLink('https://www.linkedin.com/company/ethereum-remix/')
_paq.push(['trackEvent', 'hometab', 'socialmedia', 'linkedin'])
}}
className="border-0 px-1 h-100 btn fab fa-linkedin"
></button>
</CustomTooltip>
<CustomTooltip
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id="home.remixMediumPosts" />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button
onClick={() => {
openLink('https://medium.com/remix-ide')
_paq.push(['trackEvent', 'hometab', 'socialmedia', 'medium'])
}}
className="border-0 h-100 px-1 btn fab fa-medium"
></button>
</CustomTooltip>
<CustomTooltip
placement={'top'}
tooltipId="overlay-tooltip" tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap" tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id="home.joinUsOnDiscord" />} tooltipText={button.textToolip}
tooltipTextClasses="border bg-light text-dark p-1 pr-3" tooltipTextClasses="border bg-light text-dark p-1 pr-3"
> >
<button <button
key={index}
onClick={() => { onClick={() => {
openLink('https://discord.gg/mh9hFCKkEq') openLink(button.urlLink)
_paq.push(['trackEvent', 'hometab', 'socialmedia', 'discord']) _paq.push(button.matomoTrackingEntry)
}} }}
className="border-0 h-100 pl-1 pr-0 btn fab fa-discord" className={`border-0 h-100 pl-1 pr-0 btn fab ${button.iconClass}`}
></button> ></button>
</CustomTooltip> </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}

@ -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>

@ -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,7 +391,6 @@ export function ContractDropdownUI(props: ContractDropdownProps) {
/> />
</label> </label>
)} )}
</div>
{props.remixdActivated ? ( {props.remixdActivated ? (
<CustomTooltip <CustomTooltip
placement={'right'} placement={'right'}
@ -403,20 +402,16 @@ export function ContractDropdownUI(props: ContractDropdownProps) {
</span> </span>
} }
> >
<button <i style={{ cursor: 'pointer' }} onClick={(_) => {
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>
>
<i style={{ cursor: 'pointer' }} className="fa fa-refresh mr-2 mt-2" aria-hidden="true"></i>
</button>
</CustomTooltip> </CustomTooltip>
) : null} ) : null}
</div> </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'}
tooltipClasses="text-wrap"
tooltipId="receiveEthDocstoolTip"
tooltipText={<FormattedMessage id="udapp.tooltipText8" />}
>
{ // receive method added to solidity v0.6.x. use this as diff.
props.solcVersion.canReceive === false ? (
<a href={`https://solidity.readthedocs.io/en/v${props.solcVersion.version}/contracts.html`} target="_blank" rel="noreferrer">
<i aria-hidden="true" className="fas fa-info my-2 mr-1"></i> <i aria-hidden="true" className="fas fa-info my-2 mr-1"></i>
</a> </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">
<i
className="fas fa-info-circle"
aria-hidden="true" aria-hidden="true"
></i> ></i>
</span> </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">
<CustomTooltip
placement={'auto-end'}
tooltipId="runStaticAnalysisTooltip"
tooltipClasses="text-nowrap"
tooltipText={`${intl.formatMessage({
id: 'solidity.runStaticAnalysis.iconTooltip'
})}`}
>
<button <button
id="publishOnIpfs" id="runStaticAnalysis"
className="btn btn-secondary btn-block" className="btn border btn-block"
onClick={() => { onClick={() => {
handlePublishToStorage('ipfs') 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 <CustomTooltip
placement="right" placement={'auto-end'}
tooltipId="publishOnIpfsTooltip" tooltipId="runSolidityScanTooltip"
tooltipClasses="text-nowrap" tooltipClasses="text-nowrap"
tooltipText={`${intl.formatMessage({ tooltipText={`${intl.formatMessage({
id: 'solidity.publishOn' id: 'solidity.solScan.iconTooltip'
})} Ipfs`} })}`}
>
<button
id="runSolidityScan"
className="btn border btn-block"
onClick={() => {
runSolidityScan()
}}
> >
<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>
<CustomTooltip
placement={'auto-end'}
tooltipId="publishOnIpfsTooltip"
tooltipClasses="text-nowrap"
tooltipText={`${intl.formatMessage({
id: 'solidity.publishOn'
})} Ipfs`}
>
<button <button
id="publishOnSwarm" id="publishOnIpfs"
className="btn btn-secondary btn-block" className="btn border btn-block"
onClick={() => { onClick={() => {
handlePublishToStorage('swarm') handlePublishToStorage('ipfs')
}} }}
> >
<span>
<img id="ipfsLogo" className="remixui_storageLogo mr-2" src="assets/img/ipfs.webp" />
<span>
<FormattedMessage id="solidity.publishOn" /> IPFS
</span>
</span>
</button>
</CustomTooltip>
<CustomTooltip <CustomTooltip
placement="right" placement={'auto-end'}
tooltipId="publishOnSwarmTooltip" tooltipId="publishOnSwarmTooltip"
tooltipClasses="text-nowrap" tooltipClasses="text-nowrap"
tooltipText={`${intl.formatMessage({ tooltipText={`${intl.formatMessage({
id: 'solidity.publishOn' id: 'solidity.publishOn'
})} Swarm`} })} Swarm`}
>
<button
id="publishOnSwarm"
className="btn border btn-block"
onClick={() => {
handlePublishToStorage('swarm')
}}
> >
<span> <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
placement={'auto-end'}
tooltipId="CompilationDetailsTooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id="solidity.displayContractDetails" />}
>
<button <button
data-id="compilation-details" data-id="compilation-details"
className="btn btn-secondary btn-block" className="btn border btn-block"
onClick={async () => { onClick={async () => {
details() details()
await (api as any).call('compilationDetails', 'showDetails', payload) await (api as any).call('compilationDetails', 'showDetails', payload)
}} }}
> >
<CustomTooltip <span>
placement="right" <i className="fa-regular fa-memo-pad mr-2 text-primary"></i>
tooltipId="CompilationDetailsTooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id="solidity.displayContractDetails" />}
>
<span> <span>
<FormattedMessage id="solidity.compilationDetails" /> <FormattedMessage id="solidity.compilationDetails" />
</span> </span>
</CustomTooltip> </span>
</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>

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

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

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

Loading…
Cancel
Save