Merge branch 'master' of https://github.com/ethereum/remix-project into desktopofflinenoscript

pull/4346/head
filip mertens 12 months ago
commit 7eb84b088c
  1. 25
      apps/circuit-compiler/src/app/components/container.tsx
  2. 53
      apps/circuit-compiler/src/app/services/circomPluginClient.ts
  3. 7
      apps/circuit-compiler/src/app/types/index.ts
  4. 28
      apps/remix-ide-e2e/src/commands/waitForElementNotContainsText.ts
  5. 5
      apps/remix-ide-e2e/src/helpers/init.ts
  6. 45
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  7. 26
      apps/remix-ide-e2e/src/tests/migrateFileSystem.test.ts
  8. 6
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  9. 80
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  10. 29
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  11. 97
      apps/remix-ide-e2e/src/tests/workspace_git.test.ts
  12. 1
      apps/remix-ide-e2e/src/types/index.d.ts
  13. 12
      apps/remix-ide/src/app.js
  14. 160
      apps/remix-ide/src/app/files/dgitProvider.ts
  15. 9
      apps/remix-ide/src/app/files/fileManager.ts
  16. 19
      apps/remix-ide/src/app/files/fileProvider.ts
  17. 79
      apps/remix-ide/src/app/plugins/copilot/suggestion-service/copilot-suggestion.ts
  18. 100
      apps/remix-ide/src/app/plugins/copilot/suggestion-service/suggestion-service.ts
  19. 90
      apps/remix-ide/src/app/plugins/copilot/suggestion-service/worker.js
  20. 6
      apps/remix-ide/src/app/plugins/openaigpt.tsx
  21. 21
      apps/remix-ide/src/app/plugins/solidity-script.tsx
  22. 2
      apps/remix-ide/src/app/providers/goerli-vm-fork-provider.tsx
  23. 2
      apps/remix-ide/src/app/providers/sepolia-vm-fork-provider.tsx
  24. 2
      apps/remix-ide/src/app/tabs/locales/en/filePanel.json
  25. 6
      apps/remix-ide/src/app/tabs/locales/en/home.json
  26. 11
      apps/remix-ide/src/app/tabs/locales/en/homeReleaseDetails.json
  27. 2
      apps/remix-ide/src/app/tabs/locales/en/index.js
  28. 6
      apps/remix-ide/src/app/tabs/locales/en/settings.json
  29. 6
      apps/remix-ide/src/app/tabs/locales/es/home.json
  30. 6
      apps/remix-ide/src/app/tabs/locales/fr/home.json
  31. 6
      apps/remix-ide/src/app/tabs/locales/it/home.json
  32. 6
      apps/remix-ide/src/app/tabs/locales/zh/home.json
  33. 1
      apps/remix-ide/src/app/tabs/settings-tab.tsx
  34. 10
      apps/remix-ide/src/assets/css/themes/bootstrap-cyborg.min.css
  35. 28
      apps/remix-ide/src/assets/css/themes/remix-light_powaqg.css
  36. BIN
      apps/remix-ide/src/assets/img/gnosissafeLogo.png
  37. BIN
      apps/remix-ide/src/assets/img/openzeppelinLogo.png
  38. BIN
      apps/remix-ide/src/assets/img/oxprojectLogo.png
  39. BIN
      apps/remix-ide/src/assets/img/remi_drums_whatsnew.webp
  40. BIN
      apps/remix-ide/src/assets/img/remixverticaltextLogo.png
  41. 2
      apps/remix-ide/src/blockchain/blockchain.tsx
  42. 1
      apps/remix-ide/src/blockchain/providers/vm.ts
  43. 4
      apps/remix-ide/src/remixAppManager.js
  44. 2
      apps/remix-ide/src/remixEngine.js
  45. 14
      apps/remix-ide/src/walkthroughService.js
  46. 8
      libs/ghaction-helper/package.json
  47. 8
      libs/remix-analyzer/package.json
  48. 6
      libs/remix-astwalker/package.json
  49. 6
      libs/remix-core-plugin/src/lib/compiler-artefacts.ts
  50. 14
      libs/remix-core-plugin/src/lib/constants/uups.ts
  51. 12
      libs/remix-debug/package.json
  52. 8
      libs/remix-debug/src/init.ts
  53. 2
      libs/remix-debug/test/vmCall.ts
  54. 4
      libs/remix-lib/package.json
  55. 2
      libs/remix-lib/src/execution/txExecution.ts
  56. 4
      libs/remix-lib/src/execution/txRunnerVM.ts
  57. 4
      libs/remix-lib/src/execution/txRunnerWeb3.ts
  58. 2
      libs/remix-lib/src/execution/typeConversion.ts
  59. 3
      libs/remix-lib/src/init.ts
  60. 7
      libs/remix-simulator/package.json
  61. 12
      libs/remix-simulator/src/methods/transactions.ts
  62. 2
      libs/remix-simulator/src/provider.ts
  63. 1
      libs/remix-simulator/src/vm-context.ts
  64. 6
      libs/remix-solidity/package.json
  65. 3
      libs/remix-solidity/src/compiler/compiler-input.ts
  66. 15
      libs/remix-solidity/src/compiler/compiler.ts
  67. 4
      libs/remix-solidity/src/compiler/types.ts
  68. 10
      libs/remix-tests/package.json
  69. 60
      libs/remix-ui/app/src/lib/remix-app/components/modals/enter.tsx
  70. 10
      libs/remix-ui/app/src/lib/remix-app/components/modals/matomo.tsx
  71. 9
      libs/remix-ui/app/src/lib/remix-app/interface/index.ts
  72. 62
      libs/remix-ui/app/src/lib/remix-app/remix-app.tsx
  73. 20
      libs/remix-ui/app/src/lib/remix-app/types/index.ts
  74. 102
      libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts
  75. 12
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  76. 3
      libs/remix-ui/editor/src/lib/web-types.ts
  77. 30
      libs/remix-ui/home-tab/src/lib/components/homeTabFeatured.tsx
  78. 4
      libs/remix-ui/home-tab/src/lib/components/homeTabFeaturedPlugins.tsx
  79. 1
      libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx
  80. 23
      libs/remix-ui/home-tab/src/lib/components/homeTabGetStarted.tsx
  81. 2
      libs/remix-ui/home-tab/src/lib/components/homeTablangOptions.tsx
  82. 20
      libs/remix-ui/home-tab/src/lib/components/workspaceTemplate.tsx
  83. 4
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.css
  84. 2
      libs/remix-ui/renderer/src/lib/renderer.tsx
  85. 122
      libs/remix-ui/settings/src/lib/remix-ui-settings.tsx
  86. 17
      libs/remix-ui/settings/src/lib/settingsAction.ts
  87. 45
      libs/remix-ui/settings/src/lib/settingsReducer.ts
  88. 12
      libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx
  89. 7
      libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts
  90. 2
      libs/remix-ui/terminal/src/lib/components/Context.tsx
  91. 5
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  92. 6
      libs/remix-ui/terminal/src/lib/terminalWelcome.tsx
  93. 2
      libs/remix-ui/terminal/src/lib/utils/wrapScript.ts
  94. 7
      libs/remix-ui/workspace/src/lib/actions/payload.ts
  95. 30
      libs/remix-ui/workspace/src/lib/actions/workspace.ts
  96. 1
      libs/remix-ui/workspace/src/lib/contexts/index.ts
  97. 12
      libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
  98. 17
      libs/remix-ui/workspace/src/lib/reducers/workspace.ts
  99. 22
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  100. 3
      libs/remix-ui/workspace/src/lib/types/index.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -15,9 +15,17 @@ import { PrimeValue } from '../types'
export function Container () { export function Container () {
const circuitApp = useContext(CircuitAppContext) const circuitApp = useContext(CircuitAppContext)
const showCompilerLicense = (message = 'License not available') => { const showCompilerLicense = async (message = 'License not available') => {
// @ts-ignore try {
circuitApp.plugin.call('notification', 'modal', { id: 'modal_circuit_compiler_license', title: 'Compiler License', message }) const response = await fetch('https://raw.githubusercontent.com/iden3/circom/master/COPYING')
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`)
const content = await response.text()
// @ts-ignore
circuitApp.plugin.call('notification', 'modal', { id: 'modal_circuit_compiler_license', title: 'Compiler License', message: content })
} catch (e) {
// @ts-ignore
circuitApp.plugin.call('notification', 'modal', { id: 'modal_circuit_compiler_license', title: 'Compiler License', message })
}
} }
const handleVersionSelect = (version: string) => { const handleVersionSelect = (version: string) => {
@ -53,11 +61,16 @@ export function Container () {
<article> <article>
<div className="pt-0 circuit_section"> <div className="pt-0 circuit_section">
<div className="mb-1"> <div className="mb-1">
<label className="circuit_label form-check-label" htmlFor="versionSelector"> <label className="circuit_label form-check-label">
<FormattedMessage id="circuit.compiler" /> <FormattedMessage id="circuit.compiler" />
</label> </label>
<CustomTooltip placement="top" tooltipId="showCompilerTooltip" tooltipClasses="text-nowrap" tooltipText={'See compiler license'}> <CustomTooltip
<span className="fa fa-file-text-o border-0 p-0 ml-2" onClick={() => showCompilerLicense()}></span> placement="bottom"
tooltipId="showCircumCompilerTooltip"
tooltipClasses="text-nowrap"
tooltipText='See compiler license'
>
<span className="far fa-file-certificate border-0 p-0 ml-2" onClick={() => showCompilerLicense()}></span>
</CustomTooltip> </CustomTooltip>
<VersionList setVersion={handleVersionSelect} versionList={circuitApp.appState.versionList} currentVersion={circuitApp.appState.version} /> <VersionList setVersion={handleVersionSelect} versionList={circuitApp.appState.versionList} currentVersion={circuitApp.appState.version} />
<CompileOptions setCircuitAutoCompile={handleCircuitAutoCompile} setCircuitHideWarnings={handleCircuitHideWarnings} autoCompile={circuitApp.appState.autoCompile} hideWarnings={circuitApp.appState.hideWarnings} /> <CompileOptions setCircuitAutoCompile={handleCircuitAutoCompile} setCircuitHideWarnings={handleCircuitHideWarnings} autoCompile={circuitApp.appState.autoCompile} hideWarnings={circuitApp.appState.hideWarnings} />

@ -4,7 +4,7 @@ import EventManager from 'events'
import pathModule from 'path' import pathModule from 'path'
import { parse, compile, generate_witness, generate_r1cs, compiler_list } from 'circom_wasm' import { parse, compile, generate_witness, generate_r1cs, compiler_list } from 'circom_wasm'
import { extractNameFromKey, extractParentFromKey } from '@remix-ui/helper' import { extractNameFromKey, extractParentFromKey } from '@remix-ui/helper'
import { CompilationConfig, CompilerReport } from '../types' import { CompilationConfig, CompilerReport, ResolverOutput } from '../types'
export class CircomPluginClient extends PluginClient { export class CircomPluginClient extends PluginClient {
public internalEvents: EventManager public internalEvents: EventManager
@ -37,10 +37,7 @@ export class CircomPluginClient extends PluginClient {
// @ts-ignore // @ts-ignore
fileContent = await this.call('fileManager', 'readFile', path) fileContent = await this.call('fileManager', 'readFile', path)
} }
this.lastParsedFiles = { this.lastParsedFiles = await this.resolveDependencies(path, fileContent, { [path]: { content: fileContent, parent: null } })
[path]: fileContent,
}
this.lastParsedFiles = await this.resolveDependencies(path, fileContent, this.lastParsedFiles)
const parsedOutput = parse(path, this.lastParsedFiles) const parsedOutput = parse(path, this.lastParsedFiles)
try { try {
@ -211,14 +208,13 @@ export class CircomPluginClient extends PluginClient {
this.internalEvents.emit('circuit_computing_witness_done') this.internalEvents.emit('circuit_computing_witness_done')
} }
async resolveDependencies(filePath: string, fileContent: string, output = {}, depPath: string = '', blackPath: string[] = []): Promise<Record<string, string>> { async resolveDependencies(filePath: string, fileContent: string, output: ResolverOutput = {}, depPath: string = '', parent: string = ''): Promise<Record<string, string>> {
// extract all includes // extract all includes
const includes = (fileContent.match(/include ['"].*['"]/g) || []).map((include) => include.replace(/include ['"]/g, '').replace(/['"]/g, '')) const includes = (fileContent.match(/include ['"].*['"]/g) || []).map((include) => include.replace(/include ['"]/g, '').replace(/['"]/g, ''))
await Promise.all( await Promise.all(
includes.map(async (include) => { includes.map(async (include) => {
// fix for endless recursive includes // fix for endless recursive includes
if (blackPath.includes(include)) return
let dependencyContent = '' let dependencyContent = ''
let path = include let path = include
// @ts-ignore // @ts-ignore
@ -268,20 +264,43 @@ export class CircomPluginClient extends PluginClient {
} }
} }
} }
// extract all includes from the dependency content const fileNameToInclude = extractNameFromKey(include)
const dependencyIncludes = (dependencyContent.match(/include ['"].*['"]/g) || []).map((include) => include.replace(/include ['"]/g, '').replace(/['"]/g, '')) const similarFile = Object.keys(output).find(path => {
return path.indexOf(fileNameToInclude) > -1
blackPath.push(include) })
// recursively resolve all dependencies of the dependency const isDuplicateContent = similarFile && output[similarFile] ? output[similarFile].content === dependencyContent : false
if (dependencyIncludes.length > 0) {
await this.resolveDependencies(filePath, dependencyContent, output, path, blackPath) if (output[include] && output[include].parent) {
output[include] = dependencyContent // if include import already exists, remove the include import from the parent file
const regexPattern = new RegExp(`include ['"]${include}['"];`, 'g')
output[output[include].parent].content = output[output[include].parent].content.replace(regexPattern, "")
} else if (isDuplicateContent) {
// if include import has the same content as another file, replace the include import with the file name of the other file (similarFile)
if (output[similarFile].parent) output[output[similarFile].parent].content = output[output[similarFile].parent].content.replace(similarFile, include)
if (include !== similarFile) {
output[include] = output[similarFile]
delete output[similarFile]
}
} else { } else {
output[include] = dependencyContent // extract all includes from the dependency content
const dependencyIncludes = (dependencyContent.match(/include ['"].*['"]/g) || []).map((include) => include.replace(/include ['"]/g, '').replace(/['"]/g, ''))
output[include] = {
content: dependencyContent,
parent
}
// recursively resolve all dependencies of the dependency
if (dependencyIncludes.length > 0) await this.resolveDependencies(filePath, dependencyContent, output, path, include)
} }
}) })
) )
return output const result: Record<string, string> = {}
Object.keys(output).forEach((key) => {
result[key] = output[key].content
})
return result
} }
async resolveReportPath (path: string): Promise<string> { async resolveReportPath (path: string): Promise<string> {

@ -84,4 +84,11 @@ export type CompileOptionsProps = {
setCircuitHideWarnings: (value: boolean) => void, setCircuitHideWarnings: (value: boolean) => void,
autoCompile: boolean, autoCompile: boolean,
hideWarnings: boolean hideWarnings: boolean
}
export type ResolverOutput = {
[name: string]: {
content: string,
parent: string
}
} }

@ -0,0 +1,28 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class waitForElementNotContainsText extends EventEmitter {
command (this: NightwatchBrowser, id: string, value: string, timeout = 10000): NightwatchBrowser {
let waitId // eslint-disable-line
let currentValue
const runid = setInterval(() => {
this.api.getText(id, (result) => {
currentValue = result.value
if (typeof result.value === 'string' && result.value.indexOf(value) !== -1) {
clearInterval(runid)
clearTimeout(waitId)
this.api.assert.ok(false, `WaitForElementContainsText ${id} contains ${value} . It should not`)
this.emit('complete')
}
})
}, 200)
waitId = setTimeout(() => {
clearInterval(runid)
this.api.assert.ok(true, `"${value}" wasn't found.`)
}, timeout)
return this
}
}
module.exports = waitForElementNotContainsText

@ -11,9 +11,7 @@ export default function (browser: NightwatchBrowser, callback: VoidFunction, url
browser browser
.url(url || 'http://127.0.0.1:8080') .url(url || 'http://127.0.0.1:8080')
//.switchBrowserTab(0) //.switchBrowserTab(0)
.waitForElementVisible('[id="remixTourSkipbtn"]')
.click('[id="remixTourSkipbtn"]')
.perform((done) => { .perform((done) => {
if (!loadPlugin) return done() if (!loadPlugin) return done()
browser browser
@ -54,7 +52,6 @@ export default function (browser: NightwatchBrowser, callback: VoidFunction, url
if (preloadPlugins) { if (preloadPlugins) {
initModules(browser, () => { initModules(browser, () => {
browser browser
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.waitForElementVisible('[for="autoCompile"]') .waitForElementVisible('[for="autoCompile"]')
.click('[for="autoCompile"]') .click('[for="autoCompile"]')

@ -92,6 +92,25 @@ module.exports = {
}) })
}, },
'Compile with remappings set in remappings.txt file #group1': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_remix_default' })
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(1000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.addFile('contracts/lib/storage/src/Storage.sol', { content: storageContract})
.addFile('remappings.txt', { content: 'storage=contracts/lib/storage/src' })
.addFile('contracts/Retriever.sol', { content: retrieverContract })
.verifyContracts(['Retriever', 'Storage'])
},
'Deploy and use Ballot using external web3 #group2': function (browser: NightwatchBrowser) { 'Deploy and use Ballot using external web3 #group2': function (browser: NightwatchBrowser) {
browser browser
.openFile('Untitled.sol') .openFile('Untitled.sol')
@ -510,4 +529,30 @@ object "Contract" {
} }
} }
} }
`
const storageContract = `
pragma solidity >=0.8.2 <0.9.0;
contract Storage {
uint256 public number;
function store(uint256 num) public {
number = num;
}
}
`
const retrieverContract = `
pragma solidity >=0.8.2 <0.9.0;
import "storage/Storage.sol";
contract Retriever is Storage {
function retrieve() public view returns (uint256){
return number;
}
}
` `

@ -11,8 +11,8 @@ module.exports = {
.maximizeWindow() .maximizeWindow()
.waitForElementVisible('*[data-id="skipbackup-btn"]', 5000) .waitForElementVisible('*[data-id="skipbackup-btn"]', 5000)
.click('*[data-id="skipbackup-btn"]') .click('*[data-id="skipbackup-btn"]')
.waitForElementVisible('[id="remixTourSkipbtn"]') .pause(5000)
.click('[id="remixTourSkipbtn"]') .waitForElementVisible('*[data-id="remixIdeSidePanel"]', 10000)
}, },
'Should load the testmigration url and refresh and still have test data #group7': function (browser: NightwatchBrowser) { 'Should load the testmigration url and refresh and still have test data #group7': function (browser: NightwatchBrowser) {
browser.url('http://127.0.0.1:8080?e2e_testmigration=true') browser.url('http://127.0.0.1:8080?e2e_testmigration=true')
@ -21,8 +21,9 @@ module.exports = {
.maximizeWindow() .maximizeWindow()
.waitForElementVisible('*[data-id="skipbackup-btn"]', 5000) .waitForElementVisible('*[data-id="skipbackup-btn"]', 5000)
.click('*[data-id="skipbackup-btn"]') .click('*[data-id="skipbackup-btn"]')
.waitForElementVisible('[id="remixTourSkipbtn"]') .pause(5000)
.click('[id="remixTourSkipbtn"]').refreshPage() .waitForElementVisible('*[data-id="remixIdeSidePanel"]', 10000)
.refreshPage()
}, },
'should have indexedDB storage in terminal #group1 #group7': function (browser: NightwatchBrowser) { 'should have indexedDB storage in terminal #group1 #group7': function (browser: NightwatchBrowser) {
browser.assert.containsText('*[data-id="terminalJournal"]', 'indexedDB') browser.assert.containsText('*[data-id="terminalJournal"]', 'indexedDB')
@ -32,16 +33,17 @@ module.exports = {
.pause(6000) .pause(6000)
.switchBrowserTab(0) .switchBrowserTab(0)
.maximizeWindow() .maximizeWindow()
.waitForElementVisible('[id="remixTourSkipbtn"]') .pause(5000)
.click('[id="remixTourSkipbtn"]') .waitForElementVisible('*[data-id="remixIdeSidePanel"]', 10000)
.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000)
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]') .waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.openFile('README.txt') .openFile('README.txt')
.waitForElementVisible('*[id="editorView"]', 10000)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.includes('Output from script will appear in remix terminal.')) browser.assert.ok(content.includes('Output from script will appear in remix terminal.'))
}) })
.click('*[data-id="treeViewLitreeViewItemcontracts"]') .click('*[data-id="treeViewLitreeViewItemcontracts"]')
.openFile('contracts/1_Storage.sol') .openFile('contracts/1_Storage.sol')
.waitForElementVisible('*[id="editorView"]', 10000)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.includes('function retrieve() public view returns (uint256){')) browser.assert.ok(content.includes('function retrieve() public view returns (uint256){'))
}) })
@ -53,8 +55,8 @@ module.exports = {
.maximizeWindow() .maximizeWindow()
.waitForElementVisible('*[data-id="skipbackup-btn"]', 5000) .waitForElementVisible('*[data-id="skipbackup-btn"]', 5000)
.click('*[data-id="skipbackup-btn"]') .click('*[data-id="skipbackup-btn"]')
.waitForElementVisible('[id="remixTourSkipbtn"]') .pause(5000)
.click('[id="remixTourSkipbtn"]') .waitForElementVisible('*[data-id="remixIdeSidePanel"]', 10000)
}, },
'Should generate error in migration by deleting indexedDB and falling back to local storage with test #group5': function (browser: NightwatchBrowser) { 'Should generate error in migration by deleting indexedDB and falling back to local storage with test #group5': function (browser: NightwatchBrowser) {
browser.url('http://127.0.0.1:8080?e2e_testmigration=true') browser.url('http://127.0.0.1:8080?e2e_testmigration=true')
@ -63,8 +65,8 @@ module.exports = {
.maximizeWindow().execute(('delete window.indexedDB')) .maximizeWindow().execute(('delete window.indexedDB'))
.waitForElementVisible('*[data-id="skipbackup-btn"]', 5000) .waitForElementVisible('*[data-id="skipbackup-btn"]', 5000)
.click('*[data-id="skipbackup-btn"]') .click('*[data-id="skipbackup-btn"]')
.waitForElementVisible('[id="remixTourSkipbtn"]') .pause(5000)
.click('[id="remixTourSkipbtn"]') .waitForElementVisible('*[data-id="remixIdeSidePanel"]', 10000)
}, },
'should have localstorage storage in terminal #group2 #group3 #group5': function (browser: NightwatchBrowser) { 'should have localstorage storage in terminal #group2 #group3 #group5': function (browser: NightwatchBrowser) {
browser.assert.containsText('*[data-id="terminalJournal"]', 'localstorage') browser.assert.containsText('*[data-id="terminalJournal"]', 'localstorage')
@ -74,6 +76,7 @@ module.exports = {
.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000) .waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000)
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]') .waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.openFile('TEST_README.txt') .openFile('TEST_README.txt')
.waitForElementVisible('*[id="editorView"]', 10000)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.equal(content, 'TEST README') browser.assert.equal(content, 'TEST README')
}) })
@ -96,6 +99,7 @@ module.exports = {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000) browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000)
.click('*[data-id="treeViewLitreeViewItemtest_contracts/artifacts"]') .click('*[data-id="treeViewLitreeViewItemtest_contracts/artifacts"]')
.openFile('test_contracts/artifacts/Storage_metadata.json') .openFile('test_contracts/artifacts/Storage_metadata.json')
.waitForElementVisible('*[id="editorView"]', 10000)
.getEditorValue((content) => { .getEditorValue((content) => {
const metadata = JSON.parse(content) const metadata = JSON.parse(content)
browser.assert.equal(metadata.test, 'data') browser.assert.equal(metadata.test, 'data')

@ -295,7 +295,7 @@ module.exports = {
}, null, '/') }, null, '/')
}, },
'Should get all workspaces #group2': async function (browser: NightwatchBrowser) { 'Should get all workspaces #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"default_workspace",isGitRepo:false}, {name:"emptyworkspace",isGitRepo:false}, {name:"testspace",isGitRepo:false}], null, null) await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"default_workspace",isGitRepo:false,hasGitSubmodules:false}, {name:"emptyworkspace",isGitRepo:false,hasGitSubmodules:false}, {name:"testspace",isGitRepo:false,hasGitSubmodules:false}], null, null)
}, },
'Should have set workspace event #group2': async function (browser: NightwatchBrowser) { 'Should have set workspace event #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, { event: 'setWorkspace', args: [{ name: 'newspace', isLocalhost: false }] }, 'newspace') await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, { event: 'setWorkspace', args: [{ name: 'newspace', isLocalhost: false }] }, 'newspace')
@ -309,11 +309,11 @@ module.exports = {
'Should rename workspace #group2': async function (browser: NightwatchBrowser) { 'Should rename workspace #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:renameWorkspace', null, null, ['default_workspace', 'renamed']) await clickAndCheckLog(browser, 'filePanel:renameWorkspace', null, null, ['default_workspace', 'renamed'])
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"emptyworkspace",isGitRepo:false},{name:"testspace",isGitRepo:false},{name:"newspace",isGitRepo:false},{name:"renamed",isGitRepo:false}], null, null) await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"emptyworkspace",isGitRepo:false,hasGitSubmodules:false},{name:"testspace",isGitRepo:false,hasGitSubmodules:false},{name:"newspace",isGitRepo:false,hasGitSubmodules:false},{name:"renamed",isGitRepo:false,hasGitSubmodules:false}], null, null)
}, },
'Should delete workspace #group2': async function (browser: NightwatchBrowser) { 'Should delete workspace #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:deleteWorkspace', null, null, ['testspace']) await clickAndCheckLog(browser, 'filePanel:deleteWorkspace', null, null, ['testspace'])
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"emptyworkspace",isGitRepo:false},{name:"newspace",isGitRepo:false},{name:"renamed",isGitRepo:false}], null, null) await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"emptyworkspace",isGitRepo:false,hasGitSubmodules:false},{name:"newspace",isGitRepo:false,hasGitSubmodules:false},{name:"renamed",isGitRepo:false,hasGitSubmodules:false}], null, null)
}, },
// DGIT // DGIT
'Should have changes on new workspace #group3': async function (browser: NightwatchBrowser) { 'Should have changes on new workspace #group3': async function (browser: NightwatchBrowser) {

@ -216,7 +216,7 @@ module.exports = {
.execute(() => { .execute(() => {
(document.querySelector('*[data-id="basic-http-providerModalDialogContainer-react"] input[data-id="modalDialogCustomPromp"]') as any).focus() (document.querySelector('*[data-id="basic-http-providerModalDialogContainer-react"] input[data-id="modalDialogCustomPromp"]') as any).focus()
}, [], () => { }) }, [], () => { })
.setValue('[data-id="modalDialogCustomPromp"]', 'https://remix-goerli.ethdevops.io') .setValue('[data-id="modalDialogCustomPromp"]', 'https://go.getblock.io/ee42d0a88f314707be11dd799b122cb9')
.modalFooterOKClick('basic-http-provider') .modalFooterOKClick('basic-http-provider')
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.openFile('README.txt') .openFile('README.txt')
@ -224,11 +224,11 @@ module.exports = {
.pause(1000) .pause(1000)
.executeScriptInTerminal('remix.execute(\'scripts/log_tx_block.js\')') .executeScriptInTerminal('remix.execute(\'scripts/log_tx_block.js\')')
// check if the input of the transaction is being logged (web3 call) // check if the input of the transaction is being logged (web3 call)
.waitForElementContainsText('*[data-id="terminalJournal"]', '0x775526410000000000000000000000000000000000000000000000000000000000000060464c0335b2f1609abd9de25141c0a3b49db516fc7375970dc737c32b986e88e3000000000000000000000000000000000000000000000000000000000000039e000000000000000000000000000000000000000000000000000000000000000602926b30b10e7a514d92bc71e085f5bff2687fac2856ae43ef7621bf1756fa370516d310bec5727543089be9a4d5f68471174ee528e95a2520b0ca36c2b6c6eb0000000000000000000000000000000000000000000000000000000000046f49036f5e4ea4dd042801c8841e3db8e654124305da0f11824fc1db60c405dbb39f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 120000) .waitForElementContainsText('*[data-id="terminalJournal"]', '0x2b0006fa00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004e9e0000000000000000000000000000000000000000000000000000000000004ea373ded44d6900b8b479935bee9c82176261653e334586e0fd282f569357c0777bd9d084474837ac94bf96f2e26590222a2b8e46545657c7cf06ce2833d267bd6f131b5b3fd36cb1ca3e07cf422224df0766d1a677bbdb7ee4cc0d634efa5367a302a94dac422a16b9b8d5c10fe0555924f8189f6b498bef507b1d32e7915bd4df184f51e6d79ae6a1b11d5745ce7d625cecc3bd0dc50af4f999ffb927225f5e5c019b499f5e1fdcbc70c45df61df76013d1b0d45cdf6a267dac1b4620c0db2efd251f6548509c9c69f5bd9d1ee38ac0df0c73be2774f7d2e1fb7ef5129010f29d091e3c48aed0f035fc29804c99927d33ff2a19ff526979355ac50b2542bc5d8f2d41e4f850d5981e0420807469e828b03173b96b757fbaeacda335e11b3ab8b02a48456fab35d41ca26abde751d5fca8ef5e7ba5295278b6e46ce2aab6c10b3d185a6137d3e5c28bb8dd3a797feaf35520fcb949ea074e1869e0011ef01f8162135e44bb797d3d6215ff74ffbee972c97264fc15d11c840e6a7e796dc1a418572f6dbcc842594a558e1a9e3cb7a159284e16fec758bbc303d13edc28fb6d8bb110c3a398e4ded1748da9854eb84679ad0c99bc59bea7956b521db3ed0a9057510cc11365858704989690f0d891af81b213b1f2e91e41e4998a467656eac87e7025ac2840c17f2b106df7d32a0139036bdf5d87344ca37e9ce770e0dbeb5e021d03a7d496a6695eb06d3de9258b43f3883ce155767962b52083504b19d6d609090a2f96e9724902bf1adbf57359ac1dda48a8ffe596b8d95cac1429378769a6ec2ff1c8a9c0bc343b0a6468f36696bfb202cde9f6cd5241b814096d777751b44f0cc2ac9e7ba142227e8d5f2dd8da62573953540da1abce82c59287b2f7a87a111851758c2505d8c1ded6c42a49fc5577451ee56126d2275da490baa645c3bcac0c31dabee7aa35e6cdffb56ac0d952c2583c6f50f906dfb96f5a98c49a5919031cff880bffbe371a50162a7bd0fa0398a5898eaf6ad6db868a7d807846a3592325bb4207d67ad96bac76435368962ba8944d0201c2f620fb29373a6f35c815d101af98111e9b4cc61e8ae77fc63ce375068328ec8d05b49486666fb0f756f99d2fe747c95b2a553965f304a324879393897315d310841f0a200cd156f6ca4ed2', 120000)
// check if the logsBloom is being logged (web3 call) // check if the logsBloom is being logged (web3 call)
.waitForElementContainsText('*[data-id="terminalJournal"]', '0x00000000000000000000000000100000000000000000020000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000040000000060000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000100000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000001', 120000) .waitForElementContainsText('*[data-id="terminalJournal"]', '0x0fbbd94c448fe6949f848380a1d145a974f386624b4b10aa40f9afb212b3ddeb', 120000) // hash of 4757766
// check if the logsBloom is being logged (ethers.js call) // check if the logsBloom is being logged (ethers.js call)
.waitForElementContainsText('*[data-id="terminalJournal"]', '"hex":"0x025cd8"', 120000) .waitForElementContainsText('*[data-id="terminalJournal"]', '0x9db899cb75888a630ba50a1644c243b83d2eb38525eb828a06a5e8bb5663c0b0', 120000) // hash of 4757767
}, },
'Should listen on all transactions #group8': function (browser: NightwatchBrowser) { 'Should listen on all transactions #group8': function (browser: NightwatchBrowser) {
@ -301,21 +301,17 @@ module.exports = {
}) })
.executeScriptInTerminal(`web3.eth.getCode('0x180587b00c8642e2c7ac3a758712d97e6f7bdcc7')`) // mainnet contract .executeScriptInTerminal(`web3.eth.getCode('0x180587b00c8642e2c7ac3a758712d97e6f7bdcc7')`) // mainnet contract
.waitForElementContainsText('*[data-id="terminalJournal"]', '0x608060405260043610601f5760003560e01c80635c60da1b14603157602b565b36602b576029605f565b005b6029605f565b348015603c57600080fd5b5060436097565b6040516001600160a01b03909116815260200160405180910390f35b609560917f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b60d1565b565b600060c97f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b90565b3660008037600080366000845af43d6000803e80801560ef573d6000f35b3d6000fdfea2646970667358221220969dbb4b1d8aec2bb348e26488dc1a33b6bcf0190f567d161312ab7ca9193d8d64736f6c63430008110033', 120000) .waitForElementContainsText('*[data-id="terminalJournal"]', '0x608060405260043610601f5760003560e01c80635c60da1b14603157602b565b36602b576029605f565b005b6029605f565b348015603c57600080fd5b5060436097565b6040516001600160a01b03909116815260200160405180910390f35b609560917f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b60d1565b565b600060c97f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b90565b3660008037600080366000845af43d6000803e80801560ef573d6000f35b3d6000fdfea2646970667358221220969dbb4b1d8aec2bb348e26488dc1a33b6bcf0190f567d161312ab7ca9193d8d64736f6c63430008110033', 120000)
.click('*[data-id="terminalClearConsole"]')
}, },
'Should connect to the sepolia fork and run web3.eth.getCode in the terminal #group9': function (browser: NightwatchBrowser) { 'Should connect to the sepolia fork and run web3.eth.getCode in the terminal #group9': function (browser: NightwatchBrowser) {
browser browser
.switchEnvironment('vm-custom-fork') .switchEnvironment('vm-custom-fork')
.waitForElementPresent({ .waitForElementVisible('[data-id="vm-custom-fork-modal-footer-ok-react"]')
locateStrategy: 'css selector',
selector: 'select[data-id="runTabSelectAccount"] option[value="0xdD870fA1b7C4700F2BD7f44238821C26f7392148"]',
timeout: 240000
})
.waitForElementPresent('[data-id="vm-custom-fork-modal-footer-ok-react"]')
.execute(() => { .execute(() => {
(document.querySelector('*[data-id="vm-custom-forkModalDialogContainer-react"] input[data-id="CustomForkNodeUrl"]') as any).focus() (document.querySelector('*[data-id="vm-custom-forkModalDialogContainer-react"] input[data-id="CustomForkNodeUrl"]') as any).focus()
}, [], () => { }) }, [], () => { })
.clearValue('*[data-id="CustomForkNodeUrl"]').pause(1000).setValue('*[data-id="CustomForkNodeUrl"]', 'https://remix-sepolia.ethdevops.io') .clearValue('*[data-id="CustomForkNodeUrl"]').pause(1000).setValue('*[data-id="CustomForkNodeUrl"]', 'https://go.getblock.io/ee42d0a88f314707be11dd799b122cb9')
.execute(() => { .execute(() => {
(document.querySelector('*[data-id="vm-custom-forkModalDialogContainer-react"] input[data-id="CustomForkBlockNumber"]') as any).focus() (document.querySelector('*[data-id="vm-custom-forkModalDialogContainer-react"] input[data-id="CustomForkBlockNumber"]') as any).focus()
}, [], () => { }) }, [], () => { })
@ -326,9 +322,61 @@ module.exports = {
.click('*[data-id="CustomForkEvmType"] [value="merge"]') .click('*[data-id="CustomForkEvmType"] [value="merge"]')
.pause(5000) .pause(5000)
.modalFooterOKClick('vm-custom-fork') .modalFooterOKClick('vm-custom-fork')
.waitForElementPresent({
locateStrategy: 'css selector',
selector: 'select[data-id="runTabSelectAccount"] option[value="0xdD870fA1b7C4700F2BD7f44238821C26f7392148"]',
timeout: 240000
})
.pause(5000) .pause(5000)
.executeScriptInTerminal(`web3.eth.getCode('0x75F509A4eDA030470272DfBAf99A47D587E76709')`) // sepolia contract .executeScriptInTerminal(`web3.eth.getCode('0x75F509A4eDA030470272DfBAf99A47D587E76709')`) // sepolia contract
.waitForElementContainsText('*[data-id="terminalJournal"]', byteCodeInSepolia, 120000) .waitForElementContainsText('*[data-id="terminalJournal"]', byteCodeInSepolia, 120000)
.click('*[data-id="terminalClearConsole"]')
},
'Should run a free function while being connected to mainnet #group9': function (browser: NightwatchBrowser) {
const script = `
import "https://github.com/ensdomains/ens-contracts/blob/master/contracts/utils/NameEncoder.sol";
import "hardhat/console.sol";
abstract contract ENS {
function resolver(bytes32 node) public virtual view returns (Resolver);
}
abstract contract Resolver {
function addr(bytes32 node) public virtual view returns (address);
}
function resolveENS() view {
// Same address for Mainet, Ropsten, Rinkerby, Gorli and other networks;
ENS ens = ENS(0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e);
(,bytes32 node) = NameEncoder.dnsEncodeName("vitalik.eth");
Resolver resolver = ens.resolver(node);
console.log(resolver.addr(node));
}
`
browser
// .clickLaunchIcon('udapp')
.switchEnvironment('vm-mainnet-fork')
.clickLaunchIcon('filePanel')
.addFile('test_mainnet.sol', { content: script })
const path = "//*[@class='view-line' and contains(.,'resolveENS') and contains(.,'view')]//span//span[contains(.,'(')]"
const pathRunFunction = `//li//*[@aria-label='Run the free function "resolveENS"']`
browser.waitForElementVisible('#editorView')
//.waitForElementPresent(pathRunFunction)
.pause(10000) // the parser need to parse the code
.useXpath()
.scrollToLine(16)
.click(path)
.perform(function () {
const actions = this.actions({ async: true });
return actions
.keyDown(this.Keys.SHIFT)
.keyDown(this.Keys.ALT)
.sendKeys('r')
})
.useCss()
.waitForElementContainsText('*[data-id="terminalJournal"]', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', 120000)
}, },
'Should run free function which logs in the terminal #group10': function (browser: NightwatchBrowser) { 'Should run free function which logs in the terminal #group10': function (browser: NightwatchBrowser) {
@ -342,7 +390,7 @@ module.exports = {
.addFile('test.sol', { content: script }) .addFile('test.sol', { content: script })
.scrollToLine(3) .scrollToLine(3)
const path = "//*[@class='view-line' and contains(.,'runSomething') and contains(.,'view')]//span//span[contains(.,'(')]" const path = "//*[@class='view-line' and contains(.,'runSomething') and contains(.,'view')]//span//span[contains(.,'(')]"
const pathRunFunction = `//li//*[@aria-label='Run the free function "runSomething" in the Remix VM']` const pathRunFunction = `//li//*[@aria-label='Run the free function "runSomething"']`
browser.waitForElementVisible('#editorView') browser.waitForElementVisible('#editorView')
.useXpath() .useXpath()
.click(path) .click(path)
@ -769,10 +817,10 @@ const scriptBlockAndTransaction = `
// Right click on the script name and hit "Run" to execute // Right click on the script name and hit "Run" to execute
(async () => { (async () => {
try { try {
web3.eth.getTransaction('0x022ccd55747677ac50f8d9dfd1bf5b843fa2f36438a28c1d0a0958e057bb3e2a').then(console.log) web3.eth.getTransaction('0x0d2baaed96425861677e87dcf6961d34e2b73ad9a0929c32a05607ca94f98d17').then(console.log).catch(console.error)
web3.eth.getBlock(7367447).then(console.log); web3.eth.getBlock(4757766).then(console.log).catch(console.error)
let ethersProvider = new ethers.providers.Web3Provider(web3Provider) let ethersProvider = new ethers.providers.Web3Provider(web3Provider)
ethersProvider.getBlock(7367447).then(console.log) ethersProvider.getBlock(4757767).then(console.log).catch(console.error)
} catch (e) { } catch (e) {
console.log(e.message) console.log(e.message)
} }

@ -382,6 +382,35 @@ module.exports = {
}) })
// No test file is added in upgradeable contract template // No test file is added in upgradeable contract template
}, },
'Should create circom zkp hashchecker workspace #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=hashchecker]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/calculate_hash.circom"]')
.click('*[data-id="treeViewLitreeViewItemcircuits/calculate_hash.circom"]')
.pause(1000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`template CalculateHash() {`) !== -1,
'Incorrect content')
})
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/run_setup.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/run_verification.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtemplates/groth16_verifier.sol.ejs"]')
.click('*[data-id="treeViewLitreeViewItemtemplates/groth16_verifier.sol.ejs"]')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`contract Groth16Verifier {`) !== -1,
'Incorrect content')
})
},
// WORKSPACE TEMPLATES E2E END // WORKSPACE TEMPLATES E2E END

@ -255,5 +255,98 @@ module.exports = {
// GIT BRANCHES E2E END // GIT BRANCHES E2E END
tearDown: sauce // GIT SUBMODULES E2E START
}
'Should clone a repository with submodules #group4': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.waitForElementVisible('[data-id="workspaceMenuDropdown"]')
.click('[data-id="workspaceMenuDropdown"]')
.waitForElementVisible('[data-id="workspaceclone"]')
.click('[data-id="workspaceclone"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
.setValue('[data-id="modalDialogCustomPromptTextClone"]', 'https://github.com/bunsenstraat/test-branch-submodule')
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.waitForElementPresent('.fa-spinner')
.waitForElementVisible({
selector: '*[data-id="treeViewLitreeViewItem.git"]',
timeout: 60000
})
.waitForElementContainsText('[data-id="workspacesSelect"]', 'test-branch-submodule')
.waitForElementVisible('[data-id="updatesubmodules"]')
.click('[data-id="updatesubmodules"]')
.waitForElementPresent('.fa-spinner')
.waitForElementVisible({
selector: '*[data-id="treeViewLitreeViewItem.git"]',
timeout: 60000
})
.waitForElementVisible('[data-id="treeViewDivtreeViewItemplugins"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemwebsite"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemrecursive"]')
.pause(2000)
.click('[data-id="treeViewDivtreeViewItemwebsite"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemwebsite/index.html"]')
.click('[data-id="treeViewDivtreeViewItemplugins"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemplugins/README.md"]')
.click('[data-id="treeViewDivtreeViewItemrecursive"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemrecursive/README.md"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemrecursive/plugins"]')
.click('[data-id="treeViewDivtreeViewItemrecursive/plugins"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemrecursive/plugins/build"]')
},
'When switching branches the submodules should dissappear #group4': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('[data-id="workspaceGitBranchesDropdown"]')
.click('[data-id="workspaceGitBranchesDropdown"]')
.waitForElementVisible('[data-id="custom-dropdown-menu"]')
.waitForElementContainsText('[data-id="custom-dropdown-items"]', 'origin/second')
.waitForElementPresent('[data-id="workspaceGit-origin/second"]')
.click('[data-id="workspaceGit-origin/second"]')
.waitForElementNotPresent('[data-id="treeViewDivtreeViewItemplugins"]')
.waitForElementNotPresent('[data-id="treeViewDivtreeViewItemwebsite"]')
},
'When switching to main update the modules #group4': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('[data-id="workspaceGitBranchesDropdown"]')
.click('[data-id="workspaceGitBranchesDropdown"]')
.waitForElementVisible('[data-id="custom-dropdown-menu"]')
.waitForElementContainsText('[data-id="custom-dropdown-items"]', 'origin/main')
.waitForElementPresent('[data-id="workspaceGit-origin/main"]')
.click('[data-id="workspaceGit-origin/main"]')
.waitForElementVisible('[data-id="updatesubmodules"]')
.click('[data-id="updatesubmodules"]')
.waitForElementPresent('.fa-spinner')
.waitForElementVisible({
selector: '*[data-id="treeViewLitreeViewItem.git"]',
timeout: 60000
})
.waitForElementVisible('[data-id="treeViewDivtreeViewItemplugins"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemwebsite"]')
.pause(2000)
.click('[data-id="treeViewDivtreeViewItemwebsite"]')
.click('[data-id="treeViewDivtreeViewItemwebsite"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemwebsite/index.html"]')
.click('[data-id="treeViewDivtreeViewItemplugins"]')
.click('[data-id="treeViewDivtreeViewItemplugins"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemplugins/README.md"]')
.click('[data-id="treeViewDivtreeViewItemrecursive"]')
.click('[data-id="treeViewDivtreeViewItemrecursive"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemrecursive/README.md"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemrecursive/plugins"]')
.click('[data-id="treeViewDivtreeViewItemrecursive/plugins"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemrecursive/plugins/build"]')
},
tearDown: sauce,
}
const gitmodules = `[submodule "subdemo3"]
path = subdemo3
url = https://github.com/bunsenstraat/empty3
[submodule "testactionsub"]
path = testactionsub
url = https://github.com/bunsenstraat/testactions
`

@ -69,6 +69,7 @@ declare module 'nightwatch' {
switchWorkspace: (workspaceName: string) => NightwatchBrowser switchWorkspace: (workspaceName: string) => NightwatchBrowser
switchEnvironment: (provider: string) => NightwatchBrowser switchEnvironment: (provider: string) => NightwatchBrowser
connectToExternalHttpProvider: (url: string, identifier: string) => NightwatchBrowser connectToExternalHttpProvider: (url: string, identifier: string) => NightwatchBrowser
waitForElementNotContainsText: (id: string, value: string, timeout: number = 10000) => NightwatchBrowser
} }
export interface NightwatchBrowser { export interface NightwatchBrowser {

@ -65,6 +65,7 @@ const remixLib = require('@remix-project/remix-lib')
import { QueryParams } from '@remix-project/remix-lib' import { QueryParams } from '@remix-project/remix-lib'
import { SearchPlugin } from './app/tabs/search' import { SearchPlugin } from './app/tabs/search'
import { ElectronProvider } from './app/files/electronProvider' import { ElectronProvider } from './app/files/electronProvider'
import { CopilotSuggestion } from './app/plugins/copilot/suggestion-service/copilot-suggestion'
const Storage = remixLib.Storage const Storage = remixLib.Storage
const RemixDProvider = require('./app/files/remixDProvider') const RemixDProvider = require('./app/files/remixDProvider')
@ -163,8 +164,11 @@ class AppComponent {
'6fd22d6fe5549ad4c4d8fd3ca0b7816b.mod': 35 // remix desktop '6fd22d6fe5549ad4c4d8fd3ca0b7816b.mod': 35 // remix desktop
} }
this.showMatamo = matomoDomains[window.location.hostname] && !Registry.getInstance().get('config').api.exists('settings/matomo-analytics') this.showMatamo = matomoDomains[window.location.hostname] && !Registry.getInstance().get('config').api.exists('settings/matomo-analytics')
this.walkthroughService = new WalkthroughService(appManager, this.showMatamo)
this.platform = isElectron() ? 'desktop' : 'web' this.platform = isElectron() ? 'desktop' : 'web'
this.showEnter = matomoDomains[window.location.hostname] && !localStorage.getItem('hadUsageTypeAsked')
this.walkthroughService = new WalkthroughService(appManager, !this.showMatamo || !this.showEnter)
const hosts = ['127.0.0.1:8080', '192.168.0.101:8080', 'localhost:8080'] const hosts = ['127.0.0.1:8080', '192.168.0.101:8080', 'localhost:8080']
// workaround for Electron support // workaround for Electron support
@ -223,8 +227,9 @@ class AppComponent {
// ----------------- ContractFlattener ---------------------------- // ----------------- ContractFlattener ----------------------------
const contractFlattener = new ContractFlattener() const contractFlattener = new ContractFlattener()
// ----------------- Open AI -------------------------------------- // ----------------- AI --------------------------------------
const openaigpt = new OpenAIGpt() const openaigpt = new OpenAIGpt()
const copilotSuggestion = new CopilotSuggestion()
// ----------------- import content service ------------------------ // ----------------- import content service ------------------------
const contentImport = new CompilerImports() const contentImport = new CompilerImports()
@ -349,7 +354,8 @@ class AppComponent {
contractFlattener, contractFlattener,
solidityScript, solidityScript,
templates, templates,
openaigpt openaigpt,
copilotSuggestion
]) ])
//---- fs plugin //---- fs plugin

@ -22,7 +22,7 @@ const profile = {
description: 'Decentralized git provider', description: 'Decentralized git provider',
icon: 'assets/img/fileManager.webp', icon: 'assets/img/fileManager.webp',
version: '0.0.1', version: '0.0.1',
methods: ['init', 'localStorageUsed', 'addremote', 'delremote', 'remotes', 'fetch', 'clone', 'export', 'import', 'status', 'log', 'commit', 'add', 'remove', 'reset', 'rm', 'lsfiles', 'readblob', 'resolveref', 'branches', 'branch', 'checkout', 'currentbranch', 'push', 'pin', 'pull', 'pinList', 'unPin', 'setIpfsConfig', 'zip', 'setItem', 'getItem', 'version'], methods: ['init', 'localStorageUsed', 'addremote', 'delremote', 'remotes', 'fetch', 'clone', 'export', 'import', 'status', 'log', 'commit', 'add', 'remove', 'reset', 'rm', 'lsfiles', 'readblob', 'resolveref', 'branches', 'branch', 'checkout', 'currentbranch', 'push', 'pin', 'pull', 'pinList', 'unPin', 'setIpfsConfig', 'zip', 'setItem', 'getItem', 'version', 'updateSubmodules'],
kind: 'file-system' kind: 'file-system'
} }
class DGitProvider extends Plugin { class DGitProvider extends Plugin {
@ -69,7 +69,7 @@ class DGitProvider extends Plugin {
if (!workspace) return if (!workspace) return
return { return {
fs: window.remixFileSystemCallback, fs: window.remixFileSystemCallback,
dir: addSlash(workspace.absolutePath) dir: addSlash(path.join(workspace.absolutePath, dir || '')),
} }
} }
@ -177,16 +177,39 @@ class DGitProvider extends Plugin {
if ((Registry.getInstance().get('platform').api.isDesktop())) { if ((Registry.getInstance().get('platform').api.isDesktop())) {
await this.call('isogit', 'checkout', cmd) await this.call('isogit', 'checkout', cmd)
} else { } else {
const gitmodules = await this.parseGitmodules() || []
await git.checkout({ await git.checkout({
...await this.getGitConfig(), ...await this.getGitConfig(),
...cmd ...cmd
}) })
const newgitmodules = await this.parseGitmodules() || []
// find the difference between the two gitmodule versions
const toRemove = gitmodules.filter((module) => {
return !newgitmodules.find((newmodule) => {
return newmodule.name === module.name
})
})
for (const module of toRemove) {
const path = (await this.getGitConfig(module.path)).dir
if (await window.remixFileSystem.exists(path)) {
const stat = await window.remixFileSystem.stat(path)
try {
if (stat.isDirectory()) {
await window.remixFileSystem.unlink((await this.getGitConfig(module.path)).dir)
}
} catch (e) {
// do nothing
}
}
}
} }
if (refresh) { if (refresh) {
setTimeout(async () => { setTimeout(async () => {
await this.call('fileManager', 'refresh') await this.call('fileManager', 'refresh')
}, 1000) }, 1000)
} }
this.emit('checkout') this.emit('checkout')
} }
@ -418,7 +441,7 @@ class DGitProvider extends Plugin {
...await this.parseInput(input), ...await this.parseInput(input),
...await this.getGitConfig() ...await this.getGitConfig()
} }
this.call('terminal', 'logHtml', `Cloning ${input.url}...`)
const result = await git.clone(cmd) const result = await git.clone(cmd)
if (!workspaceExists) { if (!workspaceExists) {
setTimeout(async () => { setTimeout(async () => {
@ -430,6 +453,137 @@ class DGitProvider extends Plugin {
} }
} }
async parseGitmodules (dir = '') {
try {
const gitmodules = await this.call('fileManager', 'readFile', path.join(dir, '.gitmodules'))
if (gitmodules) {
const lines = gitmodules.split('\n')
let currentModule = {}
let modules = []
for (let line of lines) {
line = line.trim()
if (line.startsWith('[')) {
if (currentModule.path) {
modules.push(currentModule)
}
currentModule = {}
currentModule.name = line.replace('[submodule "', '').replace('"]', '')
} else if (line.startsWith('url')) {
currentModule.url = line.replace('url = ', '')
} else if (line.startsWith('path')) {
currentModule.path = line.replace('path = ', '')
}
}
if (currentModule.path) {
modules.push(currentModule)
}
return modules
}
} catch (e) {
// do nothing
}
}
async updateSubmodules(input) {
try {
const currentDir = (input && input.dir) || ''
const gitmodules = await this.parseGitmodules(currentDir)
this.call('terminal', 'logHtml', `Found ${(gitmodules && gitmodules.length) || 0} submodules in ${currentDir || '/'}`)
//parse gitmodules
if (gitmodules) {
for (let module of gitmodules) {
const dir = path.join(currentDir, module.path)
const targetPath = (await this.getGitConfig(dir)).dir
if (await window.remixFileSystem.exists(targetPath)) {
const stat = await window.remixFileSystem.stat(targetPath)
try {
if (stat.isDirectory()) {
await window.remixFileSystem.unlink(targetPath)
}
} catch (e) {
// do nothing
}
}
}
for (let module of gitmodules) {
const dir = path.join(currentDir, module.path)
// if url contains git@github.com: convert it
if(module.url && module.url.startsWith('git@github.com:')) {
module.url = module.url.replace('git@github.com:', 'https://github.com/')
}
try {
const cmd = {
url: module.url,
singleBranch: true,
depth: 1,
...await this.parseInput(input),
...await this.getGitConfig(dir)
}
this.call('terminal', 'logHtml', `Cloning submodule ${dir}...`)
await git.clone(cmd)
this.call('terminal', 'logHtml', `Cloned successfully submodule ${dir}...`)
const commitHash = await git.resolveRef({
...await this.getGitConfig(currentDir),
ref: 'HEAD'
})
const result = await git.walk({
...await this.getGitConfig(currentDir),
trees: [git.TREE({ ref: commitHash })],
map: async function (filepath, [A]) {
if(filepath === module.path) {
return await A.oid()
}
}
})
if(result && result.length) {
this.call('terminal', 'logHtml', `Checking out submodule ${dir} to ${result[0]} in directory ${dir}`)
await git.fetch({
...await this.parseInput(input),
...await this.getGitConfig(dir),
singleBranch: true,
ref: result[0]
})
await git.checkout({
...await this.getGitConfig(dir),
ref: result[0]
})
const log = await git.log({
...await this.getGitConfig(dir),
})
if(log[0].oid !== result[0]) {
this.call('terminal', 'log', {
type: 'error',
value: `Could not checkout submodule to ${result[0]}`
})} else {
this.call('terminal', 'logHtml',`Checked out submodule ${dir} to ${result[0]}`)
}
}
await this.updateSubmodules({
...input,
dir
})
} catch (e) {
this.call('terminal', 'log', { type: 'error', value: `[Cloning]: Error occured! ${e}` })
console.log(e)
}
}
setTimeout(async () => {
await this.call('fileManager', 'refresh')
}, 1000)
}
} catch (e) {
this.call('terminal', 'log', { type: 'error', value: `[Cloning]: Error occured! ${e}` })
// do nothing
}
}
async push(input) { async push(input) {
const cmd = { const cmd = {
force: input.force, force: input.force,

@ -25,7 +25,7 @@ const profile = {
methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'writeMultipleFiles', 'writeFileNoRewrite', methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'writeMultipleFiles', 'writeFileNoRewrite',
'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile',
'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath',
'saveCurrentFile', 'setBatchFiles', 'isGitRepo', 'isFile', 'isDirectory' 'saveCurrentFile', 'setBatchFiles', 'isGitRepo', 'isFile', 'isDirectory', 'hasGitSubmodule'
], ],
kind: 'file-system' kind: 'file-system'
} }
@ -933,6 +933,13 @@ class FileManager extends Plugin {
return exists return exists
} }
async hasGitSubmodules(): Promise<boolean> {
const path = '.gitmodules'
const exists = await this.exists(path)
return exists
}
async moveFileIsAllowed (src: string, dest: string) { async moveFileIsAllowed (src: string, dest: string) {
try { try {

@ -188,23 +188,8 @@ export default class FileProvider {
if (!stat.isDirectory()) { if (!stat.isDirectory()) {
return await this.removeFile(path) return await this.removeFile(path)
} else { } else {
const items = await window.remixFileSystem.readdir(path) await window.remixFileSystem.unlink(path)
if (items.length !== 0) { this.event.emit('fileRemoved', this._normalizePath(path))
for (const item of items) {
const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}`
if ((await window.remixFileSystem.stat(curPath)).isDirectory()) { // delete folder
await this.remove(curPath)
} else { // delete file
await this.removeFile(curPath)
}
}
await window.remixFileSystem.rmdir(path)
this.event.emit('fileRemoved', this._normalizePath(path))
} else {
// folder is empty
await window.remixFileSystem.rmdir(path)
this.event.emit('fileRemoved', this._normalizePath(path))
}
} }
} catch (e) { } catch (e) {
console.log(e) console.log(e)

@ -0,0 +1,79 @@
import {Plugin} from '@remixproject/engine'
import {SuggestionService, SuggestOptions} from './suggestion-service'
const _paq = (window._paq = window._paq || []) //eslint-disable-line
const profile = {
name: 'copilot-suggestion',
displayName: 'copilot-suggestion',
description: 'Get Solidity suggestions in editor',
methods: ['suggest', 'init', 'uninstall', 'status', 'isActivate'],
version: '0.1.0-alpha',
maintainedBy: "Remix"
}
export class CopilotSuggestion extends Plugin {
service: SuggestionService
context: string
ready: boolean
constructor() {
super(profile)
this.service = new SuggestionService()
this.context = ''
this.service.events.on('progress', (data) => {
this.emit('loading', data)
})
this.service.events.on('done', (data) => {
})
this.service.events.on('ready', (data) => {
this.ready = true
})
}
status () {
return this.ready
}
async isActivate () {
try {
return await this.call('settings', 'get', 'settings/copilot/suggest/activate')
} catch (e) {
console.error(e)
return false
}
}
async suggest(content: string) {
if (!await this.call('settings', 'get', 'settings/copilot/suggest/activate')) return { output: [{ generated_text: ''}]}
const max_new_tokens = await this.call('settings', 'get', 'settings/copilot/suggest/max_new_tokens')
const temperature = await this.call('settings', 'get', 'settings/copilot/suggest/temperature')
const options: SuggestOptions = {
do_sample: false,
top_k: 0,
temperature,
max_new_tokens
}
return this.service.suggest(this.context ? this.context + '\n\n' + content : content, options)
}
async loadModeContent() {
let importsContent = ''
const imports = await this.call('codeParser', 'getImports')
for (const imp of imports.modules) {
try {
importsContent += '\n\n' + (await this.call('contentImport', 'resolve', imp)).content
} catch (e) {
console.log(e)
}
}
return importsContent
}
async init() {
return this.service.init()
}
async uninstall() {
this.service.terminate()
}
}

@ -0,0 +1,100 @@
import EventEmitter from 'events'
export type SuggestOptions = { max_new_tokens: number, temperature: number, top_k: number, do_sample: boolean }
export class SuggestionService {
worker: Worker
// eslint-disable-next-line @typescript-eslint/ban-types
responses: { [key: number]: Function }
events: EventEmitter
current: number
constructor() {
this.worker = new Worker(new URL('./worker.js', import.meta.url), {
type: 'module'
});
this.events = new EventEmitter()
this.responses = {}
this.current
}
terminate(): void {
this.worker.terminate()
this.worker = new Worker(new URL('./worker.js', import.meta.url), {
type: 'module'
});
}
async init() {
const onMessageReceived = (e) => {
switch (e.data.status) {
case 'initiate':
this.events.emit(e.data.status, e.data)
// Model file start load: add a new progress item to the list.
break;
case 'progress':
this.events.emit(e.data.status, e.data)
// Model file progress: update one of the progress items.
break;
case 'done':
this.events.emit(e.data.status, e.data)
// Model file loaded: remove the progress item from the list.
break;
case 'ready':
this.events.emit(e.data.status, e.data)
// Pipeline ready: the worker is ready to accept messages.
break;
case 'update':
this.events.emit(e.data.status, e.data)
// Generation update: update the output text.
break;
case 'complete':
if (this.responses[e.data.id]) {
if (this.current === e.data.id) {
this.responses[e.data.id](null, e.data)
} else {
this.responses[e.data.id]('aborted')
}
delete this.responses[e.data.id]
this.current = null
}
// Generation complete: re-enable the "Generate" button
break;
}
};
// Attach the callback function as an event listener.
this.worker.addEventListener('message', onMessageReceived)
this.worker.postMessage({
cmd: 'init',
model: 'Pipper/finetuned_sol'
})
}
suggest (content: string, options: SuggestOptions) {
return new Promise((resolve, reject) => {
if (this.current) return reject(new Error('already running'))
const timespan = Date.now()
this.current = timespan
this.worker.postMessage({
id: timespan,
cmd: 'suggest',
text: content,
max_new_tokens: options.max_new_tokens,
temperature: options.temperature,
top_k: options.top_k,
do_sample: options.do_sample
})
this.responses[timespan] = (error, result) => {
if (error) return reject(error)
resolve(result)
}
})
}
}

@ -0,0 +1,90 @@
import { pipeline, env } from '@xenova/transformers';
env.allowLocalModels = true;
const instance = null
/**
* This class uses the Singleton pattern to ensure that only one instance of the pipeline is loaded.
*/
class CodeCompletionPipeline {
static task = 'text-generation';
static model = null
static instance = null;
static async getInstance(progress_callback = null) {
if (this.instance === null) {
this.instance = pipeline(this.task, this.model, { progress_callback });
}
return this.instance;
}
}
// Listen for messages from the main thread
self.addEventListener('message', async (event) => {
const {
id, model, text, max_new_tokens, cmd,
// Generation parameters
temperature,
top_k,
do_sample,
} = event.data;
if (cmd === 'init') {
// Retrieve the code-completion pipeline. When called for the first time,
// this will load the pipeline and save it for future use.
CodeCompletionPipeline.model = model
await CodeCompletionPipeline.getInstance(x => {
// We also add a progress callback to the pipeline so that we can
// track model loading.
self.postMessage(x);
});
return
}
if (!CodeCompletionPipeline.instance) {
// Send the output back to the main thread
self.postMessage({
id,
status: 'error',
message: 'model not yet loaded'
});
}
if (cmd === 'suggest') {
// Retrieve the code-completion pipeline. When called for the first time,
// this will load the pipeline and save it for future use.
let generator = await CodeCompletionPipeline.getInstance(x => {
// We also add a progress callback to the pipeline so that we can
// track model loading.
self.postMessage(x);
});
// Actually perform the code-completion
let output = await generator(text, {
max_new_tokens,
temperature,
top_k,
do_sample,
// Allows for partial output
callback_function: x => {
/*self.postMessage({
id,
status: 'update',
output: generator.tokenizer.decode(x[0].output_token_ids, { skip_special_tokens: true })
});
*/
}
});
// Send the output back to the main thread
self.postMessage({
id,
status: 'complete',
output: output,
});
}
});

@ -38,9 +38,11 @@ export class OpenAIGpt extends Plugin {
} }
if (result && result.choices && result.choices.length) { if (result && result.choices && result.choices.length) {
this.call('terminal', 'log', { type: 'typewritersuccess', value: result.choices[0].message.content }) this.call('terminal', 'log', { type: 'typewriterwarning', value: result.choices[0].message.content })
} else if (result.error) {
this.call('terminal', 'log', { type: 'typewriterwarning', value: result.error })
} else { } else {
this.call('terminal', 'log', { type: 'typewritersuccess', value: 'No response...' }) this.call('terminal', 'log', { type: 'typewriterwarning', value: 'No response...' })
} }
return result.data return result.data
} }

@ -55,6 +55,7 @@ export class SolidityScript extends Plugin {
}) })
} }
await this.call('compilerArtefacts', 'saveCompilerAbstract', 'script.sol', compilation)
// get the contract // get the contract
const contract = compilation.getContract('SolidityScript') const contract = compilation.getContract('SolidityScript')
if (!contract) { if (!contract) {
@ -62,7 +63,7 @@ export class SolidityScript extends Plugin {
return return
} }
const bytecode = '0x' + contract.object.evm.bytecode.object const bytecode = '0x' + contract.object.evm.bytecode.object
const web3 = await this.call('blockchain', 'web3VM') const web3 = await this.call('blockchain', 'web3')
const accounts = await this.call('blockchain', 'getAccounts') const accounts = await this.call('blockchain', 'getAccounts')
if (!accounts || accounts.length === 0) { if (!accounts || accounts.length === 0) {
throw new Error('no account available') throw new Error('no account available')
@ -73,13 +74,27 @@ export class SolidityScript extends Plugin {
from: accounts[0], from: accounts[0],
data: bytecode data: bytecode
} }
const receipt = await web3.eth.sendTransaction(tx) let receipt
try {
receipt = await web3.eth.sendTransaction(tx, null, { checkRevertBeforeSending: false, ignoreGasPricing: true })
} catch (e) {
this.call('terminal', 'logHtml', e.message)
return
}
tx = { tx = {
from: accounts[0], from: accounts[0],
to: receipt.contractAddress, to: receipt.contractAddress,
data: '0x69d4394b' // function remixRun() public data: '0x69d4394b' // function remixRun() public
} }
const receiptCall = await web3.eth.sendTransaction(tx) let receiptCall
try {
receiptCall = await web3.eth.sendTransaction(tx, null, { checkRevertBeforeSending: false, ignoreGasPricing: true })
} catch (e) {
this.call('terminal', 'logHtml', e.message)
return
}
const hhlogs = await web3.remix.getHHLogsForTx(receiptCall.transactionHash) const hhlogs = await web3.remix.getHHLogsForTx(receiptCall.transactionHash)

@ -18,7 +18,7 @@ export class GoerliForkVMProvider extends BasicVMProvider {
) )
this.blockchain = blockchain this.blockchain = blockchain
this.fork = 'shanghai' this.fork = 'shanghai'
this.nodeUrl = 'https://remix-sepolia.ethdevops.io' this.nodeUrl = 'https://remix-goerli.ethdevops.io'
this.blockNumber = 'latest' this.blockNumber = 'latest'
} }

@ -18,7 +18,7 @@ export class SepoliaForkVMProvider extends BasicVMProvider {
) )
this.blockchain = blockchain this.blockchain = blockchain
this.fork = 'shanghai' this.fork = 'shanghai'
this.nodeUrl = 'https://remix-sepolia.ethdevops.io' this.nodeUrl = 'https://go.getblock.io/ee42d0a88f314707be11dd799b122cb9'
this.blockNumber = 'latest' this.blockNumber = 'latest'
} }

@ -104,6 +104,8 @@
"filePanel.burnable": "Burnable", "filePanel.burnable": "Burnable",
"filePanel.pausable": "Pausable", "filePanel.pausable": "Pausable",
"filePanel.semaphore": "Semaphore", "filePanel.semaphore": "Semaphore",
"filePanel.hashchecker": "Hash Checker",
"filePanel.rln": "Rate-Limiting Nullifier",
"filePanel.transparent": "Transparent", "filePanel.transparent": "Transparent",
"filePanel.initGitRepoTitle": "Check option to initialize workspace as a new git repository", "filePanel.initGitRepoTitle": "Check option to initialize workspace as a new git repository",
"filePanel.switchToBranchTitle1": "Checkout new branch from remote branch", "filePanel.switchToBranchTitle1": "Checkout new branch from remote branch",

@ -6,9 +6,9 @@
"home.learnMore": "Learn more", "home.learnMore": "Learn more",
"home.here": "here", "home.here": "here",
"home.featured": "Featured", "home.featured": "Featured",
"home.jumpIntoWeb3": "WE NEED YOUR HELP", "home.jumpIntoWeb3": "JUMP INTO WEB3",
"home.jumpIntoWeb3More": "Go to survey", "home.jumpIntoWeb3More": "More",
"home.jumpIntoWeb3Text": "Remixers... Have a spare moment? Please help us improve your Remix experience with this one-minute survey.", "home.jumpIntoWeb3Text": "Remix IDE is part of the Remix Project, a rich toolset that can be used for the entire journey of contract development by users of any knowledge level. Learn more on the Remix Project website.",
"home.remixYouTube": "WATCH TO LEARN", "home.remixYouTube": "WATCH TO LEARN",
"home.remixYouTubeText1": "Video Tips from the Remix Team", "home.remixYouTubeText1": "Video Tips from the Remix Team",
"home.remixYouTubeMore": "Watch", "home.remixYouTubeMore": "Watch",

@ -0,0 +1,11 @@
{
"homeReleaseDetails.title": "v0.38.0 RELEASE HIGHLIGHTS",
"homeReleaseDetails.highlight1": "Alpha release for Solidity co-pilot",
"homeReleaseDetails.highlight2": "Define Solidity remappings in remappings.txt file",
"homeReleaseDetails.highlight3": "Run free function for any selected environment",
"homeReleaseDetails.highlight4": "New Circom ZKP templates: Hash Checker & Rate Limiting Nullifier",
"homeReleaseDetails.more": "Read More"
}

@ -1,6 +1,7 @@
import debuggerJson from './debugger.json'; import debuggerJson from './debugger.json';
import filePanelJson from './filePanel.json'; import filePanelJson from './filePanel.json';
import homeJson from './home.json'; import homeJson from './home.json';
import homeReleaseDetailsJson from './homeReleaseDetails.json';
import panelJson from './panel.json'; import panelJson from './panel.json';
import pluginManagerJson from './pluginManager.json'; import pluginManagerJson from './pluginManager.json';
import searchJson from './search.json'; import searchJson from './search.json';
@ -20,6 +21,7 @@ export default {
...debuggerJson, ...debuggerJson,
...filePanelJson, ...filePanelJson,
...homeJson, ...homeJson,
...homeReleaseDetailsJson,
...panelJson, ...panelJson,
...pluginManagerJson, ...pluginManagerJson,
...searchJson, ...searchJson,

@ -36,5 +36,9 @@
"settings.port": "PORT", "settings.port": "PORT",
"settings.projectID": "PROJECT ID", "settings.projectID": "PROJECT ID",
"settings.projectSecret": "PROJECT SECRET", "settings.projectSecret": "PROJECT SECRET",
"settings.analyticsInRemix": "Analytics in Remix IDE" "settings.analyticsInRemix": "Analytics in Remix IDE",
"settings.copilot": "Solidity copilot - Alpha",
"settings.copilot.activate": "Load & Activate copilot",
"settings.copilot.max_new_tokens": "Maximum number of words to generate",
"settings.copilot.temperature": "Temperature"
} }

@ -6,9 +6,9 @@
"home.learnMore": "Aprender más", "home.learnMore": "Aprender más",
"home.here": "aquí", "home.here": "aquí",
"home.featured": "Destacado", "home.featured": "Destacado",
"home.jumpIntoWeb3": "NECESITAMOS TU AYUDA", "home.jumpIntoWeb3": "JUMP INTO WEB3",
"home.jumpIntoWeb3More": "Ir a la encuesta", "home.jumpIntoWeb3More": "More",
"home.jumpIntoWeb3Text": "Remixers... ¿Tienes un momento libre? Por favor, ayúdanos a mejorar tu experiencia en Remix con esta encuesta de un minuto.", "home.jumpIntoWeb3Text": "Remix IDE is part of the Remix Project, a rich toolset that can be used for the entire journey of contract development by users of any knowledge level. Learn more on the Remix Project website.",
"home.remixYouTube": "MIRA PARA APRENDER", "home.remixYouTube": "MIRA PARA APRENDER",
"home.remixYouTubeText1": "Vídeo de consejos del equipo de Remix", "home.remixYouTubeText1": "Vídeo de consejos del equipo de Remix",
"home.remixYouTubeMore": "Mirar", "home.remixYouTubeMore": "Mirar",

@ -6,9 +6,9 @@
"home.learnMore": "En savoir plus", "home.learnMore": "En savoir plus",
"home.here": "ici", "home.here": "ici",
"home.featured": "Recommandé", "home.featured": "Recommandé",
"home.jumpIntoWeb3": "NOUS AVONS BESOINS DE VOTRE AIDE", "home.jumpIntoWeb3": "JUMP INTO WEB3",
"home.jumpIntoWeb3More": "Accéder au sondage", "home.jumpIntoWeb3More": "More",
"home.jumpIntoWeb3Text": "Remixers... Vous avez un moment? Aidez-nous à améliorer votre expérience de remix avec cette enquête d'une minute.", "home.jumpIntoWeb3Text": "Remix IDE is part of the Remix Project, a rich toolset that can be used for the entire journey of contract development by users of any knowledge level. Learn more on the Remix Project website.",
"home.remixYouTube": "REGARDER POUR APPRENDRE", "home.remixYouTube": "REGARDER POUR APPRENDRE",
"home.remixYouTubeText1": "Conseils vidéo de l'équipe de Remix", "home.remixYouTubeText1": "Conseils vidéo de l'équipe de Remix",
"home.remixYouTubeMore": "Regarder", "home.remixYouTubeMore": "Regarder",

@ -6,9 +6,9 @@
"home.learnMore": "Scopri di più", "home.learnMore": "Scopri di più",
"home.here": "qui", "home.here": "qui",
"home.featured": "In Evidenza", "home.featured": "In Evidenza",
"home.jumpIntoWeb3": "ABBIAMO BISOGNO DEL TUO AIUTO", "home.jumpIntoWeb3": "JUMP INTO WEB3",
"home.jumpIntoWeb3More": "Vai al sondaggio", "home.jumpIntoWeb3More": "More",
"home.jumpIntoWeb3Text": "Remixers... Hai un momento libero? Aiutaci a migliorare la tua esperienza Remix con questo sondaggio di un minuto.", "home.jumpIntoWeb3Text": "Remix IDE is part of the Remix Project, a rich toolset that can be used for the entire journey of contract development by users of any knowledge level. Learn more on the Remix Project website.",
"home.remixYouTube": "GUARDA PER IMPARARE", "home.remixYouTube": "GUARDA PER IMPARARE",
"home.remixYouTubeText1": "Video di Suggerimenti dal Team di Remix", "home.remixYouTubeText1": "Video di Suggerimenti dal Team di Remix",
"home.remixYouTubeMore": "Guarda", "home.remixYouTubeMore": "Guarda",

@ -6,9 +6,9 @@
"home.learnMore": "了解更多", "home.learnMore": "了解更多",
"home.here": "这里", "home.here": "这里",
"home.featured": "精选", "home.featured": "精选",
"home.jumpIntoWeb3": "我们需要你的帮助", "home.jumpIntoWeb3": "JUMP INTO WEB3",
"home.jumpIntoWeb3More": "去填调查表", "home.jumpIntoWeb3More": "More",
"home.jumpIntoWeb3Text": "Remixers... 有空吗?请填写这份一分钟调查问卷,帮助我们改善您的 Remix 体验。", "home.jumpIntoWeb3Text": "Remix IDE is part of the Remix Project, a rich toolset that can be used for the entire journey of contract development by users of any knowledge level. Learn more on the Remix Project website.",
"home.remixYouTube": "观看学习", "home.remixYouTube": "观看学习",
"home.remixYouTubeText1": "来自 Remix 团队的视频小贴士", "home.remixYouTubeText1": "来自 Remix 团队的视频小贴士",
"home.remixYouTubeMore": "观看", "home.remixYouTubeMore": "观看",

@ -63,6 +63,7 @@ module.exports = class SettingsTab extends ViewPlugin {
updateComponent(state: any) { updateComponent(state: any) {
return ( return (
<RemixUiSettings <RemixUiSettings
plugin={this}
config={state.config} config={state.config}
editor={state.editor} editor={state.editor}
_deps={state._deps} _deps={state._deps}

@ -284,19 +284,19 @@ template {
color:#fff color:#fff
} }
.h1,h1 { .h1,h1 {
font-size:4rem font-size:3rem
} }
.h2,h2 { .h2,h2 {
font-size:3rem font-size:2.5rem
} }
.h3,h3 { .h3,h3 {
font-size:2.5rem font-size:2rem
} }
.h4,h4 { .h4,h4 {
font-size:2rem font-size:1.6rem
} }
.h5,h5 { .h5,h5 {
font-size:1.5rem font-size:1.3rem
} }
.h6,h6 { .h6,h6 {
font-size:.875rem font-size:.875rem

@ -2386,8 +2386,8 @@ fieldset:disabled a.btn {
.btn-secondary { .btn-secondary {
color: #fff; color: #fff;
background-color: #a8b3bc; background-color: #8b98a3;
border-color: #a8b3bc; border-color: #8b98a3;
} }
.btn-secondary:hover { .btn-secondary:hover {
color: #fff; color: #fff;
@ -2401,8 +2401,8 @@ fieldset:disabled a.btn {
.btn-secondary.disabled, .btn-secondary.disabled,
.btn-secondary:disabled { .btn-secondary:disabled {
color: #fff; color: #fff;
background-color: #a8b3bc; background-color: #8b98a3;
border-color: #a8b3bc; border-color: #8b98a3;
} }
.btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled):active,
.btn-secondary:not(:disabled):not(.disabled).active, .btn-secondary:not(:disabled):not(.disabled).active,
@ -2656,13 +2656,13 @@ fieldset:disabled a.btn {
} }
.btn-outline-secondary { .btn-outline-secondary {
color: #a8b3bc; color: #8b98a3;
border-color: #a8b3bc; border-color: #8b98a3;
} }
.btn-outline-secondary:hover { .btn-outline-secondary:hover {
color: #fff; color: #fff;
background-color: #a8b3bc; background-color: #8b98a3;
border-color: #a8b3bc; border-color: #8b98a3;
} }
.btn-outline-secondary:focus, .btn-outline-secondary:focus,
.btn-outline-secondary.focus { .btn-outline-secondary.focus {
@ -2670,15 +2670,15 @@ fieldset:disabled a.btn {
} }
.btn-outline-secondary.disabled, .btn-outline-secondary.disabled,
.btn-outline-secondary:disabled { .btn-outline-secondary:disabled {
color: #a8b3bc; color: #8b98a3;
background-color: transparent; background-color: transparent;
} }
.btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled):active,
.btn-outline-secondary:not(:disabled):not(.disabled).active, .btn-outline-secondary:not(:disabled):not(.disabled).active,
.show > .btn-outline-secondary.dropdown-toggle { .show > .btn-outline-secondary.dropdown-toggle {
color: #fff; color: #fff;
background-color: #a8b3bc; background-color: #8b98a3;
border-color: #a8b3bc; border-color: #8b98a3;
} }
.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled):active:focus,
.btn-outline-secondary:not(:disabled):not(.disabled).active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus,
@ -4731,7 +4731,7 @@ a.badge-primary.focus {
.badge-secondary { .badge-secondary {
color: #fff; color: #fff;
background-color: #a8b3bc; background-color: #8b98a3;
} }
a.badge-secondary:hover, a.badge-secondary:hover,
a.badge-secondary:focus { a.badge-secondary:focus {
@ -6271,7 +6271,7 @@ button.bg-dark:focus {
} }
.border-secondary { .border-secondary {
border-color: #a8b3bc !important; border-color: #8b98a3 !important;
} }
.border-success { .border-success {
@ -9308,7 +9308,7 @@ a.text-primary:focus {
} }
.text-secondary { .text-secondary {
color: #a8b3bc !important; color: #8b98a3 !important;
} }
a.text-secondary:hover, a.text-secondary:hover,

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

@ -23,7 +23,7 @@ const profile = {
name: 'blockchain', name: 'blockchain',
displayName: 'Blockchain', displayName: 'Blockchain',
description: 'Blockchain - Logic', description: 'Blockchain - Logic',
methods: ['getCode', 'getTransactionReceipt', 'addProvider', 'removeProvider', 'getCurrentFork', 'getAccounts', 'web3VM', 'getProvider'], methods: ['getCode', 'getTransactionReceipt', 'addProvider', 'removeProvider', 'getCurrentFork', 'getAccounts', 'web3VM', 'web3', 'getProvider'],
version: packageJson.version version: packageJson.version
} }

@ -62,6 +62,7 @@ export class VMProvider {
} }
} }
this.web3 = new Web3(this.provider as LegacySendAsyncProvider) this.web3 = new Web3(this.provider as LegacySendAsyncProvider)
this.web3.setConfig({ defaultTransactionType: '0x0' })
extend(this.web3) extend(this.web3)
this.executionContext.setWeb3(this.executionContext.getProvider(), this.web3) this.executionContext.setWeb3(this.executionContext.getProvider(), this.web3)
resolve({}) resolve({})

@ -76,7 +76,9 @@ let requiredModules = [ // services + layout views + system views
'contractflattener', 'contractflattener',
'solidity-script', 'solidity-script',
'openaigpt', 'openaigpt',
'home' 'home',
'doc-viewer',
'doc-gen'
] ]

@ -24,6 +24,8 @@ export class RemixEngine extends Engine {
if (name === 'isogit') return { queueTimeout: 60000 * 4 } if (name === 'isogit') return { queueTimeout: 60000 * 4 }
if (name === 'circuit-compiler') return { queueTimeout: 60000 * 4 } if (name === 'circuit-compiler') return { queueTimeout: 60000 * 4 }
if (name === 'compilerloader') return { queueTimeout: 60000 * 4 } if (name === 'compilerloader') return { queueTimeout: 60000 * 4 }
if (name === 'filePanel') return { queueTimeout: 60000 * 20 }
if (name === 'openaigpt') return { queueTimeout: 60000 * 2 }
return { queueTimeout: 10000 } return { queueTimeout: 10000 }
} }

@ -11,13 +11,21 @@ const profile = {
} }
export class WalkthroughService extends Plugin { export class WalkthroughService extends Plugin {
constructor (appManager, showMatamo) { constructor (appManager, showWalkthrough) {
super(profile) super(profile)
appManager.event.on('activate', (plugin) => { let readyToStart = 0;
if (plugin.name === 'udapp' && !showMatamo) { /*appManager.event.on('activate', (plugin) => {
if (plugin.name === 'udapp') readyToStart++
if (readyToStart == 2 && showWalkthrough) {
this.start() this.start()
} }
}) })
appManager.event.on('activate', (plugin) => {
if (plugin.name === 'solidity') readyToStart++
if (readyToStart == 2 && showWalkthrough) {
this.start()
}
})*/
} }
start () { start () {

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/ghaction-helper", "name": "@remix-project/ghaction-helper",
"version": "0.1.16", "version": "0.1.17",
"description": "Solidity Tests GitHub Action Helper", "description": "Solidity Tests GitHub Action Helper",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {
@ -19,17 +19,17 @@
}, },
"homepage": "https://github.com/ethereum/remix-project#readme", "homepage": "https://github.com/ethereum/remix-project#readme",
"devDependencies": { "devDependencies": {
"@remix-project/remix-solidity": "^0.5.22", "@remix-project/remix-solidity": "^0.5.23",
"@types/chai": "^4.3.4", "@types/chai": "^4.3.4",
"typescript": "^4.9.3" "typescript": "^4.9.3"
}, },
"dependencies": { "dependencies": {
"@ethereum-waffle/chai": "^3.4.4", "@ethereum-waffle/chai": "^3.4.4",
"@remix-project/remix-simulator": "^0.2.36", "@remix-project/remix-simulator": "^0.2.37",
"chai": "^4.3.7", "chai": "^4.3.7",
"ethers": "^5.7.2", "ethers": "^5.7.2",
"web3": "^4.1.1" "web3": "^4.1.1"
}, },
"types": "./src/index.d.ts", "types": "./src/index.d.ts",
"gitHead": "b8606fe2db170b6ef7cdd19b498db74cf2fb14b1" "gitHead": "29e14eb9e9750b43522e3667bb340ff6884d1ebf"
} }

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-analyzer", "name": "@remix-project/remix-analyzer",
"version": "0.5.45", "version": "0.5.46",
"description": "Tool to perform static analysis on Solidity smart contracts", "description": "Tool to perform static analysis on Solidity smart contracts",
"scripts": { "scripts": {
"test": "./../../node_modules/.bin/ts-node --project ../../tsconfig.base.json --require tsconfig-paths/register ./../../node_modules/.bin/tape ./test/tests.ts" "test": "./../../node_modules/.bin/ts-node --project ../../tsconfig.base.json --require tsconfig-paths/register ./../../node_modules/.bin/tape ./test/tests.ts"
@ -25,8 +25,8 @@
"@ethereumjs/tx": "^4.1.1", "@ethereumjs/tx": "^4.1.1",
"@ethereumjs/util": "^8.0.5", "@ethereumjs/util": "^8.0.5",
"@ethereumjs/vm": "^6.4.1", "@ethereumjs/vm": "^6.4.1",
"@remix-project/remix-astwalker": "^0.0.66", "@remix-project/remix-astwalker": "^0.0.67",
"@remix-project/remix-lib": "^0.5.43", "@remix-project/remix-lib": "^0.5.44",
"async": "^2.6.2", "async": "^2.6.2",
"ethers": "^5.4.2", "ethers": "^5.4.2",
"ethjs-util": "^0.1.6", "ethjs-util": "^0.1.6",
@ -50,6 +50,6 @@
"typescript": "^3.7.5" "typescript": "^3.7.5"
}, },
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "b8606fe2db170b6ef7cdd19b498db74cf2fb14b1", "gitHead": "29e14eb9e9750b43522e3667bb340ff6884d1ebf",
"main": "./src/index.js" "main": "./src/index.js"
} }

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-astwalker", "name": "@remix-project/remix-astwalker",
"version": "0.0.66", "version": "0.0.67",
"description": "Tool to walk through Solidity AST", "description": "Tool to walk through Solidity AST",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {
@ -37,7 +37,7 @@
"@ethereumjs/tx": "^4.1.1", "@ethereumjs/tx": "^4.1.1",
"@ethereumjs/util": "^8.0.5", "@ethereumjs/util": "^8.0.5",
"@ethereumjs/vm": "^6.4.1", "@ethereumjs/vm": "^6.4.1",
"@remix-project/remix-lib": "^0.5.43", "@remix-project/remix-lib": "^0.5.44",
"@types/tape": "^4.2.33", "@types/tape": "^4.2.33",
"async": "^2.6.2", "async": "^2.6.2",
"ethers": "^5.4.2", "ethers": "^5.4.2",
@ -53,6 +53,6 @@
"tap-spec": "^5.0.0" "tap-spec": "^5.0.0"
}, },
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "b8606fe2db170b6ef7cdd19b498db74cf2fb14b1", "gitHead": "29e14eb9e9750b43522e3667bb340ff6884d1ebf",
"types": "./src/index.d.ts" "types": "./src/index.d.ts"
} }

@ -5,7 +5,7 @@ import { CompilerAbstract } from '@remix-project/remix-solidity'
const profile = { const profile = {
name: 'compilerArtefacts', name: 'compilerArtefacts',
methods: ['get', 'addResolvedContract', 'getCompilerAbstract', 'getAllContractDatas', 'getLastCompilationResult', 'getArtefactsByContractName', 'getContractDataFromAddress', 'getContractDataFromByteCode'], methods: ['get', 'addResolvedContract', 'getCompilerAbstract', 'getAllContractDatas', 'getLastCompilationResult', 'getArtefactsByContractName', 'getContractDataFromAddress', 'getContractDataFromByteCode', 'saveCompilerAbstract'],
events: [], events: [],
version: '0.0.1' version: '0.0.1'
} }
@ -24,6 +24,10 @@ export class CompilerArtefacts extends Plugin {
this.compilersArtefactsPerFile = {} this.compilersArtefactsPerFile = {}
} }
saveCompilerAbstract (file: string, compilerAbstract: CompilerAbstract) {
this.compilersArtefactsPerFile[file] = compilerAbstract
}
onActivation () { onActivation () {
const saveCompilationPerFileResult = (file, source, languageVersion, data, input?) => { const saveCompilationPerFileResult = (file, source, languageVersion, data, input?) => {
this.compilersArtefactsPerFile[file] = new CompilerAbstract(languageVersion, data, source, input) this.compilersArtefactsPerFile[file] = new CompilerAbstract(languageVersion, data, source, input)

File diff suppressed because one or more lines are too long

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-debug", "name": "@remix-project/remix-debug",
"version": "0.5.36", "version": "0.5.37",
"description": "Tool to debug Ethereum transactions", "description": "Tool to debug Ethereum transactions",
"contributors": [ "contributors": [
{ {
@ -26,10 +26,10 @@
"@ethereumjs/tx": "^4.1.1", "@ethereumjs/tx": "^4.1.1",
"@ethereumjs/util": "^8.0.5", "@ethereumjs/util": "^8.0.5",
"@ethereumjs/vm": "^6.4.1", "@ethereumjs/vm": "^6.4.1",
"@remix-project/remix-astwalker": "^0.0.66", "@remix-project/remix-astwalker": "^0.0.67",
"@remix-project/remix-lib": "^0.5.43", "@remix-project/remix-lib": "^0.5.44",
"@remix-project/remix-simulator": "^0.2.36", "@remix-project/remix-simulator": "^0.2.37",
"@remix-project/remix-solidity": "^0.5.22", "@remix-project/remix-solidity": "^0.5.23",
"ansi-gray": "^0.1.1", "ansi-gray": "^0.1.1",
"async": "^2.6.2", "async": "^2.6.2",
"color-support": "^1.1.3", "color-support": "^1.1.3",
@ -69,6 +69,6 @@
}, },
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-debug#readme", "homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-debug#readme",
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "b8606fe2db170b6ef7cdd19b498db74cf2fb14b1", "gitHead": "29e14eb9e9750b43522e3667bb340ff6884d1ebf",
"types": "./src/index.d.ts" "types": "./src/index.d.ts"
} }

@ -1,5 +1,6 @@
'use strict' 'use strict'
import Web3, { Web3PluginBase } from 'web3' import Web3, { Web3PluginBase } from 'web3'
import {toNumber} from 'web3-utils'
export function extendWeb3 (web3) { export function extendWeb3 (web3) {
if(!web3.debug){ if(!web3.debug){
@ -21,11 +22,12 @@ export function setProvider (web3, url) {
export function web3DebugNode (network) { export function web3DebugNode (network) {
const web3DebugNodes = { const web3DebugNodes = {
Main: 'https://eth.getblock.io/68069907-1d3c-466e-a533-a943afd935c6/mainnet', Main: 'https://go.getblock.io/56f8bc5187aa4ac696348f67545acf38',
Holesky: 'https://go.getblock.io/7b91c53809fb49c787087e02ef84820b',
Rinkeby: 'https://remix-rinkeby.ethdevops.io', Rinkeby: 'https://remix-rinkeby.ethdevops.io',
Ropsten: 'https://remix-ropsten.ethdevops.io', Ropsten: 'https://remix-ropsten.ethdevops.io',
Goerli: 'https://remix-goerli.ethdevops.io', Goerli: 'https://remix-goerli.ethdevops.io',
Sepolia: 'https://remix-sepolia.ethdevops.io' Sepolia: 'https://go.getblock.io/ee42d0a88f314707be11dd799b122cb9'
} }
if (web3DebugNodes[network]) { if (web3DebugNodes[network]) {
return loadWeb3(web3DebugNodes[network]) return loadWeb3(web3DebugNodes[network])
@ -57,7 +59,7 @@ class Web3DebugPlugin extends Web3PluginBase {
public storageRangeAt(txBlockHash, txIndex, address, start, maxSize, cb) { public storageRangeAt(txBlockHash, txIndex, address, start, maxSize, cb) {
this.requestManager.send({ this.requestManager.send({
method: 'debug_storageRangeAt', method: 'debug_storageRangeAt',
params: [txBlockHash, txIndex, address, start, maxSize] params: [txBlockHash, toNumber(txIndex), address, start, maxSize]
}) })
.then(result => cb(null, result)) .then(result => cb(null, result))
.catch(error => cb(error)) .catch(error => cb(error))

@ -23,7 +23,7 @@ async function sendTx (web3, from, to, value, data, cb) {
value, value,
data, data,
gas: 7000000 gas: 7000000
}) }, null, { checkRevertBeforeSending: false, ignoreGasPricing: true })
cb(null, receipt.transactionHash) cb(null, receipt.transactionHash)
return receipt.transactionHash return receipt.transactionHash
} catch (e) { } catch (e) {

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-lib", "name": "@remix-project/remix-lib",
"version": "0.5.43", "version": "0.5.44",
"description": "Library to various Remix tools", "description": "Library to various Remix tools",
"contributors": [ "contributors": [
{ {
@ -55,6 +55,6 @@
}, },
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-lib#readme", "homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-lib#readme",
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "b8606fe2db170b6ef7cdd19b498db74cf2fb14b1", "gitHead": "29e14eb9e9750b43522e3667bb340ff6884d1ebf",
"types": "./src/index.d.ts" "types": "./src/index.d.ts"
} }

@ -89,7 +89,7 @@ export function checkError (execResult, compiledContracts) {
msg = '\tThe transaction ran out of gas. Please increase the Gas Limit.\n' msg = '\tThe transaction ran out of gas. Please increase the Gas Limit.\n'
ret.error = true ret.error = true
} else if (exceptionError === errorCode.REVERT || exceptionError === 'execution reverted') { } else if (exceptionError === errorCode.REVERT || exceptionError === 'execution reverted') {
const returnData = execResult.errorData const returnData = execResult.errorData || '0x00000000'
const returnDataHex = returnData.slice(2, 10) const returnDataHex = returnData.slice(2, 10)
let customError let customError
if (compiledContracts) { if (compiledContracts) {

@ -31,13 +31,13 @@ export class TxRunnerVM {
nextNonceForCall: number nextNonceForCall: number
getVMObject: () => any getVMObject: () => any
constructor (vmaccounts, api, getVMObject) { constructor (vmaccounts, api, getVMObject, blockNumber) {
this.event = new EventManager() this.event = new EventManager()
this.logsManager = new LogsManager() this.logsManager = new LogsManager()
// has a default for now for backwards compatibility // has a default for now for backwards compatibility
this.getVMObject = getVMObject this.getVMObject = getVMObject
this.commonContext = this.getVMObject().common this.commonContext = this.getVMObject().common
this.blockNumber = 0 this.blockNumber = blockNumber || 0
this.pendingTxs = {} this.pendingTxs = {}
this.vmaccounts = vmaccounts this.vmaccounts = vmaccounts
this.queusTxs = [] this.queusTxs = []

@ -65,7 +65,7 @@ export class TxRunnerWeb3 {
promptCb( promptCb(
async (value) => { async (value) => {
try { try {
const res = await (this.getWeb3() as any).eth.personal.sendTransaction({...tx, value}) const res = await (this.getWeb3() as any).eth.personal.sendTransaction({...tx, value}, { checkRevertBeforeSending: false, ignoreGasPricing: true })
cb(null, res.transactionHash) cb(null, res.transactionHash)
} catch (e) { } catch (e) {
console.log(`Send transaction failed: ${e.message} . if you use an injected provider, please check it is properly unlocked. `) console.log(`Send transaction failed: ${e.message} . if you use an injected provider, please check it is properly unlocked. `)
@ -81,7 +81,7 @@ export class TxRunnerWeb3 {
) )
} else { } else {
try { try {
const res = await this.getWeb3().eth.sendTransaction(tx, null, { checkRevertBeforeSending: false }) const res = await this.getWeb3().eth.sendTransaction(tx, null, { checkRevertBeforeSending: false, ignoreGasPricing: true})
cb(null, res.transactionHash) cb(null, res.transactionHash)
} catch (e) { } catch (e) {
console.log(`Send transaction failed: ${e.message} . if you use an injected provider, please check it is properly unlocked. `) console.log(`Send transaction failed: ${e.message} . if you use an injected provider, please check it is properly unlocked. `)

@ -24,6 +24,8 @@ function convertToString (v) {
return ret return ret
} else if (BN.isBN(v) || (v.constructor && v.constructor.name === 'BigNumber') || isBigInt(v)) { } else if (BN.isBN(v) || (v.constructor && v.constructor.name === 'BigNumber') || isBigInt(v)) {
return v.toString(10) return v.toString(10)
} else if (v._isBigNumber) {
return toInt(v._hex)
} else if (v._isBuffer) { } else if (v._isBuffer) {
return bufferToHex(v) return bufferToHex(v)
} else if (typeof v === 'object') { } else if (typeof v === 'object') {

@ -1,5 +1,6 @@
'use strict' 'use strict'
import Web3, { Web3PluginBase } from 'web3' import Web3, { Web3PluginBase } from 'web3'
import {toNumber} from 'web3-utils'
export function extendWeb3 (web3) { export function extendWeb3 (web3) {
if(!web3.debug){ if(!web3.debug){
@ -38,7 +39,7 @@ class Web3DebugPlugin extends Web3PluginBase {
public storageRangeAt(txBlockHash, txIndex, address, start, maxSize, cb) { public storageRangeAt(txBlockHash, txIndex, address, start, maxSize, cb) {
this.requestManager.send({ this.requestManager.send({
method: 'debug_storageRangeAt', method: 'debug_storageRangeAt',
params: [txBlockHash, txIndex, address, start, maxSize] params: [txBlockHash, toNumber(txIndex), address, start, maxSize]
}) })
.then(result => cb(null, result)) .then(result => cb(null, result))
.catch(error => cb(error)) .catch(error => cb(error))

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-simulator", "name": "@remix-project/remix-simulator",
"version": "0.2.36", "version": "0.2.37",
"description": "Ethereum IDE and tools for the web", "description": "Ethereum IDE and tools for the web",
"contributors": [ "contributors": [
{ {
@ -22,7 +22,7 @@
"@ethereumjs/tx": "^4.1.1", "@ethereumjs/tx": "^4.1.1",
"@ethereumjs/util": "^8.0.5", "@ethereumjs/util": "^8.0.5",
"@ethereumjs/vm": "^6.4.1", "@ethereumjs/vm": "^6.4.1",
"@remix-project/remix-lib": "^0.5.43", "@remix-project/remix-lib": "^0.5.44",
"ansi-gray": "^0.1.1", "ansi-gray": "^0.1.1",
"async": "^3.1.0", "async": "^3.1.0",
"body-parser": "^1.18.2", "body-parser": "^1.18.2",
@ -37,6 +37,7 @@
"rlp": "^3.0.0", "rlp": "^3.0.0",
"string-similarity": "^4.0.4", "string-similarity": "^4.0.4",
"time-stamp": "^2.0.0", "time-stamp": "^2.0.0",
"tslib": "^2.3.0",
"web3": "^4.1.1", "web3": "^4.1.1",
"web3-utils": "^4.0.5" "web3-utils": "^4.0.5"
}, },
@ -69,6 +70,6 @@
}, },
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-simulator#readme", "homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-simulator#readme",
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "b8606fe2db170b6ef7cdd19b498db74cf2fb14b1", "gitHead": "29e14eb9e9750b43522e3667bb340ff6884d1ebf",
"types": "./src/index.d.ts" "types": "./src/index.d.ts"
} }

@ -32,7 +32,7 @@ export class Transactions {
this.tags = {} this.tags = {}
} }
init (accounts) { init (accounts, blockNumber) {
this.accounts = accounts this.accounts = accounts
const api = { const api = {
logMessage: (msg) => { logMessage: (msg) => {
@ -55,7 +55,7 @@ export class Transactions {
} }
} }
this.txRunnerVMInstance = new TxRunnerVM(accounts, api, _ => this.vmContext.vmObject()) this.txRunnerVMInstance = new TxRunnerVM(accounts, api, _ => this.vmContext.vmObject(), blockNumber)
this.txRunnerInstance = new TxRunner(this.txRunnerVMInstance, {}) this.txRunnerInstance = new TxRunner(this.txRunnerVMInstance, {})
this.txRunnerInstance.vmaccounts = accounts this.txRunnerInstance.vmaccounts = accounts
} }
@ -158,10 +158,7 @@ export class Transactions {
this.vmContext.web3().flagNextAsDoNotRecordEvmSteps() this.vmContext.web3().flagNextAsDoNotRecordEvmSteps()
processTx(this.txRunnerInstance, payload, true, (error, value: VMexecutionResult) => { processTx(this.txRunnerInstance, payload, true, (error, value: VMexecutionResult) => {
if (error) return cb(error) if (error) return cb(error)
const result: any = value.result const result: any = value.result
if (result.execResult && result.execResult.exceptionError && result.execResult.exceptionError.errorType === 'EvmError') {
return cb(result.execResult.exceptionError.error)
}
if ((result as any).receipt?.status === '0x0' || (result as any).receipt?.status === 0) { if ((result as any).receipt?.status === '0x0' || (result as any).receipt?.status === 0) {
try { try {
const msg = `0x${result.execResult.returnValue.toString('hex') || '0'}` const msg = `0x${result.execResult.returnValue.toString('hex') || '0'}`
@ -172,6 +169,9 @@ export class Transactions {
return cb(e.message) return cb(e.message)
} }
} }
if (result.execResult && result.execResult.exceptionError && result.execResult.exceptionError.errorType === 'EvmError') {
return cb(result.execResult.exceptionError.error)
}
let gasUsed = Number(toNumber(result.execResult.executionGasUsed)) let gasUsed = Number(toNumber(result.execResult.executionGasUsed))
if (result.execResult.gasRefund) { if (result.execResult.gasRefund) {
gasUsed += Number(toNumber(result.execResult.gasRefund)) gasUsed += Number(toNumber(result.execResult.gasRefund))

@ -60,7 +60,7 @@ export class Provider {
this.pendingRequests = [] this.pendingRequests = []
await this.vmContext.init() await this.vmContext.init()
await this.Accounts.resetAccounts() await this.Accounts.resetAccounts()
this.Transactions.init(this.Accounts.accounts) this.Transactions.init(this.Accounts.accounts, this.vmContext.blockNumber)
this.initialized = true this.initialized = true
if (this.pendingRequests.length > 0) { if (this.pendingRequests.length > 0) {
this.pendingRequests.map((req) => { this.pendingRequests.map((req) => {

@ -313,6 +313,7 @@ export class VMContext {
provider: this.nodeUrl, provider: this.nodeUrl,
blockTag: '0x' + block.toString(16) blockTag: '0x' + block.toString(16)
}) })
this.blockNumber = block
} else { } else {
stateManager = new CustomEthersStateManager({ stateManager = new CustomEthersStateManager({
provider: this.nodeUrl, provider: this.nodeUrl,

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-solidity", "name": "@remix-project/remix-solidity",
"version": "0.5.22", "version": "0.5.23",
"description": "Tool to load and run Solidity compiler", "description": "Tool to load and run Solidity compiler",
"main": "src/index.js", "main": "src/index.js",
"types": "src/index.d.ts", "types": "src/index.d.ts",
@ -19,7 +19,7 @@
"@ethereumjs/tx": "^4.1.1", "@ethereumjs/tx": "^4.1.1",
"@ethereumjs/util": "^8.0.5", "@ethereumjs/util": "^8.0.5",
"@ethereumjs/vm": "^6.4.1", "@ethereumjs/vm": "^6.4.1",
"@remix-project/remix-lib": "^0.5.43", "@remix-project/remix-lib": "^0.5.44",
"async": "^2.6.2", "async": "^2.6.2",
"eslint-scope": "^5.0.0", "eslint-scope": "^5.0.0",
"ethers": "^5.4.2", "ethers": "^5.4.2",
@ -57,5 +57,5 @@
}, },
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-solidity#readme", "homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-solidity#readme",
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "b8606fe2db170b6ef7cdd19b498db74cf2fb14b1" "gitHead": "29e14eb9e9750b43522e3667bb340ff6884d1ebf"
} }

@ -17,7 +17,8 @@ export default (sources: Source, opts: CompilerInputOptions): string => {
'': ['ast'], '': ['ast'],
'*': ['abi', 'metadata', 'devdoc', 'userdoc', 'storageLayout', 'evm.legacyAssembly', 'evm.bytecode', 'evm.deployedBytecode', 'evm.methodIdentifiers', 'evm.gasEstimates', 'evm.assembly'] '*': ['abi', 'metadata', 'devdoc', 'userdoc', 'storageLayout', 'evm.legacyAssembly', 'evm.bytecode', 'evm.deployedBytecode', 'evm.methodIdentifiers', 'evm.gasEstimates', 'evm.assembly']
} }
} },
remappings: opts.remappings || []
} }
} }
if (opts.evmVersion) { if (opts.evmVersion) {

@ -32,6 +32,7 @@ export class Compiler {
runs: 200, runs: 200,
evmVersion: null, evmVersion: null,
language: 'Solidity', language: 'Solidity',
remappings: [],
compilationStartTime: null, compilationStartTime: null,
target: null, target: null,
useFileConfiguration: false, useFileConfiguration: false,
@ -213,12 +214,11 @@ export class Compiler {
let input = "" let input = ""
try { try {
if (source && source.sources) { if (source && source.sources) {
const { optimize, runs, evmVersion, language, useFileConfiguration, configFileContent } = this.state const { optimize, runs, evmVersion, language, remappings, useFileConfiguration, configFileContent } = this.state
if (useFileConfiguration) { if (useFileConfiguration) {
input = compilerInputForConfigFile(source.sources, JSON.parse(configFileContent)) input = compilerInputForConfigFile(source.sources, JSON.parse(configFileContent))
} else { } else {
input = compilerInput(source.sources, { optimize, runs, evmVersion, language }) input = compilerInput(source.sources, { optimize, runs, evmVersion, language, remappings })
} }
result = JSON.parse(remoteCompiler.compile(input, { import: missingInputsCallback })) result = JSON.parse(remoteCompiler.compile(input, { import: missingInputsCallback }))
@ -331,15 +331,18 @@ export class Compiler {
this.state.compileJSON = (source: SourceWithTarget, timeStamp: number) => { this.state.compileJSON = (source: SourceWithTarget, timeStamp: number) => {
if (source && source.sources) { if (source && source.sources) {
const { optimize, runs, evmVersion, language, useFileConfiguration, configFileContent } = this.state const { optimize, runs, evmVersion, language, remappings, useFileConfiguration, configFileContent } = this.state
jobs.push({ sources: source }) jobs.push({ sources: source })
let input = "" let input = ""
try { try {
if (useFileConfiguration) { if (useFileConfiguration) {
input = compilerInputForConfigFile(source.sources, JSON.parse(configFileContent)) const compilerInput = JSON.parse(configFileContent)
if (compilerInput.settings.remappings?.length) compilerInput.settings.remappings.push(...remappings)
else compilerInput.settings.remappings = remappings
input = compilerInputForConfigFile(source.sources, compilerInput)
} else { } else {
input = compilerInput(source.sources, { optimize, runs, evmVersion, language }) input = compilerInput(source.sources, { optimize, runs, evmVersion, language, remappings })
} }
} catch (exception) { } catch (exception) {
this.onCompilationFinished({ error: { formattedMessage: exception.message } }, [], source, "", this.state.currentVersion) this.onCompilationFinished({ error: { formattedMessage: exception.message } }, [], source, "", this.state.currentVersion)

@ -147,7 +147,8 @@ export interface CompilerInputOptions {
[fileName: string]: Record<string, string> [fileName: string]: Record<string, string>
}, },
evmVersion?: EVMVersion, evmVersion?: EVMVersion,
language?: Language language?: Language,
remappings?: string[]
} }
export type EVMVersion = 'homestead' | 'tangerineWhistle' | 'spuriousDragon' | 'byzantium' | 'constantinople' | 'petersburg' | 'istanbul' | 'berlin' | 'london' | 'paris' | null export type EVMVersion = 'homestead' | 'tangerineWhistle' | 'spuriousDragon' | 'byzantium' | 'constantinople' | 'petersburg' | 'istanbul' | 'berlin' | 'london' | 'paris' | null
@ -168,6 +169,7 @@ export interface CompilerState {
runs: number runs: number
evmVersion: EVMVersion| null, evmVersion: EVMVersion| null,
language: Language, language: Language,
remappings: string[]
compilationStartTime: number| null, compilationStartTime: number| null,
target: string | null, target: string | null,
useFileConfiguration: boolean, useFileConfiguration: boolean,

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-tests", "name": "@remix-project/remix-tests",
"version": "0.2.36", "version": "0.2.37",
"description": "Tool to test Solidity smart contracts", "description": "Tool to test Solidity smart contracts",
"main": "src/index.js", "main": "src/index.js",
"types": "./src/index.d.ts", "types": "./src/index.d.ts",
@ -41,9 +41,9 @@
"@ethereumjs/tx": "^4.1.1", "@ethereumjs/tx": "^4.1.1",
"@ethereumjs/util": "^8.0.5", "@ethereumjs/util": "^8.0.5",
"@ethereumjs/vm": "^6.4.1", "@ethereumjs/vm": "^6.4.1",
"@remix-project/remix-lib": "^0.5.43", "@remix-project/remix-lib": "^0.5.44",
"@remix-project/remix-simulator": "^0.2.36", "@remix-project/remix-simulator": "^0.2.37",
"@remix-project/remix-solidity": "^0.5.22", "@remix-project/remix-solidity": "^0.5.23",
"@remix-project/remix-url-resolver": "^0.0.42", "@remix-project/remix-url-resolver": "^0.0.42",
"ansi-gray": "^0.1.1", "ansi-gray": "^0.1.1",
"async": "^2.6.0", "async": "^2.6.0",
@ -78,5 +78,5 @@
"typescript": "^3.3.1" "typescript": "^3.3.1"
}, },
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "b8606fe2db170b6ef7cdd19b498db74cf2fb14b1" "gitHead": "29e14eb9e9750b43522e3667bb340ff6884d1ebf"
} }

@ -0,0 +1,60 @@
import React, {useContext, useEffect, useState} from 'react'
import {AppContext} from '../../context/context'
import {UsageTypes} from '../../types'
import { type } from 'os'
interface EnterDialogProps {
hide: boolean,
handleUserChoice: (userChoice: UsageTypes) => void,
}
const EnterDialog = (props: EnterDialogProps) => {
const [visibility, setVisibility] = useState<boolean>(false)
const {showEnter} = useContext(AppContext)
useEffect(() => {
setVisibility(!props.hide)
}, [props.hide])
const enterAs = async (uType) => {
props.handleUserChoice(uType)
}
const modalClass = (visibility && showEnter) ? "d-flex" : "d-none"
return (
<div
data-id={`EnterModalDialogContainer-react`}
data-backdrop="static"
data-keyboard="false"
className={"modal " + modalClass}
role="dialog"
>
<div className="modal-dialog align-self-center pb-4" role="document">
<div
tabIndex={-1}
className={'modal-content remixModalContent mb-4'}
onKeyDown={({keyCode}) => {
}}
>
<div className="modal-header d-flex flex-column">
<h3 className='text-dark'>Welcome to Remix IDE</h3>
<div className='d-flex flex-row pt-2'>
<h6 className="modal-title" data-id={`EnterModalDialogModalTitle-react`}>
To load the project with the most efficient setup we would like to know your experience type.
</h6>
<i className="text-dark fal fa-door-open text-center" style={{minWidth: "100px", fontSize: "xxx-large"}}></i>
</div>
</div>
<div className="modal-body text-break remixModalBody d-flex flex-row p-3 justify-content-between" data-id={`EnterModalDialogModalBody-react`}>
<button className="btn-secondary" data-id="beginnerbtn" style={{minWidth: "100px"}} onClick={() => {enterAs(UsageTypes.Beginner)}}>Beginner</button>
<button className="btn-secondary" data-id="tutorbtn" style={{minWidth: "100px"}} onClick={() => {enterAs(UsageTypes.Tutor)}}>Teacher</button>
<button className="btn-secondary" data-id="prototyperbtn" style={{minWidth: "100px"}} onClick={() => {enterAs(UsageTypes.Prototyper)}}>Prototyper</button>
<button className="btn-secondary" data-id="productionbtn" style={{minWidth: "100px"}} onClick={() => {enterAs(UsageTypes.Production)}}>Production User</button>
</div>
</div>
</div>
</div>
)
}
export default EnterDialog

@ -25,11 +25,7 @@ const MatomoDialog = (props) => {
</p> </p>
<p>We realize that our users have sensitive information in their code and that their privacy - your privacy - must be protected.</p> <p>We realize that our users have sensitive information in their code and that their privacy - your privacy - must be protected.</p>
<p> <p>
All data collected through Matomo is stored on our own server - no data is ever given to third parties. Our analytics reports are public:{' '} All data collected through Matomo is stored on our own server - no data is ever given to third parties.
<a href="https://matomo.ethereum.org/index.php?module=MultiSites&action=index&idSite=23&period=day&date=yesterday" target="_blank" rel="noreferrer">
take a look
</a>
.
</p> </p>
<p>We do not collect nor store any personally identifiable information (PII).</p> <p>We do not collect nor store any personally identifiable information (PII).</p>
<p> <p>
@ -61,16 +57,12 @@ const MatomoDialog = (props) => {
const declineModal = async () => { const declineModal = async () => {
settings.updateMatomoAnalyticsChoice(false) settings.updateMatomoAnalyticsChoice(false)
_paq.push(['optUserOut']) _paq.push(['optUserOut'])
appManager.call('walkthrough', 'start')
setVisible(false) setVisible(false)
} }
const handleModalOkClick = async () => { const handleModalOkClick = async () => {
_paq.push(['forgetUserOptOut']) _paq.push(['forgetUserOptOut'])
// @TODO remove next line when https://github.com/matomo-org/matomo/commit/9e10a150585522ca30ecdd275007a882a70c6df5 is used
document.cookie = 'mtm_consent_removed=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'
settings.updateMatomoAnalyticsChoice(true) settings.updateMatomoAnalyticsChoice(true)
appManager.call('walkthrough', 'start')
setVisible(false) setVisible(false)
} }

@ -15,7 +15,7 @@ export interface AppModal {
message: string | JSX.Element message: string | JSX.Element
okLabel: string | JSX.Element okLabel: string | JSX.Element
okFn?: (value?:any) => void okFn?: (value?:any) => void
cancelLabel: string | JSX.Element cancelLabel?: string | JSX.Element
cancelFn?: () => void, cancelFn?: () => void,
modalType?: ModalTypes, modalType?: ModalTypes,
defaultValue?: string defaultValue?: string
@ -37,3 +37,10 @@ export interface ModalState {
focusModal: AppModal, focusModal: AppModal,
focusToaster: {message: (string | JSX.Element), timestamp: number } focusToaster: {message: (string | JSX.Element), timestamp: number }
} }
export interface forceChoiceModal {
id: string
title?: string,
message: string | JSX.Element,
}

@ -2,6 +2,7 @@ import React, {useEffect, useRef, useState} from 'react'
import './style/remix-app.css' import './style/remix-app.css'
import {RemixUIMainPanel} from '@remix-ui/panel' import {RemixUIMainPanel} from '@remix-ui/panel'
import MatomoDialog from './components/modals/matomo' import MatomoDialog from './components/modals/matomo'
import EnterDialog from './components/modals/enter'
import OriginWarning from './components/modals/origin-warning' import OriginWarning from './components/modals/origin-warning'
import DragBar from './components/dragbar/dragbar' import DragBar from './components/dragbar/dragbar'
import {AppProvider} from './context/provider' import {AppProvider} from './context/provider'
@ -9,13 +10,22 @@ import AppDialogs from './components/modals/dialogs'
import DialogViewPlugin from './components/modals/dialogViewPlugin' import DialogViewPlugin from './components/modals/dialogViewPlugin'
import { AppContext, appProviderContextType } from './context/context' import { AppContext, appProviderContextType } from './context/context'
import { FormattedMessage, IntlProvider } from 'react-intl' import { FormattedMessage, IntlProvider } from 'react-intl'
import { CustomTooltip } from '@remix-ui/helper'; import {CustomTooltip} from '@remix-ui/helper'
import {UsageTypes} from './types'
declare global {
interface Window {
_paq: any
}
}
const _paq = (window._paq = window._paq || [])
interface IRemixAppUi { interface IRemixAppUi {
app: any app: any
} }
const RemixApp = (props: IRemixAppUi) => { const RemixApp = (props: IRemixAppUi) => {
const [appReady, setAppReady] = useState<boolean>(false) const [appReady, setAppReady] = useState<boolean>(false)
const [showEnterDialog, setShowEnterDialog] = useState<boolean>(true)
const [hideSidePanel, setHideSidePanel] = useState<boolean>(false) const [hideSidePanel, setHideSidePanel] = useState<boolean>(false)
const [maximiseTrigger, setMaximiseTrigger] = useState<number>(0) const [maximiseTrigger, setMaximiseTrigger] = useState<number>(0)
const [resetTrigger, setResetTrigger] = useState<number>(0) const [resetTrigger, setResetTrigger] = useState<number>(0)
@ -39,6 +49,8 @@ const RemixApp = (props: IRemixAppUi) => {
console.log('app activate', new Date().toLocaleString()) console.log('app activate', new Date().toLocaleString())
activateApp() activateApp()
} }
const hadUsageTypeAsked = localStorage.getItem('hadUsageTypeAsked')
setShowEnterDialog(!hadUsageTypeAsked)
}, []) }, [])
function setListeners() { function setListeners() {
@ -81,6 +93,7 @@ const RemixApp = (props: IRemixAppUi) => {
const value: appProviderContextType = { const value: appProviderContextType = {
settings: props.app.settings, settings: props.app.settings,
showMatamo: props.app.showMatamo, showMatamo: props.app.showMatamo,
showEnter: props.app.showEnter,
appManager: props.app.appManager, appManager: props.app.appManager,
modal: props.app.notification, modal: props.app.notification,
layout: props.app.layout, layout: props.app.layout,
@ -88,17 +101,60 @@ const RemixApp = (props: IRemixAppUi) => {
online: online online: online
} }
const handleUserChosenType = async (type) => {
setShowEnterDialog(false)
localStorage.setItem('hadUsageTypeAsked', type)
await props.app.appManager.call('walkthrough', 'start')
// Use the type to setup the UI accordingly
switch (type) {
case UsageTypes.Beginner: {
await props.app.appManager.call('manager', 'activatePlugin', 'LearnEth')
// const wName = 'Playground'
// const workspaces = await props.app.appManager.call('filePanel', 'getWorkspaces')
// if (!workspaces.find((workspace) => workspace.name === wName)) {
// await props.app.appManager.call('filePanel', 'createWorkspace', wName, 'playground')
// }
// await props.app.appManager.call('filePanel', 'switchToWorkspace', { name: wName, isLocalHost: false })
_paq.push(['trackEvent', 'enterDialog', 'usageType', 'beginner'])
break
}
case UsageTypes.Tutor: {
_paq.push(['trackEvent', 'enterDialog', 'usageType', 'tutor'])
break
}
case UsageTypes.Prototyper: {
_paq.push(['trackEvent', 'enterDialog', 'usageType', 'prototyper'])
break
}
case UsageTypes.Production: {
_paq.push(['trackEvent', 'enterDialog', 'usageType', 'production'])
break
}
default: throw new Error()
}
}
return ( return (
//@ts-ignore //@ts-ignore
<IntlProvider locale={locale.code} messages={locale.messages}> <IntlProvider locale={locale.code} messages={locale.messages}>
<AppProvider value={value}> <AppProvider value={value}>
<OriginWarning></OriginWarning> <OriginWarning></OriginWarning>
<MatomoDialog hide={!appReady}></MatomoDialog> <MatomoDialog hide={!appReady} okFn={() => {setShowEnterDialog(true)}}></MatomoDialog>
<EnterDialog hide={!showEnterDialog} handleUserChoice={(type) => handleUserChosenType(type)}></EnterDialog>
<div className={`remixIDE ${appReady ? '' : 'd-none'}`} data-id="remixIDE"> <div className={`remixIDE ${appReady ? '' : 'd-none'}`} data-id="remixIDE">
<div id="icon-panel" data-id="remixIdeIconPanel" className="custom_icon_panel iconpanel bg-light"> <div id="icon-panel" data-id="remixIdeIconPanel" className="custom_icon_panel iconpanel bg-light">
{props.app.menuicons.render()} {props.app.menuicons.render()}
</div> </div>
<div ref={sidePanelRef} id="side-panel" data-id="remixIdeSidePanel" className={`sidepanel border-right border-left ${hideSidePanel ? 'd-none' : ''}`}> <div
ref={sidePanelRef}
id="side-panel"
data-id="remixIdeSidePanel"
className={`sidepanel border-right border-left ${hideSidePanel ? 'd-none' : ''}`}
>
{props.app.sidePanel.render()} {props.app.sidePanel.render()}
</div> </div>
<DragBar <DragBar

@ -1,8 +1,16 @@
export const enum ModalTypes { export const enum ModalTypes {
alert = 'alert', alert = 'alert',
confirm = 'confirm', confirm = 'confirm',
prompt = 'prompt', prompt = 'prompt',
password = 'password', password = 'password',
default = 'default', default = 'default',
form = 'form', form = 'form',
forceChoice = 'forceChoice'
} }
export const enum UsageTypes {
Beginner = 1,
Tutor,
Prototyper,
Production,
}

@ -0,0 +1,102 @@
/* eslint-disable no-control-regex */
import { EditorUIProps, monacoTypes } from '@remix-ui/editor';
import axios, {AxiosResponse} from 'axios'
const controller = new AbortController();
const { signal } = controller;
const result: string = ''
export class RemixInLineCompletionProvider implements monacoTypes.languages.InlineCompletionsProvider {
props: EditorUIProps
monaco: any
constructor(props: any, monaco: any) {
this.props = props
this.monaco = monaco
}
async provideInlineCompletions(model: monacoTypes.editor.ITextModel, position: monacoTypes.Position, context: monacoTypes.languages.InlineCompletionContext, token: monacoTypes.CancellationToken): Promise<monacoTypes.languages.InlineCompletions<monacoTypes.languages.InlineCompletion>> {
if (context.selectedSuggestionInfo) {
return;
}
// get text before the position of the completion
const word = model.getValueInRange({
startLineNumber: 1,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column,
});
if (!word.endsWith(' ') && !word.endsWith('\n') && !word.endsWith(';') && !word.endsWith('.')) {
return;
}
try {
const isActivate = await this.props.plugin.call('copilot-suggestion', 'isActivate')
if (!isActivate) return
} catch (err) {
return;
}
try {
const split = word.split('\n')
if (split.length < 2) return
const ask = split[split.length - 2].trimStart()
if (split[split.length - 1].trim() === '' && ask.startsWith('///')) {
// use the code generation model
const {data} = await axios.post('https://gpt-chat.remixproject.org/infer', {comment: ask.replace('///', '')})
const parsedData = JSON.parse(data).trimStart()
const item: monacoTypes.languages.InlineCompletion = {
insertText: parsedData
};
return {
items: [item],
enableForwardStability: true
}
}
} catch (e) {
console.error(e)
}
// abort if there is a signal
if (token.isCancellationRequested) {
return
}
let result
try {
result = await this.props.plugin.call('copilot-suggestion', 'suggest', word)
} catch (err) {
return
}
const generatedText = (result as any).output[0].generated_text as string
// the generated text remove a space from the context...
const clean = generatedText.replace('@custom:dev-run-script', '@custom:dev-run-script ').replace(word, '')
const item: monacoTypes.languages.InlineCompletion = {
insertText: clean
};
// abort if there is a signal
if (token.isCancellationRequested) {
return
}
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.');
}
}

@ -19,6 +19,8 @@ import { RemixCodeActionProvider } from './providers/codeActionProvider'
import './remix-ui-editor.css' 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'
const _paq = (window._paq = window._paq || [])
enum MarkerSeverity { enum MarkerSeverity {
Hint = 1, Hint = 1,
@ -708,6 +710,7 @@ export const EditorUI = (props: EditorUIProps) => {
Generate the documentation for the function ${currentFunction.current} using the Doxygen style syntax Generate the documentation for the function ${currentFunction.current} using the Doxygen style syntax
` `
await props.plugin.call('openaigpt', 'message', message) await props.plugin.call('openaigpt', 'message', message)
_paq.push(['trackEvent', 'ai', 'openai', 'generateDocumentation'])
}, },
} }
@ -726,6 +729,7 @@ export const EditorUI = (props: EditorUIProps) => {
Explain the function ${currentFunction.current} Explain the function ${currentFunction.current}
` `
await props.plugin.call('openaigpt', 'message', message) await props.plugin.call('openaigpt', 'message', message)
_paq.push(['trackEvent', 'ai', 'openai', 'explainFunction'])
}, },
} }
@ -733,7 +737,7 @@ export const EditorUI = (props: EditorUIProps) => {
let freeFunctionAction let freeFunctionAction
const executeFreeFunctionAction = { const executeFreeFunctionAction = {
id: 'executeFreeFunction', id: 'executeFreeFunction',
label: 'Run a free function in the Remix VM', label: 'Run a free function',
contextMenuOrder: 0, // choose the order contextMenuOrder: 0, // choose the order
contextMenuGroupId: 'execute', // create a new grouping contextMenuGroupId: 'execute', // create a new grouping
precondition: 'freeFunctionCondition', precondition: 'freeFunctionCondition',
@ -791,7 +795,7 @@ export const EditorUI = (props: EditorUIProps) => {
const { nodesAtPosition } = await retrieveNodesAtPosition(props.editorAPI, props.plugin) const { nodesAtPosition } = await retrieveNodesAtPosition(props.editorAPI, props.plugin)
const freeFunctionNode = nodesAtPosition.find((node) => node.kind === 'freeFunction') const freeFunctionNode = nodesAtPosition.find((node) => node.kind === 'freeFunction')
if (freeFunctionNode) { if (freeFunctionNode) {
executeFreeFunctionAction.label = `Run the free function "${freeFunctionNode.name}" in the Remix VM` executeFreeFunctionAction.label = `Run the free function "${freeFunctionNode.name}"`
freeFunctionAction = editor.addAction(executeFreeFunctionAction) freeFunctionAction = editor.addAction(executeFreeFunctionAction)
} }
const functionImpl = nodesAtPosition.find((node) => node.kind === 'function') const functionImpl = nodesAtPosition.find((node) => node.kind === 'function')
@ -867,6 +871,7 @@ export const EditorUI = (props: EditorUIProps) => {
monacoRef.current.languages.registerReferenceProvider('remix-solidity', new RemixReferenceProvider(props, monaco)) monacoRef.current.languages.registerReferenceProvider('remix-solidity', new RemixReferenceProvider(props, monaco))
monacoRef.current.languages.registerHoverProvider('remix-solidity', new RemixHoverProvider(props, monaco)) monacoRef.current.languages.registerHoverProvider('remix-solidity', new RemixHoverProvider(props, monaco))
monacoRef.current.languages.registerCompletionItemProvider('remix-solidity', new RemixCompletionProvider(props, monaco)) monacoRef.current.languages.registerCompletionItemProvider('remix-solidity', new RemixCompletionProvider(props, monaco))
monacoRef.current.languages.registerInlineCompletionsProvider('remix-solidity', new RemixInLineCompletionProvider(props, monaco))
monaco.languages.registerCodeActionProvider('remix-solidity', new RemixCodeActionProvider(props, monaco)) monaco.languages.registerCodeActionProvider('remix-solidity', new RemixCodeActionProvider(props, monaco))
loadTypes(monacoRef.current) loadTypes(monacoRef.current)
@ -883,6 +888,9 @@ export const EditorUI = (props: EditorUIProps) => {
options={{ options={{
glyphMargin: true, glyphMargin: true,
readOnly: (!editorRef.current || !props.currentFile) && editorModelsState[props.currentFile]?.readOnly, readOnly: (!editorRef.current || !props.currentFile) && editorModelsState[props.currentFile]?.readOnly,
inlineSuggest: {
enabled: true,
}
}} }}
defaultValue={defaultEditorValue} defaultValue={defaultEditorValue}
/> />

@ -215,6 +215,7 @@ export const loadTypes = async (monaco) => {
declare global { declare global {
const remix: PluginClient; const remix: PluginClient;
const web3Provider; const web3Provider;
const require;
} }
` `
monaco.languages.typescript.typescriptDefaults.addExtraLib(indexRemixApi) monaco.languages.typescript.typescriptDefaults.addExtraLib(indexRemixApi)
@ -230,4 +231,4 @@ export const loadTypes = async (monaco) => {
const loadedElement = document.createElement('span') const loadedElement = document.createElement('span')
loadedElement.setAttribute('data-id', 'typesloaded') loadedElement.setAttribute('data-id', 'typesloaded')
document.body.appendChild(loadedElement) document.body.appendChild(loadedElement)
} }

@ -40,6 +40,32 @@ function HomeTabFeatured() {
autoPlaySpeed={15000} autoPlaySpeed={15000}
dotListClass="position-relative mt-2" dotListClass="position-relative mt-2"
> >
<div className="mx-1 px-1 d-flex">
<a href="https://medium.com/remix-ide/remix-release-v0-38-0-dccd551b6f1e" target="__blank">
<img src={'assets/img/remi_drums_whatsnew.webp'} style={{flex: '1', height: '170px', maxWidth: '170px'}} alt=""></img>
</a>
<div className="h6 w-50 p-2 pl-4 align-self-center" style={{flex: '1'}}>
<h5>
<FormattedMessage id="homeReleaseDetails.title" />
</h5>
<div style={{fontSize: '0.8rem'}} className="mb-3">
<ul>
<li style={{padding: '0.15rem'}}><FormattedMessage id="homeReleaseDetails.highlight1" /></li>
<li style={{padding: '0.15rem'}}><FormattedMessage id="homeReleaseDetails.highlight2" /></li>
<li style={{padding: '0.15rem'}}><FormattedMessage id="homeReleaseDetails.highlight3" /></li>
<li style={{padding: '0.15rem'}}><FormattedMessage id="homeReleaseDetails.highlight4" /></li>
</ul>
</div>
<a
className="remixui_home_text btn-sm btn-secondary mt-2 text-decoration-none mb-3"
onClick={() => _paq.push(['trackEvent', 'hometab', 'featuredSection', 'seeFullChangelog'])}
target="__blank"
href="https://medium.com/remix-ide/remix-release-v0-38-0-dccd551b6f1e"
>
<FormattedMessage id="homeReleaseDetails.more" />
</a>
</div>
</div>
<div className="mx-1 px-1 d-flex"> <div className="mx-1 px-1 d-flex">
<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'} style={{flex: '1', height: '170px', maxWidth: '170px'}} alt=""></img>
@ -48,14 +74,14 @@ function HomeTabFeatured() {
<h5> <h5>
<FormattedMessage id="home.jumpIntoWeb3" /> <FormattedMessage id="home.jumpIntoWeb3" />
</h5> </h5>
<div style={{fontSize: '0.8rem'}} className="mb-3"> <div style={{fontSize: '0.8rem', lineHeight: '1.25rem'}} className="mb-3">
<FormattedMessage id="home.jumpIntoWeb3Text" /> <FormattedMessage id="home.jumpIntoWeb3Text" />
</div> </div>
<a <a
className="remixui_home_text btn-sm btn-secondary mt-2 text-decoration-none mb-3" className="remixui_home_text btn-sm btn-secondary mt-2 text-decoration-none mb-3"
onClick={() => _paq.push(['trackEvent', 'hometab', 'featuredSection', 'jumpIntoWeb3'])} onClick={() => _paq.push(['trackEvent', 'hometab', 'featuredSection', 'jumpIntoWeb3'])}
target="__blank" target="__blank"
href="https://us8.list-manage.com/survey?u=5a84beb6d688fe180c0da482a&id=1148d10f8c" href="https://remix-project.org/"
> >
<FormattedMessage id="home.jumpIntoWeb3More" /> <FormattedMessage id="home.jumpIntoWeb3More" />
</a> </a>

@ -74,8 +74,8 @@ function HomeTabFeaturedPlugins({plugin}: HomeTabFeaturedPluginsProps) {
} }
const startCookbook = async () => { const startCookbook = async () => {
await plugin.appManager.activatePlugin(['cookbookdev']) await plugin.appManager.activatePlugin(['cookbookdev'])
plugin.verticalIcons.select('cookbook.dev') plugin.verticalIcons.select('cookbookdev')
_paq.push(['trackEvent', 'hometabActivate', 'userActivate', 'cookbook.dev']) _paq.push(['trackEvent', 'hometabActivate', 'userActivate', 'cookbookdev'])
} }
const startSolidityUnitTesting = async () => { const startSolidityUnitTesting = async () => {
await plugin.appManager.activatePlugin(['solidity', 'solidityUnitTesting']) await plugin.appManager.activatePlugin(['solidity', 'solidityUnitTesting'])

@ -201,6 +201,7 @@ contract HelloWorld {
const handleSwichToRecentWorkspace = async (e, workspaceName) => { const handleSwichToRecentWorkspace = async (e, workspaceName) => {
e.preventDefault() e.preventDefault()
_paq.push(['trackEvent', 'hometab', 'filesSection', 'loadRecentWorkspace'])
await plugin.call('filePanel', 'switchToWorkspace', { name: workspaceName, isLocalhost: false }) await plugin.call('filePanel', 'switchToWorkspace', { name: workspaceName, isLocalhost: false })
} }

@ -79,10 +79,7 @@ function HomeTabGetStarted({plugin}: HomeTabGetStartedProps) {
return ( return (
<div className="pl-2" id="hTGetStartedSection"> <div className="pl-2" id="hTGetStartedSection">
<label style={{fontSize: '1.2rem'}}> <label style={{fontSize: '1.2rem'}}>
<span className="mr-2"> <FormattedMessage id="home.projectTemplates" />
<FormattedMessage id="home.getStarted" />
</span>
- <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">
<ThemeContext.Provider value={themeFilter}> <ThemeContext.Provider value={themeFilter}>
@ -114,48 +111,54 @@ function HomeTabGetStarted({plugin}: HomeTabGetStartedProps) {
> >
<WorkspaceTemplate <WorkspaceTemplate
gsID="sUTLogo" gsID="sUTLogo"
workspaceTitle="Gnosis Safe MultiSig" workspaceTitle="MultiSig"
description={ description={
intl.formatMessage({ id: 'home.gnosisSafeMultisigTemplateDesc' }) intl.formatMessage({ id: 'home.gnosisSafeMultisigTemplateDesc' })
} }
projectLogo="assets/img/gnosissafeLogo.png"
callback={() => createWorkspace("gnosisSafeMultisig")} callback={() => createWorkspace("gnosisSafeMultisig")}
/> />
<WorkspaceTemplate <WorkspaceTemplate
gsID="sUTLogo" gsID="sUTLogo"
workspaceTitle="0xProject ERC20" workspaceTitle="ERC20"
description={ description={
intl.formatMessage({ id: 'home.zeroxErc20TemplateDesc' }) intl.formatMessage({ id: 'home.zeroxErc20TemplateDesc' })
} }
projectLogo="assets/img/oxprojectLogo.png"
callback={() => createWorkspace("zeroxErc20")} callback={() => createWorkspace("zeroxErc20")}
/> />
<WorkspaceTemplate <WorkspaceTemplate
gsID="sourcifyLogo" gsID="sourcifyLogo"
workspaceTitle="OpenZeppelin ERC20" workspaceTitle="ERC20"
description={intl.formatMessage({id: 'home.ozerc20TemplateDesc'})} description={intl.formatMessage({id: 'home.ozerc20TemplateDesc'})}
projectLogo="assets/img/openzeppelinLogo.png"
callback={() => createWorkspace('ozerc20')} callback={() => createWorkspace('ozerc20')}
/> />
<WorkspaceTemplate <WorkspaceTemplate
gsID="sUTLogo" gsID="sUTLogo"
workspaceTitle="OpenZeppelin ERC721" workspaceTitle="ERC721"
description={intl.formatMessage({ description={intl.formatMessage({
id: 'home.ozerc721TemplateDesc' id: 'home.ozerc721TemplateDesc'
})} })}
projectLogo="assets/img/openzeppelinLogo.png"
callback={() => createWorkspace("ozerc721")} callback={() => createWorkspace("ozerc721")}
/> />
<WorkspaceTemplate <WorkspaceTemplate
gsID="sUTLogo" gsID="sUTLogo"
workspaceTitle="OpenZeppelin ERC1155" workspaceTitle="ERC1155"
description={intl.formatMessage({ description={intl.formatMessage({
id: 'home.ozerc1155TemplateDesc' id: 'home.ozerc1155TemplateDesc'
})} })}
projectLogo="assets/img/openzeppelinLogo.png"
callback={() => createWorkspace("ozerc1155")} callback={() => createWorkspace("ozerc1155")}
/> />
<WorkspaceTemplate <WorkspaceTemplate
gsID="solhintLogo" gsID="solhintLogo"
workspaceTitle="Remix Basic" workspaceTitle="Basic"
description={intl.formatMessage({ description={intl.formatMessage({
id: 'home.remixDefaultTemplateDesc' id: 'home.remixDefaultTemplateDesc'
})} })}
projectLogo="assets/img/remixverticaltextLogo.png"
callback={() => createWorkspace("remixDefault")} callback={() => createWorkspace("remixDefault")}
/> />
</Carousel> </Carousel>

@ -28,7 +28,7 @@ export function LanguageOptions({ plugin }: { plugin: any }) {
<> <>
<div style={{position: 'absolute', right: "1rem", paddingTop: "0.4rem"}}> <div style={{position: 'absolute', right: "1rem", paddingTop: "0.4rem"}}>
<Dropdown> <Dropdown>
<Dropdown.Toggle title={langOptions} id="languagedropdown" size="sm" style={{backgroundColor: 'var(--secondary)'}}> <Dropdown.Toggle title={langOptions} id="languagedropdown" size="sm" style={{backgroundColor: 'var(--secondary)', color: 'var(--text)'}}>
{langOptions} {langOptions}
</Dropdown.Toggle> </Dropdown.Toggle>
<Dropdown.Menu className="dropdown-menu langSelector" style={{ paddingTop: "0px", paddingBottom: "0px", minWidth: 'fit-content', backgroundColor: 'var(--body-bg)'}}> <Dropdown.Menu className="dropdown-menu langSelector" style={{ paddingTop: "0px", paddingBottom: "0px", minWidth: 'fit-content', backgroundColor: 'var(--body-bg)'}}>

@ -1,23 +1,33 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
import React, {useContext} from 'react' import React, {useContext} from 'react'
import {CustomTooltip} from '@remix-ui/helper'
import {ThemeContext} from '../themeContext'
interface WorkspaceTemplateProps { interface WorkspaceTemplateProps {
gsID: string gsID: string
workspaceTitle: string workspaceTitle: string
projectLogo: string
callback: any callback: any
description: string description: string
} }
function WorkspaceTemplate({gsID, workspaceTitle, description, callback}: WorkspaceTemplateProps) { function WorkspaceTemplate({gsID, workspaceTitle, description, projectLogo, callback}: WorkspaceTemplateProps) {
const themeFilter = useContext(ThemeContext)
return ( return (
<div className="d-flex remixui_home_workspaceTemplate"> <div className="d-flex remixui_home_workspaceTemplate">
<button <button
className="btn border-secondary p-1 d-flex flex-column text-nowrap justify-content-center align-items-center mr-2 remixui_home_workspaceTemplate" className="btn border-secondary p-1 d-flex flex-column text-nowrap justify-content-center mr-2 remixui_home_workspaceTemplate"
data-id={'landingPageStart' + gsID} data-id={'landingPageStart' + gsID}
onClick={() => callback()} onClick={() => callback()}
> >
<div className="mb-2 w-100 p-2 h-100 align-items-start d-flex flex-column"> <div className="w-100 p-1 h-100 align-items-center d-flex flex-column">
<label className="h6 pb-1 text-uppercase text-dark remixui_home_cursorStyle">{workspaceTitle}</label> <CustomTooltip placement={'top'} tooltipClasses="text-wrap" tooltipId="etherscan-receipt-proxy-status" tooltipText={description}>
<div className="remixui_home_gtDescription">{description}</div> <div className='d-flex flex-column align-items-center'>
<label className="h5 pb-1 mt-1 text-uppercase remixui_home_cursorStyle" style={{color: themeFilter.name == "dark" ? "var(--white)" : "var(--black)"}}>{workspaceTitle}</label>
<img className="" src={projectLogo} alt="" style={{height: "20px", width: "fit-content", filter: themeFilter.name == "dark" ? "invert(1)" : "invert(0)"}} />
</div>
</CustomTooltip>
</div> </div>
</button> </button>
</div> </div>

@ -76,8 +76,8 @@
text-align: left; text-align: left;
} }
.remixui_home_cursorStyle { .remixui_home_cursorStyle {
cursor: pointer; cursor: pointer;
font-size: 0.8rem; font-weight: 900;
} }
.remixui_home_envButton { .remixui_home_envButton {
width: 220px; width: 220px;

@ -78,7 +78,7 @@ export const Renderer = ({message, opt = {}, plugin}: RendererProps) => {
explain why the error occurred and how to fix it. explain why the error occurred and how to fix it.
` `
await plugin.call('openaigpt', 'message', message) await plugin.call('openaigpt', 'message', message)
_paq.push(['trackEvent', 'GPTSupport', 'askGPT']) _paq.push(['trackEvent', 'ai', 'openai', 'explainSolidityError'])
} catch (err) { } catch (err) {
console.error('unable to askGtp') console.error('unable to askGtp')
console.error(err) console.error(err)

@ -1,11 +1,16 @@
import React, {useState, useReducer, useEffect, useCallback} from 'react' // eslint-disable-line import {ViewPlugin} from '@remixproject/engine-web'
import React, {useState, useRef, useReducer, useEffect, useCallback} from 'react' // eslint-disable-line
import {AppModal, AlertModal, ModalTypes} from '@remix-ui/app'
import {labels, textDark, textSecondary} from './constants' import {labels, textDark, textSecondary} from './constants'
import './remix-ui-settings.css' import './remix-ui-settings.css'
import { import {
generateContractMetadat, generateContractMetadat,
personal, personal,
copilotActivate,
copilotMaxNewToken,
copilotTemperature,
textWrapEventAction, textWrapEventAction,
useMatomoAnalytics, useMatomoAnalytics,
saveTokenToast, saveTokenToast,
@ -23,10 +28,10 @@ import {RemixUiLocaleModule, LocaleModule} from '@remix-ui/locale-module'
import {FormattedMessage, useIntl} from 'react-intl' import {FormattedMessage, useIntl} from 'react-intl'
import {GithubSettings} from './github-settings' import {GithubSettings} from './github-settings'
import {EtherscanSettings} from './etherscan-settings' import {EtherscanSettings} from './etherscan-settings'
import {CustomTooltip} from '@remix-ui/helper'
/* eslint-disable-next-line */ /* eslint-disable-next-line */
export interface RemixUiSettingsProps { export interface RemixUiSettingsProps {
plugin: ViewPlugin
config: any config: any
editor: any editor: any
_deps: any _deps: any
@ -48,6 +53,8 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const [ipfsProtocol, setipfsProtocol] = useState('') const [ipfsProtocol, setipfsProtocol] = useState('')
const [ipfsProjectId, setipfsProjectId] = useState('') const [ipfsProjectId, setipfsProjectId] = useState('')
const [ipfsProjectSecret, setipfsProjectSecret] = useState('') const [ipfsProjectSecret, setipfsProjectSecret] = useState('')
const copilotDownload = useRef(null)
const intl = useIntl() const intl = useIntl()
const initValue = () => { const initValue = () => {
const metadataConfig = props.config.get('settings/generate-contract-metadata') const metadataConfig = props.config.get('settings/generate-contract-metadata')
@ -122,6 +129,57 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
textWrapEventAction(props.config, props.editor, event.target.checked, dispatch) textWrapEventAction(props.config, props.editor, event.target.checked, dispatch)
} }
const onchangeCopilotActivate = async (event) => {
if (!event.target.checked) {
copilotActivate(props.config, event.target.checked, dispatch)
props.plugin.call('copilot-suggestion', 'uninstall')
return
}
const message = <div>Please wait while the copilot is downloaded. <span ref={copilotDownload}>0</span>/100 .</div>
props.plugin.on('copilot-suggestion', 'loading', (data) => {
if (!copilotDownload.current) return
const loaded = ((data.loaded / data.total) * 100).toString()
const dot = loaded.match(/(.*)\./g)
copilotDownload.current.innerText = dot ? dot[0].replace('.', '') : loaded
})
const modalActivate: AppModal = {
id: 'loadcopilotActivate',
title: 'Download Solidity copilot',
modalType: ModalTypes.default,
okLabel: 'Close',
message,
okFn: async() => {
props.plugin.off('copilot-suggestion', 'loading')
if (await props.plugin.call('copilot-suggestion', 'status')) {
copilotActivate(props.config, true, dispatch)
} else {
props.plugin.call('copilot-suggestion', 'uninstall')
copilotActivate(props.config, false, dispatch)
}
},
hideFn: async () => {
props.plugin.off('copilot-suggestion', 'loading')
if (await props.plugin.call('copilot-suggestion', 'status')) {
copilotActivate(props.config, true, dispatch)
} else {
props.plugin.call('copilot-suggestion', 'uninstall')
copilotActivate(props.config, false, dispatch)
}
}
}
props.plugin.call('copilot-suggestion', 'init')
props.plugin.call('notification', 'modal', modalActivate)
}
const onchangeCopilotMaxNewToken = (event) => {
copilotMaxNewToken(props.config, parseInt(event.target.value), dispatch)
}
const onchangeCopilotTemperature = (event) => {
copilotTemperature(props.config, parseInt(event.target.value) / 100, dispatch)
}
const onchangePersonal = (event) => { const onchangePersonal = (event) => {
personal(props.config, event.target.checked, dispatch) personal(props.config, event.target.checked, dispatch)
} }
@ -377,6 +435,63 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
saveIpfsSettingsToast(props.config, dispatchToast, ipfsUrl, ipfsProtocol, ipfsPort, ipfsProjectId, ipfsProjectSecret) saveIpfsSettingsToast(props.config, dispatchToast, ipfsUrl, ipfsProtocol, ipfsPort, ipfsProjectId, ipfsProjectSecret)
} }
const isCopilotActivated = props.config.get('settings/copilot/suggest/activate') || false
const copilotMaxnewToken = props.config.get('settings/copilot/suggest/max_new_tokens')
if (!copilotMaxnewToken) props.config.set('settings/copilot/suggest/max_new_tokens', 5)
const copilotTemperatureValue = (props.config.get('settings/copilot/suggest/temperature')) * 100
if (!copilotTemperatureValue) props.config.set('settings/copilot/suggest/temperature', 0.5)
if (isCopilotActivated) props.plugin.call('copilot-suggestion', 'init')
const copilotSettings = () => (
<div className="border-top">
<div className="card-body pt-3 pb-2">
<h6 className="card-title">
<FormattedMessage id="settings.copilot" />
</h6>
<div className="pt-2 mb-0">
<div className="text-secondary mb-0 h6">
<div>
<div className="custom-control custom-checkbox mb-1">
<input onChange={onchangeCopilotActivate} id="copilot-activate" type="checkbox" className="custom-control-input" checked={isCopilotActivated} />
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/copilot/suggest/activate')}`} htmlFor="copilot-activate">
<FormattedMessage id="settings.copilot.activate" />
</label>
</div>
</div>
</div>
</div>
<div className="pt-2 mb-0">
<div className="text-secondary mb-0 h6">
<div>
<div className="mb-1">
<label className={`form-check-label align-middle ${getTextClass('settings/copilot/suggest/max_new_tokens')}`} htmlFor="copilot-activate">
<FormattedMessage id="settings.copilot.max_new_tokens" /> - <span>{copilotMaxnewToken}</span>
</label>
<input onChange={onchangeCopilotMaxNewToken} id="copilot-max-new-token" value={copilotMaxnewToken} min='1' max='150' type="range" className="custom-range" />
</div>
</div>
</div>
</div>
<div className="pt-2 mb-0">
<div className="text-secondary mb-0 h6">
<div>
<div className="mb-1">
<label className={`form-check-label align-middle ${getTextClass('settings/copilot/suggest/temperature')}`} htmlFor="copilot-activate">
<FormattedMessage id="settings.copilot.temperature" /> - <span>{copilotTemperatureValue / 100}</span>
</label>
<input onChange={onchangeCopilotTemperature} id="copilot-temperature" value={copilotTemperatureValue} min='0' max='100' type="range" className="custom-range" />
</div>
</div>
</div>
</div>
</div>
</div>
)
const ipfsSettings = () => ( const ipfsSettings = () => (
<div className="border-top"> <div className="border-top">
<div className="card-body pt-3 pb-2"> <div className="card-body pt-3 pb-2">
@ -455,6 +570,7 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
<div> <div>
{state.message ? <Toaster message={state.message} /> : null} {state.message ? <Toaster message={state.message} /> : null}
{generalConfig()} {generalConfig()}
{copilotSettings()}
<GithubSettings <GithubSettings
saveToken={(githubToken: string, githubUserName: string, githubEmail: string) => { saveToken={(githubToken: string, githubUserName: string, githubEmail: string) => {
saveTokenToast(props.config, dispatchToast, githubToken, 'gist-access-token') saveTokenToast(props.config, dispatchToast, githubToken, 'gist-access-token')
@ -480,7 +596,7 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
{swarmSettings()} {swarmSettings()}
{ipfsSettings()} {ipfsSettings()}
<RemixUiThemeModule themeModule={props._deps.themeModule} /> <RemixUiThemeModule themeModule={props._deps.themeModule} />
<RemixUiLocaleModule localeModule={props._deps.localeModule} /> <RemixUiLocaleModule localeModule={props._deps.localeModule} />
</div> </div>
) )
} }

@ -24,13 +24,26 @@ export const personal = (config, checked, dispatch) => {
dispatch({ type: 'personal', payload: { isChecked: checked, textClass: checked ? textDark : textSecondary } }) dispatch({ type: 'personal', payload: { isChecked: checked, textClass: checked ? textDark : textSecondary } })
} }
export const copilotActivate = (config, checked, dispatch) => {
config.set('settings/copilot/suggest/activate', checked)
dispatch({ type: 'copilot/suggest/activate', payload: { isChecked: checked, textClass: checked ? textDark : textSecondary } })
}
export const copilotMaxNewToken = (config, checked, dispatch) => {
config.set('settings/copilot/suggest/max_new_tokens', checked)
dispatch({ type: 'copilot/suggest/max_new_tokens', payload: { isChecked: checked, textClass: checked ? textDark : textSecondary } })
}
export const copilotTemperature = (config, checked, dispatch) => {
config.set('settings/copilot/suggest/temperature', checked)
dispatch({ type: 'copilot/suggest/temperature', payload: { isChecked: checked, textClass: checked ? textDark : textSecondary } })
}
export const useMatomoAnalytics = (config, checked, dispatch) => { export const useMatomoAnalytics = (config, checked, dispatch) => {
config.set('settings/matomo-analytics', checked) config.set('settings/matomo-analytics', checked)
dispatch({ type: 'useMatomoAnalytics', payload: { isChecked: checked, textClass: checked ? textDark : textSecondary } }) dispatch({ type: 'useMatomoAnalytics', payload: { isChecked: checked, textClass: checked ? textDark : textSecondary } })
if (checked) { if (checked) {
_paq.push(['forgetUserOptOut']) _paq.push(['forgetUserOptOut'])
// @TODO remove next line when https://github.com/matomo-org/matomo/commit/9e10a150585522ca30ecdd275007a882a70c6df5 is used
document.cookie = 'mtm_consent_removed=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'
} else { } else {
_paq.push(['optUserOut']) _paq.push(['optUserOut'])
} }

@ -41,6 +41,21 @@ export const initialState = {
name: 'displayErrors', name: 'displayErrors',
isChecked: true, isChecked: true,
textClass: textSecondary textClass: textSecondary
},
{
name: 'copilot/suggest/activate',
isChecked: false,
textClass: textSecondary
},
{
name: 'copilot/suggest/max_new_tokens',
value: 5,
textClass: textSecondary
},
{
name: 'copilot/suggest/temperature',
value: 0.5,
textClass: textSecondary
} }
] ]
} }
@ -128,6 +143,36 @@ export const settingReducer = (state, action) => {
return { return {
...state ...state
} }
case 'copilot/suggest/activate':
state.elementState.map(element => {
if (element.name === 'copilot/suggest/activate') {
element.isChecked = action.payload.isChecked
element.textClass = action.payload.textClass
}
})
return {
...state
}
case 'copilot/suggest/max_new_tokens':
state.elementState.map(element => {
if (element.name === 'copilot/suggest/max_new_tokens') {
element.value = action.payload.value
element.textClass = action.payload.textClass
}
})
return {
...state
}
case 'copilot/suggest/temperature':
state.elementState.map(element => {
if (element.name === 'useShowGasInEditor') {
element.value = action.payload.value
element.textClass = action.payload.textClass
}
})
return {
...state
}
default: default:
return initialState return initialState
} }

@ -66,6 +66,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
compiledFileName: '', compiledFileName: '',
includeNightlies: false, includeNightlies: false,
language: 'Solidity', language: 'Solidity',
remappings: [],
evmVersion: '', evmVersion: '',
createFileOnce: true, createFileOnce: true,
onlyDownloaded: false onlyDownloaded: false
@ -819,15 +820,20 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
</label> </label>
<CustomTooltip <CustomTooltip
placement="top" placement="bottom"
tooltipId="promptCompilerTooltip" tooltipId="promptCompilerTooltip"
tooltipClasses="text-nowrap" tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id="solidity.addACustomCompilerWithURL" />} tooltipText={<FormattedMessage id="solidity.addACustomCompilerWithURL" />}
> >
<span className="far fa-plus border-0 p-0 ml-3" onClick={() => promptCompiler()}></span> <span className="far fa-plus border-0 p-0 ml-3" onClick={() => promptCompiler()}></span>
</CustomTooltip> </CustomTooltip>
<CustomTooltip placement="top" tooltipId="showCompilerTooltip" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="solidity.seeCompilerLicense" />}> <CustomTooltip
<span className="fa fa-file-text-o border-0 p-0 ml-2" onClick={() => showCompilerLicense()}></span> placement="bottom"
tooltipId="showCompilerTooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id="solidity.seeCompilerLicense" />}
>
<span className="far fa-file-certificate border-0 p-0 ml-2" onClick={() => showCompilerLicense()}></span>
</CustomTooltip> </CustomTooltip>
<CompilerDropdown <CompilerDropdown
allversions={state.allversions} allversions={state.allversions}

@ -106,10 +106,15 @@ export class CompileTabLogic {
compileFile (target) { compileFile (target) {
if (!target) throw new Error('No target provided for compiliation') if (!target) throw new Error('No target provided for compiliation')
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.api.readFile(target).then((content) => { this.api.readFile(target).then(async(content) => {
const sources = { [target]: { content } } const sources = { [target]: { content } }
this.event.emit('removeAnnotations') this.event.emit('removeAnnotations')
this.event.emit('startingCompilation') this.event.emit('startingCompilation')
if(await this.api.fileExists('remappings.txt')) {
this.api.readFile('remappings.txt').then(remappings => {
this.compiler.set('remappings', remappings.split('\n').filter(Boolean))
})
} else this.compiler.set('remappings', [])
if (this.configFilePath) { if (this.configFilePath) {
this.api.readFile(this.configFilePath).then( contentConfig => { this.api.readFile(this.configFilePath).then( contentConfig => {
this.compiler.set('configFileContent', contentConfig) this.compiler.set('configFileContent', contentConfig)

@ -16,7 +16,7 @@ const Context = ({opts, provider}: {opts; provider: string}) => {
const i = data.receipt ? data.transactionIndex : data.transactionIndex const i = data.receipt ? data.transactionIndex : data.transactionIndex
const value = val ? typeConversion.toInt(val) : 0 const value = val ? typeConversion.toInt(val) : 0
if (provider.startsWith('vm')) { if (provider && provider.startsWith('vm')) {
return ( return (
<div> <div>
<span> <span>

@ -28,6 +28,7 @@ import RenderKnownTransactions from './components/RenderKnownTransactions' // es
import parse from 'html-react-parser' import parse from 'html-react-parser'
import { EMPTY_BLOCK, KNOWN_TRANSACTION, RemixUiTerminalProps, UNKNOWN_TRANSACTION } from './types/terminalTypes' import { EMPTY_BLOCK, KNOWN_TRANSACTION, RemixUiTerminalProps, UNKNOWN_TRANSACTION } from './types/terminalTypes'
import { wrapScript } from './utils/wrapScript' import { wrapScript } from './utils/wrapScript'
const _paq = (window._paq = window._paq || [])
/* eslint-disable-next-line */ /* eslint-disable-next-line */
export interface ClipboardEvent<T = Element> extends SyntheticEvent<T, any> { export interface ClipboardEvent<T = Element> extends SyntheticEvent<T, any> {
@ -229,6 +230,10 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
try { try {
if (script.trim().startsWith('git')) { if (script.trim().startsWith('git')) {
// await this.call('git', 'execute', script) code might be used in the future // await this.call('git', 'execute', script) code might be used in the future
} else if (script.trim().startsWith('gpt')) {
call('terminal', 'log',{ type: 'warn', value: `> ${script}` })
await call('openaigpt', 'message', script)
_paq.push(['trackEvent', 'ai', 'openai', 'askFromTerminal'])
} else { } else {
await call('scriptRunner', 'execute', script) await call('scriptRunner', 'execute', script)
} }

@ -46,7 +46,7 @@ const TerminalWelcomeMessage = ({packageJson, storage}) => {
<ul className="ml-0 mr-4"> <ul className="ml-0 mr-4">
<li key="web3-152"> <li key="web3-152">
<a target="_blank" href="https://web3js.readthedocs.io/en/1.0/"> <a target="_blank" href="https://web3js.readthedocs.io/en/1.0/">
web3 version 1.5.2 web3.js
</a> </a>
</li> </li>
<li key="ethers-console"> <li key="ethers-console">
@ -54,7 +54,9 @@ const TerminalWelcomeMessage = ({packageJson, storage}) => {
ethers.js ethers.js
</a>{' '} </a>{' '}
</li> </li>
<li key="remix-console">remix</li> <li key="ethers-console">
gpt <i>&lt;your question here&gt;</i> {' '}
</li>
</ul> </ul>
<div> <div>
<FormattedMessage id="terminal.welcomeText10" />. <FormattedMessage id="terminal.welcomeText10" />.

@ -1,5 +1,5 @@
export const wrapScript = (script) => { export const wrapScript = (script) => {
const isKnownScript = ['remix.', 'console.', 'git'].some(prefix => script.trim().startsWith(prefix)) const isKnownScript = ['remix.', 'console.', 'git', 'gpt'].some(prefix => script.trim().startsWith(prefix))
if (isKnownScript) return script if (isKnownScript) return script
return ` return `
try { try {

@ -294,6 +294,13 @@ export const setCurrentWorkspaceIsGitRepo = (isRepo: boolean): Action<'SET_CURRE
} }
} }
export const setCurrentWorkspaceHasGitSubmodules = (hasGitSubmodules: boolean): Action<'SET_CURRENT_WORKSPACE_HAS_GIT_SUBMODULES'> => {
return {
type: 'SET_CURRENT_WORKSPACE_HAS_GIT_SUBMODULES',
payload: hasGitSubmodules
}
}
export const setGitConfig = (config: {username: string, token: string, email: string}): Action<'SET_GIT_CONFIG'> => { export const setGitConfig = (config: {username: string, token: string, email: string}): Action<'SET_GIT_CONFIG'> => {
return { return {
type: 'SET_GIT_CONFIG', type: 'SET_GIT_CONFIG',

@ -25,7 +25,8 @@ import {
setRenameWorkspace, setRenameWorkspace,
setCurrentWorkspaceIsGitRepo, setCurrentWorkspaceIsGitRepo,
setGitConfig, setGitConfig,
setElectronRecentFolders setElectronRecentFolders,
setCurrentWorkspaceHasGitSubmodules,
} from './payload' } from './payload'
import { addSlash, checkSlash, checkSpecialChars } from '@remix-ui/helper' import { addSlash, checkSlash, checkSpecialChars } from '@remix-ui/helper'
@ -79,6 +80,11 @@ export const setPlugin = (filePanelPlugin, reducerDispatch) => {
plugin.on('settings', 'configChanged', async () => { plugin.on('settings', 'configChanged', async () => {
await getGitConfig() await getGitConfig()
}) })
plugin.on('fileManager', 'fileAdded', async (filePath: string) => {
if(filePath.includes('.gitmodules')) {
await checkGit()
}
})
getGitConfig() getGitConfig()
} }
@ -178,7 +184,7 @@ export const createWorkspace = async (
const isActive = await plugin.call('manager', 'isActive', 'dgit') const isActive = await plugin.call('manager', 'isActive', 'dgit')
if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit') if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit')
} }
if (workspaceTemplateName === 'semaphore') { if (workspaceTemplateName === 'semaphore' || workspaceTemplateName === 'hashchecker' || workspaceTemplateName === 'rln') {
const isCircomActive = await plugin.call('manager', 'isActive', 'circuit-compiler') const isCircomActive = await plugin.call('manager', 'isActive', 'circuit-compiler')
if (!isCircomActive) await plugin.call('manager', 'activatePlugin', 'circuit-compiler') if (!isCircomActive) await plugin.call('manager', 'activatePlugin', 'circuit-compiler')
} }
@ -442,7 +448,6 @@ export const switchToWorkspace = async (name: string) => {
await plugin.fileProviders.workspace.setWorkspace(name) await plugin.fileProviders.workspace.setWorkspace(name)
await plugin.setWorkspace({ name, isLocalhost: false }) await plugin.setWorkspace({ name, isLocalhost: false })
const isGitRepo = await plugin.fileManager.isGitRepo() const isGitRepo = await plugin.fileManager.isGitRepo()
if (isGitRepo) { if (isGitRepo) {
const isActive = await plugin.call('manager', 'isActive', 'dgit') const isActive = await plugin.call('manager', 'isActive', 'dgit')
if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit') if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit')
@ -532,9 +537,9 @@ export const uploadFolder = async (target, targetFolder: string, cb?: (err: Erro
} }
} }
export const getWorkspaces = async (): Promise<{ name: string; isGitRepo: boolean; branches?: { remote: any; name: string }[]; currentBranch?: string }[]> | undefined => { export const getWorkspaces = async (): Promise<{ name: string; isGitRepo: boolean; hasGitSubmodules: boolean; branches?: { remote: any; name: string }[]; currentBranch?: string }[]> | undefined => {
try { try {
const workspaces: { name: string; isGitRepo: boolean; branches?: { remote: any; name: string }[]; currentBranch?: string }[] = await new Promise((resolve, reject) => { const workspaces: { name: string; isGitRepo: boolean; hasGitSubmodules: boolean; branches?: { remote: any; name: string }[]; currentBranch?: string }[] = await new Promise((resolve, reject) => {
const workspacesPath = plugin.fileProviders.workspace.workspacesPath const workspacesPath = plugin.fileProviders.workspace.workspacesPath
plugin.fileProviders.browser.resolveDirectory('/' + workspacesPath, (error, items) => { plugin.fileProviders.browser.resolveDirectory('/' + workspacesPath, (error, items) => {
@ -546,7 +551,7 @@ export const getWorkspaces = async (): Promise<{ name: string; isGitRepo: boolea
.filter((item) => items[item].isDirectory) .filter((item) => items[item].isDirectory)
.map(async (folder) => { .map(async (folder) => {
const isGitRepo: boolean = await plugin.fileProviders.browser.exists('/' + folder + '/.git') const isGitRepo: boolean = await plugin.fileProviders.browser.exists('/' + folder + '/.git')
const hasGitSubmodules: boolean = await plugin.fileProviders.browser.exists('/' + folder + '/.gitmodules')
if (isGitRepo) { if (isGitRepo) {
let branches = [] let branches = []
let currentBranch = null let currentBranch = null
@ -558,11 +563,13 @@ export const getWorkspaces = async (): Promise<{ name: string; isGitRepo: boolea
isGitRepo, isGitRepo,
branches, branches,
currentBranch, currentBranch,
hasGitSubmodules
} }
} else { } else {
return { return {
name: folder.replace(workspacesPath + '/', ''), name: folder.replace(workspacesPath + '/', ''),
isGitRepo, isGitRepo,
hasGitSubmodules
} }
} }
}) })
@ -638,7 +645,9 @@ export const cloneRepository = async (url: string) => {
export const checkGit = async () => { export const checkGit = async () => {
const isGitRepo = await plugin.fileManager.isGitRepo() const isGitRepo = await plugin.fileManager.isGitRepo()
const hasGitSubmodule = await plugin.fileManager.hasGitSubmodules()
dispatch(setCurrentWorkspaceIsGitRepo(isGitRepo)) dispatch(setCurrentWorkspaceIsGitRepo(isGitRepo))
dispatch(setCurrentWorkspaceHasGitSubmodules(hasGitSubmodule))
await refreshBranches() await refreshBranches()
const currentBranch = await plugin.call('dGitProvider', 'currentbranch') const currentBranch = await plugin.call('dGitProvider', 'currentbranch')
dispatch(setCurrentWorkspaceCurrentBranch(currentBranch)) dispatch(setCurrentWorkspaceCurrentBranch(currentBranch))
@ -803,6 +812,15 @@ export const createHelperScripts = async (script: string) => {
plugin.call('notification', 'toast', 'scripts added in the "scripts" folder') plugin.call('notification', 'toast', 'scripts added in the "scripts" folder')
} }
export const updateGitSubmodules = async () => {
dispatch(cloneRepositoryRequest())
const config = plugin.registry.get('config').api
const token = config.get('settings/gist-access-token')
const repoConfig = { token }
await plugin.call('dGitProvider', 'updateSubmodules', repoConfig)
dispatch(cloneRepositorySuccess())
}
export const checkoutRemoteBranch = async (branch: string, remote: string) => { export const checkoutRemoteBranch = async (branch: string, remote: string) => {
const localChanges = await hasLocalChanges() const localChanges = await hasLocalChanges()

@ -49,6 +49,7 @@ export const FileSystemContext = createContext<{
dispatchOpenElectronFolder: (path: string) => Promise<void> dispatchOpenElectronFolder: (path: string) => Promise<void>
dispatchGetElectronRecentFolders: () => Promise<void> dispatchGetElectronRecentFolders: () => Promise<void>
dispatchRemoveRecentFolder: (path: string) => Promise<void> dispatchRemoveRecentFolder: (path: string) => Promise<void>
dispatchUpdateGitSubmodules: () => Promise<void>
}>(null) }>(null)

@ -46,7 +46,10 @@ import {
createTsSolGithubAction, createTsSolGithubAction,
createSlitherGithubAction, createSlitherGithubAction,
createHelperScripts, createHelperScripts,
openElectronFolder, getElectronRecentFolders, removeRecentElectronFolder openElectronFolder,
getElectronRecentFolders,
removeRecentElectronFolder,
updateGitSubmodules
} from '../actions' } from '../actions'
import {Modal, WorkspaceProps, WorkspaceTemplate} from '../types' import {Modal, WorkspaceProps, WorkspaceTemplate} from '../types'
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
@ -238,6 +241,10 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
} }
const dispatchUpdateGitSubmodules = async () => {
await updateGitSubmodules()
}
useEffect(() => { useEffect(() => {
dispatchInitWorkspace() dispatchInitWorkspace()
}, []) }, [])
@ -362,7 +369,8 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
dispatchCreateHelperScripts, dispatchCreateHelperScripts,
dispatchOpenElectronFolder, dispatchOpenElectronFolder,
dispatchGetElectronRecentFolders, dispatchGetElectronRecentFolders,
dispatchRemoveRecentFolder dispatchRemoveRecentFolder,
dispatchUpdateGitSubmodules
} }
return ( return (
<FileSystemContext.Provider value={value}> <FileSystemContext.Provider value={value}>

@ -9,6 +9,7 @@ export interface BrowserState {
workspaces: { workspaces: {
name: string name: string
isGitRepo: boolean isGitRepo: boolean
hasGitSubmodules?: boolean
branches?: { branches?: {
remote: any remote: any
name: string name: string
@ -801,6 +802,22 @@ export const browserReducer = (state = browserInitialState, action: Actions) =>
} }
} }
case 'SET_CURRENT_WORKSPACE_HAS_GIT_SUBMODULES': {
const payload = action.payload
return {
...state,
browser: {
...state.browser,
workspaces: state.browser.workspaces.map((workspace) => {
if (workspace.name === state.browser.currentWorkspace)
workspace.hasGitSubmodules = payload
return workspace
})
}
}
}
case 'SET_GIT_CONFIG': { case 'SET_GIT_CONFIG': {
const payload = const payload =
action.payload action.payload

@ -28,6 +28,7 @@ export function Workspace() {
const [selectedWorkspace, setSelectedWorkspace] = useState<{ const [selectedWorkspace, setSelectedWorkspace] = useState<{
name: string name: string
isGitRepo: boolean isGitRepo: boolean
hasGitSubmodules?: boolean
branches?: {remote: any; name: string}[] branches?: {remote: any; name: string}[]
currentBranch?: string currentBranch?: string
}>(null) }>(null)
@ -155,7 +156,6 @@ export function Workspace() {
useEffect(() => { useEffect(() => {
const workspace = global.fs.browser.workspaces.find((workspace) => workspace.name === currentWorkspace) const workspace = global.fs.browser.workspaces.find((workspace) => workspace.name === currentWorkspace)
setSelectedWorkspace(workspace) setSelectedWorkspace(workspace)
}, [currentWorkspace]) }, [currentWorkspace])
@ -665,6 +665,14 @@ export function Workspace() {
setShowBranches(isOpen) setShowBranches(isOpen)
} }
const updateSubModules = async () => {
try {
await global.dispatchUpdateGitSubmodules()
} catch (e) {
console.error(e)
}
}
const handleBranchFilterChange = (e: ChangeEvent<HTMLInputElement>) => { const handleBranchFilterChange = (e: ChangeEvent<HTMLInputElement>) => {
const branchFilter = e.target.value const branchFilter = e.target.value
@ -758,6 +766,12 @@ export function Workspace() {
<option style={{fontSize: 'small'}} value="semaphore"> <option style={{fontSize: 'small'}} value="semaphore">
{intl.formatMessage({id: 'filePanel.semaphore'})} {intl.formatMessage({id: 'filePanel.semaphore'})}
</option> </option>
<option style={{fontSize: 'small'}} value="hashchecker">
{intl.formatMessage({id: 'filePanel.hashchecker'})}
</option>
<option style={{fontSize: 'small'}} value="rln">
{intl.formatMessage({id: 'filePanel.rln'})}
</option>
</optgroup> </optgroup>
</select> </select>
@ -1141,6 +1155,12 @@ export function Workspace() {
<div className={`bg-light border-top ${selectedWorkspace.isGitRepo && currentBranch ? 'd-block' : 'd-none'}`} data-id="workspaceGitPanel"> <div className={`bg-light border-top ${selectedWorkspace.isGitRepo && currentBranch ? 'd-block' : 'd-none'}`} data-id="workspaceGitPanel">
<div className="d-flex justify-space-between p-1"> <div className="d-flex justify-space-between p-1">
<div className="mr-auto text-uppercase text-dark pt-2 pl-2">GIT</div> <div className="mr-auto text-uppercase text-dark pt-2 pl-2">GIT</div>
{selectedWorkspace.hasGitSubmodules?
<div className="pt-1 mr-1">
{global.fs.browser.isRequestingCloning ? <div style={{ height: 30 }} className='btn btn-sm border text-muted small'><i className="fad fa-spinner fa-spin"></i> updating submodules</div> :
<div style={{ height: 30 }} onClick={updateSubModules} data-id='updatesubmodules' className='btn btn-sm border text-muted small'>update submodules</div>}
</div>
: null}
<div className="pt-1 mr-1" data-id="workspaceGitBranchesDropdown"> <div className="pt-1 mr-1" data-id="workspaceGitBranchesDropdown">
<Dropdown style={{height: 30, minWidth: 80}} onToggle={toggleBranches} show={showBranches} drop={'up'}> <Dropdown style={{height: 30, minWidth: 80}} onToggle={toggleBranches} show={showBranches} drop={'up'}>
<Dropdown.Toggle <Dropdown.Toggle

@ -18,7 +18,7 @@ export interface JSONStandardInput {
} }
} }
export type MenuItems = action[] export type MenuItems = action[]
export type WorkspaceTemplate = 'gist-template' | 'code-template' | 'remixDefault' | 'blank' | 'ozerc20' | 'zeroxErc20' | 'ozerc721' | 'playground' | 'semaphore' export type WorkspaceTemplate = 'gist-template' | 'code-template' | 'remixDefault' | 'blank' | 'ozerc20' | 'zeroxErc20' | 'ozerc721' | 'playground' | 'semaphore' | 'hashchecker' | 'rln'
export interface WorkspaceProps { export interface WorkspaceProps {
plugin: FilePanelType plugin: FilePanelType
} }
@ -294,6 +294,7 @@ export interface ActionPayloadTypes {
SET_CURRENT_WORKSPACE_BRANCHES: { remote: string | undefined; name: string }[], SET_CURRENT_WORKSPACE_BRANCHES: { remote: string | undefined; name: string }[],
SET_CURRENT_WORKSPACE_CURRENT_BRANCH: string, SET_CURRENT_WORKSPACE_CURRENT_BRANCH: string,
SET_CURRENT_WORKSPACE_IS_GITREPO: boolean, SET_CURRENT_WORKSPACE_IS_GITREPO: boolean,
SET_CURRENT_WORKSPACE_HAS_GIT_SUBMODULES: boolean,
SET_GIT_CONFIG: { SET_GIT_CONFIG: {
username: string; username: string;
token: string; token: string;

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

Loading…
Cancel
Save