1spacelint
bunsenstraat 1 year ago
parent c37fa3d924
commit 3c43c1d797
  1. 4
      .eslintrc.json
  2. 8
      apps/remix-ide/background.js
  3. 88
      apps/remix-ide/ci/download_e2e_assets.js
  4. 20
      apps/remix-ide/ci/lint-targets.js
  5. 124
      apps/remix-ide/ci/makeMockCompiler.js
  6. 102
      apps/remix-ide/ci/sauceDisconnect.js
  7. 30
      apps/remix-ide/ci/splice_tests.js
  8. 836
      apps/remix-ide/src/app.js
  9. 66
      apps/remix-ide/src/app/components/hidden-panel.tsx
  10. 96
      apps/remix-ide/src/app/components/main-panel.tsx
  11. 82
      apps/remix-ide/src/app/components/panel.ts
  12. 224
      apps/remix-ide/src/app/components/plugin-manager-component.js
  13. 206
      apps/remix-ide/src/app/components/preload.tsx
  14. 150
      apps/remix-ide/src/app/components/side-panel.tsx
  15. 202
      apps/remix-ide/src/app/components/vertical-icons.tsx
  16. 848
      apps/remix-ide/src/app/editor/editor.js
  17. 1136
      apps/remix-ide/src/app/files/dgitProvider.js
  18. 1500
      apps/remix-ide/src/app/files/fileManager.ts
  19. 552
      apps/remix-ide/src/app/files/fileProvider.js
  20. 104
      apps/remix-ide/src/app/files/fileSystem.ts
  21. 308
      apps/remix-ide/src/app/files/filesystems/fileSystemUtility.ts
  22. 156
      apps/remix-ide/src/app/files/filesystems/indexedDB.ts
  23. 98
      apps/remix-ide/src/app/files/filesystems/localStorage.ts
  24. 32
      apps/remix-ide/src/app/files/foundry-handle.js
  25. 32
      apps/remix-ide/src/app/files/git-handle.js
  26. 32
      apps/remix-ide/src/app/files/hardhat-handle.js
  27. 406
      apps/remix-ide/src/app/files/remixDProvider.js
  28. 34
      apps/remix-ide/src/app/files/slither-handle.js
  29. 32
      apps/remix-ide/src/app/files/truffle-handle.js
  30. 132
      apps/remix-ide/src/app/files/workspaceFileProvider.js
  31. 286
      apps/remix-ide/src/app/panels/file-panel.js
  32. 166
      apps/remix-ide/src/app/panels/layout.ts
  33. 666
      apps/remix-ide/src/app/panels/tab-proxy.js
  34. 218
      apps/remix-ide/src/app/panels/terminal.js
  35. 482
      apps/remix-ide/src/app/plugins/code-format.ts
  36. 64
      apps/remix-ide/src/app/plugins/code-format/index.ts
  37. 356
      apps/remix-ide/src/app/plugins/code-format/parser.ts
  38. 42
      apps/remix-ide/src/app/plugins/config.ts
  39. 112
      apps/remix-ide/src/app/plugins/contractFlattener.tsx
  40. 118
      apps/remix-ide/src/app/plugins/file-decorator.ts
  41. 40
      apps/remix-ide/src/app/plugins/notification.tsx
  42. 976
      apps/remix-ide/src/app/plugins/parser/code-parser.tsx
  43. 72
      apps/remix-ide/src/app/plugins/parser/services/antlr-worker.ts
  44. 410
      apps/remix-ide/src/app/plugins/parser/services/code-parser-antlr-service.ts
  45. 432
      apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts
  46. 112
      apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts
  47. 130
      apps/remix-ide/src/app/plugins/parser/services/code-parser-imports.ts
  48. 232
      apps/remix-ide/src/app/plugins/parser/types/antlr-types.ts
  49. 280
      apps/remix-ide/src/app/plugins/permission-handler-plugin.tsx
  50. 284
      apps/remix-ide/src/app/plugins/remixd-handle.tsx
  51. 144
      apps/remix-ide/src/app/plugins/solidity-script.tsx
  52. 626
      apps/remix-ide/src/app/plugins/solidity-umlgen.tsx
  53. 92
      apps/remix-ide/src/app/plugins/storage.ts
  54. 170
      apps/remix-ide/src/app/providers/abstract-provider.tsx
  55. 186
      apps/remix-ide/src/app/providers/custom-vm-fork-provider.tsx
  56. 56
      apps/remix-ide/src/app/providers/external-http-provider.tsx
  57. 40
      apps/remix-ide/src/app/providers/foundry-provider.tsx
  58. 40
      apps/remix-ide/src/app/providers/ganache-provider.tsx
  59. 44
      apps/remix-ide/src/app/providers/goerli-vm-fork-provider.tsx
  60. 38
      apps/remix-ide/src/app/providers/hardhat-provider.tsx
  61. 84
      apps/remix-ide/src/app/providers/injected-L2-provider.tsx
  62. 18
      apps/remix-ide/src/app/providers/injected-arbitrum-one-provider.tsx
  63. 18
      apps/remix-ide/src/app/providers/injected-optimism-provider.tsx
  64. 48
      apps/remix-ide/src/app/providers/injected-provider-default.tsx
  65. 30
      apps/remix-ide/src/app/providers/injected-provider-trustwallet.tsx
  66. 138
      apps/remix-ide/src/app/providers/injected-provider.tsx
  67. 44
      apps/remix-ide/src/app/providers/mainnet-vm-fork-provider.tsx
  68. 44
      apps/remix-ide/src/app/providers/sepolia-vm-fork-provider.tsx
  69. 154
      apps/remix-ide/src/app/providers/vm-provider.tsx
  70. 50
      apps/remix-ide/src/app/state/registry.ts
  71. 148
      apps/remix-ide/src/app/tabs/analysis-tab.js
  72. 142
      apps/remix-ide/src/app/tabs/compile-and-run.ts
  73. 270
      apps/remix-ide/src/app/tabs/compile-tab.js
  74. 174
      apps/remix-ide/src/app/tabs/debugger-tab.js
  75. 106
      apps/remix-ide/src/app/tabs/locale-module.js
  76. 24
      apps/remix-ide/src/app/tabs/locales/en/index.js
  77. 24
      apps/remix-ide/src/app/tabs/locales/zh/index.js
  78. 92
      apps/remix-ide/src/app/tabs/network-module.js
  79. 494
      apps/remix-ide/src/app/tabs/runTab/model/recorder.js
  80. 42
      apps/remix-ide/src/app/tabs/search.tsx
  81. 128
      apps/remix-ide/src/app/tabs/settings-tab.tsx
  82. 264
      apps/remix-ide/src/app/tabs/test-tab.js
  83. 194
      apps/remix-ide/src/app/tabs/theme-module.js
  84. 132
      apps/remix-ide/src/app/tabs/web3-provider.js
  85. 58
      apps/remix-ide/src/app/udapp/make-udapp.js
  86. 326
      apps/remix-ide/src/app/udapp/run-tab.js
  87. 52
      apps/remix-ide/src/app/ui/landing-page/landing-page.js
  88. 1252
      apps/remix-ide/src/app/ui/styles-guide/styleGuideClean.js
  89. 1506
      apps/remix-ide/src/blockchain/blockchain.tsx
  90. 322
      apps/remix-ide/src/blockchain/execution-context.js
  91. 18
      apps/remix-ide/src/blockchain/helper.ts
  92. 86
      apps/remix-ide/src/blockchain/providers/injected.ts
  93. 104
      apps/remix-ide/src/blockchain/providers/node.ts
  94. 176
      apps/remix-ide/src/blockchain/providers/vm.ts
  95. 130
      apps/remix-ide/src/blockchain/providers/worker-vm.ts
  96. 82
      apps/remix-ide/src/config.js
  97. 26
      apps/remix-ide/src/index.tsx
  98. 272
      apps/remix-ide/src/lib/helper.js
  99. 38
      apps/remix-ide/src/registry.js
  100. 408
      apps/remix-ide/src/remixAppManager.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -48,7 +48,7 @@
"no-empty": "off",
"jsx-a11y/anchor-is-valid": "off",
"@typescript-eslint/no-inferrable-types": "off",
"indent": ["error", 2]
"indent": ["error", 1]
}
},
{
@ -60,7 +60,7 @@
"plugin:@nrwl/nx/javascript"
],
"rules": {
"indent": ["error", 2]
"indent": ["error", 1]
}
}
],

@ -2,9 +2,9 @@
'use strict'
chrome.browserAction.onClicked.addListener(function (tab) {
chrome.storage.sync.set({ 'chrome-app-sync': true })
chrome.storage.sync.set({ 'chrome-app-sync': true })
chrome.tabs.create({ 'url': chrome.extension.getURL('index.html') }, function (tab) {
// tab opened
})
chrome.tabs.create({ 'url': chrome.extension.getURL('index.html') }, function (tab) {
// tab opened
})
})

@ -5,8 +5,8 @@ const { exit } = require('process');
const child = child_process.spawnSync('grep -r --include="*.json" --include="*.ts" --include="*.tsx" "+commit" apps/**/* libs/**/*', [], { encoding: 'utf8', cwd: process.cwd(), shell: true });
if (child.error) {
console.log("ERROR: ", child);
exit(1);
console.log("ERROR: ", child);
exit(1);
}
@ -15,63 +15,63 @@ let soljson =[];
const quotedVersionsRegex = /['"v]\d*\.\d*\.\d*\+commit\.[\d\w]*/g;
let quotedVersionsRegexMatch = child.stdout.match(quotedVersionsRegex)
if(quotedVersionsRegexMatch){
let soljson2 = quotedVersionsRegexMatch.map((item) => item.replace('\'', 'v').replace('"', 'v'))
console.log('non nightly soljson versions found: ', soljson2);
if(soljson2) soljson = soljson.concat(soljson2);
let soljson2 = quotedVersionsRegexMatch.map((item) => item.replace('\'', 'v').replace('"', 'v'))
console.log('non nightly soljson versions found: ', soljson2);
if(soljson2) soljson = soljson.concat(soljson2);
}
const nightlyVersionsRegex = /\d*\.\d*\.\d-nightly.*\+commit\.[\d\w]*/g
const nightlyVersionsRegexMatch = child.stdout.match(nightlyVersionsRegex)
if(nightlyVersionsRegexMatch){
let soljson3 = nightlyVersionsRegexMatch.map((item) => 'v' + item);
console.log('nightly soljson versions found: ', soljson3);
if(soljson3) soljson = soljson.concat(soljson3);
let soljson3 = nightlyVersionsRegexMatch.map((item) => 'v' + item);
console.log('nightly soljson versions found: ', soljson3);
if(soljson3) soljson = soljson.concat(soljson3);
}
if (soljson) {
// filter out duplicates
soljson = soljson.filter((item, index) => soljson.indexOf(item) === index);
// filter out duplicates
soljson = soljson.filter((item, index) => soljson.indexOf(item) === index);
// manually add some versions
soljson.push('v0.7.6+commit.7338295f');
// manually add some versions
soljson.push('v0.7.6+commit.7338295f');
console.log('soljson versions found: ', soljson, soljson.length);
console.log('soljson versions found: ', soljson, soljson.length);
for (let i = 0; i < soljson.length; i++) {
const version = soljson[i];
if (version) {
let url = ''
// if nightly
if (version.includes('nightly')) {
url = `https://binaries.soliditylang.org/bin/soljson-${version}.js`;
}else{
url = `https://binaries.soliditylang.org/wasm/soljson-${version}.js`;
}
const dir = './dist/apps/remix-ide/assets/js/soljson';
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
const path = `./dist/apps/remix-ide/assets/js/soljson/soljson-${version}.js`;
// check if the file exists
const exists = fs.existsSync(path);
if (!exists) {
console.log('URL:', url)
try {
// use curl to download the file
child_process.exec(`curl -o ${path} ${url}`, { encoding: 'utf8', cwd: process.cwd(), shell: true })
} catch (e) {
console.log('Failed to download soljson' + version + ' from ' + url)
}
}
for (let i = 0; i < soljson.length; i++) {
const version = soljson[i];
if (version) {
let url = ''
// if nightly
if (version.includes('nightly')) {
url = `https://binaries.soliditylang.org/bin/soljson-${version}.js`;
}else{
url = `https://binaries.soliditylang.org/wasm/soljson-${version}.js`;
}
const dir = './dist/apps/remix-ide/assets/js/soljson';
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
const path = `./dist/apps/remix-ide/assets/js/soljson/soljson-${version}.js`;
// check if the file exists
const exists = fs.existsSync(path);
if (!exists) {
console.log('URL:', url)
try {
// use curl to download the file
child_process.exec(`curl -o ${path} ${url}`, { encoding: 'utf8', cwd: process.cwd(), shell: true })
} catch (e) {
console.log('Failed to download soljson' + version + ' from ' + url)
}
}
}
}
}
}
}

@ -5,15 +5,15 @@ const file = fs.readFileSync('projects.json')
const projects = JSON.parse(file)
console.log(Object.keys(projects.graph.nodes))
for(let node of Object.keys(projects.graph.nodes)){
if(projects.graph.nodes[node].data.targets.lint){
console.log(projects.graph.nodes[node].data.name)
const result = spawnSync('yarn', ['lint', projects.graph.nodes[node].data.name])
if(result.status == 0){
console.log('success')
}else{
console.log(result.stdout.toString())
console.log(result.stderr.toString())
exit(1)
}
if(projects.graph.nodes[node].data.targets.lint){
console.log(projects.graph.nodes[node].data.name)
const result = spawnSync('yarn', ['lint', projects.graph.nodes[node].data.name])
if(result.status == 0){
console.log('success')
}else{
console.log(result.stdout.toString())
console.log(result.stderr.toString())
exit(1)
}
}
}

@ -7,82 +7,82 @@ var defaultVersion = 'soljson-v0.8.18+commit.87f61d96.js'
const path = require('path')
compiler.loadRemoteVersion(defaultVersion, (error, solcSnapshot) => {
console.log('solcSnapshot: ', solcSnapshot)
if (error) console.log(error)
var compilationResult = {}
console.log('solcSnapshot: ', solcSnapshot)
if (error) console.log(error)
var compilationResult = {}
const testsFolder = path.resolve(__dirname + '/../test-browser/tests/') + '/' // eslint-disable-line
gatherCompilationResults(testsFolder, compilationResult, solcSnapshot)
replaceSolCompiler(compilationResult, solcSnapshot)
gatherCompilationResults(testsFolder, compilationResult, solcSnapshot)
replaceSolCompiler(compilationResult, solcSnapshot)
})
function gatherCompilationResults (dir, compilationResult, solcSnapshot) {
var filenames = fs.readdirSync(dir, 'utf8')
filenames.map(function (item, i) {
if (item.endsWith('.js')) {
var testDef = require(dir + item)
if ('@sources' in testDef) {
var sources = testDef['@sources']()
for (var files in sources) {
compile(solcSnapshot, sources[files], true, function (result) {
compilationResult[result.key] = result
})
compile(solcSnapshot, sources[files], false, function (result) {
compilationResult[result.key] = result
})
}
}
var filenames = fs.readdirSync(dir, 'utf8')
filenames.map(function (item, i) {
if (item.endsWith('.js')) {
var testDef = require(dir + item)
if ('@sources' in testDef) {
var sources = testDef['@sources']()
for (var files in sources) {
compile(solcSnapshot, sources[files], true, function (result) {
compilationResult[result.key] = result
})
compile(solcSnapshot, sources[files], false, function (result) {
compilationResult[result.key] = result
})
}
})
return compilationResult
}
}
})
return compilationResult
}
function compile (solcSnapshot, source, optimization, runs, addCompilationResult) {
var missingInputs = []
try {
var input = compilerInput(source, {optimize: optimization, runs: runs})
var result = solcSnapshot.compileStandardWrapper(input, function (path) {
missingInputs.push(path)
})
input = input.replace(/(\t)|(\n)|(\\n)|( )/g, '')
} catch (e) {
console.log(e)
}
if (result && (result.error || (result.errors && result.errors.length > 0))) {
console.log(result.error, result.errors)
}
if (result) {
console.log(result.error, result.errors)
}
var ret = {
key: input,
source: source,
optimization: optimization,
missingInputs: missingInputs,
result: result
}
addCompilationResult(ret)
var missingInputs = []
try {
var input = compilerInput(source, {optimize: optimization, runs: runs})
var result = solcSnapshot.compileStandardWrapper(input, function (path) {
missingInputs.push(path)
})
input = input.replace(/(\t)|(\n)|(\\n)|( )/g, '')
} catch (e) {
console.log(e)
}
if (result && (result.error || (result.errors && result.errors.length > 0))) {
console.log(result.error, result.errors)
}
if (result) {
console.log(result.error, result.errors)
}
var ret = {
key: input,
source: source,
optimization: optimization,
missingInputs: missingInputs,
result: result
}
addCompilationResult(ret)
}
function replaceSolCompiler (results, solcSnapshot) {
const compilerPath = path.resolve(__dirname + '/../test-browser/mockcompiler/compiler.js') // eslint-disable-line
const soljsonPath = path.resolve(__dirname + '/../soljson.js') // eslint-disable-line
fs.readFile(compilerPath, 'utf8', function (error, data) {
if (error) {
console.log(error)
process.exit(1)
return
}
console.log(solcSnapshot.version())
data = data + '\n\nvar mockCompilerVersion = \'' + solcSnapshot.version() + '\''
data = data + '\n\nvar mockData = ' + JSON.stringify(results) + ';\n'
fs.writeFile(soljsonPath, data, 'utf8', function (error) {
if (error) {
console.log(error)
process.exit(1)
return
}
})
fs.readFile(compilerPath, 'utf8', function (error, data) {
if (error) {
console.log(error)
process.exit(1)
return
}
console.log(solcSnapshot.version())
data = data + '\n\nvar mockCompilerVersion = \'' + solcSnapshot.version() + '\''
data = data + '\n\nvar mockData = ' + JSON.stringify(results) + ';\n'
fs.writeFile(soljsonPath, data, 'utf8', function (error) {
if (error) {
console.log(error)
process.exit(1)
return
}
})
})
}

@ -7,69 +7,69 @@ var accessKey = process.argv[3]
var tunnelName = process.argv[4]
function removeTunnel () {
const requestPath = `/rest/v1/${userName}/tunnels`
console.log(requestPath)
callSauce(requestPath, 'GET', function (error, result) {
if (error) {
const requestPath = `/rest/v1/${userName}/tunnels`
console.log(requestPath)
callSauce(requestPath, 'GET', function (error, result) {
if (error) {
console.log(error)
} else {
var data = JSON.parse(result)
for (var k in data) {
retrieveTunnel(data[k], function (error, result) {
if (error) {
console.log(error)
} else {
var data = JSON.parse(result)
for (var k in data) {
retrieveTunnel(data[k], function (error, result) {
if (error) {
console.log(error)
} else if (result.identtifier === tunnelName) {
deleteTunnel(result.id, function () {
console.log('tunnel deleted ' + data[k] + ' ' + tunnelName)
})
}
})
}
}
})
} else if (result.identtifier === tunnelName) {
deleteTunnel(result.id, function () {
console.log('tunnel deleted ' + data[k] + ' ' + tunnelName)
})
}
})
}
}
})
}
function retrieveTunnel (tunnelid, callback) {
const requestPath = `/rest/v1/${userName}/tunnels/${tunnelid}`
callSauce(requestPath, 'GET', function (error, result) {
if (error) {
callback(error)
} else {
callback(null, {'identtifier': JSON.parse(result).tunnel_identifier, 'id': tunnelid})
}
})
const requestPath = `/rest/v1/${userName}/tunnels/${tunnelid}`
callSauce(requestPath, 'GET', function (error, result) {
if (error) {
callback(error)
} else {
callback(null, {'identtifier': JSON.parse(result).tunnel_identifier, 'id': tunnelid})
}
})
}
function deleteTunnel (tunnelid, callback) {
const requestPath = `/rest/v1/${userName}/tunnels/${tunnelid}`
callSauce(requestPath, 'DELETE', callback)
const requestPath = `/rest/v1/${userName}/tunnels/${tunnelid}`
callSauce(requestPath, 'DELETE', callback)
}
function callSauce (requestPath, type, callback) {
function responseCallback (res) {
res.setEncoding('utf8')
console.log('Response: ', res.statusCode, JSON.stringify(res.headers))
res.on('data', function onData (chunk) {
console.log('BODY: ' + chunk)
callback(null, chunk)
})
// eslint-disable-next-line @typescript-eslint/no-empty-function
res.on('end', function onEnd () {})
}
function responseCallback (res) {
res.setEncoding('utf8')
console.log('Response: ', res.statusCode, JSON.stringify(res.headers))
res.on('data', function onData (chunk) {
console.log('BODY: ' + chunk)
callback(null, chunk)
})
// eslint-disable-next-line @typescript-eslint/no-empty-function
res.on('end', function onEnd () {})
}
var req = https.request({
hostname: 'saucelabs.com',
path: requestPath,
method: type,
auth: userName + ':' + accessKey
}, responseCallback)
var req = https.request({
hostname: 'saucelabs.com',
path: requestPath,
method: type,
auth: userName + ':' + accessKey
}, responseCallback)
req.on('error', function onError (e) {
console.log('problem with request: ' + e.message)
callback(e.message)
})
req.write('')
req.end()
req.on('error', function onError (e) {
console.log('problem with request: ' + e.message)
callback(e.message)
})
req.write('')
req.end()
}
removeTunnel()

@ -7,21 +7,21 @@ let args = process.argv.slice(2)
const jobsize = args[0] || 10;
const job = args[1] || 0;
exec(cmd, (error, stdout, stderr) => {
if (error) {
console.error(`error: ${error.message}`);
return;
}
if (error) {
console.error(`error: ${error.message}`);
return;
}
if (stderr) {
console.error(`stderr: ${stderr}`);
return;
}
if (stderr) {
console.error(`stderr: ${stderr}`);
return;
}
let files = stdout.split('\n').filter(f => f.includes('.test')).map(f => f.replace('dist/apps/remix-ide-e2e/src/tests/', '')).map(f => f.replace('.js', ''))
let splitIndex = Math.ceil(files.length / jobsize);
const parts = []
for (let i = 0; i < jobsize; i++) {
parts.push(files.slice(i * splitIndex, (i + 1) * splitIndex))
}
console.log(parts[job].join('\n'))
let files = stdout.split('\n').filter(f => f.includes('.test')).map(f => f.replace('dist/apps/remix-ide-e2e/src/tests/', '')).map(f => f.replace('.js', ''))
let splitIndex = Math.ceil(files.length / jobsize);
const parts = []
for (let i = 0; i < jobsize; i++) {
parts.push(files.slice(i * splitIndex, (i + 1) * splitIndex))
}
console.log(parts[job].join('\n'))
});

@ -75,436 +75,436 @@ const Terminal = require('./app/panels/terminal')
const { TabProxy } = require('./app/panels/tab-proxy.js')
class AppComponent {
constructor() {
this.appManager = new RemixAppManager({})
this.queryParams = new QueryParams()
this._components = {}
// setup storage
const configStorage = new Storage('config-v0.8:')
// load app config
const config = new Config(configStorage)
Registry.getInstance().put({ api: config, name: 'config' })
// load file system
this._components.filesProviders = {}
this._components.filesProviders.browser = new FileProvider('browser')
Registry.getInstance().put({
api: this._components.filesProviders.browser,
name: 'fileproviders/browser'
})
this._components.filesProviders.localhost = new RemixDProvider(
this.appManager
)
Registry.getInstance().put({
api: this._components.filesProviders.localhost,
name: 'fileproviders/localhost'
})
this._components.filesProviders.workspace = new WorkspaceFileProvider()
Registry.getInstance().put({
api: this._components.filesProviders.workspace,
name: 'fileproviders/workspace'
})
Registry.getInstance().put({
api: this._components.filesProviders,
name: 'fileproviders'
})
constructor() {
this.appManager = new RemixAppManager({})
this.queryParams = new QueryParams()
this._components = {}
// setup storage
const configStorage = new Storage('config-v0.8:')
// load app config
const config = new Config(configStorage)
Registry.getInstance().put({ api: config, name: 'config' })
// load file system
this._components.filesProviders = {}
this._components.filesProviders.browser = new FileProvider('browser')
Registry.getInstance().put({
api: this._components.filesProviders.browser,
name: 'fileproviders/browser'
})
this._components.filesProviders.localhost = new RemixDProvider(
this.appManager
)
Registry.getInstance().put({
api: this._components.filesProviders.localhost,
name: 'fileproviders/localhost'
})
this._components.filesProviders.workspace = new WorkspaceFileProvider()
Registry.getInstance().put({
api: this._components.filesProviders.workspace,
name: 'fileproviders/workspace'
})
Registry.getInstance().put({
api: this._components.filesProviders,
name: 'fileproviders'
})
}
async run() {
// APP_MANAGER
const appManager = this.appManager
const pluginLoader = this.appManager.pluginLoader
this.panels = {}
this.workspace = pluginLoader.get()
this.engine = new RemixEngine()
this.engine.register(appManager);
const matomoDomains = {
'remix-alpha.ethereum.org': 27,
'remix-beta.ethereum.org': 25,
'remix.ethereum.org': 23,
'6fd22d6fe5549ad4c4d8fd3ca0b7816b.mod': 35 // remix desktop
}
async run() {
// APP_MANAGER
const appManager = this.appManager
const pluginLoader = this.appManager.pluginLoader
this.panels = {}
this.workspace = pluginLoader.get()
this.engine = new RemixEngine()
this.engine.register(appManager);
const matomoDomains = {
'remix-alpha.ethereum.org': 27,
'remix-beta.ethereum.org': 25,
'remix.ethereum.org': 23,
'6fd22d6fe5549ad4c4d8fd3ca0b7816b.mod': 35 // remix desktop
}
this.showMatamo =
this.showMatamo =
matomoDomains[window.location.hostname] &&
!Registry.getInstance()
.get('config')
.api.exists('settings/matomo-analytics')
this.walkthroughService = new WalkthroughService(
appManager,
this.showMatamo
)
const hosts = ['127.0.0.1:8080', '192.168.0.101:8080', 'localhost:8080']
// workaround for Electron support
if (!isElectron() && !hosts.includes(window.location.host)) {
// Oops! Accidentally trigger refresh or bookmark.
window.onbeforeunload = function () {
return 'Are you sure you want to leave?'
}
}
.get('config')
.api.exists('settings/matomo-analytics')
this.walkthroughService = new WalkthroughService(
appManager,
this.showMatamo
)
const hosts = ['127.0.0.1:8080', '192.168.0.101:8080', 'localhost:8080']
// workaround for Electron support
if (!isElectron() && !hosts.includes(window.location.host)) {
// Oops! Accidentally trigger refresh or bookmark.
window.onbeforeunload = function () {
return 'Are you sure you want to leave?'
}
}
// SERVICES
// ----------------- gist service ---------------------------------
this.gistHandler = new GistHandler()
// ----------------- theme service ---------------------------------
this.themeModule = new ThemeModule()
// ----------------- locale service ---------------------------------
this.localeModule = new LocaleModule()
Registry.getInstance().put({ api: this.themeModule, name: 'themeModule' })
Registry.getInstance().put({ api: this.localeModule, name: 'localeModule' })
// ----------------- editor service ----------------------------
const editor = new Editor() // wrapper around ace editor
Registry.getInstance().put({ api: editor, name: 'editor' })
editor.event.register('requiringToSaveCurrentfile', () =>
fileManager.saveCurrentFile()
)
// ----------------- fileManager service ----------------------------
const fileManager = new FileManager(editor, appManager)
Registry.getInstance().put({ api: fileManager, name: 'filemanager' })
// ----------------- dGit provider ---------------------------------
const dGitProvider = new DGitProvider()
// ----------------- Storage plugin ---------------------------------
const storagePlugin = new StoragePlugin()
// ------- FILE DECORATOR PLUGIN ------------------
const fileDecorator = new FileDecorator()
// ------- CODE FORMAT PLUGIN ------------------
const codeFormat = new CodeFormat()
//----- search
const search = new SearchPlugin()
//---------------- Solidity UML Generator -------------------------
const solidityumlgen = new SolidityUmlGen(appManager)
// ----------------- ContractFlattener ----------------------------
const contractFlattener = new ContractFlattener()
// ----------------- import content service ------------------------
const contentImport = new CompilerImports()
const blockchain = new Blockchain(Registry.getInstance().get('config').api)
// ----------------- compilation metadata generation service ---------
const compilerMetadataGenerator = new CompilerMetadata()
// ----------------- compilation result service (can keep track of compilation results) ----------------------------
const compilersArtefacts = new CompilerArtefacts() // store all the compilation results (key represent a compiler name)
Registry.getInstance().put({
api: compilersArtefacts,
name: 'compilersartefacts'
})
// service which fetch contract artifacts from sourve-verify, put artifacts in remix and compile it
const fetchAndCompile = new FetchAndCompile()
// ----------------- network service (resolve network id / name) -----
const networkModule = new NetworkModule(blockchain)
// ----------------- represent the current selected web3 provider ----
const web3Provider = new Web3ProviderModule(blockchain)
const vmProviderCustomFork = new CustomForkVMProvider(blockchain)
const vmProviderMainnetFork = new MainnetForkVMProvider(blockchain)
const vmProviderSepoliaFork = new SepoliaForkVMProvider(blockchain)
const vmProviderGoerliFork = new GoerliForkVMProvider(blockchain)
const vmProviderShanghai = new ShanghaiVMProvider(blockchain)
const vmProviderMerge = new MergeVMProvider(blockchain)
const vmProviderBerlin = new BerlinVMProvider(blockchain)
const vmProviderLondon = new LondonVMProvider(blockchain)
const hardhatProvider = new HardhatProvider(blockchain)
const ganacheProvider = new GanacheProvider(blockchain)
const foundryProvider = new FoundryProvider(blockchain)
const externalHttpProvider = new ExternalHttpProvider(blockchain)
const trustWalletInjectedProvider = new InjectedProviderTrustWallet()
const defaultInjectedProvider = new InjectedProviderDefault
const injected0ptimismProvider = new Injected0ptimismProvider()
const injectedArbitrumOneProvider = new InjectedArbitrumOneProvider()
// ----------------- convert offset to line/column service -----------
const offsetToLineColumnConverter = new OffsetToLineColumnConverter()
Registry.getInstance().put({
api: offsetToLineColumnConverter,
name: 'offsettolinecolumnconverter'
})
// ----------------- run script after each compilation results -----------
const compileAndRun = new CompileAndRun()
// -------------------Terminal----------------------------------------
makeUdapp(blockchain, compilersArtefacts, domEl => terminal.logHtml(domEl))
const terminal = new Terminal(
{ appManager, blockchain },
{
getPosition: event => {
const limitUp = 36
const limitDown = 20
const height = window.innerHeight
let newpos = event.pageY < limitUp ? limitUp : event.pageY
newpos = newpos < height - limitDown ? newpos : height - limitDown
return height - newpos
}
}
)
const codeParser = new CodeParser(new AstWalker())
const solidityScript = new SolidityScript()
this.notification = new NotificationPlugin()
const configPlugin = new ConfigPlugin()
this.layout = new Layout()
const permissionHandler = new PermissionHandlerPlugin()
this.engine.register([
permissionHandler,
this.layout,
this.notification,
this.gistHandler,
configPlugin,
blockchain,
contentImport,
this.themeModule,
this.localeModule,
editor,
fileManager,
compilerMetadataGenerator,
compilersArtefacts,
networkModule,
offsetToLineColumnConverter,
codeParser,
fileDecorator,
codeFormat,
terminal,
web3Provider,
compileAndRun,
fetchAndCompile,
dGitProvider,
storagePlugin,
vmProviderShanghai,
vmProviderMerge,
vmProviderBerlin,
vmProviderLondon,
vmProviderSepoliaFork,
vmProviderGoerliFork,
vmProviderMainnetFork,
vmProviderCustomFork,
hardhatProvider,
ganacheProvider,
foundryProvider,
externalHttpProvider,
defaultInjectedProvider,
trustWalletInjectedProvider,
injected0ptimismProvider,
injectedArbitrumOneProvider,
this.walkthroughService,
search,
solidityumlgen,
contractFlattener,
solidityScript
])
// LAYOUT & SYSTEM VIEWS
const appPanel = new MainPanel()
Registry.getInstance().put({ api: this.mainview, name: 'mainview' })
const tabProxy = new TabProxy(fileManager, editor)
this.engine.register([appPanel, tabProxy])
// those views depend on app_manager
this.menuicons = new VerticalIcons()
this.sidePanel = new SidePanel()
this.hiddenPanel = new HiddenPanel()
const pluginManagerComponent = new PluginManagerComponent(
appManager,
this.engine
)
const filePanel = new FilePanel(appManager)
const landingPage = new LandingPage(
appManager,
this.menuicons,
fileManager,
filePanel,
contentImport
)
this.settings = new SettingsTab(
Registry.getInstance().get('config').api,
editor,
appManager
)
this.engine.register([
this.menuicons,
landingPage,
this.hiddenPanel,
this.sidePanel,
filePanel,
pluginManagerComponent,
this.settings
])
// CONTENT VIEWS & DEFAULT PLUGINS
const openZeppelinProxy = new OpenZeppelinProxy(blockchain)
const linkLibraries = new LinkLibraries(blockchain)
const deployLibraries = new DeployLibraries(blockchain)
const compileTab = new CompileTab(
Registry.getInstance().get('config').api,
Registry.getInstance().get('filemanager').api
)
const run = new RunTab(
blockchain,
Registry.getInstance().get('config').api,
Registry.getInstance().get('filemanager').api,
Registry.getInstance().get('editor').api,
filePanel,
Registry.getInstance().get('compilersartefacts').api,
networkModule,
Registry.getInstance().get('fileproviders/browser').api
)
const analysis = new AnalysisTab()
const debug = new DebuggerTab()
const test = new TestTab(
Registry.getInstance().get('filemanager').api,
Registry.getInstance().get('offsettolinecolumnconverter').api,
filePanel,
compileTab,
appManager,
contentImport
)
this.engine.register([
compileTab,
run,
debug,
analysis,
test,
filePanel.remixdHandle,
filePanel.hardhatHandle,
filePanel.foundryHandle,
filePanel.truffleHandle,
filePanel.slitherHandle,
linkLibraries,
deployLibraries,
openZeppelinProxy,
run.recorder
])
this.layout.panels = {
tabs: { plugin: tabProxy, active: true },
editor: { plugin: editor, active: true },
main: { plugin: appPanel, active: false },
terminal: { plugin: terminal, active: true, minimized: false }
// SERVICES
// ----------------- gist service ---------------------------------
this.gistHandler = new GistHandler()
// ----------------- theme service ---------------------------------
this.themeModule = new ThemeModule()
// ----------------- locale service ---------------------------------
this.localeModule = new LocaleModule()
Registry.getInstance().put({ api: this.themeModule, name: 'themeModule' })
Registry.getInstance().put({ api: this.localeModule, name: 'localeModule' })
// ----------------- editor service ----------------------------
const editor = new Editor() // wrapper around ace editor
Registry.getInstance().put({ api: editor, name: 'editor' })
editor.event.register('requiringToSaveCurrentfile', () =>
fileManager.saveCurrentFile()
)
// ----------------- fileManager service ----------------------------
const fileManager = new FileManager(editor, appManager)
Registry.getInstance().put({ api: fileManager, name: 'filemanager' })
// ----------------- dGit provider ---------------------------------
const dGitProvider = new DGitProvider()
// ----------------- Storage plugin ---------------------------------
const storagePlugin = new StoragePlugin()
// ------- FILE DECORATOR PLUGIN ------------------
const fileDecorator = new FileDecorator()
// ------- CODE FORMAT PLUGIN ------------------
const codeFormat = new CodeFormat()
//----- search
const search = new SearchPlugin()
//---------------- Solidity UML Generator -------------------------
const solidityumlgen = new SolidityUmlGen(appManager)
// ----------------- ContractFlattener ----------------------------
const contractFlattener = new ContractFlattener()
// ----------------- import content service ------------------------
const contentImport = new CompilerImports()
const blockchain = new Blockchain(Registry.getInstance().get('config').api)
// ----------------- compilation metadata generation service ---------
const compilerMetadataGenerator = new CompilerMetadata()
// ----------------- compilation result service (can keep track of compilation results) ----------------------------
const compilersArtefacts = new CompilerArtefacts() // store all the compilation results (key represent a compiler name)
Registry.getInstance().put({
api: compilersArtefacts,
name: 'compilersartefacts'
})
// service which fetch contract artifacts from sourve-verify, put artifacts in remix and compile it
const fetchAndCompile = new FetchAndCompile()
// ----------------- network service (resolve network id / name) -----
const networkModule = new NetworkModule(blockchain)
// ----------------- represent the current selected web3 provider ----
const web3Provider = new Web3ProviderModule(blockchain)
const vmProviderCustomFork = new CustomForkVMProvider(blockchain)
const vmProviderMainnetFork = new MainnetForkVMProvider(blockchain)
const vmProviderSepoliaFork = new SepoliaForkVMProvider(blockchain)
const vmProviderGoerliFork = new GoerliForkVMProvider(blockchain)
const vmProviderShanghai = new ShanghaiVMProvider(blockchain)
const vmProviderMerge = new MergeVMProvider(blockchain)
const vmProviderBerlin = new BerlinVMProvider(blockchain)
const vmProviderLondon = new LondonVMProvider(blockchain)
const hardhatProvider = new HardhatProvider(blockchain)
const ganacheProvider = new GanacheProvider(blockchain)
const foundryProvider = new FoundryProvider(blockchain)
const externalHttpProvider = new ExternalHttpProvider(blockchain)
const trustWalletInjectedProvider = new InjectedProviderTrustWallet()
const defaultInjectedProvider = new InjectedProviderDefault
const injected0ptimismProvider = new Injected0ptimismProvider()
const injectedArbitrumOneProvider = new InjectedArbitrumOneProvider()
// ----------------- convert offset to line/column service -----------
const offsetToLineColumnConverter = new OffsetToLineColumnConverter()
Registry.getInstance().put({
api: offsetToLineColumnConverter,
name: 'offsettolinecolumnconverter'
})
// ----------------- run script after each compilation results -----------
const compileAndRun = new CompileAndRun()
// -------------------Terminal----------------------------------------
makeUdapp(blockchain, compilersArtefacts, domEl => terminal.logHtml(domEl))
const terminal = new Terminal(
{ appManager, blockchain },
{
getPosition: event => {
const limitUp = 36
const limitDown = 20
const height = window.innerHeight
let newpos = event.pageY < limitUp ? limitUp : event.pageY
newpos = newpos < height - limitDown ? newpos : height - limitDown
return height - newpos
}
}
)
const codeParser = new CodeParser(new AstWalker())
const solidityScript = new SolidityScript()
this.notification = new NotificationPlugin()
const configPlugin = new ConfigPlugin()
this.layout = new Layout()
const permissionHandler = new PermissionHandlerPlugin()
this.engine.register([
permissionHandler,
this.layout,
this.notification,
this.gistHandler,
configPlugin,
blockchain,
contentImport,
this.themeModule,
this.localeModule,
editor,
fileManager,
compilerMetadataGenerator,
compilersArtefacts,
networkModule,
offsetToLineColumnConverter,
codeParser,
fileDecorator,
codeFormat,
terminal,
web3Provider,
compileAndRun,
fetchAndCompile,
dGitProvider,
storagePlugin,
vmProviderShanghai,
vmProviderMerge,
vmProviderBerlin,
vmProviderLondon,
vmProviderSepoliaFork,
vmProviderGoerliFork,
vmProviderMainnetFork,
vmProviderCustomFork,
hardhatProvider,
ganacheProvider,
foundryProvider,
externalHttpProvider,
defaultInjectedProvider,
trustWalletInjectedProvider,
injected0ptimismProvider,
injectedArbitrumOneProvider,
this.walkthroughService,
search,
solidityumlgen,
contractFlattener,
solidityScript
])
// LAYOUT & SYSTEM VIEWS
const appPanel = new MainPanel()
Registry.getInstance().put({ api: this.mainview, name: 'mainview' })
const tabProxy = new TabProxy(fileManager, editor)
this.engine.register([appPanel, tabProxy])
// those views depend on app_manager
this.menuicons = new VerticalIcons()
this.sidePanel = new SidePanel()
this.hiddenPanel = new HiddenPanel()
const pluginManagerComponent = new PluginManagerComponent(
appManager,
this.engine
)
const filePanel = new FilePanel(appManager)
const landingPage = new LandingPage(
appManager,
this.menuicons,
fileManager,
filePanel,
contentImport
)
this.settings = new SettingsTab(
Registry.getInstance().get('config').api,
editor,
appManager
)
this.engine.register([
this.menuicons,
landingPage,
this.hiddenPanel,
this.sidePanel,
filePanel,
pluginManagerComponent,
this.settings
])
// CONTENT VIEWS & DEFAULT PLUGINS
const openZeppelinProxy = new OpenZeppelinProxy(blockchain)
const linkLibraries = new LinkLibraries(blockchain)
const deployLibraries = new DeployLibraries(blockchain)
const compileTab = new CompileTab(
Registry.getInstance().get('config').api,
Registry.getInstance().get('filemanager').api
)
const run = new RunTab(
blockchain,
Registry.getInstance().get('config').api,
Registry.getInstance().get('filemanager').api,
Registry.getInstance().get('editor').api,
filePanel,
Registry.getInstance().get('compilersartefacts').api,
networkModule,
Registry.getInstance().get('fileproviders/browser').api
)
const analysis = new AnalysisTab()
const debug = new DebuggerTab()
const test = new TestTab(
Registry.getInstance().get('filemanager').api,
Registry.getInstance().get('offsettolinecolumnconverter').api,
filePanel,
compileTab,
appManager,
contentImport
)
this.engine.register([
compileTab,
run,
debug,
analysis,
test,
filePanel.remixdHandle,
filePanel.hardhatHandle,
filePanel.foundryHandle,
filePanel.truffleHandle,
filePanel.slitherHandle,
linkLibraries,
deployLibraries,
openZeppelinProxy,
run.recorder
])
this.layout.panels = {
tabs: { plugin: tabProxy, active: true },
editor: { plugin: editor, active: true },
main: { plugin: appPanel, active: false },
terminal: { plugin: terminal, active: true, minimized: false }
}
}
async activate() {
const queryParams = new QueryParams()
const params = queryParams.get()
async activate() {
const queryParams = new QueryParams()
const params = queryParams.get()
try {
this.engine.register(await this.appManager.registeredPlugins())
} catch (e) {
console.log("couldn't register iframe plugins", e.message)
}
await this.appManager.activatePlugin(['layout'])
await this.appManager.activatePlugin(['notification'])
await this.appManager.activatePlugin(['editor'])
await this.appManager.activatePlugin(['permissionhandler', 'theme', 'locale', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter'])
await this.appManager.activatePlugin(['mainPanel', 'menuicons', 'tabs'])
await this.appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await this.appManager.activatePlugin(['home'])
await this.appManager.activatePlugin(['settings', 'config'])
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'codeParser', 'codeFormatter', 'fileDecorator', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder'])
await this.appManager.activatePlugin(['solidity-script'])
this.appManager.on(
'filePanel',
'workspaceInitializationCompleted',
async () => {
// for e2e tests
const loadedElement = document.createElement('span')
loadedElement.setAttribute('data-id', 'workspaceloaded')
document.body.appendChild(loadedElement)
await this.appManager.registerContextMenuItems()
try {
this.engine.register(await this.appManager.registeredPlugins())
} catch (e) {
console.log("couldn't register iframe plugins", e.message)
}
await this.appManager.activatePlugin(['layout'])
await this.appManager.activatePlugin(['notification'])
await this.appManager.activatePlugin(['editor'])
await this.appManager.activatePlugin(['permissionhandler', 'theme', 'locale', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter'])
await this.appManager.activatePlugin(['mainPanel', 'menuicons', 'tabs'])
await this.appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await this.appManager.activatePlugin(['home'])
await this.appManager.activatePlugin(['settings', 'config'])
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'codeParser', 'codeFormatter', 'fileDecorator', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder'])
await this.appManager.activatePlugin(['solidity-script'])
this.appManager.on(
'filePanel',
'workspaceInitializationCompleted',
async () => {
// for e2e tests
const loadedElement = document.createElement('span')
loadedElement.setAttribute('data-id', 'workspaceloaded')
document.body.appendChild(loadedElement)
await this.appManager.registerContextMenuItems()
}
)
await this.appManager.activatePlugin(['filePanel'])
// Set workspace after initial activation
this.appManager.on('editor', 'editorMounted', () => {
if (Array.isArray(this.workspace)) {
this.appManager
.activatePlugin(this.workspace)
.then(async () => {
try {
if (params.deactivate) {
await this.appManager.deactivatePlugin(
params.deactivate.split(',')
)
}
} catch (e) {
console.log(e)
}
)
await this.appManager.activatePlugin(['filePanel'])
// Set workspace after initial activation
this.appManager.on('editor', 'editorMounted', () => {
if (Array.isArray(this.workspace)) {
this.appManager
.activatePlugin(this.workspace)
.then(async () => {
try {
if (params.deactivate) {
await this.appManager.deactivatePlugin(
params.deactivate.split(',')
)
}
} catch (e) {
console.log(e)
}
if (params.code && (!params.activate || params.activate.split(',').includes('solidity'))) {
// if code is given in url we focus on solidity plugin
this.menuicons.select('solidity')
} else {
// If plugins are loaded from the URL params, we focus on the last one.
if (
this.appManager.pluginLoader.current === 'queryParams' &&
if (params.code && (!params.activate || params.activate.split(',').includes('solidity'))) {
// if code is given in url we focus on solidity plugin
this.menuicons.select('solidity')
} else {
// If plugins are loaded from the URL params, we focus on the last one.
if (
this.appManager.pluginLoader.current === 'queryParams' &&
this.workspace.length > 0
) {
this.menuicons.select(this.workspace[this.workspace.length - 1])
} else {
this.appManager.call('tabs', 'focus', 'home')
}
}
if (params.call) {
const callDetails = params.call.split('//')
if (callDetails.length > 1) {
this.appManager.call('notification', 'toast', `initiating ${callDetails[0]} and calling "${callDetails[1]}" ...`)
// @todo(remove the timeout when activatePlugin is on 0.3.0)
await this.appManager.call(...callDetails).catch(console.error)
}
}
if (params.calls) {
const calls = params.calls.split("///");
// call all functions in the list, one after the other
for (const call of calls) {
const callDetails = call.split("//");
if (callDetails.length > 1) {
this.appManager.call(
"notification",
"toast",
`initiating ${callDetails[0]} and calling "${callDetails[1]}" ...`
);
// @todo(remove the timeout when activatePlugin is on 0.3.0)
try {
await this.appManager.call(...callDetails)
} catch (e) {
console.error(e)
}
}
}
}
})
.catch(console.error)
) {
this.menuicons.select(this.workspace[this.workspace.length - 1])
} else {
this.appManager.call('tabs', 'focus', 'home')
}
}
const loadedElement = document.createElement('span')
loadedElement.setAttribute('data-id', 'apploaded')
document.body.appendChild(loadedElement)
})
// activate solidity plugin
this.appManager.activatePlugin(['solidity', 'udapp', 'deploy-libraries', 'link-libraries', 'openzeppelin-proxy'])
}
if (params.call) {
const callDetails = params.call.split('//')
if (callDetails.length > 1) {
this.appManager.call('notification', 'toast', `initiating ${callDetails[0]} and calling "${callDetails[1]}" ...`)
// @todo(remove the timeout when activatePlugin is on 0.3.0)
await this.appManager.call(...callDetails).catch(console.error)
}
}
if (params.calls) {
const calls = params.calls.split("///");
// call all functions in the list, one after the other
for (const call of calls) {
const callDetails = call.split("//");
if (callDetails.length > 1) {
this.appManager.call(
"notification",
"toast",
`initiating ${callDetails[0]} and calling "${callDetails[1]}" ...`
);
// @todo(remove the timeout when activatePlugin is on 0.3.0)
try {
await this.appManager.call(...callDetails)
} catch (e) {
console.error(e)
}
}
}
}
})
.catch(console.error)
}
const loadedElement = document.createElement('span')
loadedElement.setAttribute('data-id', 'apploaded')
document.body.appendChild(loadedElement)
})
// activate solidity plugin
this.appManager.activatePlugin(['solidity', 'udapp', 'deploy-libraries', 'link-libraries', 'openzeppelin-proxy'])
}
}
export default AppComponent

@ -6,45 +6,45 @@ import { RemixPluginPanel } from '@remix-ui/panel'
import { PluginViewWrapper } from '@remix-ui/helper'
const profile = {
name: 'hiddenPanel',
displayName: 'Hidden Panel',
description: 'Remix IDE hidden panel',
version: packageJson.version,
methods: ['addView', 'removeView']
name: 'hiddenPanel',
displayName: 'Hidden Panel',
description: 'Remix IDE hidden panel',
version: packageJson.version,
methods: ['addView', 'removeView']
}
export class HiddenPanel extends AbstractPanel {
el: HTMLElement
dispatch: React.Dispatch<any> = () => {}
constructor () {
super(profile)
this.el = document.createElement('div')
this.el.setAttribute('class', 'pluginsContainer')
}
el: HTMLElement
dispatch: React.Dispatch<any> = () => {}
constructor () {
super(profile)
this.el = document.createElement('div')
this.el.setAttribute('class', 'pluginsContainer')
}
addView (profile: any, view: any): void {
super.removeView(profile)
super.addView(profile, view)
this.renderComponent()
}
addView (profile: any, view: any): void {
super.removeView(profile)
super.addView(profile, view)
this.renderComponent()
}
updateComponent (state: any) {
return <RemixPluginPanel header={<></>} plugins={state.plugins}/>
}
updateComponent (state: any) {
return <RemixPluginPanel header={<></>} plugins={state.plugins}/>
}
setDispatch (dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
}
setDispatch (dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
}
render() {
return (
<div className='pluginsContainer'><PluginViewWrapper plugin={this} /></div>
);
}
render() {
return (
<div className='pluginsContainer'><PluginViewWrapper plugin={this} /></div>
);
}
renderComponent () {
this.dispatch({
plugins: this.plugins,
})
}
renderComponent () {
this.dispatch({
plugins: this.plugins,
})
}
}

@ -5,64 +5,64 @@ import packageJson from '../../../../../package.json'
import { PluginViewWrapper } from '@remix-ui/helper'
const profile = {
name: 'mainPanel',
displayName: 'Main Panel',
description: 'Remix IDE main panel',
version: packageJson.version,
methods: ['addView', 'removeView', 'showContent']
name: 'mainPanel',
displayName: 'Main Panel',
description: 'Remix IDE main panel',
version: packageJson.version,
methods: ['addView', 'removeView', 'showContent']
}
export class MainPanel extends AbstractPanel {
element: HTMLDivElement
dispatch: React.Dispatch<any> = () => {}
constructor (config) {
super(profile)
this.element = document.createElement('div')
this.element.setAttribute('data-id', 'mainPanelPluginsContainer')
this.element.setAttribute('style', 'height: 100%; width: 100%;')
// this.config = config
}
element: HTMLDivElement
dispatch: React.Dispatch<any> = () => {}
constructor (config) {
super(profile)
this.element = document.createElement('div')
this.element.setAttribute('data-id', 'mainPanelPluginsContainer')
this.element.setAttribute('style', 'height: 100%; width: 100%;')
// this.config = config
}
setDispatch (dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
}
setDispatch (dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
}
onActivation () {
this.renderComponent()
}
onActivation () {
this.renderComponent()
}
focus (name) {
this.emit('focusChanged', name)
super.focus(name)
this.renderComponent()
}
focus (name) {
this.emit('focusChanged', name)
super.focus(name)
this.renderComponent()
}
addView (profile, view) {
super.addView(profile, view)
this.renderComponent()
}
addView (profile, view) {
super.addView(profile, view)
this.renderComponent()
}
removeView (profile) {
super.removeView(profile)
this.renderComponent()
}
removeView (profile) {
super.removeView(profile)
this.renderComponent()
}
async showContent (name) {
super.showContent(name)
this.renderComponent()
}
async showContent (name) {
super.showContent(name)
this.renderComponent()
}
renderComponent () {
this.dispatch({
plugins: this.plugins
})
}
renderComponent () {
this.dispatch({
plugins: this.plugins
})
}
render() {
return <div style={{height: '100%', width: '100%'}} data-id='mainPanelPluginsContainer'><PluginViewWrapper plugin={this} /></div>
}
render() {
return <div style={{height: '100%', width: '100%'}} data-id='mainPanelPluginsContainer'><PluginViewWrapper plugin={this} /></div>
}
updateComponent (state: any) {
return <RemixPluginPanel header={<></>} plugins={state.plugins}/>
}
updateComponent (state: any) {
return <RemixPluginPanel header={<></>} plugins={state.plugins}/>
}
}

@ -5,58 +5,58 @@ import { PluginRecord } from '@remix-ui/panel'
import EventManager from '../../lib/events'
export class AbstractPanel extends HostPlugin {
events: EventEmitter
event: any
public plugins: Record<string, PluginRecord> = {}
constructor (profile) {
super(profile)
this.events = new EventEmitter()
this.event = new EventManager()
}
events: EventEmitter
event: any
public plugins: Record<string, PluginRecord> = {}
constructor (profile) {
super(profile)
this.events = new EventEmitter()
this.event = new EventManager()
}
currentFocus (): string {
return Object.values(this.plugins).find(plugin => {
return plugin.active
}).profile.name
}
currentFocus (): string {
return Object.values(this.plugins).find(plugin => {
return plugin.active
}).profile.name
}
addView (profile, view) {
if (this.plugins[profile.name]) throw new Error(`Plugin ${profile.name} already rendered`)
this.plugins[profile.name] = {
profile: profile,
view: view,
active: false,
class: 'plugItIn active'
}
addView (profile, view) {
if (this.plugins[profile.name]) throw new Error(`Plugin ${profile.name} already rendered`)
this.plugins[profile.name] = {
profile: profile,
view: view,
active: false,
class: 'plugItIn active'
}
}
removeView (profile) {
this.emit('pluginDisabled', profile.name)
this.call('menuicons', 'unlinkContent', profile)
this.remove(profile.name)
}
removeView (profile) {
this.emit('pluginDisabled', profile.name)
this.call('menuicons', 'unlinkContent', profile)
this.remove(profile.name)
}
/**
/**
* Remove a plugin from the panel
* @param {String} name The name of the plugin to remove
*/
remove (name) {
delete this.plugins[name]
}
remove (name) {
delete this.plugins[name]
}
/**
/**
* Display the content of this specific plugin
* @param {String} name The name of the plugin to display the content
*/
showContent (name) {
if (!this.plugins[name]) throw new Error(`Plugin ${name} is not yet activated`)
Object.values(this.plugins).forEach(plugin => {
plugin.active = false
})
this.plugins[name].active = true
}
showContent (name) {
if (!this.plugins[name]) throw new Error(`Plugin ${name} is not yet activated`)
Object.values(this.plugins).forEach(plugin => {
plugin.active = false
})
this.plugins[name].active = true
}
focus (name) {
this.showContent(name)
}
focus (name) {
this.showContent(name)
}
}

@ -6,145 +6,145 @@ import { PluginViewWrapper } from '@remix-ui/helper'
const _paq = window._paq = window._paq || []
const profile = {
name: 'pluginManager',
displayName: 'Plugin manager',
methods: [],
events: [],
icon: 'assets/img/pluginManager.webp',
description: 'Start/stop services, modules and plugins',
kind: 'settings',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/plugin_manager.html',
version: packageJson.version,
maintainedBy: "Remix"
name: 'pluginManager',
displayName: 'Plugin manager',
methods: [],
events: [],
icon: 'assets/img/pluginManager.webp',
description: 'Start/stop services, modules and plugins',
kind: 'settings',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/plugin_manager.html',
version: packageJson.version,
maintainedBy: "Remix"
}
class PluginManagerComponent extends ViewPlugin {
constructor (appManager, engine) {
super(profile)
this.appManager = appManager
this.engine = engine
this.htmlElement = document.createElement('div')
this.htmlElement.setAttribute('id', 'pluginManager')
this.filter = ''
this.activePlugins = []
this.inactivePlugins = []
this.activeProfiles = this.appManager.actives
this._paq = _paq
this.dispatch = null
this.listenOnEvent()
}
/**
constructor (appManager, engine) {
super(profile)
this.appManager = appManager
this.engine = engine
this.htmlElement = document.createElement('div')
this.htmlElement.setAttribute('id', 'pluginManager')
this.filter = ''
this.activePlugins = []
this.inactivePlugins = []
this.activeProfiles = this.appManager.actives
this._paq = _paq
this.dispatch = null
this.listenOnEvent()
}
/**
* Checks and returns true or false if plugin name
* passed in exists in the actives string array in
* RemixAppManager
* @param {string} name name of Plugin
*/
isActive = (name) =>{
return this.appManager.actives.includes(name)
}
isActive = (name) =>{
return this.appManager.actives.includes(name)
}
/**
/**
* Delegates to method activatePlugin in
* RemixAppManager to enable plugin activation
* @param {string} name name of Plugin
*/
activateP = (name) => {
this.appManager.activatePlugin(name)
_paq.push(['trackEvent', 'manager', 'activate', name])
}
activateP = (name) => {
this.appManager.activatePlugin(name)
_paq.push(['trackEvent', 'manager', 'activate', name])
}
/**
/**
* Takes the name of a local plugin and does both
* activation and registration
* @param {Profile} pluginName
* @returns {void}
*/
activateAndRegisterLocalPlugin = async (localPlugin) => {
if (localPlugin) {
this.engine.register(localPlugin)
this.appManager.activatePlugin(localPlugin.profile.name)
this.getAndFilterPlugins()
localStorage.setItem('plugins/local', JSON.stringify(localPlugin.profile))
}
activateAndRegisterLocalPlugin = async (localPlugin) => {
if (localPlugin) {
this.engine.register(localPlugin)
this.appManager.activatePlugin(localPlugin.profile.name)
this.getAndFilterPlugins()
localStorage.setItem('plugins/local', JSON.stringify(localPlugin.profile))
}
}
/**
/**
* Calls and triggers the event deactivatePlugin
* with with manager permission passing in the name
* of the plugin
* @param {string} name name of Plugin
*/
deactivateP = (name) => {
this.call('manager', 'deactivatePlugin', name)
_paq.push(['trackEvent', 'manager', 'deactivate', name])
}
setDispatch (dispatch) {
this.dispatch = dispatch
this.renderComponent()
}
updateComponent(state){
return <RemixUiPluginManager
pluginComponent={state}/>
}
renderComponent () {
if(this.dispatch) this.dispatch({...this, activePlugins: this.activePlugins, inactivePlugins: this.inactivePlugins})
}
render () {
return (
<div id='pluginManager'><PluginViewWrapper plugin={this} /></div>
);
}
getAndFilterPlugins = (filter) => {
this.filter = typeof filter === 'string' ? filter.toLowerCase() : this.filter
const isFiltered = (profile) => (profile.displayName ? profile.displayName : profile.name).toLowerCase().includes(this.filter)
const isNotRequired = (profile) => !this.appManager.isRequired(profile.name)
const isNotDependent = (profile) => !this.appManager.isDependent(profile.name)
const isNotHome = (profile) => profile.name !== 'home'
const sortByName = (profileA, profileB) => {
const nameA = ((profileA.displayName) ? profileA.displayName : profileA.name).toUpperCase()
const nameB = ((profileB.displayName) ? profileB.displayName : profileB.name).toUpperCase()
return (nameA < nameB) ? -1 : (nameA > nameB) ? 1 : 0
}
const activatedPlugins = []
const deactivatedPlugins = []
const tempArray = this.appManager.getAll()
.filter(isFiltered)
.filter(isNotRequired)
.filter(isNotDependent)
.filter(isNotHome)
.sort(sortByName)
tempArray.forEach(profile => {
if (this.appManager.actives.includes(profile.name)) {
activatedPlugins.push(profile)
} else {
deactivatedPlugins.push(profile)
}
})
this.activePlugins = activatedPlugins
this.inactivePlugins = deactivatedPlugins
this.renderComponent()
}
listenOnEvent () {
this.engine.event.on('onRegistration', () => this.renderComponent())
this.appManager.event.on('activate', () => {
this.getAndFilterPlugins()
})
this.appManager.event.on('deactivate', () => {
this.getAndFilterPlugins()
})
deactivateP = (name) => {
this.call('manager', 'deactivatePlugin', name)
_paq.push(['trackEvent', 'manager', 'deactivate', name])
}
setDispatch (dispatch) {
this.dispatch = dispatch
this.renderComponent()
}
updateComponent(state){
return <RemixUiPluginManager
pluginComponent={state}/>
}
renderComponent () {
if(this.dispatch) this.dispatch({...this, activePlugins: this.activePlugins, inactivePlugins: this.inactivePlugins})
}
render () {
return (
<div id='pluginManager'><PluginViewWrapper plugin={this} /></div>
);
}
getAndFilterPlugins = (filter) => {
this.filter = typeof filter === 'string' ? filter.toLowerCase() : this.filter
const isFiltered = (profile) => (profile.displayName ? profile.displayName : profile.name).toLowerCase().includes(this.filter)
const isNotRequired = (profile) => !this.appManager.isRequired(profile.name)
const isNotDependent = (profile) => !this.appManager.isDependent(profile.name)
const isNotHome = (profile) => profile.name !== 'home'
const sortByName = (profileA, profileB) => {
const nameA = ((profileA.displayName) ? profileA.displayName : profileA.name).toUpperCase()
const nameB = ((profileB.displayName) ? profileB.displayName : profileB.name).toUpperCase()
return (nameA < nameB) ? -1 : (nameA > nameB) ? 1 : 0
}
const activatedPlugins = []
const deactivatedPlugins = []
const tempArray = this.appManager.getAll()
.filter(isFiltered)
.filter(isNotRequired)
.filter(isNotDependent)
.filter(isNotHome)
.sort(sortByName)
tempArray.forEach(profile => {
if (this.appManager.actives.includes(profile.name)) {
activatedPlugins.push(profile)
} else {
deactivatedPlugins.push(profile)
}
})
this.activePlugins = activatedPlugins
this.inactivePlugins = deactivatedPlugins
this.renderComponent()
}
listenOnEvent () {
this.engine.event.on('onRegistration', () => this.renderComponent())
this.appManager.event.on('activate', () => {
this.getAndFilterPlugins()
})
this.appManager.event.on('deactivate', () => {
this.getAndFilterPlugins()
})
}
}
module.exports = PluginManagerComponent

@ -11,134 +11,134 @@ const _paq = window._paq = window._paq || []
export const Preload = () => {
const [supported, setSupported] = useState<boolean>(true)
const [error, setError] = useState<boolean>(false)
const [showDownloader, setShowDownloader] = useState<boolean>(false)
const remixFileSystems = useRef<fileSystems>(new fileSystems())
const remixIndexedDB = useRef<fileSystem>(new indexedDBFileSystem())
const localStorageFileSystem = useRef<fileSystem>(new localStorageFS())
// url parameters to e2e test the fallbacks and error warnings
const testmigrationFallback = useRef<boolean>(window.location.hash.includes('e2e_testmigration_fallback=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:')
const testmigrationResult = useRef<boolean>(window.location.hash.includes('e2e_testmigration=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:')
const testBlockStorage = useRef<boolean>(window.location.hash.includes('e2e_testblock_storage=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:')
const [supported, setSupported] = useState<boolean>(true)
const [error, setError] = useState<boolean>(false)
const [showDownloader, setShowDownloader] = useState<boolean>(false)
const remixFileSystems = useRef<fileSystems>(new fileSystems())
const remixIndexedDB = useRef<fileSystem>(new indexedDBFileSystem())
const localStorageFileSystem = useRef<fileSystem>(new localStorageFS())
// url parameters to e2e test the fallbacks and error warnings
const testmigrationFallback = useRef<boolean>(window.location.hash.includes('e2e_testmigration_fallback=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:')
const testmigrationResult = useRef<boolean>(window.location.hash.includes('e2e_testmigration=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:')
const testBlockStorage = useRef<boolean>(window.location.hash.includes('e2e_testblock_storage=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:')
function loadAppComponent() {
import('../../app').then((AppComponent) => {
const appComponent = new AppComponent.default()
appComponent.run().then(() => {
render(
<>
<RemixApp app={appComponent} />
</>,
document.getElementById('root')
)
})
}).catch(err => {
_paq.push(['trackEvent', 'Preload', 'error', err && err.message])
console.error('Error loading Remix:', err)
setError(true)
})
}
function loadAppComponent() {
import('../../app').then((AppComponent) => {
const appComponent = new AppComponent.default()
appComponent.run().then(() => {
render(
<>
<RemixApp app={appComponent} />
</>,
document.getElementById('root')
)
})
}).catch(err => {
_paq.push(['trackEvent', 'Preload', 'error', err && err.message])
console.error('Error loading Remix:', err)
setError(true)
})
}
const downloadBackup = async () => {
setShowDownloader(false)
const fsUtility = new fileSystemUtility()
await fsUtility.downloadBackup(remixFileSystems.current.fileSystems['localstorage'])
await migrateAndLoad()
}
const downloadBackup = async () => {
setShowDownloader(false)
const fsUtility = new fileSystemUtility()
await fsUtility.downloadBackup(remixFileSystems.current.fileSystems['localstorage'])
await migrateAndLoad()
}
const migrateAndLoad = async () => {
setShowDownloader(false)
const fsUtility = new fileSystemUtility()
const migrationResult = await fsUtility.migrate(localStorageFileSystem.current, remixIndexedDB.current)
_paq.push(['trackEvent', 'Migrate', 'result', migrationResult?'success' : 'fail'])
await setFileSystems()
}
const migrateAndLoad = async () => {
setShowDownloader(false)
const fsUtility = new fileSystemUtility()
const migrationResult = await fsUtility.migrate(localStorageFileSystem.current, remixIndexedDB.current)
_paq.push(['trackEvent', 'Migrate', 'result', migrationResult?'success' : 'fail'])
await setFileSystems()
}
const setFileSystems = async() => {
const fsLoaded = await remixFileSystems.current.setFileSystem([(testmigrationFallback.current || testBlockStorage.current)? null: remixIndexedDB.current, testBlockStorage.current? null:localStorageFileSystem.current])
if (fsLoaded) {
console.log(fsLoaded.name + ' activated')
_paq.push(['trackEvent', 'Storage', 'activate', fsLoaded.name])
loadAppComponent()
} else {
_paq.push(['trackEvent', 'Storage', 'error', 'no supported storage'])
setSupported(false)
}
const setFileSystems = async() => {
const fsLoaded = await remixFileSystems.current.setFileSystem([(testmigrationFallback.current || testBlockStorage.current)? null: remixIndexedDB.current, testBlockStorage.current? null:localStorageFileSystem.current])
if (fsLoaded) {
console.log(fsLoaded.name + ' activated')
_paq.push(['trackEvent', 'Storage', 'activate', fsLoaded.name])
loadAppComponent()
} else {
_paq.push(['trackEvent', 'Storage', 'error', 'no supported storage'])
setSupported(false)
}
}
const testmigration = async() => {
if (testmigrationResult.current) {
const fsUtility = new fileSystemUtility()
fsUtility.populateWorkspace(migrationTestData, remixFileSystems.current.fileSystems['localstorage'].fs)
}
const testmigration = async() => {
if (testmigrationResult.current) {
const fsUtility = new fileSystemUtility()
fsUtility.populateWorkspace(migrationTestData, remixFileSystems.current.fileSystems['localstorage'].fs)
}
}
useEffect(() => {
async function loadStorage() {
await remixFileSystems.current.addFileSystem(remixIndexedDB.current) || _paq.push(['trackEvent', 'Storage', 'error', 'indexedDB not supported'])
await remixFileSystems.current.addFileSystem(localStorageFileSystem.current) || _paq.push(['trackEvent', 'Storage', 'error', 'localstorage not supported'])
await testmigration()
remixIndexedDB.current.loaded && await remixIndexedDB.current.checkWorkspaces()
localStorageFileSystem.current.loaded && await localStorageFileSystem.current.checkWorkspaces()
remixIndexedDB.current.loaded && ( (remixIndexedDB.current.hasWorkSpaces || !localStorageFileSystem.current.hasWorkSpaces)? await setFileSystems():setShowDownloader(true))
!remixIndexedDB.current.loaded && await setFileSystems()
}
loadStorage()
}, [])
useEffect(() => {
async function loadStorage() {
await remixFileSystems.current.addFileSystem(remixIndexedDB.current) || _paq.push(['trackEvent', 'Storage', 'error', 'indexedDB not supported'])
await remixFileSystems.current.addFileSystem(localStorageFileSystem.current) || _paq.push(['trackEvent', 'Storage', 'error', 'localstorage not supported'])
await testmigration()
remixIndexedDB.current.loaded && await remixIndexedDB.current.checkWorkspaces()
localStorageFileSystem.current.loaded && await localStorageFileSystem.current.checkWorkspaces()
remixIndexedDB.current.loaded && ( (remixIndexedDB.current.hasWorkSpaces || !localStorageFileSystem.current.hasWorkSpaces)? await setFileSystems():setShowDownloader(true))
!remixIndexedDB.current.loaded && await setFileSystems()
}
loadStorage()
}, [])
return <>
<div className='preload-container'>
<div className='preload-logo pb-4'>
{logo}
<div className="info-secondary splash">
return <>
<div className='preload-container'>
<div className='preload-logo pb-4'>
{logo}
<div className="info-secondary splash">
REMIX IDE
<br />
<span className='version'> v{packageJson.version}</span>
</div>
</div>
{!supported ?
<div className='preload-info-container alert alert-warning'>
<br />
<span className='version'> v{packageJson.version}</span>
</div>
</div>
{!supported ?
<div className='preload-info-container alert alert-warning'>
Your browser does not support any of the filesystems required by Remix.
Either change the settings in your browser or use a supported browser.
</div> : null}
{error ?
<div className='preload-info-container alert alert-danger text-left'>
</div> : null}
{error ?
<div className='preload-info-container alert alert-danger text-left'>
An unknown error has occurred while loading the application.<br></br>
Doing a hard refresh might fix this issue:<br></br>
<div className='pt-2'>
<div className='pt-2'>
Windows:<br></br>
- Chrome: CTRL + F5 or CTRL + Reload Button<br></br>
- Firefox: CTRL + SHIFT + R or CTRL + F5<br></br>
</div>
<div className='pt-2'>
</div>
<div className='pt-2'>
MacOS:<br></br>
- Chrome & FireFox: CMD + SHIFT + R or SHIFT + Reload Button<br></br>
</div>
<div className='pt-2'>
</div>
<div className='pt-2'>
Linux:<br></br>
- Chrome & FireFox: CTRL + SHIFT + R<br></br>
</div>
</div> : null}
{showDownloader ?
<div className='preload-info-container alert alert-info'>
</div>
</div> : null}
{showDownloader ?
<div className='preload-info-container alert alert-info'>
This app will be updated now. Please download a backup of your files now to make sure you don't lose your work.
<br></br>
<br></br>
You don't need to do anything else, your files will be available when the app loads.
<div onClick={async () => { await downloadBackup() }} data-id='downloadbackup-btn' className='btn btn-primary mt-1'>download backup</div>
<div onClick={async () => { await migrateAndLoad() }} data-id='skipbackup-btn' className='btn btn-primary mt-1'>skip backup</div>
</div> : null}
{(supported && !error && !showDownloader) ?
<div>
<i className="fas fa-spinner fa-spin fa-2x"></i>
</div> : null}
</div>
</>
<div onClick={async () => { await downloadBackup() }} data-id='downloadbackup-btn' className='btn btn-primary mt-1'>download backup</div>
<div onClick={async () => { await migrateAndLoad() }} data-id='skipbackup-btn' className='btn btn-primary mt-1'>skip backup</div>
</div> : null}
{(supported && !error && !showDownloader) ?
<div>
<i className="fas fa-spinner fa-spin fa-2x"></i>
</div> : null}
</div>
</>
}
const logo = <svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100">
<path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z" />
<path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z" />
<path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z" />
<path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z" />
<path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z" />
<path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z" />
</svg>

@ -8,94 +8,94 @@ import { PluginViewWrapper } from '@remix-ui/helper'
// const csjs = require('csjs-inject')
const sidePanel = {
name: 'sidePanel',
displayName: 'Side Panel',
description: 'Remix IDE side panel',
version: packageJson.version,
methods: ['addView', 'removeView', 'currentFocus']
name: 'sidePanel',
displayName: 'Side Panel',
description: 'Remix IDE side panel',
version: packageJson.version,
methods: ['addView', 'removeView', 'currentFocus']
}
export class SidePanel extends AbstractPanel {
sideelement: any
dispatch: React.Dispatch<any> = () => {}
constructor() {
super(sidePanel)
this.sideelement = document.createElement('section')
this.sideelement.setAttribute('class', 'panel plugin-manager')
}
sideelement: any
dispatch: React.Dispatch<any> = () => {}
constructor() {
super(sidePanel)
this.sideelement = document.createElement('section')
this.sideelement.setAttribute('class', 'panel plugin-manager')
}
onActivation() {
this.renderComponent()
// Toggle content
this.on('menuicons', 'toggleContent', (name) => {
if (!this.plugins[name]) return
if (this.plugins[name].active) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('toggle', name)
this.events.emit('toggle', name)
return
}
this.showContent(name)
// TODO: Only keep `this.emit` (issue#2210)
this.emit('showing', name)
this.events.emit('showing', name)
})
// Force opening
this.on('menuicons', 'showContent', (name) => {
if (!this.plugins[name]) return
this.showContent(name)
// TODO: Only keep `this.emit` (issue#2210)
this.emit('showing', name)
this.events.emit('showing', name)
})
}
onActivation() {
this.renderComponent()
// Toggle content
this.on('menuicons', 'toggleContent', (name) => {
if (!this.plugins[name]) return
if (this.plugins[name].active) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('toggle', name)
this.events.emit('toggle', name)
return
}
this.showContent(name)
// TODO: Only keep `this.emit` (issue#2210)
this.emit('showing', name)
this.events.emit('showing', name)
})
// Force opening
this.on('menuicons', 'showContent', (name) => {
if (!this.plugins[name]) return
this.showContent(name)
// TODO: Only keep `this.emit` (issue#2210)
this.emit('showing', name)
this.events.emit('showing', name)
})
}
focus(name) {
this.emit('focusChanged', name)
super.focus(name)
}
focus(name) {
this.emit('focusChanged', name)
super.focus(name)
}
removeView(profile) {
if (this.plugins[profile.name].active) this.call('menuicons', 'select', 'filePanel')
super.removeView(profile)
this.emit('pluginDisabled', profile.name)
this.call('menuicons', 'unlinkContent', profile)
this.renderComponent()
}
removeView(profile) {
if (this.plugins[profile.name].active) this.call('menuicons', 'select', 'filePanel')
super.removeView(profile)
this.emit('pluginDisabled', profile.name)
this.call('menuicons', 'unlinkContent', profile)
this.renderComponent()
}
addView(profile, view) {
super.addView(profile, view)
this.call('menuicons', 'linkContent', profile)
this.renderComponent()
}
addView(profile, view) {
super.addView(profile, view)
this.call('menuicons', 'linkContent', profile)
this.renderComponent()
}
/**
/**
* Display content and update the header
* @param {String} name The name of the plugin to display
*/
async showContent(name) {
super.showContent(name)
this.emit('focusChanged', name)
this.renderComponent()
}
async showContent(name) {
super.showContent(name)
this.emit('focusChanged', name)
this.renderComponent()
}
setDispatch (dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
}
setDispatch (dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
}
render() {
return (
<section className='panel plugin-manager'> <PluginViewWrapper plugin={this} /></section>
);
}
render() {
return (
<section className='panel plugin-manager'> <PluginViewWrapper plugin={this} /></section>
);
}
updateComponent(state: any) {
return <RemixPluginPanel header={<RemixUIPanelHeader plugins={state.plugins}></RemixUIPanelHeader>} plugins={state.plugins} />
}
updateComponent(state: any) {
return <RemixPluginPanel header={<RemixUIPanelHeader plugins={state.plugins}></RemixUIPanelHeader>} plugins={state.plugins} />
}
renderComponent() {
this.dispatch({
plugins: this.plugins
})
}
renderComponent() {
this.dispatch({
plugins: this.plugins
})
}
}

@ -8,119 +8,119 @@ import { Profile } from '@remixproject/plugin-utils'
import { PluginViewWrapper } from '@remix-ui/helper'
const profile = {
name: 'menuicons',
displayName: 'Vertical Icons',
description: 'Remix IDE vertical icons',
version: packageJson.version,
methods: ['select', 'unlinkContent', 'linkContent'],
events: ['toggleContent', 'showContent']
name: 'menuicons',
displayName: 'Vertical Icons',
description: 'Remix IDE vertical icons',
version: packageJson.version,
methods: ['select', 'unlinkContent', 'linkContent'],
events: ['toggleContent', 'showContent']
}
export class VerticalIcons extends Plugin {
events: EventEmitter
htmlElement: HTMLDivElement
icons: Record<string, IconRecord> = {}
dispatch: React.Dispatch<any> = () => {}
constructor () {
super(profile)
this.events = new EventEmitter()
this.htmlElement = document.createElement('div')
this.htmlElement.setAttribute('id', 'icon-panel')
events: EventEmitter
htmlElement: HTMLDivElement
icons: Record<string, IconRecord> = {}
dispatch: React.Dispatch<any> = () => {}
constructor () {
super(profile)
this.events = new EventEmitter()
this.htmlElement = document.createElement('div')
this.htmlElement.setAttribute('id', 'icon-panel')
}
renderComponent () {
const fixedOrder = ['filePanel', 'search', 'solidity','udapp', 'debugger', 'solidityStaticAnalysis', 'solidityUnitTesting', 'pluginManager']
const divived = Object.values(this.icons).map((value) => { return {
...value,
isRequired: fixedOrder.indexOf(value.profile.name) > -1
}}).sort((a,b) => {
return a.timestamp - b.timestamp
})
const required = divived.filter((value) => value.isRequired).sort((a,b) => {
return fixedOrder.indexOf(a.profile.name) - fixedOrder.indexOf(b.profile.name)
})
const sorted: IconRecord[] = [
...required,
...divived.filter((value) => { return !value.isRequired })
]
this.dispatch({
verticalIconsPlugin: this,
icons: sorted
})
}
setDispatch (dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
}
onActivation () {
this.renderComponent()
this.on('sidePanel', 'focusChanged', (name: string) => {
Object.keys(this.icons).map((o) => {
this.icons[o].active = false
})
this.icons[name].active = true
this.renderComponent()
})
}
async linkContent (profile: Profile) {
if (!profile.icon) return
if (!profile.kind) profile.kind = 'none'
this.icons[profile.name] = {
profile: profile,
active: false,
canbeDeactivated: await this.call('manager', 'canDeactivate', this.profile, profile),
timestamp: Date.now()
}
this.renderComponent()
}
renderComponent () {
const fixedOrder = ['filePanel', 'search', 'solidity','udapp', 'debugger', 'solidityStaticAnalysis', 'solidityUnitTesting', 'pluginManager']
unlinkContent (profile: Profile) {
delete this.icons[profile.name]
this.renderComponent()
}
const divived = Object.values(this.icons).map((value) => { return {
...value,
isRequired: fixedOrder.indexOf(value.profile.name) > -1
}}).sort((a,b) => {
return a.timestamp - b.timestamp
})
async activateHome() {
await this.call('manager', 'activatePlugin', 'home')
await this.call('tabs', 'focus', 'home')
}
const required = divived.filter((value) => value.isRequired).sort((a,b) => {
return fixedOrder.indexOf(a.profile.name) - fixedOrder.indexOf(b.profile.name)
})
const sorted: IconRecord[] = [
...required,
...divived.filter((value) => { return !value.isRequired })
]
this.dispatch({
verticalIconsPlugin: this,
icons: sorted
})
}
setDispatch (dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
}
onActivation () {
this.renderComponent()
this.on('sidePanel', 'focusChanged', (name: string) => {
Object.keys(this.icons).map((o) => {
this.icons[o].active = false
})
this.icons[name].active = true
this.renderComponent()
})
}
async linkContent (profile: Profile) {
if (!profile.icon) return
if (!profile.kind) profile.kind = 'none'
this.icons[profile.name] = {
profile: profile,
active: false,
canbeDeactivated: await this.call('manager', 'canDeactivate', this.profile, profile),
timestamp: Date.now()
}
this.renderComponent()
}
unlinkContent (profile: Profile) {
delete this.icons[profile.name]
this.renderComponent()
}
async activateHome() {
await this.call('manager', 'activatePlugin', 'home')
await this.call('tabs', 'focus', 'home')
}
/**
/**
* Set an icon as active
* @param {string} name Name of profile of the module to activate
*/
select (name: string) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('showContent', name)
this.events.emit('showContent', name)
}
select (name: string) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('showContent', name)
this.events.emit('showContent', name)
}
/**
/**
* Toggles the side panel for plugin
* @param {string} name Name of profile of the module to activate
*/
toggle (name: string) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('toggleContent', name)
this.events.emit('toggleContent', name)
}
updateComponent(state: any){
return <RemixUiVerticalIconsPanel
verticalIconsPlugin={state.verticalIconsPlugin}
icons={state.icons}
/>
}
render() {
return (
<div id='icon-panel'><PluginViewWrapper plugin={this} /></div>
);
}
toggle (name: string) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('toggleContent', name)
this.events.emit('toggleContent', name)
}
updateComponent(state: any){
return <RemixUiVerticalIconsPanel
verticalIconsPlugin={state.verticalIconsPlugin}
icons={state.icons}
/>
}
render() {
return (
<div id='icon-panel'><PluginViewWrapper plugin={this} /></div>
);
}
}

@ -9,448 +9,448 @@ import { PluginViewWrapper } from '@remix-ui/helper'
const EventManager = require('../../lib/events')
const profile = {
displayName: 'Editor',
name: 'editor',
description: 'service - editor',
version: packageJson.version,
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel','addErrorMarker', 'clearErrorMarkers', 'getText'],
displayName: 'Editor',
name: 'editor',
description: 'service - editor',
version: packageJson.version,
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel','addErrorMarker', 'clearErrorMarkers', 'getText'],
}
class Editor extends Plugin {
constructor () {
super(profile)
this._themes = {
light: 'light',
dark: 'vs-dark',
remixDark: 'remix-dark'
}
this.registeredDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {}, lineTextPerFile: {} }
this.currentDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {}, lineTextPerFile: {} }
// Init
this.event = new EventManager()
this.sessions = {}
this.readOnlySessions = {}
this.previousInput = ''
this.saveTimeout = null
this.emptySession = null
this.modes = {
sol: 'sol',
yul: 'sol',
mvir: 'move',
js: 'javascript',
py: 'python',
vy: 'python',
zok: 'zokrates',
lex: 'lexon',
txt: 'text',
json: 'json',
abi: 'json',
rs: 'rust',
cairo: 'cairo',
ts: 'typescript',
move: 'move'
}
this.activated = false
this.events = {
onBreakPointAdded: (file, line) => this.triggerEvent('breakpointAdded', [file, line]),
onBreakPointCleared: (file, line) => this.triggerEvent('breakpointCleared', [file, line]),
onDidChangeContent: (file) => this._onChange(file),
onEditorMounted: () => this.triggerEvent('editorMounted', [])
}
// to be implemented by the react component
this.api = {}
this.dispatch = null
this.ref = null
}
setDispatch (dispatch) {
this.dispatch = dispatch
}
updateComponent(state) {
return <EditorUI
editorAPI={state.api}
themeType={state.currentThemeType}
currentFile={state.currentFile}
events={state.events}
plugin={state.plugin}
/>
}
render () {
return <div ref={(element)=>{
this.ref = element
this.ref.currentContent = () => this.currentContent() // used by e2e test
this.ref.setCurrentContent = (value) => {
if (this.sessions[this.currentFile]) {
this.sessions[this.currentFile].setValue(value)
this._onChange(this.currentFile)
}
}
this.ref.gotoLine = (line, column) => this.gotoLine(line, column || 0)
this.ref.getCursorPosition = () => this.getCursorPosition()
this.ref.addDecoration = (marker, filePath, typeOfDecoration) => this.addDecoration(marker, filePath, typeOfDecoration)
this.ref.clearDecorationsByPlugin = (filePath, plugin, typeOfDecoration) => this.clearDecorationsByPlugin(filePath, plugin, typeOfDecoration)
this.ref.keepDecorationsFor = (name, typeOfDecoration) => this.keepDecorationsFor(name, typeOfDecoration)
}} id='editorView'>
<PluginViewWrapper plugin={this} />
</div>
}
renderComponent () {
this.dispatch({
api: this.api,
currentThemeType: this.currentThemeType,
currentFile: this.currentFile,
events: this.events,
plugin: this
})
}
triggerEvent (name, params) {
this.event.trigger(name, params) // internal stack
this.emit(name, ...params) // plugin stack
}
async onActivation () {
this.activated = true
this.on('sidePanel', 'focusChanged', (name) => {
this.keepDecorationsFor(name, 'sourceAnnotationsPerFile')
this.keepDecorationsFor(name, 'markerPerFile')
})
this.on('sidePanel', 'pluginDisabled', (name) => {
this.clearAllDecorationsFor(name)
})
this.on('theme', 'themeLoaded', (theme) => {
this.currentThemeType = theme.quality
this.renderComponent()
})
this.on('fileManager', 'noFileSelected', async () => {
this.currentFile = null
this.renderComponent()
})
try {
this.currentThemeType = (await this.call('theme', 'currentTheme')).quality
} catch (e) {
console.log('unable to select the theme ' + e.message)
}
this.renderComponent()
}
onDeactivation () {
this.off('sidePanel', 'focusChanged')
this.off('sidePanel', 'pluginDisabled')
}
async _onChange (file) {
this.triggerEvent('didChangeFile', [file])
const currentFile = await this.call('fileManager', 'file')
if (!currentFile) {
return
}
if (currentFile !== file) {
return
}
const input = this.get(currentFile)
if (!input) {
return
}
// if there's no change, don't do anything
if (input === this.previousInput) {
return
}
this.previousInput = input
// fire storage update
// NOTE: save at most once per 5 seconds
if (this.saveTimeout) {
window.clearTimeout(this.saveTimeout)
constructor () {
super(profile)
this._themes = {
light: 'light',
dark: 'vs-dark',
remixDark: 'remix-dark'
}
this.registeredDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {}, lineTextPerFile: {} }
this.currentDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {}, lineTextPerFile: {} }
// Init
this.event = new EventManager()
this.sessions = {}
this.readOnlySessions = {}
this.previousInput = ''
this.saveTimeout = null
this.emptySession = null
this.modes = {
sol: 'sol',
yul: 'sol',
mvir: 'move',
js: 'javascript',
py: 'python',
vy: 'python',
zok: 'zokrates',
lex: 'lexon',
txt: 'text',
json: 'json',
abi: 'json',
rs: 'rust',
cairo: 'cairo',
ts: 'typescript',
move: 'move'
}
this.activated = false
this.events = {
onBreakPointAdded: (file, line) => this.triggerEvent('breakpointAdded', [file, line]),
onBreakPointCleared: (file, line) => this.triggerEvent('breakpointCleared', [file, line]),
onDidChangeContent: (file) => this._onChange(file),
onEditorMounted: () => this.triggerEvent('editorMounted', [])
}
// to be implemented by the react component
this.api = {}
this.dispatch = null
this.ref = null
}
setDispatch (dispatch) {
this.dispatch = dispatch
}
updateComponent(state) {
return <EditorUI
editorAPI={state.api}
themeType={state.currentThemeType}
currentFile={state.currentFile}
events={state.events}
plugin={state.plugin}
/>
}
render () {
return <div ref={(element)=>{
this.ref = element
this.ref.currentContent = () => this.currentContent() // used by e2e test
this.ref.setCurrentContent = (value) => {
if (this.sessions[this.currentFile]) {
this.sessions[this.currentFile].setValue(value)
this._onChange(this.currentFile)
}
this.saveTimeout = window.setTimeout(() => {
this.triggerEvent('contentChanged', [])
this.triggerEvent('requiringToSaveCurrentfile', [])
}, 500)
}
_switchSession (path) {
if (path === this.currentFile) return
this.triggerEvent('sessionSwitched', [])
this.currentFile = path
this.renderComponent()
}
/**
}
this.ref.gotoLine = (line, column) => this.gotoLine(line, column || 0)
this.ref.getCursorPosition = () => this.getCursorPosition()
this.ref.addDecoration = (marker, filePath, typeOfDecoration) => this.addDecoration(marker, filePath, typeOfDecoration)
this.ref.clearDecorationsByPlugin = (filePath, plugin, typeOfDecoration) => this.clearDecorationsByPlugin(filePath, plugin, typeOfDecoration)
this.ref.keepDecorationsFor = (name, typeOfDecoration) => this.keepDecorationsFor(name, typeOfDecoration)
}} id='editorView'>
<PluginViewWrapper plugin={this} />
</div>
}
renderComponent () {
this.dispatch({
api: this.api,
currentThemeType: this.currentThemeType,
currentFile: this.currentFile,
events: this.events,
plugin: this
})
}
triggerEvent (name, params) {
this.event.trigger(name, params) // internal stack
this.emit(name, ...params) // plugin stack
}
async onActivation () {
this.activated = true
this.on('sidePanel', 'focusChanged', (name) => {
this.keepDecorationsFor(name, 'sourceAnnotationsPerFile')
this.keepDecorationsFor(name, 'markerPerFile')
})
this.on('sidePanel', 'pluginDisabled', (name) => {
this.clearAllDecorationsFor(name)
})
this.on('theme', 'themeLoaded', (theme) => {
this.currentThemeType = theme.quality
this.renderComponent()
})
this.on('fileManager', 'noFileSelected', async () => {
this.currentFile = null
this.renderComponent()
})
try {
this.currentThemeType = (await this.call('theme', 'currentTheme')).quality
} catch (e) {
console.log('unable to select the theme ' + e.message)
}
this.renderComponent()
}
onDeactivation () {
this.off('sidePanel', 'focusChanged')
this.off('sidePanel', 'pluginDisabled')
}
async _onChange (file) {
this.triggerEvent('didChangeFile', [file])
const currentFile = await this.call('fileManager', 'file')
if (!currentFile) {
return
}
if (currentFile !== file) {
return
}
const input = this.get(currentFile)
if (!input) {
return
}
// if there's no change, don't do anything
if (input === this.previousInput) {
return
}
this.previousInput = input
// fire storage update
// NOTE: save at most once per 5 seconds
if (this.saveTimeout) {
window.clearTimeout(this.saveTimeout)
}
this.saveTimeout = window.setTimeout(() => {
this.triggerEvent('contentChanged', [])
this.triggerEvent('requiringToSaveCurrentfile', [])
}, 500)
}
_switchSession (path) {
if (path === this.currentFile) return
this.triggerEvent('sessionSwitched', [])
this.currentFile = path
this.renderComponent()
}
/**
* Get Ace mode base of the extension of the session file
* @param {string} path Path of the file
*/
_getMode (path) {
if (!path) return this.modes.txt
const root = path.split('#')[0].split('?')[0]
let ext = root.indexOf('.') !== -1 ? /[^.]+$/.exec(root) : null
if (ext) ext = ext[0]
else ext = 'txt'
return ext && this.modes[ext] ? this.modes[ext] : this.modes.txt
}
async handleTypeScriptDependenciesOf (path, content, readFile, exists) {
if (path.endsWith('.ts')) {
// extract the import, resolve their content
// and add the imported files to Monaco through the `addModel`
// so Monaco can provide auto completion
const paths = path.split('/')
paths.pop()
const fromPath = paths.join('/') // get current execution context path
for (const match of content.matchAll(/import\s+.*\s+from\s+(?:"(.*?)"|'(.*?)')/g)) {
let pathDep = match[2]
if (pathDep.startsWith('./') || pathDep.startsWith('../')) pathDep = resolve(fromPath, pathDep)
if (pathDep.startsWith('/')) pathDep = pathDep.substring(1)
if (!pathDep.endsWith('.ts')) pathDep = pathDep + '.ts'
try {
// we can't use the fileManager plugin call directly
// because it's itself called in a plugin context, and that causes a timeout in the plugin stack
const pathExists = await exists(pathDep)
let contentDep = ''
if (pathExists) {
contentDep = await readFile(pathDep)
if (contentDep !== '') {
this.emit('addModel', contentDep, 'typescript', pathDep, this.readOnlySessions[path])
}
} else {
console.log("The file ", pathDep, " can't be found.")
}
} catch (e) {
console.log(e)
}
_getMode (path) {
if (!path) return this.modes.txt
const root = path.split('#')[0].split('?')[0]
let ext = root.indexOf('.') !== -1 ? /[^.]+$/.exec(root) : null
if (ext) ext = ext[0]
else ext = 'txt'
return ext && this.modes[ext] ? this.modes[ext] : this.modes.txt
}
async handleTypeScriptDependenciesOf (path, content, readFile, exists) {
if (path.endsWith('.ts')) {
// extract the import, resolve their content
// and add the imported files to Monaco through the `addModel`
// so Monaco can provide auto completion
const paths = path.split('/')
paths.pop()
const fromPath = paths.join('/') // get current execution context path
for (const match of content.matchAll(/import\s+.*\s+from\s+(?:"(.*?)"|'(.*?)')/g)) {
let pathDep = match[2]
if (pathDep.startsWith('./') || pathDep.startsWith('../')) pathDep = resolve(fromPath, pathDep)
if (pathDep.startsWith('/')) pathDep = pathDep.substring(1)
if (!pathDep.endsWith('.ts')) pathDep = pathDep + '.ts'
try {
// we can't use the fileManager plugin call directly
// because it's itself called in a plugin context, and that causes a timeout in the plugin stack
const pathExists = await exists(pathDep)
let contentDep = ''
if (pathExists) {
contentDep = await readFile(pathDep)
if (contentDep !== '') {
this.emit('addModel', contentDep, 'typescript', pathDep, this.readOnlySessions[path])
}
} else {
console.log("The file ", pathDep, " can't be found.")
}
} catch (e) {
console.log(e)
}
}
}
}
/**
/**
* Create an editor session
* @param {string} path path of the file
* @param {string} content Content of the file to open
* @param {string} mode Mode for this file [Default is `text`]
*/
async _createSession (path, content, mode) {
if (!this.activated) return
async _createSession (path, content, mode) {
if (!this.activated) return
this.emit('addModel', content, mode, path, this.readOnlySessions[path])
return {
path,
language: mode,
setValue: (content) => {
this.emit('setValue', path, content)
},
getValue: () => {
return this.api.getValue(path, content)
},
dispose: () => {
this.emit('disposeModel', path)
}
}
}
/**
this.emit('addModel', content, mode, path, this.readOnlySessions[path])
return {
path,
language: mode,
setValue: (content) => {
this.emit('setValue', path, content)
},
getValue: () => {
return this.api.getValue(path, content)
},
dispose: () => {
this.emit('disposeModel', path)
}
}
}
/**
* Attempts to find the string in the current document
* @param {string} string
*/
find (string) {
return this.api.findMatches(this.currentFile, string)
}
find (string) {
return this.api.findMatches(this.currentFile, string)
}
addModel(path, content) {
this.emit('addModel', content, this._getMode(path), path, this.readOnlySessions[path])
}
addModel(path, content) {
this.emit('addModel', content, this._getMode(path), path, this.readOnlySessions[path])
}
/**
/**
* Display an Empty read-only session
*/
displayEmptyReadOnlySession () {
if (!this.activated) return
this.currentFile = null
this.emit('addModel', '', 'text', '_blank', true)
}
displayEmptyReadOnlySession () {
if (!this.activated) return
this.currentFile = null
this.emit('addModel', '', 'text', '_blank', true)
}
/**
/**
* Set the text in the current session, if any.
* @param {string} url Address of the text to replace.
* @param {string} text New text to be place.
*/
setText (url, text) {
if (this.sessions[url]) {
this.sessions[url].setValue(text)
}
setText (url, text) {
if (this.sessions[url]) {
this.sessions[url].setValue(text)
}
}
/**
/**
* Get the text in the current session, if any.
* @param {string} url Address of the content to retrieve.
*/
getText (url) {
if (this.sessions[url]) {
return this.sessions[url].getValue()
}
getText (url) {
if (this.sessions[url]) {
return this.sessions[url].getValue()
}
}
/**
/**
* Upsert and open a session.
* @param {string} path Path of the session to open.
* @param {string} content Content of the document or update.
*/
async open (path, content) {
/*
async open (path, content) {
/*
we have the following cases:
- URL prepended with "localhost"
- URL prepended with "browser"
- URL not prepended with the file explorer. We assume (as it is in the whole app, that this is a "browser" URL
*/
if (!this.sessions[path]) {
this.readOnlySessions[path] = false
const session = await this._createSession(path, content, this._getMode(path))
this.sessions[path] = session
} else if (this.sessions[path].getValue() !== content) {
this.sessions[path].setValue(content)
}
this._switchSession(path)
if (!this.sessions[path]) {
this.readOnlySessions[path] = false
const session = await this._createSession(path, content, this._getMode(path))
this.sessions[path] = session
} else if (this.sessions[path].getValue() !== content) {
this.sessions[path].setValue(content)
}
this._switchSession(path)
}
/**
/**
* Upsert and Open a session and set it as Read-only.
* @param {string} path Path of the session to open.
* @param {string} content Content of the document or update.
*/
async openReadOnly (path, content) {
if (!this.sessions[path]) {
this.readOnlySessions[path] = true
const session = await this._createSession(path, content, this._getMode(path))
this.sessions[path] = session
}
this._switchSession(path)
async openReadOnly (path, content) {
if (!this.sessions[path]) {
this.readOnlySessions[path] = true
const session = await this._createSession(path, content, this._getMode(path))
this.sessions[path] = session
}
this._switchSession(path)
}
/**
/**
* Content of the current session
* @return {String} content of the file referenced by @arg path
*/
currentContent () {
return this.get(this.current())
}
currentContent () {
return this.get(this.current())
}
/**
/**
* Content of the session targeted by @arg path
* if @arg path is null, the content of the current session is returned
* @param {string} path Path of the session to get.
* @return {String} content of the file referenced by @arg path
*/
get (path) {
if (!path || this.currentFile === path) {
return this.api.getValue(path)
} else if (this.sessions[path]) {
return this.sessions[path].getValue()
}
get (path) {
if (!path || this.currentFile === path) {
return this.api.getValue(path)
} else if (this.sessions[path]) {
return this.sessions[path].getValue()
}
}
/**
/**
* Path of the currently editing file
* returns `undefined` if no session is being editer
* @return {String} path of the current session
*/
current () {
return this.currentFile
}
current () {
return this.currentFile
}
/**
/**
* The position of the cursor
*/
getCursorPosition (offset = true) {
return this.api.getCursorPosition(offset)
}
getCursorPosition (offset = true) {
return this.api.getCursorPosition(offset)
}
/**
/**
* Remove the current session from the list of sessions.
*/
discardCurrentSession () {
if (this.sessions[this.currentFile]) {
delete this.sessions[this.currentFile]
this.currentFile = null
}
discardCurrentSession () {
if (this.sessions[this.currentFile]) {
delete this.sessions[this.currentFile]
this.currentFile = null
}
}
/**
/**
* Remove a session based on its path.
* @param {string} path
*/
discard (path) {
if (this.sessions[path]) {
this.sessions[path].dispose()
delete this.sessions[path]
}
if (this.currentFile === path) this.currentFile = null
discard (path) {
if (this.sessions[path]) {
this.sessions[path].dispose()
delete this.sessions[path]
}
if (this.currentFile === path) this.currentFile = null
}
/**
/**
* Increment the font size (in pixels) for the editor text.
* @param {number} incr The amount of pixels to add to the font.
*/
editorFontSize (incr) {
if (!this.activated) return
const newSize = this.api.getFontSize() + incr
if (newSize >= 6) {
this.emit('setFontSize', newSize)
}
editorFontSize (incr) {
if (!this.activated) return
const newSize = this.api.getFontSize() + incr
if (newSize >= 6) {
this.emit('setFontSize', newSize)
}
}
/**
/**
* Resize the editor, and sets whether or not line wrapping is enabled.
* @param {boolean} useWrapMode Enable (or disable) wrap mode
*/
resize (useWrapMode) {
if (!this.activated) return
this.emit('setWordWrap', useWrapMode)
}
resize (useWrapMode) {
if (!this.activated) return
this.emit('setWordWrap', useWrapMode)
}
/**
/**
* Moves the cursor and focus to the specified line and column number
* @param {number} line
* @param {number} col
*/
gotoLine (line, col) {
if (!this.activated) return
this.emit('focus')
this.emit('revealLine', line + 1, col)
}
gotoLine (line, col) {
if (!this.activated) return
this.emit('focus')
this.emit('revealLine', line + 1, col)
}
/**
/**
* Reveals the range in the editor.
* @param {number} startLineNumber
* @param {number} startColumn
* @param {number} endLineNumber
* @param {number} endColumn
*/
revealRange (startLineNumber, startColumn, endLineNumber, endColumn) {
if (!this.activated) return
this.emit('focus')
console.log(startLineNumber, startColumn, endLineNumber, endColumn)
this.emit('revealRange', startLineNumber, startColumn, endLineNumber, endColumn)
}
/**
revealRange (startLineNumber, startColumn, endLineNumber, endColumn) {
if (!this.activated) return
this.emit('focus')
console.log(startLineNumber, startColumn, endLineNumber, endColumn)
this.emit('revealRange', startLineNumber, startColumn, endLineNumber, endColumn)
}
/**
* Scrolls to a line. If center is true, it puts the line in middle of screen (or attempts to).
* @param {number} line The line to scroll to
*/
scrollToLine (line) {
if (!this.activated) return
this.emit('revealLine', line + 1, 0)
}
scrollToLine (line) {
if (!this.activated) return
this.emit('revealLine', line + 1, 0)
}
/**
/**
* Clears all the decorations for the given @arg filePath and @arg plugin, if none is given, the current sesssion is used.
* An annotation has the following shape:
column: -1
@ -461,22 +461,22 @@ class Editor extends Plugin {
* @param {String} plugin
* @param {String} typeOfDecoration
*/
clearDecorationsByPlugin (filePath, plugin, typeOfDecoration) {
if (filePath && !this.sessions[filePath]) throw new Error('file not found' + filePath)
const path = filePath || this.currentFile
const { currentDecorations, registeredDecorations } = this.api.clearDecorationsByPlugin(path, plugin, typeOfDecoration, this.registeredDecorations[typeOfDecoration][filePath] || [], this.currentDecorations[typeOfDecoration][filePath] || [])
this.currentDecorations[typeOfDecoration][filePath] = currentDecorations
this.registeredDecorations[typeOfDecoration][filePath] = registeredDecorations
}
keepDecorationsFor (plugin, typeOfDecoration) {
if (!this.currentFile) return
const { currentDecorations } = this.api.keepDecorationsFor(this.currentFile, plugin, typeOfDecoration, this.registeredDecorations[typeOfDecoration][this.currentFile] || [], this.currentDecorations[typeOfDecoration][this.currentFile] || [])
this.currentDecorations[typeOfDecoration][this.currentFile] = currentDecorations
}
/**
clearDecorationsByPlugin (filePath, plugin, typeOfDecoration) {
if (filePath && !this.sessions[filePath]) throw new Error('file not found' + filePath)
const path = filePath || this.currentFile
const { currentDecorations, registeredDecorations } = this.api.clearDecorationsByPlugin(path, plugin, typeOfDecoration, this.registeredDecorations[typeOfDecoration][filePath] || [], this.currentDecorations[typeOfDecoration][filePath] || [])
this.currentDecorations[typeOfDecoration][filePath] = currentDecorations
this.registeredDecorations[typeOfDecoration][filePath] = registeredDecorations
}
keepDecorationsFor (plugin, typeOfDecoration) {
if (!this.currentFile) return
const { currentDecorations } = this.api.keepDecorationsFor(this.currentFile, plugin, typeOfDecoration, this.registeredDecorations[typeOfDecoration][this.currentFile] || [], this.currentDecorations[typeOfDecoration][this.currentFile] || [])
this.currentDecorations[typeOfDecoration][this.currentFile] = currentDecorations
}
/**
* Clears all the decorations and for all the sessions for the given @arg plugin
* An annotation has the following shape:
column: -1
@ -485,25 +485,25 @@ class Editor extends Plugin {
type: "warning"
* @param {String} filePath
*/
clearAllDecorationsFor (plugin) {
for (const session in this.sessions) {
this.clearDecorationsByPlugin(session, plugin, 'sourceAnnotationsPerFile')
this.clearDecorationsByPlugin(session, plugin, 'markerPerFile')
}
clearAllDecorationsFor (plugin) {
for (const session in this.sessions) {
this.clearDecorationsByPlugin(session, plugin, 'sourceAnnotationsPerFile')
this.clearDecorationsByPlugin(session, plugin, 'markerPerFile')
}
}
// error markers
async addErrorMarker (error){
const { from } = this.currentRequest
this.api.addErrorMarker(error, from)
}
// error markers
async addErrorMarker (error){
const { from } = this.currentRequest
this.api.addErrorMarker(error, from)
}
async clearErrorMarkers(sources){
const { from } = this.currentRequest
this.api.clearErrorMarkers(sources, from)
}
async clearErrorMarkers(sources){
const { from } = this.currentRequest
this.api.clearErrorMarkers(sources, from)
}
/**
/**
* Clears all the annotations for the given @arg filePath, the plugin name is retrieved from the context, if none is given, the current sesssion is used.
* An annotation has the following shape:
column: -1
@ -513,30 +513,30 @@ class Editor extends Plugin {
* @param {String} filePath
* @param {String} plugin
*/
clearAnnotations (filePath) {
filePath = filePath || this.currentFile
const { from } = this.currentRequest
this.clearDecorationsByPlugin(filePath, from, 'sourceAnnotationsPerFile')
}
async addDecoration (decoration, filePath, typeOfDecoration) {
if (!filePath) return
filePath = await this.call('fileManager', 'getPathFromUrl', filePath)
filePath = filePath.file
if (!this.sessions[filePath]) return
const path = filePath || this.currentFile
const { from } = this.currentRequest
decoration.from = from
const { currentDecorations, registeredDecorations } = this.api.addDecoration(decoration, path, typeOfDecoration)
if (!this.registeredDecorations[typeOfDecoration][filePath]) this.registeredDecorations[typeOfDecoration][filePath] = []
this.registeredDecorations[typeOfDecoration][filePath].push(...registeredDecorations)
if (!this.currentDecorations[typeOfDecoration][filePath]) this.currentDecorations[typeOfDecoration][filePath] = []
this.currentDecorations[typeOfDecoration][filePath].push(...currentDecorations)
}
/**
clearAnnotations (filePath) {
filePath = filePath || this.currentFile
const { from } = this.currentRequest
this.clearDecorationsByPlugin(filePath, from, 'sourceAnnotationsPerFile')
}
async addDecoration (decoration, filePath, typeOfDecoration) {
if (!filePath) return
filePath = await this.call('fileManager', 'getPathFromUrl', filePath)
filePath = filePath.file
if (!this.sessions[filePath]) return
const path = filePath || this.currentFile
const { from } = this.currentRequest
decoration.from = from
const { currentDecorations, registeredDecorations } = this.api.addDecoration(decoration, path, typeOfDecoration)
if (!this.registeredDecorations[typeOfDecoration][filePath]) this.registeredDecorations[typeOfDecoration][filePath] = []
this.registeredDecorations[typeOfDecoration][filePath].push(...registeredDecorations)
if (!this.currentDecorations[typeOfDecoration][filePath]) this.currentDecorations[typeOfDecoration][filePath] = []
this.currentDecorations[typeOfDecoration][filePath].push(...currentDecorations)
}
/**
* Add an annotation to the current session.
* An annotation has the following shape:
column: -1
@ -546,38 +546,38 @@ class Editor extends Plugin {
* @param {Object} annotation
* @param {String} filePath
*/
async addAnnotation (annotation, filePath) {
filePath = filePath || this.currentFile
await this.addDecoration(annotation, filePath, 'sourceAnnotationsPerFile')
}
async highlight (position, filePath, highlightColor, opt = { focus: true }) {
filePath = filePath || this.currentFile
if (opt.focus) {
await this.call('fileManager', 'open', filePath)
this.scrollToLine(position.start.line)
}
await this.addDecoration({ position }, filePath, 'markerPerFile')
}
discardHighlight () {
const { from } = this.currentRequest
for (const session in this.sessions) {
this.clearDecorationsByPlugin(session, from, 'markerPerFile', this.registeredDecorations, this.currentDecorations)
}
}
async addLineText (lineText, filePath) {
filePath = filePath || this.currentFile
await this.addDecoration(lineText, filePath, 'lineTextPerFile')
}
discardLineTexts() {
const { from } = this.currentRequest
for (const session in this.sessions) {
this.clearDecorationsByPlugin(session, from, 'lineTextPerFile', this.registeredDecorations, this.currentDecorations)
}
}
async addAnnotation (annotation, filePath) {
filePath = filePath || this.currentFile
await this.addDecoration(annotation, filePath, 'sourceAnnotationsPerFile')
}
async highlight (position, filePath, highlightColor, opt = { focus: true }) {
filePath = filePath || this.currentFile
if (opt.focus) {
await this.call('fileManager', 'open', filePath)
this.scrollToLine(position.start.line)
}
await this.addDecoration({ position }, filePath, 'markerPerFile')
}
discardHighlight () {
const { from } = this.currentRequest
for (const session in this.sessions) {
this.clearDecorationsByPlugin(session, from, 'markerPerFile', this.registeredDecorations, this.currentDecorations)
}
}
async addLineText (lineText, filePath) {
filePath = filePath || this.currentFile
await this.addDecoration(lineText, filePath, 'lineTextPerFile')
}
discardLineTexts() {
const { from } = this.currentRequest
for (const session in this.sessions) {
this.clearDecorationsByPlugin(session, from, 'lineTextPerFile', this.registeredDecorations, this.currentDecorations)
}
}
}
module.exports = Editor

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -7,318 +7,318 @@ const pathModule = require('path')
const Storage = remixLib.Storage
class FileProvider {
constructor (name) {
this.event = new EventManager()
this.type = name
this.providerExternalsStorage = new Storage('providerExternals:')
this.externalFolders = [this.type + '/swarm', this.type + '/ipfs', this.type + '/github', this.type + '/gists', this.type + '/https']
this.reverseKey = this.type + '-reverse-'
}
addNormalizedName (path, url) {
if (this.type) path = this.type + '/' + path
this.providerExternalsStorage.set(path, url)
this.providerExternalsStorage.set(this.reverseKey + url, path)
}
removeNormalizedName (path) {
const value = this.providerExternalsStorage.get(path)
this.providerExternalsStorage.remove(path)
this.providerExternalsStorage.remove(this.reverseKey + value)
}
normalizedNameExists (path) {
return this.providerExternalsStorage.exists(path)
}
getNormalizedName (path) {
return this.providerExternalsStorage.get(path)
}
getPathFromUrl (url) {
return this.providerExternalsStorage.get(this.reverseKey + url)
}
getUrlFromPath (path) {
if (!path.startsWith(this.type)) path = this.type + '/' + path
return this.providerExternalsStorage.get(path)
}
isExternalFolder (path) {
return this.externalFolders.includes(path)
}
async discardChanges (path, toastCb, modalCb) {
this.remove(path)
const compilerImport = new CompilerImports()
this.providerExternalsStorage.keys().map(value => {
if (value.indexOf(path) === 0) {
compilerImport.import(
this.getNormalizedName(value),
true,
(loadingMsg) => { toastCb(loadingMsg) },
async (error, content, cleanUrl, type, url) => {
if (error) {
modalCb(error)
} else {
await this.addExternal(type + '/' + cleanUrl, content, url)
}
}
)
constructor (name) {
this.event = new EventManager()
this.type = name
this.providerExternalsStorage = new Storage('providerExternals:')
this.externalFolders = [this.type + '/swarm', this.type + '/ipfs', this.type + '/github', this.type + '/gists', this.type + '/https']
this.reverseKey = this.type + '-reverse-'
}
addNormalizedName (path, url) {
if (this.type) path = this.type + '/' + path
this.providerExternalsStorage.set(path, url)
this.providerExternalsStorage.set(this.reverseKey + url, path)
}
removeNormalizedName (path) {
const value = this.providerExternalsStorage.get(path)
this.providerExternalsStorage.remove(path)
this.providerExternalsStorage.remove(this.reverseKey + value)
}
normalizedNameExists (path) {
return this.providerExternalsStorage.exists(path)
}
getNormalizedName (path) {
return this.providerExternalsStorage.get(path)
}
getPathFromUrl (url) {
return this.providerExternalsStorage.get(this.reverseKey + url)
}
getUrlFromPath (path) {
if (!path.startsWith(this.type)) path = this.type + '/' + path
return this.providerExternalsStorage.get(path)
}
isExternalFolder (path) {
return this.externalFolders.includes(path)
}
async discardChanges (path, toastCb, modalCb) {
this.remove(path)
const compilerImport = new CompilerImports()
this.providerExternalsStorage.keys().map(value => {
if (value.indexOf(path) === 0) {
compilerImport.import(
this.getNormalizedName(value),
true,
(loadingMsg) => { toastCb(loadingMsg) },
async (error, content, cleanUrl, type, url) => {
if (error) {
modalCb(error)
} else {
await this.addExternal(type + '/' + cleanUrl, content, url)
}
})
}
)
}
})
}
async exists (path) {
// todo check the type (directory/file) as well #2386
// currently it is not possible to have a file and folder with same path
const ret = await this._exists(path)
return ret
}
async _exists (path) {
path = this.getPathFromUrl(path) || path // ensure we actually use the normalized path from here
var unprefixedpath = this.removePrefix(path)
return path === this.type ? true : await window.remixFileSystem.exists(unprefixedpath)
}
init (cb) {
cb()
}
async get (path, cb) {
cb = cb || function () { /* do nothing. */ }
path = this.getPathFromUrl(path) || path // ensure we actually use the normalized path from here
var unprefixedpath = this.removePrefix(path)
try {
const content = await window.remixFileSystem.readFile(unprefixedpath, 'utf8')
if (cb) cb(null, content)
return content
} catch (err) {
if (cb) cb(err, null)
throw new Error(err)
}
async exists (path) {
// todo check the type (directory/file) as well #2386
// currently it is not possible to have a file and folder with same path
const ret = await this._exists(path)
return ret
}
async _exists (path) {
path = this.getPathFromUrl(path) || path // ensure we actually use the normalized path from here
var unprefixedpath = this.removePrefix(path)
return path === this.type ? true : await window.remixFileSystem.exists(unprefixedpath)
}
async set (path, content, cb) {
cb = cb || function () { /* do nothing. */ }
var unprefixedpath = this.removePrefix(path)
const exists = await window.remixFileSystem.exists(unprefixedpath)
if (exists && await window.remixFileSystem.readFile(unprefixedpath, 'utf8') === content) {
if (cb) cb()
return null
}
init (cb) {
cb()
await this.createDir(path.substr(0, path.lastIndexOf('/')))
try {
await window.remixFileSystem.writeFile(unprefixedpath, content, 'utf8')
} catch (e) {
if (cb) cb(e)
return false
}
async get (path, cb) {
cb = cb || function () { /* do nothing. */ }
path = this.getPathFromUrl(path) || path // ensure we actually use the normalized path from here
var unprefixedpath = this.removePrefix(path)
try {
const content = await window.remixFileSystem.readFile(unprefixedpath, 'utf8')
if (cb) cb(null, content)
return content
} catch (err) {
if (cb) cb(err, null)
throw new Error(err)
}
if (!exists) {
this.event.emit('fileAdded', this._normalizePath(unprefixedpath), false)
} else {
this.event.emit('fileChanged', this._normalizePath(unprefixedpath))
}
async set (path, content, cb) {
cb = cb || function () { /* do nothing. */ }
var unprefixedpath = this.removePrefix(path)
const exists = await window.remixFileSystem.exists(unprefixedpath)
if (exists && await window.remixFileSystem.readFile(unprefixedpath, 'utf8') === content) {
if (cb) cb()
return null
}
await this.createDir(path.substr(0, path.lastIndexOf('/')))
if (cb) cb()
return true
}
async createDir (path, cb) {
const unprefixedpath = this.removePrefix(path)
await this.forceCreateDir(unprefixedpath)
if (cb) cb()
}
async forceCreateDir (path) {
const paths = path.split('/')
if (paths.length && paths[0] === '') paths.shift()
let currentCheck = ''
for (const value of paths) {
currentCheck = currentCheck + '/' + value
if (!await window.remixFileSystem.exists(currentCheck)) {
try {
await window.remixFileSystem.writeFile(unprefixedpath, content, 'utf8')
} catch (e) {
if (cb) cb(e)
return false
}
if (!exists) {
this.event.emit('fileAdded', this._normalizePath(unprefixedpath), false)
} else {
this.event.emit('fileChanged', this._normalizePath(unprefixedpath))
}
if (cb) cb()
return true
}
async createDir (path, cb) {
const unprefixedpath = this.removePrefix(path)
await this.forceCreateDir(unprefixedpath)
if (cb) cb()
}
async forceCreateDir (path) {
const paths = path.split('/')
if (paths.length && paths[0] === '') paths.shift()
let currentCheck = ''
for (const value of paths) {
currentCheck = currentCheck + '/' + value
if (!await window.remixFileSystem.exists(currentCheck)) {
try {
await window.remixFileSystem.mkdir(currentCheck)
this.event.emit('folderAdded', this._normalizePath(currentCheck))
} catch (error) {
console.log(error)
}
}
await window.remixFileSystem.mkdir(currentCheck)
this.event.emit('folderAdded', this._normalizePath(currentCheck))
} catch (error) {
console.log(error)
}
}
}
// this will not add a folder as readonly but keep the original url to be able to restore it later
async addExternal (path, content, url) {
if (url) this.addNormalizedName(path, url)
return await this.set(path, content)
}
isReadOnly (path) {
return false
}
async isDirectory (path) {
const unprefixedpath = this.removePrefix(path)
return path === this.type ? true : (await window.remixFileSystem.stat(unprefixedpath)).isDirectory()
}
async isFile (path) {
path = this.getPathFromUrl(path) || path // ensure we actually use the normalized path from here
path = this.removePrefix(path)
return (await window.remixFileSystem.stat(path)).isFile()
}
/**
}
// this will not add a folder as readonly but keep the original url to be able to restore it later
async addExternal (path, content, url) {
if (url) this.addNormalizedName(path, url)
return await this.set(path, content)
}
isReadOnly (path) {
return false
}
async isDirectory (path) {
const unprefixedpath = this.removePrefix(path)
return path === this.type ? true : (await window.remixFileSystem.stat(unprefixedpath)).isDirectory()
}
async isFile (path) {
path = this.getPathFromUrl(path) || path // ensure we actually use the normalized path from here
path = this.removePrefix(path)
return (await window.remixFileSystem.stat(path)).isFile()
}
/**
* Removes the folder recursively
* @param {*} path is the folder to be removed
*/
async remove (path) {
path = this.removePrefix(path)
if (await window.remixFileSystem.exists(path)) {
const stat = await window.remixFileSystem.stat(path)
try {
if (!stat.isDirectory()) {
return await this.removeFile(path)
} else {
const items = await window.remixFileSystem.readdir(path)
if (items.length !== 0) {
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) {
console.log(e)
return false
async remove (path) {
path = this.removePrefix(path)
if (await window.remixFileSystem.exists(path)) {
const stat = await window.remixFileSystem.stat(path)
try {
if (!stat.isDirectory()) {
return await this.removeFile(path)
} else {
const items = await window.remixFileSystem.readdir(path)
if (items.length !== 0) {
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) {
console.log(e)
return false
}
}
}
/**
/**
* copy the folder recursively (internal use)
* @param {string} path is the folder to be copied over
* @param {Function} visitFile is a function called for each visited files
* @param {Function} visitFolder is a function called for each visited folders
*/
async _copyFolderToJsonInternal (path, visitFile, visitFolder) {
visitFile = visitFile || function () { /* do nothing. */ }
visitFolder = visitFolder || function () { /* do nothing. */ }
const json = {}
path = this.removePrefix(path)
if (await window.remixFileSystem.exists(path)) {
try {
const items = await window.remixFileSystem.readdir(path)
visitFolder({ path })
if (items.length !== 0) {
for (const item of items) {
const file = {}
const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}`
if ((await window.remixFileSystem.stat(curPath)).isDirectory()) {
file.children = await this._copyFolderToJsonInternal(curPath, visitFile, visitFolder)
} else {
file.content = await window.remixFileSystem.readFile(curPath, 'utf8')
visitFile({ path: curPath, content: file.content })
}
json[curPath] = file
}
}
} catch (e) {
console.log(e)
throw new Error(e)
async _copyFolderToJsonInternal (path, visitFile, visitFolder) {
visitFile = visitFile || function () { /* do nothing. */ }
visitFolder = visitFolder || function () { /* do nothing. */ }
const json = {}
path = this.removePrefix(path)
if (await window.remixFileSystem.exists(path)) {
try {
const items = await window.remixFileSystem.readdir(path)
visitFolder({ path })
if (items.length !== 0) {
for (const item of items) {
const file = {}
const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}`
if ((await window.remixFileSystem.stat(curPath)).isDirectory()) {
file.children = await this._copyFolderToJsonInternal(curPath, visitFile, visitFolder)
} else {
file.content = await window.remixFileSystem.readFile(curPath, 'utf8')
visitFile({ path: curPath, content: file.content })
}
json[curPath] = file
}
}
return json
} catch (e) {
console.log(e)
throw new Error(e)
}
}
return json
}
/**
/**
* copy the folder recursively
* @param {string} path is the folder to be copied over
* @param {Function} visitFile is a function called for each visited files
* @param {Function} visitFolder is a function called for each visited folders
*/
async copyFolderToJson (path, visitFile, visitFolder) {
visitFile = visitFile || function () { /* do nothing. */ }
visitFolder = visitFolder || function () { /* do nothing. */ }
return await this._copyFolderToJsonInternal(path, visitFile, visitFolder)
}
async removeFile (path) {
path = this.removePrefix(path)
if (await window.remixFileSystem.exists(path) && !(await window.remixFileSystem.stat(path)).isDirectory()) {
await window.remixFileSystem.unlink(path)
this.event.emit('fileRemoved', this._normalizePath(path))
return true
} else return false
}
async rename (oldPath, newPath, isFolder) {
var unprefixedoldPath = this.removePrefix(oldPath)
var unprefixednewPath = this.removePrefix(newPath)
if (await this._exists(unprefixedoldPath)) {
await window.remixFileSystem.rename(unprefixedoldPath, unprefixednewPath)
this.event.emit('fileRenamed',
this._normalizePath(unprefixedoldPath),
this._normalizePath(unprefixednewPath),
isFolder
)
return true
}
return false
async copyFolderToJson (path, visitFile, visitFolder) {
visitFile = visitFile || function () { /* do nothing. */ }
visitFolder = visitFolder || function () { /* do nothing. */ }
return await this._copyFolderToJsonInternal(path, visitFile, visitFolder)
}
async removeFile (path) {
path = this.removePrefix(path)
if (await window.remixFileSystem.exists(path) && !(await window.remixFileSystem.stat(path)).isDirectory()) {
await window.remixFileSystem.unlink(path)
this.event.emit('fileRemoved', this._normalizePath(path))
return true
} else return false
}
async rename (oldPath, newPath, isFolder) {
var unprefixedoldPath = this.removePrefix(oldPath)
var unprefixednewPath = this.removePrefix(newPath)
if (await this._exists(unprefixedoldPath)) {
await window.remixFileSystem.rename(unprefixedoldPath, unprefixednewPath)
this.event.emit('fileRenamed',
this._normalizePath(unprefixedoldPath),
this._normalizePath(unprefixednewPath),
isFolder
)
return true
}
async resolveDirectory (path, cb) {
path = this.removePrefix(path)
if (path.indexOf('/') !== 0) path = '/' + path
try {
const files = await window.remixFileSystem.readdir(path)
const ret = {}
if (files) {
for (let element of files) {
path = path.replace(/^\/|\/$/g, '') // remove first and last slash
element = element.replace(/^\/|\/$/g, '') // remove first and last slash
const absPath = (path === '/' ? '' : path) + '/' + element
ret[absPath.indexOf('/') === 0 ? absPath.substr(1, absPath.length) : absPath] = { isDirectory: (await window.remixFileSystem.stat(absPath)).isDirectory() }
// ^ ret does not accept path starting with '/'
}
}
if (cb) cb(null, ret)
return ret
} catch (error) {
if (cb) cb(error, null)
return false
}
async resolveDirectory (path, cb) {
path = this.removePrefix(path)
if (path.indexOf('/') !== 0) path = '/' + path
try {
const files = await window.remixFileSystem.readdir(path)
const ret = {}
if (files) {
for (let element of files) {
path = path.replace(/^\/|\/$/g, '') // remove first and last slash
element = element.replace(/^\/|\/$/g, '') // remove first and last slash
const absPath = (path === '/' ? '' : path) + '/' + element
ret[absPath.indexOf('/') === 0 ? absPath.substr(1, absPath.length) : absPath] = { isDirectory: (await window.remixFileSystem.stat(absPath)).isDirectory() }
// ^ ret does not accept path starting with '/'
}
}
if (cb) cb(null, ret)
return ret
} catch (error) {
if (cb) cb(error, null)
}
}
removePrefix (path) {
path = path.indexOf(this.type) === 0 ? path.replace(this.type, '') : path
if (path === '') return '/'
return path
}
removePrefix (path) {
path = path.indexOf(this.type) === 0 ? path.replace(this.type, '') : path
if (path === '') return '/'
return path
}
_normalizePath (path) {
return this.type + path
}
_normalizePath (path) {
return this.type + path
}
isSubDirectory (parent, child) {
if (!parent) return false
if (parent === child) return true
const relative = pathModule.relative(parent, child)
isSubDirectory (parent, child) {
if (!parent) return false
if (parent === child) return true
const relative = pathModule.relative(parent, child)
return !!relative && relative.split(pathModule.sep)[0] !== '..'
}
return !!relative && relative.split(pathModule.sep)[0] !== '..'
}
}
module.exports = FileProvider

@ -1,71 +1,71 @@
export class fileSystem {
name: string
enabled: boolean
available: boolean
fs: any
fsCallBack: any;
hasWorkSpaces: boolean
loaded: boolean
load: () => Promise<unknown>
test: () => Promise<unknown>
name: string
enabled: boolean
available: boolean
fs: any
fsCallBack: any;
hasWorkSpaces: boolean
loaded: boolean
load: () => Promise<unknown>
test: () => Promise<unknown>
constructor() {
this.available = false
this.enabled = false
this.hasWorkSpaces = false
this.loaded = false
}
constructor() {
this.available = false
this.enabled = false
this.hasWorkSpaces = false
this.loaded = false
}
checkWorkspaces = async () => {
try {
await this.fs.stat('.workspaces')
this.hasWorkSpaces = true
} catch (e) {
checkWorkspaces = async () => {
try {
await this.fs.stat('.workspaces')
this.hasWorkSpaces = true
} catch (e) {
}
}
}
set = async () => {
const w = (window as any)
if (!this.loaded) return false
w.remixFileSystem = this.fs
w.remixFileSystem.name = this.name
w.remixFileSystemCallback = this.fsCallBack
return true
}
set = async () => {
const w = (window as any)
if (!this.loaded) return false
w.remixFileSystem = this.fs
w.remixFileSystem.name = this.name
w.remixFileSystemCallback = this.fsCallBack
return true
}
}
export class fileSystems {
fileSystems: Record<string, fileSystem>
constructor() {
this.fileSystems = {}
}
fileSystems: Record<string, fileSystem>
constructor() {
this.fileSystems = {}
}
addFileSystem = async (fs: fileSystem): Promise<boolean> => {
try {
this.fileSystems[fs.name] = fs
await fs.test() && await fs.load()
console.log(fs.name + ' is loaded...')
return true
} catch (e) {
console.log(fs.name + ' not available...')
return false
}
addFileSystem = async (fs: fileSystem): Promise<boolean> => {
try {
this.fileSystems[fs.name] = fs
await fs.test() && await fs.load()
console.log(fs.name + ' is loaded...')
return true
} catch (e) {
console.log(fs.name + ' not available...')
return false
}
/**
}
/**
* sets filesystem using list as fallback
* @param {string[]} names
* @returns {Promise}
*/
setFileSystem = async (filesystems?: fileSystem[]): Promise<fileSystem> => {
for (const fs of filesystems) {
if (fs && this.fileSystems[fs.name]) {
const result = await this.fileSystems[fs.name].set()
if (result) return this.fileSystems[fs.name]
}
}
return null
setFileSystem = async (filesystems?: fileSystem[]): Promise<fileSystem> => {
for (const fs of filesystems) {
if (fs && this.fileSystems[fs.name]) {
const result = await this.fileSystems[fs.name].set()
if (result) return this.fileSystems[fs.name]
}
}
return null
}
}

@ -3,189 +3,189 @@ import JSZip from "jszip"
import { fileSystem } from "../fileSystem"
const _paq = window._paq = window._paq || []
export class fileSystemUtility {
migrate = async (fsFrom: fileSystem, fsTo: fileSystem) => {
try {
await fsFrom.checkWorkspaces()
await fsTo.checkWorkspaces()
if (fsTo.hasWorkSpaces) {
console.log(`${fsTo.name} already has files`)
return true
}
if (!fsFrom.hasWorkSpaces) {
console.log('no files to migrate')
return true
}
const fromFiles = await this.copyFolderToJson('/', null, null, fsFrom.fs)
await this.populateWorkspace(fromFiles, fsTo.fs)
const toFiles = await this.copyFolderToJson('/', null, null, fsTo.fs)
if (hashMessage(JSON.stringify(toFiles)) === hashMessage(JSON.stringify(fromFiles))) {
console.log('file migration successful')
return true
} else {
_paq.push(['trackEvent', 'Migrate', 'error', 'hash mismatch'])
console.log('file migration failed falling back to ' + fsFrom.name)
fsTo.loaded = false
return false
}
} catch (err) {
console.log(err)
_paq.push(['trackEvent', 'Migrate', 'error', err && err.message])
console.log('file migration failed falling back to ' + fsFrom.name)
fsTo.loaded = false
return false
}
migrate = async (fsFrom: fileSystem, fsTo: fileSystem) => {
try {
await fsFrom.checkWorkspaces()
await fsTo.checkWorkspaces()
if (fsTo.hasWorkSpaces) {
console.log(`${fsTo.name} already has files`)
return true
}
if (!fsFrom.hasWorkSpaces) {
console.log('no files to migrate')
return true
}
const fromFiles = await this.copyFolderToJson('/', null, null, fsFrom.fs)
await this.populateWorkspace(fromFiles, fsTo.fs)
const toFiles = await this.copyFolderToJson('/', null, null, fsTo.fs)
if (hashMessage(JSON.stringify(toFiles)) === hashMessage(JSON.stringify(fromFiles))) {
console.log('file migration successful')
return true
} else {
_paq.push(['trackEvent', 'Migrate', 'error', 'hash mismatch'])
console.log('file migration failed falling back to ' + fsFrom.name)
fsTo.loaded = false
return false
}
} catch (err) {
console.log(err)
_paq.push(['trackEvent', 'Migrate', 'error', err && err.message])
console.log('file migration failed falling back to ' + fsFrom.name)
fsTo.loaded = false
return false
}
downloadBackup = async (fs: fileSystem) => {
try {
const zip = new JSZip()
zip.file("readme.txt", "This is a Remix backup file.\nThis zip should be used by the restore backup tool in Remix.\nThe .workspaces directory contains your workspaces.")
await fs.checkWorkspaces()
await this.copyFolderToJson('/', null, null, fs.fs, ({ path, content }) => {
zip.file(path, content)
})
const blob = await zip.generateAsync({ type: 'blob' })
const today = new Date()
const date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate()
const time = today.getHours() + 'h' + today.getMinutes() + 'min'
this.saveAs(blob, `remix-backup-at-${time}-${date}.zip`)
_paq.push(['trackEvent','Backup','download','preload'])
} catch (err) {
_paq.push(['trackEvent','Backup','error',err && err.message])
console.log(err)
}
}
downloadBackup = async (fs: fileSystem) => {
try {
const zip = new JSZip()
zip.file("readme.txt", "This is a Remix backup file.\nThis zip should be used by the restore backup tool in Remix.\nThe .workspaces directory contains your workspaces.")
await fs.checkWorkspaces()
await this.copyFolderToJson('/', null, null, fs.fs, ({ path, content }) => {
zip.file(path, content)
})
const blob = await zip.generateAsync({ type: 'blob' })
const today = new Date()
const date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate()
const time = today.getHours() + 'h' + today.getMinutes() + 'min'
this.saveAs(blob, `remix-backup-at-${time}-${date}.zip`)
_paq.push(['trackEvent','Backup','download','preload'])
} catch (err) {
_paq.push(['trackEvent','Backup','error',err && err.message])
console.log(err)
}
populateWorkspace = async (json, fs) => {
for (const item in json) {
const isFolder = json[item].content === undefined
if (isFolder) {
await this.createDir(item, fs)
await this.populateWorkspace(json[item].children, fs)
} else {
await fs.writeFile(item, json[item].content, 'utf8')
}
}
}
populateWorkspace = async (json, fs) => {
for (const item in json) {
const isFolder = json[item].content === undefined
if (isFolder) {
await this.createDir(item, fs)
await this.populateWorkspace(json[item].children, fs)
} else {
await fs.writeFile(item, json[item].content, 'utf8')
}
}
}
/**
/**
* copy the folder recursively
* @param {string} path is the folder to be copied over
* @param {Function} visitFile is a function called for each visited files
* @param {Function} visitFolder is a function called for each visited folders
*/
copyFolderToJson = async (path, visitFile, visitFolder, fs, cb = null) => {
visitFile = visitFile || (() => { })
visitFolder = visitFolder || (() => { })
return await this._copyFolderToJsonInternal(path, visitFile, visitFolder, fs, cb)
}
copyFolderToJson = async (path, visitFile, visitFolder, fs, cb = null) => {
visitFile = visitFile || (() => { })
visitFolder = visitFolder || (() => { })
return await this._copyFolderToJsonInternal(path, visitFile, visitFolder, fs, cb)
}
/**
/**
* copy the folder recursively (internal use)
* @param {string} path is the folder to be copied over
* @param {Function} visitFile is a function called for each visited files
* @param {Function} visitFolder is a function called for each visited folders
*/
async _copyFolderToJsonInternal(path, visitFile, visitFolder, fs, cb) {
visitFile = visitFile || function () { /* do nothing. */ }
visitFolder = visitFolder || function () { /* do nothing. */ }
const json = {}
// path = this.removePrefix(path)
if (await fs.exists(path)) {
const items = await fs.readdir(path)
visitFolder({ path })
if (items.length !== 0) {
for (const item of items) {
const file: any = {}
const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}`
if ((await fs.stat(curPath)).isDirectory()) {
file.children = await this._copyFolderToJsonInternal(curPath, visitFile, visitFolder, fs, cb)
} else {
file.content = await fs.readFile(curPath, 'utf8')
if (cb) cb({ path: curPath, content: file.content })
visitFile({ path: curPath, content: file.content })
}
json[curPath] = file
}
}
}
return json
}
createDir = async (path, fs) => {
const paths = path.split('/')
if (paths.length && paths[0] === '') paths.shift()
let currentCheck = ''
for (const value of paths) {
currentCheck = currentCheck + (currentCheck ? '/' : '') + value
if (!await fs.exists(currentCheck)) {
await fs.mkdir(currentCheck)
}
async _copyFolderToJsonInternal(path, visitFile, visitFolder, fs, cb) {
visitFile = visitFile || function () { /* do nothing. */ }
visitFolder = visitFolder || function () { /* do nothing. */ }
const json = {}
// path = this.removePrefix(path)
if (await fs.exists(path)) {
const items = await fs.readdir(path)
visitFolder({ path })
if (items.length !== 0) {
for (const item of items) {
const file: any = {}
const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}`
if ((await fs.stat(curPath)).isDirectory()) {
file.children = await this._copyFolderToJsonInternal(curPath, visitFile, visitFolder, fs, cb)
} else {
file.content = await fs.readFile(curPath, 'utf8')
if (cb) cb({ path: curPath, content: file.content })
visitFile({ path: curPath, content: file.content })
}
json[curPath] = file
}
}
}
saveAs = (blob, name) => {
const node = document.createElement('a')
node.download = name
node.rel = 'noopener'
node.href = URL.createObjectURL(blob)
setTimeout(function () { URL.revokeObjectURL(node.href) }, 4E4) // 40s
setTimeout(function () {
try {
node.dispatchEvent(new MouseEvent('click'))
} catch (e) {
const evt = document.createEvent('MouseEvents')
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,
20, false, false, false, false, 0, null)
node.dispatchEvent(evt)
}
}, 0) // 40s
return json
}
createDir = async (path, fs) => {
const paths = path.split('/')
if (paths.length && paths[0] === '') paths.shift()
let currentCheck = ''
for (const value of paths) {
currentCheck = currentCheck + (currentCheck ? '/' : '') + value
if (!await fs.exists(currentCheck)) {
await fs.mkdir(currentCheck)
}
}
}
saveAs = (blob, name) => {
const node = document.createElement('a')
node.download = name
node.rel = 'noopener'
node.href = URL.createObjectURL(blob)
setTimeout(function () { URL.revokeObjectURL(node.href) }, 4E4) // 40s
setTimeout(function () {
try {
node.dispatchEvent(new MouseEvent('click'))
} catch (e) {
const evt = document.createEvent('MouseEvents')
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,
20, false, false, false, false, 0, null)
node.dispatchEvent(evt)
}
}, 0) // 40s
}
}
/* eslint-disable no-template-curly-in-string */
export const migrationTestData = {
'.workspaces': {
'.workspaces': {
children: {
'.workspaces/default_workspace': {
children: {
'.workspaces/default_workspace': {
children: {
'.workspaces/default_workspace/README.txt': {
content: 'TEST README'
}
}
},
'.workspaces/emptyspace': {
'.workspaces/default_workspace/README.txt': {
content: 'TEST README'
}
}
},
'.workspaces/emptyspace': {
},
'.workspaces/workspace_test': {
},
'.workspaces/workspace_test': {
children: {
'.workspaces/workspace_test/TEST_README.txt': {
content: 'TEST README'
},
'.workspaces/workspace_test/test_contracts': {
children: {
'.workspaces/workspace_test/test_contracts/1_Storage.sol': {
content: 'testing'
},
'.workspaces/workspace_test/test_contracts/artifacts': {
children: {
'.workspaces/workspace_test/TEST_README.txt': {
content: 'TEST README'
},
'.workspaces/workspace_test/test_contracts': {
children: {
'.workspaces/workspace_test/test_contracts/1_Storage.sol': {
content: 'testing'
},
'.workspaces/workspace_test/test_contracts/artifacts': {
children: {
'.workspaces/workspace_test/test_contracts/artifacts/Storage_metadata.json': {
content: '{ "test": "data" }'
}
}
}
}
}
'.workspaces/workspace_test/test_contracts/artifacts/Storage_metadata.json': {
content: '{ "test": "data" }'
}
}
}
}
}
}
}
}
}
}

@ -2,90 +2,90 @@ import LightningFS from "@isomorphic-git/lightning-fs"
import { fileSystem } from "../fileSystem"
export class IndexedDBStorage extends LightningFS {
base: LightningFS.PromisifedFS
addSlash: (file: string) => string
extended: { exists: (path: string) => Promise<unknown>; rmdir: (path: any) => Promise<void>; readdir: (path: any) => Promise<string[]>; unlink: (path: any) => Promise<void>; mkdir: (path: any) => Promise<void>; readFile: (path: any, options: any) => Promise<Uint8Array>; rename: (from: any, to: any) => Promise<void>; writeFile: (path: any, content: any, options: any) => Promise<void>; stat: (path: any) => Promise<import("fs").Stats>; init(name: string, opt?: LightningFS.FSConstructorOptions): void; activate(): Promise<void>; deactivate(): Promise<void>; lstat(filePath: string): Promise<import("fs").Stats>; readlink(filePath: string): Promise<string>; symlink(target: string, filePath: string): Promise<void> }
constructor(name: string) {
super(name)
this.addSlash = (file) => {
if (!file.startsWith('/')) file = '/' + file
return file
}
this.base = this.promises
this.extended = {
...this.promises,
exists: async (path: string) => {
return new Promise((resolve) => {
this.base.stat(this.addSlash(path)).then(() => resolve(true)).catch(() => resolve(false))
})
},
rmdir: async (path) => {
return this.base.rmdir(this.addSlash(path))
},
readdir: async (path) => {
return this.base.readdir(this.addSlash(path))
},
unlink: async (path) => {
return this.base.unlink(this.addSlash(path))
},
mkdir: async (path) => {
return this.base.mkdir(this.addSlash(path))
},
readFile: async (path, options) => {
return this.base.readFile(this.addSlash(path), options)
},
rename: async (from, to) => {
return this.base.rename(this.addSlash(from), this.addSlash(to))
},
writeFile: async (path, content, options) => {
return this.base.writeFile(this.addSlash(path), content, options)
},
stat: async (path) => {
return this.base.stat(this.addSlash(path))
}
}
base: LightningFS.PromisifedFS
addSlash: (file: string) => string
extended: { exists: (path: string) => Promise<unknown>; rmdir: (path: any) => Promise<void>; readdir: (path: any) => Promise<string[]>; unlink: (path: any) => Promise<void>; mkdir: (path: any) => Promise<void>; readFile: (path: any, options: any) => Promise<Uint8Array>; rename: (from: any, to: any) => Promise<void>; writeFile: (path: any, content: any, options: any) => Promise<void>; stat: (path: any) => Promise<import("fs").Stats>; init(name: string, opt?: LightningFS.FSConstructorOptions): void; activate(): Promise<void>; deactivate(): Promise<void>; lstat(filePath: string): Promise<import("fs").Stats>; readlink(filePath: string): Promise<string>; symlink(target: string, filePath: string): Promise<void> }
constructor(name: string) {
super(name)
this.addSlash = (file) => {
if (!file.startsWith('/')) file = '/' + file
return file
}
this.base = this.promises
this.extended = {
...this.promises,
exists: async (path: string) => {
return new Promise((resolve) => {
this.base.stat(this.addSlash(path)).then(() => resolve(true)).catch(() => resolve(false))
})
},
rmdir: async (path) => {
return this.base.rmdir(this.addSlash(path))
},
readdir: async (path) => {
return this.base.readdir(this.addSlash(path))
},
unlink: async (path) => {
return this.base.unlink(this.addSlash(path))
},
mkdir: async (path) => {
return this.base.mkdir(this.addSlash(path))
},
readFile: async (path, options) => {
return this.base.readFile(this.addSlash(path), options)
},
rename: async (from, to) => {
return this.base.rename(this.addSlash(from), this.addSlash(to))
},
writeFile: async (path, content, options) => {
return this.base.writeFile(this.addSlash(path), content, options)
},
stat: async (path) => {
return this.base.stat(this.addSlash(path))
}
}
}
}
export class indexedDBFileSystem extends fileSystem {
constructor() {
super()
this.name = 'indexedDB'
}
constructor() {
super()
this.name = 'indexedDB'
}
load = async () => {
return new Promise((resolve, reject) => {
try {
const fs = new IndexedDBStorage('RemixFileSystem')
fs.init('RemixFileSystem')
this.fs = fs.extended
this.fsCallBack = fs
this.loaded = true
resolve(true)
} catch (e) {
reject(e)
}
})
}
load = async () => {
return new Promise((resolve, reject) => {
try {
const fs = new IndexedDBStorage('RemixFileSystem')
fs.init('RemixFileSystem')
this.fs = fs.extended
this.fsCallBack = fs
this.loaded = true
resolve(true)
} catch (e) {
reject(e)
}
})
}
test = async () => {
return new Promise((resolve, reject) => {
if (!window.indexedDB) {
this.available = false
reject('No indexedDB on window')
}
const request = window.indexedDB.open("RemixTestDataBase");
request.onerror = () => {
this.available = false
reject('Error creating test database')
};
request.onsuccess = () => {
window.indexedDB.deleteDatabase("RemixTestDataBase");
this.available = true
resolve(true)
};
})
}
test = async () => {
return new Promise((resolve, reject) => {
if (!window.indexedDB) {
this.available = false
reject('No indexedDB on window')
}
const request = window.indexedDB.open("RemixTestDataBase");
request.onerror = () => {
this.available = false
reject('Error creating test database')
};
request.onsuccess = () => {
window.indexedDB.deleteDatabase("RemixTestDataBase");
this.available = true
resolve(true)
};
})
}
}

@ -2,56 +2,56 @@ import { fileSystem } from "../fileSystem";
export class localStorageFS extends fileSystem {
constructor() {
super()
this.name = 'localstorage'
}
load = async () => {
const me = this
return new Promise((resolve, reject) => {
try {
const w = window as any
w.BrowserFS.install(window)
w.BrowserFS.configure({
fs: 'LocalStorage'
}, async function (e) {
if (e) {
console.log('BrowserFS Error: ' + e)
reject(e)
} else {
me.fs = { ...window.require('fs') }
me.fsCallBack = window.require('fs')
me.fs.readdir = me.fs.readdirSync
me.fs.readFile = me.fs.readFileSync
me.fs.writeFile = me.fs.writeFileSync
me.fs.stat = me.fs.statSync
me.fs.unlink = me.fs.unlinkSync
me.fs.rmdir = me.fs.rmdirSync
me.fs.mkdir = me.fs.mkdirSync
me.fs.rename = me.fs.renameSync
me.fs.exists = me.fs.existsSync
me.loaded = true
resolve(true)
}
})
} catch (e) {
console.log('BrowserFS is not ready!')
reject(e)
}
constructor() {
super()
this.name = 'localstorage'
}
load = async () => {
const me = this
return new Promise((resolve, reject) => {
try {
const w = window as any
w.BrowserFS.install(window)
w.BrowserFS.configure({
fs: 'LocalStorage'
}, async function (e) {
if (e) {
console.log('BrowserFS Error: ' + e)
reject(e)
} else {
me.fs = { ...window.require('fs') }
me.fsCallBack = window.require('fs')
me.fs.readdir = me.fs.readdirSync
me.fs.readFile = me.fs.readFileSync
me.fs.writeFile = me.fs.writeFileSync
me.fs.stat = me.fs.statSync
me.fs.unlink = me.fs.unlinkSync
me.fs.rmdir = me.fs.rmdirSync
me.fs.mkdir = me.fs.mkdirSync
me.fs.rename = me.fs.renameSync
me.fs.exists = me.fs.existsSync
me.loaded = true
resolve(true)
}
})
}
} catch (e) {
console.log('BrowserFS is not ready!')
reject(e)
}
})
}
test = async () => {
return new Promise((resolve, reject) => {
const test = 'test';
try {
localStorage.setItem(test, test);
localStorage.removeItem(test);
resolve(true)
} catch(e) {
reject(e)
}
})
}
test = async () => {
return new Promise((resolve, reject) => {
const test = 'test';
try {
localStorage.setItem(test, test);
localStorage.removeItem(test);
resolve(true)
} catch(e) {
reject(e)
}
})
}
}

@ -2,25 +2,25 @@ import { WebsocketPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
const profile = {
name: 'foundry',
displayName: 'Foundry',
url: 'ws://127.0.0.1:65525',
methods: ['sync'],
description: 'Using Remixd daemon, allow to access foundry API',
kind: 'other',
version: packageJson.version
name: 'foundry',
displayName: 'Foundry',
url: 'ws://127.0.0.1:65525',
methods: ['sync'],
description: 'Using Remixd daemon, allow to access foundry API',
kind: 'other',
version: packageJson.version
}
export class FoundryHandle extends WebsocketPlugin {
constructor () {
super(profile)
}
constructor () {
super(profile)
}
callPluginMethod(key, payload = []) {
if (this.socket.readyState !== this.socket.OPEN) {
console.log(`${this.profile.name} connection is not opened.`)
return false
}
return super.callPluginMethod(key, payload)
callPluginMethod(key, payload = []) {
if (this.socket.readyState !== this.socket.OPEN) {
console.log(`${this.profile.name} connection is not opened.`)
return false
}
return super.callPluginMethod(key, payload)
}
}

@ -2,25 +2,25 @@ import { WebsocketPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
const profile = {
name: 'git',
displayName: 'Git',
url: 'ws://127.0.0.1:65521',
methods: ['execute'],
description: 'Using Remixd daemon, allow to access git API',
kind: 'other',
version: packageJson.version
name: 'git',
displayName: 'Git',
url: 'ws://127.0.0.1:65521',
methods: ['execute'],
description: 'Using Remixd daemon, allow to access git API',
kind: 'other',
version: packageJson.version
}
export class GitHandle extends WebsocketPlugin {
constructor () {
super(profile)
}
constructor () {
super(profile)
}
callPluginMethod(key, payload = []) {
if (this.socket.readyState !== this.socket.OPEN) {
console.log(`${this.profile.name} connection is not opened.`)
return false
}
return super.callPluginMethod(key, payload)
callPluginMethod(key, payload = []) {
if (this.socket.readyState !== this.socket.OPEN) {
console.log(`${this.profile.name} connection is not opened.`)
return false
}
return super.callPluginMethod(key, payload)
}
}

@ -2,25 +2,25 @@ import { WebsocketPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
const profile = {
name: 'hardhat',
displayName: 'Hardhat',
url: 'ws://127.0.0.1:65522',
methods: ['compile', 'sync'],
description: 'Using Remixd daemon, allow to access hardhat API',
kind: 'other',
version: packageJson.version
name: 'hardhat',
displayName: 'Hardhat',
url: 'ws://127.0.0.1:65522',
methods: ['compile', 'sync'],
description: 'Using Remixd daemon, allow to access hardhat API',
kind: 'other',
version: packageJson.version
}
export class HardhatHandle extends WebsocketPlugin {
constructor () {
super(profile)
}
constructor () {
super(profile)
}
callPluginMethod(key, payload = []) {
if (this.socket.readyState !== this.socket.OPEN) {
console.log(`${this.profile.name} connection is not opened.`)
return false
}
return super.callPluginMethod(key, payload)
callPluginMethod(key, payload = []) {
if (this.socket.readyState !== this.socket.OPEN) {
console.log(`${this.profile.name} connection is not opened.`)
return false
}
return super.callPluginMethod(key, payload)
}
}

@ -2,209 +2,209 @@
const FileProvider = require('./fileProvider')
module.exports = class RemixDProvider extends FileProvider {
constructor (appManager) {
super('localhost')
this._appManager = appManager
this.error = { EEXIST: 'File already exists' }
this._isReady = false
this._readOnlyFiles = {}
this._readOnlyMode = false
this.filesContent = {}
this.files = {}
}
_registerEvent () {
var remixdEvents = ['connecting', 'connected', 'errored', 'closed']
remixdEvents.forEach((event) => {
this._appManager.on('remixd', event, (value) => {
this.event.emit(event, value)
})
})
this._appManager.on('remixd', 'folderAdded', (path) => {
this.event.emit('folderAdded', path)
})
this._appManager.on('remixd', 'fileAdded', (path) => {
this.event.emit('fileAdded', path)
})
this._appManager.on('remixd', 'fileChanged', (path) => {
this.event.emit('fileChanged', path)
})
this._appManager.on('remixd', 'fileRemoved', (path) => {
this.event.emit('fileRemoved', path)
})
this._appManager.on('remixd', 'fileRenamed', (oldPath, newPath) => {
this.event.emit('fileRenamed', oldPath, newPath)
})
this._appManager.on('remixd', 'rootFolderChanged', (path) => {
this.event.emit('rootFolderChanged', path)
constructor (appManager) {
super('localhost')
this._appManager = appManager
this.error = { EEXIST: 'File already exists' }
this._isReady = false
this._readOnlyFiles = {}
this._readOnlyMode = false
this.filesContent = {}
this.files = {}
}
_registerEvent () {
var remixdEvents = ['connecting', 'connected', 'errored', 'closed']
remixdEvents.forEach((event) => {
this._appManager.on('remixd', event, (value) => {
this.event.emit(event, value)
})
})
this._appManager.on('remixd', 'folderAdded', (path) => {
this.event.emit('folderAdded', path)
})
this._appManager.on('remixd', 'fileAdded', (path) => {
this.event.emit('fileAdded', path)
})
this._appManager.on('remixd', 'fileChanged', (path) => {
this.event.emit('fileChanged', path)
})
this._appManager.on('remixd', 'fileRemoved', (path) => {
this.event.emit('fileRemoved', path)
})
this._appManager.on('remixd', 'fileRenamed', (oldPath, newPath) => {
this.event.emit('fileRenamed', oldPath, newPath)
})
this._appManager.on('remixd', 'rootFolderChanged', (path) => {
this.event.emit('rootFolderChanged', path)
})
this._appManager.on('remixd', 'removed', (path) => {
this.event.emit('fileRemoved', path)
})
this._appManager.on('remixd', 'changed', (path) => {
this.get(path, (_error, content) => {
this.event.emit('fileExternallyChanged', path, content)
})
})
}
isConnected () {
return this._isReady
}
close (cb) {
this._isReady = false
cb()
this.event.emit('disconnected')
}
preInit () {
this.event.emit('loadingLocalhost')
}
init (cb) {
if (this._isReady) return cb && cb()
this._appManager.call('remixd', 'folderIsReadOnly', {})
.then((result) => {
this._isReady = true
this._readOnlyMode = result
this.event.emit('readOnlyModeChanged', result)
this._registerEvent()
this.event.emit('connected')
cb && cb()
}).catch((error) => {
cb && cb(error)
})
}
exists (path) {
if (!this._isReady) throw new Error('provider not ready')
const unprefixedpath = this.removePrefix(path)
return this._appManager.call('remixd', 'exists', { path: unprefixedpath })
.then((result) => {
return result
})
.catch((error) => {
throw new Error(error)
})
}
async get (path, cb) {
if (!this._isReady) return cb && cb('provider not ready')
var unprefixedpath = this.removePrefix(path)
try{
const file = await this._appManager.call('remixd', 'get', { path: unprefixedpath })
this.filesContent[path] = file.content
if (file.readonly) { this._readOnlyFiles[path] = 1 }
if(cb) cb(null, file.content)
return file.content
} catch(error) {
if (error) console.log(error)
if(cb) return cb(null, this.filesContent[path])
}
}
async set (path, content, cb) {
if (!this._isReady) return cb && cb('provider not ready')
const unprefixedpath = this.removePrefix(path)
return this._appManager.call('remixd', 'set', { path: unprefixedpath, content: content }).then(async (result) => {
if (cb) return cb(null, result)
}).catch((error) => {
if (cb) return cb(error)
throw new Error(error)
})
}
async createDir (path, cb) {
if (!this._isReady) return cb && cb('provider not ready')
const unprefixedpath = this.removePrefix(path)
return this._appManager.call('remixd', 'createDir', { path: unprefixedpath })
}
isReadOnly (path) {
return this._readOnlyMode || this._readOnlyFiles[path] === 1
}
remove (path) {
return new Promise((resolve, reject) => {
if (!this._isReady) return reject(new Error('provider not ready'))
const unprefixedpath = this.removePrefix(path)
this._appManager.call('remixd', 'remove', { path: unprefixedpath })
.then(result => {
const path = unprefixedpath
delete this.filesContent[path]
resolve(true)
this.init()
}).catch(error => {
if (error) console.log(error)
resolve(false)
})
this._appManager.on('remixd', 'removed', (path) => {
this.event.emit('fileRemoved', path)
})
this._appManager.on('remixd', 'changed', (path) => {
this.get(path, (_error, content) => {
this.event.emit('fileExternallyChanged', path, content)
})
})
}
rename (oldPath, newPath, isFolder) {
const unprefixedoldPath = this.removePrefix(oldPath)
const unprefixednewPath = this.removePrefix(newPath)
if (!this._isReady) return new Promise((resolve, reject) => reject(new Error('provider not ready')))
return this._appManager.call('remixd', 'rename', { oldPath: unprefixedoldPath, newPath: unprefixednewPath })
.then(result => {
const newPath = unprefixednewPath
const oldPath = unprefixedoldPath
this.filesContent[newPath] = this.filesContent[oldPath]
delete this.filesContent[oldPath]
this.init(() => {
this.event.emit('fileRenamed', oldPath, newPath, isFolder)
})
}
isConnected () {
return this._isReady
}
close (cb) {
this._isReady = false
cb()
this.event.emit('disconnected')
}
preInit () {
this.event.emit('loadingLocalhost')
}
init (cb) {
if (this._isReady) return cb && cb()
this._appManager.call('remixd', 'folderIsReadOnly', {})
.then((result) => {
this._isReady = true
this._readOnlyMode = result
this.event.emit('readOnlyModeChanged', result)
this._registerEvent()
this.event.emit('connected')
cb && cb()
}).catch((error) => {
cb && cb(error)
})
}
exists (path) {
if (!this._isReady) throw new Error('provider not ready')
const unprefixedpath = this.removePrefix(path)
return this._appManager.call('remixd', 'exists', { path: unprefixedpath })
.then((result) => {
return result
})
.catch((error) => {
throw new Error(error)
})
}
async get (path, cb) {
if (!this._isReady) return cb && cb('provider not ready')
var unprefixedpath = this.removePrefix(path)
try{
const file = await this._appManager.call('remixd', 'get', { path: unprefixedpath })
this.filesContent[path] = file.content
if (file.readonly) { this._readOnlyFiles[path] = 1 }
if(cb) cb(null, file.content)
return file.content
} catch(error) {
if (error) console.log(error)
if(cb) return cb(null, this.filesContent[path])
}
}
async set (path, content, cb) {
if (!this._isReady) return cb && cb('provider not ready')
const unprefixedpath = this.removePrefix(path)
return this._appManager.call('remixd', 'set', { path: unprefixedpath, content: content }).then(async (result) => {
if (cb) return cb(null, result)
}).catch((error) => {
if (cb) return cb(error)
throw new Error(error)
})
}
async createDir (path, cb) {
if (!this._isReady) return cb && cb('provider not ready')
const unprefixedpath = this.removePrefix(path)
return this._appManager.call('remixd', 'createDir', { path: unprefixedpath })
}
isReadOnly (path) {
return this._readOnlyMode || this._readOnlyFiles[path] === 1
}
remove (path) {
return new Promise((resolve, reject) => {
if (!this._isReady) return reject(new Error('provider not ready'))
const unprefixedpath = this.removePrefix(path)
this._appManager.call('remixd', 'remove', { path: unprefixedpath })
.then(result => {
const path = unprefixedpath
delete this.filesContent[path]
resolve(true)
this.init()
}).catch(error => {
if (error) console.log(error)
resolve(false)
})
})
}
rename (oldPath, newPath, isFolder) {
const unprefixedoldPath = this.removePrefix(oldPath)
const unprefixednewPath = this.removePrefix(newPath)
if (!this._isReady) return new Promise((resolve, reject) => reject(new Error('provider not ready')))
return this._appManager.call('remixd', 'rename', { oldPath: unprefixedoldPath, newPath: unprefixednewPath })
.then(result => {
const newPath = unprefixednewPath
const oldPath = unprefixedoldPath
this.filesContent[newPath] = this.filesContent[oldPath]
delete this.filesContent[oldPath]
this.init(() => {
this.event.emit('fileRenamed', oldPath, newPath, isFolder)
})
return result
}).catch(error => {
console.log(error)
if (this.error[error.code]) error = this.error[error.code]
this.event.emit('fileRenamedError', this.error[error.code])
})
}
isExternalFolder (path) {
return false
}
removePrefix (path) {
path = path.indexOf(this.type) === 0 ? path.replace(this.type, '') : path
if (path[0] === '/') return path.substring(1)
if (path === '') return '/'
return path
}
resolveDirectory (path, callback) {
if (path[0] === '/') path = path.substring(1)
const unprefixedpath = this.removePrefix(path)
if (!this._isReady) return callback && callback('provider not ready')
this._appManager.call('remixd', 'resolveDirectory', { path: unprefixedpath }).then((result) => {
callback(null, result)
}).catch(callback)
}
async isDirectory (path) {
const unprefixedpath = this.removePrefix(path)
if (!this._isReady) throw new Error('provider not ready')
return await this._appManager.call('remixd', 'isDirectory', { path: unprefixedpath })
}
async isFile (path) {
const unprefixedpath = this.removePrefix(path)
if (!this._isReady) throw new Error('provider not ready')
return await this._appManager.call('remixd', 'isFile', { path: unprefixedpath })
}
return result
}).catch(error => {
console.log(error)
if (this.error[error.code]) error = this.error[error.code]
this.event.emit('fileRenamedError', this.error[error.code])
})
}
isExternalFolder (path) {
return false
}
removePrefix (path) {
path = path.indexOf(this.type) === 0 ? path.replace(this.type, '') : path
if (path[0] === '/') return path.substring(1)
if (path === '') return '/'
return path
}
resolveDirectory (path, callback) {
if (path[0] === '/') path = path.substring(1)
const unprefixedpath = this.removePrefix(path)
if (!this._isReady) return callback && callback('provider not ready')
this._appManager.call('remixd', 'resolveDirectory', { path: unprefixedpath }).then((result) => {
callback(null, result)
}).catch(callback)
}
async isDirectory (path) {
const unprefixedpath = this.removePrefix(path)
if (!this._isReady) throw new Error('provider not ready')
return await this._appManager.call('remixd', 'isDirectory', { path: unprefixedpath })
}
async isFile (path) {
const unprefixedpath = this.removePrefix(path)
if (!this._isReady) throw new Error('provider not ready')
return await this._appManager.call('remixd', 'isFile', { path: unprefixedpath })
}
}

@ -2,26 +2,26 @@ import { WebsocketPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
const profile = {
name: 'slither',
displayName: 'Slither',
url: 'ws://127.0.0.1:65523',
methods: ['analyse'],
description: 'Using Remixd daemon, run slither static analysis',
kind: 'other',
version: packageJson.version,
documentation: 'https://remix-ide.readthedocs.io/en/latest/slither.html'
name: 'slither',
displayName: 'Slither',
url: 'ws://127.0.0.1:65523',
methods: ['analyse'],
description: 'Using Remixd daemon, run slither static analysis',
kind: 'other',
version: packageJson.version,
documentation: 'https://remix-ide.readthedocs.io/en/latest/slither.html'
}
export class SlitherHandle extends WebsocketPlugin {
constructor () {
super(profile)
}
constructor () {
super(profile)
}
callPluginMethod(key, payload = []) {
if (this.socket.readyState !== this.socket.OPEN) {
console.log(`${this.profile.name} connection is not opened.`)
return false
}
return super.callPluginMethod(key, payload)
callPluginMethod(key, payload = []) {
if (this.socket.readyState !== this.socket.OPEN) {
console.log(`${this.profile.name} connection is not opened.`)
return false
}
return super.callPluginMethod(key, payload)
}
}

@ -2,26 +2,26 @@ import { WebsocketPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
const profile = {
name: 'truffle',
displayName: 'truffle',
url: 'ws://127.0.0.1:65524',
methods: ['compile', 'sync'],
description: 'Using Remixd daemon, allow to access truffle API',
kind: 'other',
version: packageJson.version
name: 'truffle',
displayName: 'truffle',
url: 'ws://127.0.0.1:65524',
methods: ['compile', 'sync'],
description: 'Using Remixd daemon, allow to access truffle API',
kind: 'other',
version: packageJson.version
}
export class TruffleHandle extends WebsocketPlugin {
constructor () {
super(profile)
}
constructor () {
super(profile)
}
callPluginMethod(key, payload = []) {
if (this.socket.readyState !== this.socket.OPEN) {
console.log(`${this.profile.name} connection is not opened.`)
return false
}
return super.callPluginMethod(key, payload)
callPluginMethod(key, payload = []) {
if (this.socket.readyState !== this.socket.OPEN) {
console.log(`${this.profile.name} connection is not opened.`)
return false
}
return super.callPluginMethod(key, payload)
}
}

@ -4,84 +4,84 @@ const EventManager = require('events')
const FileProvider = require('./fileProvider')
class WorkspaceFileProvider extends FileProvider {
constructor () {
super('')
this.workspacesPath = '.workspaces'
this.workspace = null
this.event = new EventManager()
}
constructor () {
super('')
this.workspacesPath = '.workspaces'
this.workspace = null
this.event = new EventManager()
}
setWorkspace (workspace) {
if (!workspace) return
workspace = workspace.replace(/^\/|\/$/g, '') // remove first and last slash
this.workspace = workspace
}
setWorkspace (workspace) {
if (!workspace) return
workspace = workspace.replace(/^\/|\/$/g, '') // remove first and last slash
this.workspace = workspace
}
getWorkspace () {
return this.workspace
}
getWorkspace () {
return this.workspace
}
isReady () {
return this.workspace !== null
}
isReady () {
return this.workspace !== null
}
clearWorkspace () {
this.workspace = null
}
clearWorkspace () {
this.workspace = null
}
removePrefix (path) {
if (!path) path = '/'
path = path.replace(/^\/|\/$/g, '') // remove first and last slash
path = path.replace(/^\.\/+/, '') // remove ./ from start of string
if (path.startsWith(this.workspacesPath + '/' + this.workspace)) return path
path = super.removePrefix(path)
let ret = this.workspacesPath + '/' + this.workspace + '/' + (path === '/' ? '' : path)
removePrefix (path) {
if (!path) path = '/'
path = path.replace(/^\/|\/$/g, '') // remove first and last slash
path = path.replace(/^\.\/+/, '') // remove ./ from start of string
if (path.startsWith(this.workspacesPath + '/' + this.workspace)) return path
path = super.removePrefix(path)
let ret = this.workspacesPath + '/' + this.workspace + '/' + (path === '/' ? '' : path)
ret = ret.replace(/^\/|\/$/g, '')
if (!this.isSubDirectory(this.workspacesPath + '/' + this.workspace, ret)) throw new Error('Cannot read/write to path outside workspace')
return ret
}
ret = ret.replace(/^\/|\/$/g, '')
if (!this.isSubDirectory(this.workspacesPath + '/' + this.workspace, ret)) throw new Error('Cannot read/write to path outside workspace')
return ret
}
resolveDirectory (path, callback) {
super.resolveDirectory(path, (error, files) => {
if (error) return callback(error)
const unscoped = {}
for (const file in files) {
unscoped[file.replace(this.workspacesPath + '/' + this.workspace + '/', '')] = files[file]
}
callback(null, unscoped)
})
}
resolveDirectory (path, callback) {
super.resolveDirectory(path, (error, files) => {
if (error) return callback(error)
const unscoped = {}
for (const file in files) {
unscoped[file.replace(this.workspacesPath + '/' + this.workspace + '/', '')] = files[file]
}
callback(null, unscoped)
})
}
async copyFolderToJson (directory, visitFile, visitFolder) {
visitFile = visitFile || function () { /* do nothing. */ }
visitFolder = visitFolder || function () { /* do nothing. */ }
const regex = new RegExp(`.workspaces/${this.workspace}/`, 'g')
let json = await super._copyFolderToJsonInternal(directory, ({ path, content }) => {
visitFile({ path: path.replace(regex, ''), content })
}, ({ path }) => {
visitFolder({ path: path.replace(regex, '') })
})
json = JSON.stringify(json).replace(regex, '')
return JSON.parse(json)
}
async copyFolderToJson (directory, visitFile, visitFolder) {
visitFile = visitFile || function () { /* do nothing. */ }
visitFolder = visitFolder || function () { /* do nothing. */ }
const regex = new RegExp(`.workspaces/${this.workspace}/`, 'g')
let json = await super._copyFolderToJsonInternal(directory, ({ path, content }) => {
visitFile({ path: path.replace(regex, ''), content })
}, ({ path }) => {
visitFolder({ path: path.replace(regex, '') })
})
json = JSON.stringify(json).replace(regex, '')
return JSON.parse(json)
}
_normalizePath (path) {
return path.replace(this.workspacesPath + '/' + this.workspace + '/', '')
}
_normalizePath (path) {
return path.replace(this.workspacesPath + '/' + this.workspace + '/', '')
}
async createWorkspace (name) {
try {
if (!name) name = 'default_workspace'
const path = this.workspacesPath + '/' + name
async createWorkspace (name) {
try {
if (!name) name = 'default_workspace'
const path = this.workspacesPath + '/' + name
await super.forceCreateDir(path)
this.setWorkspace(name)
this.event.emit('createWorkspace', name)
} catch (e) {
throw new Error(e)
}
await super.forceCreateDir(path)
this.setWorkspace(name)
this.event.emit('createWorkspace', name)
} catch (e) {
throw new Error(e)
}
}
}
module.exports = WorkspaceFileProvider

@ -28,43 +28,43 @@ const { SlitherHandle } = require('../files/slither-handle.js')
*/
const profile = {
name: 'filePanel',
displayName: 'File explorer',
methods: ['createNewFile', 'uploadFile', 'getCurrentWorkspace', 'getAvailableWorkspaceName', 'getWorkspaces', 'createWorkspace', 'setWorkspace', 'registerContextMenuItem', 'renameWorkspace', 'deleteWorkspace'],
events: ['setWorkspace', 'workspaceRenamed', 'workspaceDeleted', 'workspaceCreated'],
icon: 'assets/img/fileManager.webp',
description: 'Remix IDE file explorer',
kind: 'fileexplorer',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/file_explorer.html',
version: packageJson.version,
maintainedBy: 'Remix'
name: 'filePanel',
displayName: 'File explorer',
methods: ['createNewFile', 'uploadFile', 'getCurrentWorkspace', 'getAvailableWorkspaceName', 'getWorkspaces', 'createWorkspace', 'setWorkspace', 'registerContextMenuItem', 'renameWorkspace', 'deleteWorkspace'],
events: ['setWorkspace', 'workspaceRenamed', 'workspaceDeleted', 'workspaceCreated'],
icon: 'assets/img/fileManager.webp',
description: 'Remix IDE file explorer',
kind: 'fileexplorer',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/file_explorer.html',
version: packageJson.version,
maintainedBy: 'Remix'
}
module.exports = class Filepanel extends ViewPlugin {
constructor (appManager) {
super(profile)
this.registry = Registry.getInstance()
this.fileProviders = this.registry.get('fileproviders').api
this.fileManager = this.registry.get('filemanager').api
this.el = document.createElement('div')
this.el.setAttribute('id', 'fileExplorerView')
this.remixdHandle = new RemixdHandle(this.fileProviders.localhost, appManager)
this.hardhatHandle = new HardhatHandle()
this.foundryHandle = new FoundryHandle()
this.truffleHandle = new TruffleHandle()
this.slitherHandle = new SlitherHandle()
this.workspaces = []
this.appManager = appManager
this.currentWorkspaceMetadata = null
}
render () {
return <div id='fileExplorerView'><FileSystemProvider plugin={this} /></div>
}
/**
constructor (appManager) {
super(profile)
this.registry = Registry.getInstance()
this.fileProviders = this.registry.get('fileproviders').api
this.fileManager = this.registry.get('filemanager').api
this.el = document.createElement('div')
this.el.setAttribute('id', 'fileExplorerView')
this.remixdHandle = new RemixdHandle(this.fileProviders.localhost, appManager)
this.hardhatHandle = new HardhatHandle()
this.foundryHandle = new FoundryHandle()
this.truffleHandle = new TruffleHandle()
this.slitherHandle = new SlitherHandle()
this.workspaces = []
this.appManager = appManager
this.currentWorkspaceMetadata = null
}
render () {
return <div id='fileExplorerView'><FileSystemProvider plugin={this} /></div>
}
/**
* @param item { id: string, name: string, type?: string[], path?: string[], extension?: string[], pattern?: string[] }
* typically:
* group 0 for file manipulations
@ -77,112 +77,112 @@ module.exports = class Filepanel extends ViewPlugin {
* group 7 for generating resource files (UML, documentation, ...)
* @param callback (...args) => void
*/
registerContextMenuItem (item) {
return new Promise((resolve, reject) => {
this.emit('registerContextMenuItemReducerEvent', item, (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
}
removePluginActions (plugin) {
return new Promise((resolve, reject) => {
this.emit('removePluginActionsReducerEvent', plugin, (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
}
getCurrentWorkspace () {
return this.currentWorkspaceMetadata
}
getWorkspaces () {
return this.workspaces
}
getAvailableWorkspaceName (name) {
if(!this.workspaces) return name
let index = 1
let workspace = this.workspaces.find(workspace => workspace.name === name + ' - ' + index)
while (workspace) {
index++
workspace = this.workspaces.find(workspace => workspace.name === name + ' - ' + index)
}
return name + ' - ' + index
}
setWorkspaces (workspaces) {
this.workspaces = workspaces
}
createNewFile () {
return new Promise((resolve, reject) => {
this.emit('createNewFileInputReducerEvent', '/', (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
}
uploadFile (target) {
return new Promise((resolve, reject) => {
return this.emit('uploadFileReducerEvent', '/', target, (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
}
createWorkspace (workspaceName, workspaceTemplateName, isEmpty) {
return new Promise((resolve, reject) => {
this.emit('createWorkspaceReducerEvent', workspaceName, workspaceTemplateName, isEmpty, (err, data) => {
if (err) reject(err)
else resolve(data || true)
})
})
}
renameWorkspace (oldName, workspaceName) {
return new Promise((resolve, reject) => {
this.emit('renameWorkspaceReducerEvent', oldName, workspaceName, (err, data) => {
if (err) reject(err)
else resolve(data || true)
})
})
}
deleteWorkspace (workspaceName) {
return new Promise((resolve, reject) => {
this.emit('deleteWorkspaceReducerEvent', workspaceName, (err, data) => {
if (err) reject(err)
else resolve(data || true)
})
})
}
setWorkspace (workspace) {
const workspaceProvider = this.fileProviders.workspace
this.currentWorkspaceMetadata = { name: workspace.name, isLocalhost: workspace.isLocalhost, absolutePath: `${workspaceProvider.workspacesPath}/${workspace.name}` }
if (workspace.name !== " - connect to localhost - ") {
localStorage.setItem('currentWorkspace', workspace.name)
}
this.emit('setWorkspace', workspace)
}
workspaceRenamed (oldName, workspaceName) {
this.emit('workspaceRenamed', oldName, workspaceName)
}
workspaceDeleted (workspace) {
this.emit('workspaceDeleted', workspace)
}
workspaceCreated (workspace) {
this.emit('workspaceCreated', workspace)
}
/** end section */
registerContextMenuItem (item) {
return new Promise((resolve, reject) => {
this.emit('registerContextMenuItemReducerEvent', item, (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
}
removePluginActions (plugin) {
return new Promise((resolve, reject) => {
this.emit('removePluginActionsReducerEvent', plugin, (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
}
getCurrentWorkspace () {
return this.currentWorkspaceMetadata
}
getWorkspaces () {
return this.workspaces
}
getAvailableWorkspaceName (name) {
if(!this.workspaces) return name
let index = 1
let workspace = this.workspaces.find(workspace => workspace.name === name + ' - ' + index)
while (workspace) {
index++
workspace = this.workspaces.find(workspace => workspace.name === name + ' - ' + index)
}
return name + ' - ' + index
}
setWorkspaces (workspaces) {
this.workspaces = workspaces
}
createNewFile () {
return new Promise((resolve, reject) => {
this.emit('createNewFileInputReducerEvent', '/', (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
}
uploadFile (target) {
return new Promise((resolve, reject) => {
return this.emit('uploadFileReducerEvent', '/', target, (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
}
createWorkspace (workspaceName, workspaceTemplateName, isEmpty) {
return new Promise((resolve, reject) => {
this.emit('createWorkspaceReducerEvent', workspaceName, workspaceTemplateName, isEmpty, (err, data) => {
if (err) reject(err)
else resolve(data || true)
})
})
}
renameWorkspace (oldName, workspaceName) {
return new Promise((resolve, reject) => {
this.emit('renameWorkspaceReducerEvent', oldName, workspaceName, (err, data) => {
if (err) reject(err)
else resolve(data || true)
})
})
}
deleteWorkspace (workspaceName) {
return new Promise((resolve, reject) => {
this.emit('deleteWorkspaceReducerEvent', workspaceName, (err, data) => {
if (err) reject(err)
else resolve(data || true)
})
})
}
setWorkspace (workspace) {
const workspaceProvider = this.fileProviders.workspace
this.currentWorkspaceMetadata = { name: workspace.name, isLocalhost: workspace.isLocalhost, absolutePath: `${workspaceProvider.workspacesPath}/${workspace.name}` }
if (workspace.name !== " - connect to localhost - ") {
localStorage.setItem('currentWorkspace', workspace.name)
}
this.emit('setWorkspace', workspace)
}
workspaceRenamed (oldName, workspaceName) {
this.emit('workspaceRenamed', oldName, workspaceName)
}
workspaceDeleted (workspace) {
this.emit('workspaceDeleted', workspace)
}
workspaceCreated (workspace) {
this.emit('workspaceCreated', workspace)
}
/** end section */
}

@ -4,9 +4,9 @@ import { EventEmitter } from 'events'
import { QueryParams } from '@remix-project/remix-lib'
const profile: Profile = {
name: 'layout',
description: 'layout',
methods: ['minimize', 'maximiseSidePanel', 'resetSidePanel']
name: 'layout',
description: 'layout',
methods: ['minimize', 'maximiseSidePanel', 'resetSidePanel']
}
interface panelState {
@ -28,90 +28,90 @@ export type PanelConfiguration = {
}
export class Layout extends Plugin {
event: any
panels: panels
maximised: { [key: string]: boolean }
constructor () {
super(profile)
this.maximised = {}
this.event = new EventEmitter()
}
event: any
panels: panels
maximised: { [key: string]: boolean }
constructor () {
super(profile)
this.maximised = {}
this.event = new EventEmitter()
}
async onActivation (): Promise<void> {
this.on('fileManager', 'currentFileChanged', () => {
this.panels.editor.active = true
this.panels.main.active = false
this.event.emit('change', null)
})
this.on('tabs', 'openFile', () => {
this.panels.editor.active = true
this.panels.main.active = false
this.event.emit('change', null)
})
this.on('tabs', 'switchApp', (name: string) => {
this.call('mainPanel', 'showContent', name)
this.panels.editor.active = false
this.panels.main.active = true
this.event.emit('change', null)
})
this.on('tabs', 'closeApp', (name: string) => {
this.panels.editor.active = true
this.panels.main.active = false
this.event.emit('change', null)
})
this.on('manager', 'activate', (profile: Profile) => {
switch (profile.name) {
case 'filePanel':
this.call('menuicons', 'select', 'filePanel')
break
}
})
this.on('sidePanel', 'focusChanged', async (name) => {
const current = await this.call('sidePanel', 'currentFocus')
if (this.maximised[current]) {
this.event.emit('maximisesidepanel')
} else {
this.event.emit('resetsidepanel')
}
})
document.addEventListener('keypress', e => {
if (e.shiftKey && e.ctrlKey) {
if (e.code === 'KeyF') {
// Ctrl+Shift+F
this.call('menuicons', 'select', 'filePanel')
} else if (e.code === 'KeyA') {
// Ctrl+Shift+A
this.call('menuicons', 'select', 'pluginManager')
}
e.preventDefault()
}
})
const queryParams = new QueryParams()
const params = queryParams.get() as PanelConfiguration
if (params.minimizeterminal || params.embed) {
this.panels.terminal.minimized = true
this.event.emit('change', this.panels)
this.emit('change', this.panels)
}
if (params.minimizesidepanel || params.embed) {
this.event.emit('minimizesidepanel')
async onActivation (): Promise<void> {
this.on('fileManager', 'currentFileChanged', () => {
this.panels.editor.active = true
this.panels.main.active = false
this.event.emit('change', null)
})
this.on('tabs', 'openFile', () => {
this.panels.editor.active = true
this.panels.main.active = false
this.event.emit('change', null)
})
this.on('tabs', 'switchApp', (name: string) => {
this.call('mainPanel', 'showContent', name)
this.panels.editor.active = false
this.panels.main.active = true
this.event.emit('change', null)
})
this.on('tabs', 'closeApp', (name: string) => {
this.panels.editor.active = true
this.panels.main.active = false
this.event.emit('change', null)
})
this.on('manager', 'activate', (profile: Profile) => {
switch (profile.name) {
case 'filePanel':
this.call('menuicons', 'select', 'filePanel')
break
}
})
this.on('sidePanel', 'focusChanged', async (name) => {
const current = await this.call('sidePanel', 'currentFocus')
if (this.maximised[current]) {
this.event.emit('maximisesidepanel')
} else {
this.event.emit('resetsidepanel')
}
})
document.addEventListener('keypress', e => {
if (e.shiftKey && e.ctrlKey) {
if (e.code === 'KeyF') {
// Ctrl+Shift+F
this.call('menuicons', 'select', 'filePanel')
} else if (e.code === 'KeyA') {
// Ctrl+Shift+A
this.call('menuicons', 'select', 'pluginManager')
}
e.preventDefault()
}
})
const queryParams = new QueryParams()
const params = queryParams.get() as PanelConfiguration
if (params.minimizeterminal || params.embed) {
this.panels.terminal.minimized = true
this.event.emit('change', this.panels)
this.emit('change', this.panels)
}
minimize (name: string, minimized:boolean): void {
this.panels[name].minimized = minimized
this.event.emit('change', null)
if (params.minimizesidepanel || params.embed) {
this.event.emit('minimizesidepanel')
}
}
async maximiseSidePanel () {
this.event.emit('maximisesidepanel')
const current = await this.call('sidePanel', 'currentFocus')
this.maximised[current] = true
}
minimize (name: string, minimized:boolean): void {
this.panels[name].minimized = minimized
this.event.emit('change', null)
}
async resetSidePanel () {
this.event.emit('resetsidepanel')
const current = await this.call('sidePanel', 'currentFocus')
this.maximised[current] = false
}
async maximiseSidePanel () {
this.event.emit('maximisesidepanel')
const current = await this.call('sidePanel', 'currentFocus')
this.maximised[current] = true
}
async resetSidePanel () {
this.event.emit('resetsidepanel')
const current = await this.call('sidePanel', 'currentFocus')
this.maximised[current] = false
}
}

@ -5,359 +5,359 @@ import { PluginViewWrapper, getPathIcon } from '@remix-ui/helper'
const EventEmitter = require('events')
const profile = {
name: 'tabs',
methods: ['focus'],
kind: 'other'
name: 'tabs',
methods: ['focus'],
kind: 'other'
}
export class TabProxy extends Plugin {
constructor (fileManager, editor) {
super(profile)
this.event = new EventEmitter()
this.fileManager = fileManager
this.editor = editor
this.data = {}
this._view = {}
this._handlers = {}
this.loadedTabs = []
this.dispatch = null
this.themeQuality = 'dark'
}
async onActivation () {
this.on('theme', 'themeChanged', (theme) => {
this.themeQuality = theme.quality
// update invert for all icons
this.renderComponent()
})
this.on('fileManager', 'filesAllClosed', () => {
this.call('manager', 'activatePlugin', 'home')
this.focus('home')
})
this.on('fileManager', 'fileRemoved', (name) => {
const workspace = this.fileManager.currentWorkspace()
if (this.fileManager.mode === 'browser') {
name = name.startsWith(workspace + '/') ? name : workspace + '/' + name
// If deleted file is not current file and not an active tab in editor,
// ensure current file is active in the editor
if (this.fileManager.currentFile() && name !== this.fileManager.currentFile()) {
const currentFile = this.fileManager.currentFile()
const currentFileTabPath = currentFile.startsWith(workspace + '/') ? currentFile : workspace + '/' + currentFile
this.removeTab(name, { name: currentFileTabPath })
} else this.removeTab(name)
} else {
name = name.startsWith(this.fileManager.mode + '/') ? name : this.fileManager.mode + '/' + name
this.removeTab(name)
}
constructor (fileManager, editor) {
super(profile)
this.event = new EventEmitter()
this.fileManager = fileManager
this.editor = editor
this.data = {}
this._view = {}
this._handlers = {}
this.loadedTabs = []
this.dispatch = null
this.themeQuality = 'dark'
}
async onActivation () {
this.on('theme', 'themeChanged', (theme) => {
this.themeQuality = theme.quality
// update invert for all icons
this.renderComponent()
})
this.on('fileManager', 'filesAllClosed', () => {
this.call('manager', 'activatePlugin', 'home')
this.focus('home')
})
this.on('fileManager', 'fileRemoved', (name) => {
const workspace = this.fileManager.currentWorkspace()
if (this.fileManager.mode === 'browser') {
name = name.startsWith(workspace + '/') ? name : workspace + '/' + name
// If deleted file is not current file and not an active tab in editor,
// ensure current file is active in the editor
if (this.fileManager.currentFile() && name !== this.fileManager.currentFile()) {
const currentFile = this.fileManager.currentFile()
const currentFileTabPath = currentFile.startsWith(workspace + '/') ? currentFile : workspace + '/' + currentFile
this.removeTab(name, { name: currentFileTabPath })
} else this.removeTab(name)
} else {
name = name.startsWith(this.fileManager.mode + '/') ? name : this.fileManager.mode + '/' + name
this.removeTab(name)
}
})
this.on('fileManager', 'fileClosed', (name) => {
const workspace = this.fileManager.currentWorkspace()
if (this.fileManager.mode === 'browser') {
name = name.startsWith(workspace + '/') ? name : workspace + '/' + name
let tabIndex = this.loadedTabs.findIndex(tab => tab.name === name)
// If tab doesn't exist, check if tab is opened because of abrupt disconnection with remixd
if (tabIndex === -1) {
const nameArray = name.split('/')
nameArray.shift()
name = 'localhost' + '/' + nameArray.join('/')
tabIndex = this.loadedTabs.findIndex(tab => tab.name === name)
if(tabIndex !== -1) this.removeTab(name)
} else this.removeTab(name)
} else {
name = name.startsWith(this.fileManager.mode + '/') ? name : this.fileManager.mode + '/' + name
this.removeTab(name)
}
})
this.on('fileManager', 'currentFileChanged', (file) => {
const workspace = this.fileManager.currentWorkspace()
if (this.fileManager.mode === 'browser') {
const workspacePath = workspace + '/' + file
if (this._handlers[workspacePath]) {
this.tabsApi.activateTab(workspacePath)
return
}
this.addTab(workspacePath, '', async () => {
await this.fileManager.open(file)
this.event.emit('openFile', file)
this.emit('openFile', file)
},
async () => {
await this.fileManager.closeFile(file)
this.event.emit('closeFile', file)
this.emit('closeFile', file)
})
this.tabsApi.activateTab(workspacePath)
} else {
const path = file.startsWith(this.fileManager.mode + '/') ? file : this.fileManager.mode + '/' + file
this.on('fileManager', 'fileClosed', (name) => {
const workspace = this.fileManager.currentWorkspace()
if (this.fileManager.mode === 'browser') {
name = name.startsWith(workspace + '/') ? name : workspace + '/' + name
let tabIndex = this.loadedTabs.findIndex(tab => tab.name === name)
// If tab doesn't exist, check if tab is opened because of abrupt disconnection with remixd
if (tabIndex === -1) {
const nameArray = name.split('/')
nameArray.shift()
name = 'localhost' + '/' + nameArray.join('/')
tabIndex = this.loadedTabs.findIndex(tab => tab.name === name)
if(tabIndex !== -1) this.removeTab(name)
} else this.removeTab(name)
} else {
name = name.startsWith(this.fileManager.mode + '/') ? name : this.fileManager.mode + '/' + name
this.removeTab(name)
}
if (this._handlers[path]) {
this.tabsApi.activateTab(path)
return
}
this.addTab(path, '', async () => {
await this.fileManager.open(file)
this.event.emit('openFile', file)
this.emit('openFile', file)
},
async () => {
await this.fileManager.closeFile(file)
this.event.emit('closeFile', file)
this.emit('closeFile', file)
})
this.on('fileManager', 'currentFileChanged', (file) => {
const workspace = this.fileManager.currentWorkspace()
if (this.fileManager.mode === 'browser') {
const workspacePath = workspace + '/' + file
if (this._handlers[workspacePath]) {
this.tabsApi.activateTab(workspacePath)
return
}
this.addTab(workspacePath, '', async () => {
await this.fileManager.open(file)
this.event.emit('openFile', file)
this.emit('openFile', file)
},
async () => {
await this.fileManager.closeFile(file)
this.event.emit('closeFile', file)
this.emit('closeFile', file)
})
this.tabsApi.activateTab(workspacePath)
} else {
const path = file.startsWith(this.fileManager.mode + '/') ? file : this.fileManager.mode + '/' + file
if (this._handlers[path]) {
this.tabsApi.activateTab(path)
return
}
this.addTab(path, '', async () => {
await this.fileManager.open(file)
this.event.emit('openFile', file)
this.emit('openFile', file)
},
async () => {
await this.fileManager.closeFile(file)
this.event.emit('closeFile', file)
this.emit('closeFile', file)
})
this.tabsApi.activateTab(path)
this.tabsApi.activateTab(path)
}
})
this.on('fileManager', 'fileRenamed', (oldName, newName, isFolder) => {
const workspace = this.fileManager.currentWorkspace()
if (this.fileManager.mode === 'browser') {
if (isFolder) {
for (const tab of this.loadedTabs) {
if (tab.name.indexOf(workspace + '/' + oldName + '/') === 0) {
const newTabName = workspace + '/' + newName + tab.name.slice(workspace + '/' + oldName.length, tab.name.length)
this.renameTab(tab.name, newTabName)
}
})
this.on('fileManager', 'fileRenamed', (oldName, newName, isFolder) => {
const workspace = this.fileManager.currentWorkspace()
if (this.fileManager.mode === 'browser') {
if (isFolder) {
for (const tab of this.loadedTabs) {
if (tab.name.indexOf(workspace + '/' + oldName + '/') === 0) {
const newTabName = workspace + '/' + newName + tab.name.slice(workspace + '/' + oldName.length, tab.name.length)
this.renameTab(tab.name, newTabName)
}
}
return
}
// should change the tab title too
this.renameTab(workspace + '/' + oldName, workspace + '/' + newName)
} else {
if (isFolder) {
for (const tab of this.loadedTabs) {
if (tab.name.indexOf(this.fileManager.mode + '/' + oldName + '/') === 0) {
const newTabName = this.fileManager.mode + '/' + newName + tab.name.slice(this.fileManager.mode + '/' + oldName.length, tab.name.length)
this.renameTab(tab.name, newTabName)
}
}
return
}
// should change the tab title too
this.renameTab(this.fileManager.mode + '/' + oldName, this.fileManager.mode + '/' + newName)
}
return
}
// should change the tab title too
this.renameTab(workspace + '/' + oldName, workspace + '/' + newName)
} else {
if (isFolder) {
for (const tab of this.loadedTabs) {
if (tab.name.indexOf(this.fileManager.mode + '/' + oldName + '/') === 0) {
const newTabName = this.fileManager.mode + '/' + newName + tab.name.slice(this.fileManager.mode + '/' + oldName.length, tab.name.length)
this.renameTab(tab.name, newTabName)
}
})
this.on('manager', 'pluginActivated', ({ name, location, displayName, icon, description }) => {
if (location === 'mainPanel') {
this.addTab(
name,
displayName,
() => this.emit('switchApp', name),
() => {
if (name === 'home' && this.loadedTabs.length === 1 && this.loadedTabs[0].id === "home") {
const files = Object.keys(this.editor.sessions)
files.forEach(filepath => this.editor.discard(filepath))
}
this.emit('closeApp', name)
this.call('manager', 'deactivatePlugin', name)
},
icon,
description
)
this.switchTab(name)
}
return
}
// should change the tab title too
this.renameTab(this.fileManager.mode + '/' + oldName, this.fileManager.mode + '/' + newName)
}
})
this.on('manager', 'pluginActivated', ({ name, location, displayName, icon, description }) => {
if (location === 'mainPanel') {
this.addTab(
name,
displayName,
() => this.emit('switchApp', name),
() => {
if (name === 'home' && this.loadedTabs.length === 1 && this.loadedTabs[0].id === "home") {
const files = Object.keys(this.editor.sessions)
files.forEach(filepath => this.editor.discard(filepath))
}
})
this.on('manager', 'pluginDeactivated', (profile) => {
this.removeTab(profile.name)
})
this.on('fileDecorator', 'fileDecoratorsChanged', async (items) => {
this.tabsApi.setFileDecorations(items)
})
this.emit('closeApp', name)
this.call('manager', 'deactivatePlugin', name)
},
icon,
description
)
this.switchTab(name)
}
})
this.on('manager', 'pluginDeactivated', (profile) => {
this.removeTab(profile.name)
})
this.on('fileDecorator', 'fileDecoratorsChanged', async (items) => {
this.tabsApi.setFileDecorations(items)
})
try {
this.themeQuality = (await this.call('theme', 'currentTheme') ).quality
} catch (e) {
console.log('theme plugin has an issue: ', e)
}
this.renderComponent()
try {
this.themeQuality = (await this.call('theme', 'currentTheme') ).quality
} catch (e) {
console.log('theme plugin has an issue: ', e)
}
focus (name) {
this.emit('switchApp', name)
this.tabsApi.activateTab(name)
this.renderComponent()
}
focus (name) {
this.emit('switchApp', name)
this.tabsApi.activateTab(name)
}
switchTab (tabName) {
if (this._handlers[tabName]) {
this._handlers[tabName].switchTo()
this.tabsApi.activateTab(tabName)
}
switchTab (tabName) {
if (this._handlers[tabName]) {
this._handlers[tabName].switchTo()
this.tabsApi.activateTab(tabName)
}
}
switchNextTab () {
const active = this.tabsApi.active()
if (active && this._handlers[active]) {
const handlers = Object.keys(this._handlers)
let i = handlers.indexOf(active)
if (i >= 0) {
i = handlers[i + 1] ? i + 1 : 0
this.switchTab(handlers[i])
}
}
switchNextTab () {
const active = this.tabsApi.active()
if (active && this._handlers[active]) {
const handlers = Object.keys(this._handlers)
let i = handlers.indexOf(active)
if (i >= 0) {
i = handlers[i + 1] ? i + 1 : 0
this.switchTab(handlers[i])
}
}
}
switchPreviousTab () {
const active = this.tabsApi.active()
if (active && this._handlers[active]) {
const handlers = Object.keys(this._handlers)
let i = handlers.indexOf(active)
if (i >= 0) {
i = handlers[i - 1] ? i - 1 : handlers.length - 1
this.switchTab(handlers[i])
}
}
switchPreviousTab () {
const active = this.tabsApi.active()
if (active && this._handlers[active]) {
const handlers = Object.keys(this._handlers)
let i = handlers.indexOf(active)
if (i >= 0) {
i = handlers[i - 1] ? i - 1 : handlers.length - 1
this.switchTab(handlers[i])
}
}
}
renameTab (oldName, newName) {
// The new tab is being added by FileManager
this.removeTab(oldName)
}
addTab (name, title, switchTo, close, icon, description = '') {
if (this._handlers[name]) return this.renderComponent()
var slash = name.split('/')
const tabPath = slash.reverse()
const tempTitle = []
if (!title) {
for (let i = 0; i < tabPath.length; i++) {
tempTitle.push(tabPath[i])
const formatPath = [...tempTitle].reverse()
const index = this.loadedTabs.findIndex(({ title }) => title === formatPath.join('/'))
if (index === -1) {
title = formatPath.join('/')
const titleLength = formatPath.length
this.loadedTabs.push({
id: name,
name,
title,
icon,
tooltip: name,
iconClass: getPathIcon(name)
})
formatPath.shift()
if (formatPath.length > 0) {
const index = this.loadedTabs.findIndex(({ title }) => title === formatPath.join('/'))
if (index > -1) {
const duplicateTabName = this.loadedTabs[index].name
const duplicateTabTooltip = this.loadedTabs[index].description
const duplicateTabPath = duplicateTabName.split('/')
const duplicateTabFormatPath = [...duplicateTabPath].reverse()
const duplicateTabTitle = duplicateTabFormatPath.slice(0, titleLength).reverse().join('/')
this.loadedTabs[index] = {
id: duplicateTabName,
name: duplicateTabName,
title: duplicateTabTitle,
icon,
tooltip: duplicateTabTooltip || duplicateTabTitle,
iconClass: getPathIcon(duplicateTabName)
}
}
}
break
}
}
} else {
this.loadedTabs.push({
id: name,
name,
title,
}
renameTab (oldName, newName) {
// The new tab is being added by FileManager
this.removeTab(oldName)
}
addTab (name, title, switchTo, close, icon, description = '') {
if (this._handlers[name]) return this.renderComponent()
var slash = name.split('/')
const tabPath = slash.reverse()
const tempTitle = []
if (!title) {
for (let i = 0; i < tabPath.length; i++) {
tempTitle.push(tabPath[i])
const formatPath = [...tempTitle].reverse()
const index = this.loadedTabs.findIndex(({ title }) => title === formatPath.join('/'))
if (index === -1) {
title = formatPath.join('/')
const titleLength = formatPath.length
this.loadedTabs.push({
id: name,
name,
title,
icon,
tooltip: name,
iconClass: getPathIcon(name)
})
formatPath.shift()
if (formatPath.length > 0) {
const index = this.loadedTabs.findIndex(({ title }) => title === formatPath.join('/'))
if (index > -1) {
const duplicateTabName = this.loadedTabs[index].name
const duplicateTabTooltip = this.loadedTabs[index].description
const duplicateTabPath = duplicateTabName.split('/')
const duplicateTabFormatPath = [...duplicateTabPath].reverse()
const duplicateTabTitle = duplicateTabFormatPath.slice(0, titleLength).reverse().join('/')
this.loadedTabs[index] = {
id: duplicateTabName,
name: duplicateTabName,
title: duplicateTabTitle,
icon,
tooltip: description || title,
iconClass: getPathIcon(name)
})
}
this.renderComponent()
this._handlers[name] = { switchTo, close }
}
removeTab (name, currentFileTab) {
delete this._handlers[name]
let previous = currentFileTab
this.loadedTabs = this.loadedTabs.filter((tab, index) => {
if (!previous && tab.name === name) {
if(index - 1 >= 0 && this.loadedTabs[index - 1])
previous = this.loadedTabs[index - 1]
else if (index + 1 && this.loadedTabs[index + 1])
previous = this.loadedTabs[index + 1]
tooltip: duplicateTabTooltip || duplicateTabTitle,
iconClass: getPathIcon(duplicateTabName)
}
}
return tab.name !== name
})
this.renderComponent()
if (previous) this.switchTab(previous.name)
}
addHandler (type, fn) {
this.handlers[type] = fn
}
setDispatch (dispatch) {
this.dispatch = dispatch
this.renderComponent()
}
break
}
}
} else {
this.loadedTabs.push({
id: name,
name,
title,
icon,
tooltip: description || title,
iconClass: getPathIcon(name)
})
}
updateComponent(state) {
return <TabsUI
plugin={state.plugin}
tabs={state.loadedTabs}
onSelect={state.onSelect}
onClose={state.onClose}
onZoomIn={state.onZoomIn}
onZoomOut={state.onZoomOut}
onReady={state.onReady}
themeQuality={state.themeQuality}
/>
this.renderComponent()
this._handlers[name] = { switchTo, close }
}
removeTab (name, currentFileTab) {
delete this._handlers[name]
let previous = currentFileTab
this.loadedTabs = this.loadedTabs.filter((tab, index) => {
if (!previous && tab.name === name) {
if(index - 1 >= 0 && this.loadedTabs[index - 1])
previous = this.loadedTabs[index - 1]
else if (index + 1 && this.loadedTabs[index + 1])
previous = this.loadedTabs[index + 1]
}
return tab.name !== name
})
this.renderComponent()
if (previous) this.switchTab(previous.name)
}
addHandler (type, fn) {
this.handlers[type] = fn
}
setDispatch (dispatch) {
this.dispatch = dispatch
this.renderComponent()
}
updateComponent(state) {
return <TabsUI
plugin={state.plugin}
tabs={state.loadedTabs}
onSelect={state.onSelect}
onClose={state.onClose}
onZoomIn={state.onZoomIn}
onZoomOut={state.onZoomOut}
onReady={state.onReady}
themeQuality={state.themeQuality}
/>
}
renderComponent () {
const onSelect = (index) => {
if (this.loadedTabs[index]) {
const name = this.loadedTabs[index].name
if (this._handlers[name]) this._handlers[name].switchTo()
this.emit('tabCountChanged', this.loadedTabs.length)
}
}
renderComponent () {
const onSelect = (index) => {
if (this.loadedTabs[index]) {
const name = this.loadedTabs[index].name
if (this._handlers[name]) this._handlers[name].switchTo()
this.emit('tabCountChanged', this.loadedTabs.length)
}
}
const onClose = (index) => {
if (this.loadedTabs[index]) {
const name = this.loadedTabs[index].name
if (this._handlers[name]) this._handlers[name].close()
this.emit('tabCountChanged', this.loadedTabs.length)
}
}
const onZoomIn = () => this.editor.editorFontSize(1)
const onZoomOut = () => this.editor.editorFontSize(-1)
const onReady = (api) => { this.tabsApi = api }
this.dispatch({
plugin: this,
loadedTabs: this.loadedTabs,
onSelect,
onClose,
onZoomIn,
onZoomOut,
onReady,
themeQuality: this.themeQuality
})
const onClose = (index) => {
if (this.loadedTabs[index]) {
const name = this.loadedTabs[index].name
if (this._handlers[name]) this._handlers[name].close()
this.emit('tabCountChanged', this.loadedTabs.length)
}
}
renderTabsbar () {
return <div><PluginViewWrapper plugin={this} /></div>
}
const onZoomIn = () => this.editor.editorFontSize(1)
const onZoomOut = () => this.editor.editorFontSize(-1)
const onReady = (api) => { this.tabsApi = api }
this.dispatch({
plugin: this,
loadedTabs: this.loadedTabs,
onSelect,
onClose,
onZoomIn,
onZoomOut,
onReady,
themeQuality: this.themeQuality
})
}
renderTabsbar () {
return <div><PluginViewWrapper plugin={this} /></div>
}
}

@ -16,122 +16,122 @@ const KONSOLES = []
function register (api) { KONSOLES.push(api) }
const profile = {
displayName: 'Terminal',
name: 'terminal',
methods: ['log', 'logHtml'],
events: [],
description: 'Remix IDE terminal',
version: packageJson.version
displayName: 'Terminal',
name: 'terminal',
methods: ['log', 'logHtml'],
events: [],
description: 'Remix IDE terminal',
version: packageJson.version
}
class Terminal extends Plugin {
constructor (opts, api) {
super(profile)
this.fileImport = new CompilerImports()
this.event = new EventManager()
this.globalRegistry = Registry.getInstance()
this.element = document.createElement('div')
this.element.setAttribute('class', 'panel')
this.element.setAttribute('id', 'terminal-view')
this.element.setAttribute('data-id', 'terminalContainer-view')
this.eventsDecoder = this.globalRegistry.get('eventsDecoder').api
this.txListener = this.globalRegistry.get('txlistener').api
this._deps = {
fileManager: this.globalRegistry.get('filemanager').api,
editor: this.globalRegistry.get('editor').api,
compilersArtefacts: this.globalRegistry.get('compilersartefacts').api,
offsetToLineColumnConverter: this.globalRegistry.get('offsettolinecolumnconverter').api
}
this.commandHelp = {
'remix.loadgist(id)': 'Load a gist in the file explorer.',
'remix.loadurl(url)': 'Load the given url in the file explorer. The url can be of type github, swarm, ipfs or raw http',
'remix.execute(filepath)': 'Run the script specified by file path. If filepath is empty, script currently displayed in the editor is executed.',
'remix.exeCurrent()': 'Run the script currently displayed in the editor',
'remix.help()': 'Display this help message'
}
this.blockchain = opts.blockchain
this.vm = vm
this._api = api
this._opts = opts
this.config = this.globalRegistry.get('config').api
this.version = packageJson.version
this.data = {
lineLength: opts.lineLength || 80, // ????
session: [],
activeFilters: { commands: {}, input: '' },
filterFns: {}
}
this._view = { el: null, bar: null, input: null, term: null, journal: null, cli: null }
this._components = {}
this._commands = {}
this.commands = {}
this._JOURNAL = []
this._jobs = []
this._INDEX = {}
this._INDEX.all = []
this._INDEX.allMain = []
this._INDEX.commands = {}
this._INDEX.commandsMain = {}
if (opts.shell) this._shell = opts.shell // ???
register(this)
this.event.register('debuggingRequested', async (hash) => {
// TODO should probably be in the run module
if (!await this._opts.appManager.isActive('debugger')) await this._opts.appManager.activatePlugin('debugger')
this.call('menuicons', 'select', 'debugger')
this.call('debugger', 'debug', hash)
})
this.dispatch = null
}
onActivation() {
this.renderComponent()
}
onDeactivation () {
this.off('scriptRunner', 'log')
this.off('scriptRunner', 'info')
this.off('scriptRunner', 'warn')
this.off('scriptRunner', 'error')
constructor (opts, api) {
super(profile)
this.fileImport = new CompilerImports()
this.event = new EventManager()
this.globalRegistry = Registry.getInstance()
this.element = document.createElement('div')
this.element.setAttribute('class', 'panel')
this.element.setAttribute('id', 'terminal-view')
this.element.setAttribute('data-id', 'terminalContainer-view')
this.eventsDecoder = this.globalRegistry.get('eventsDecoder').api
this.txListener = this.globalRegistry.get('txlistener').api
this._deps = {
fileManager: this.globalRegistry.get('filemanager').api,
editor: this.globalRegistry.get('editor').api,
compilersArtefacts: this.globalRegistry.get('compilersartefacts').api,
offsetToLineColumnConverter: this.globalRegistry.get('offsettolinecolumnconverter').api
}
logHtml (html) {
this.terminalApi.logHtml(html)
this.commandHelp = {
'remix.loadgist(id)': 'Load a gist in the file explorer.',
'remix.loadurl(url)': 'Load the given url in the file explorer. The url can be of type github, swarm, ipfs or raw http',
'remix.execute(filepath)': 'Run the script specified by file path. If filepath is empty, script currently displayed in the editor is executed.',
'remix.exeCurrent()': 'Run the script currently displayed in the editor',
'remix.help()': 'Display this help message'
}
log (message, type) {
this.terminalApi.log(message, type)
}
setDispatch(dispatch) {
this.dispatch = dispatch
}
render () {
return <div id='terminal-view' className='panel' data-id='terminalContainer-view'><PluginViewWrapper plugin={this}/></div>
}
updateComponent(state) {
return <RemixUiTerminal
plugin={state.plugin}
onReady={state.onReady}
/>
}
renderComponent () {
const onReady = (api) => { this.terminalApi = api }
this.dispatch({
plugin: this,
onReady: onReady
})
this.blockchain = opts.blockchain
this.vm = vm
this._api = api
this._opts = opts
this.config = this.globalRegistry.get('config').api
this.version = packageJson.version
this.data = {
lineLength: opts.lineLength || 80, // ????
session: [],
activeFilters: { commands: {}, input: '' },
filterFns: {}
}
this._view = { el: null, bar: null, input: null, term: null, journal: null, cli: null }
this._components = {}
this._commands = {}
this.commands = {}
this._JOURNAL = []
this._jobs = []
this._INDEX = {}
this._INDEX.all = []
this._INDEX.allMain = []
this._INDEX.commands = {}
this._INDEX.commandsMain = {}
if (opts.shell) this._shell = opts.shell // ???
register(this)
this.event.register('debuggingRequested', async (hash) => {
// TODO should probably be in the run module
if (!await this._opts.appManager.isActive('debugger')) await this._opts.appManager.activatePlugin('debugger')
this.call('menuicons', 'select', 'debugger')
this.call('debugger', 'debug', hash)
})
this.dispatch = null
}
scroll2bottom () {
setTimeout(function () {
// do nothing.
}, 0)
}
onActivation() {
this.renderComponent()
}
onDeactivation () {
this.off('scriptRunner', 'log')
this.off('scriptRunner', 'info')
this.off('scriptRunner', 'warn')
this.off('scriptRunner', 'error')
}
logHtml (html) {
this.terminalApi.logHtml(html)
}
log (message, type) {
this.terminalApi.log(message, type)
}
setDispatch(dispatch) {
this.dispatch = dispatch
}
render () {
return <div id='terminal-view' className='panel' data-id='terminalContainer-view'><PluginViewWrapper plugin={this}/></div>
}
updateComponent(state) {
return <RemixUiTerminal
plugin={state.plugin}
onReady={state.onReady}
/>
}
renderComponent () {
const onReady = (api) => { this.terminalApi = api }
this.dispatch({
plugin: this,
onReady: onReady
})
}
scroll2bottom () {
setTimeout(function () {
// do nothing.
}, 0)
}
}
module.exports = Terminal

@ -8,279 +8,279 @@ import toml from 'toml'
import { filePathFilter, AnyFilter } from '@jsdevtools/file-path-filter'
const profile = {
name: 'codeFormatter',
desciption: 'prettier plugin for Remix',
methods: ['format'],
events: [''],
version: '0.0.1'
name: 'codeFormatter',
desciption: 'prettier plugin for Remix',
methods: ['format'],
events: [''],
version: '0.0.1'
}
const defaultOptions = {
"overrides": [
{
"files": "*.sol",
"options": {
"printWidth": 80,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false,
}
},
{
"files": "*.yml",
"options": {
}
},
{
"files": "*.yaml",
"options": {
}
},
{
"files": "*.toml",
"options": {
}
},
{
"files": "*.json",
"options": {
}
},
{
"files": "*.js",
"options": {
}
},
{
"files": "*.ts",
"options": {
}
}
]
"overrides": [
{
"files": "*.sol",
"options": {
"printWidth": 80,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false,
}
},
{
"files": "*.yml",
"options": {
}
},
{
"files": "*.yaml",
"options": {
}
},
{
"files": "*.toml",
"options": {
}
},
{
"files": "*.json",
"options": {
}
},
{
"files": "*.js",
"options": {
}
},
{
"files": "*.ts",
"options": {
}
}
]
}
export class CodeFormat extends Plugin {
prettier: any
ts: any
babel: any
espree: any
yml: any
sol: any
prettier: any
ts: any
babel: any
espree: any
yml: any
sol: any
constructor() {
super(profile)
}
constructor() {
super(profile)
}
async format(file: string) {
async format(file: string) {
// lazy load
if (!this.prettier) {
this.prettier = await import('prettier/standalone')
this.ts = await import('prettier/parser-typescript')
this.babel = await import('prettier/parser-babel')
this.espree = await import('prettier/parser-espree')
this.yml = await import('prettier/parser-yaml')
}
// lazy load
if (!this.prettier) {
this.prettier = await import('prettier/standalone')
this.ts = await import('prettier/parser-typescript')
this.babel = await import('prettier/parser-babel')
this.espree = await import('prettier/parser-espree')
this.yml = await import('prettier/parser-yaml')
}
try {
const content = await this.call('fileManager', 'readFile', file)
if (!content) return
let parserName = ''
let options: Options = {
}
switch (path.extname(file)) {
case '.sol':
parserName = 'solidity-parse'
break
case '.ts':
parserName = 'typescript'
options = {
...options,
trailingComma: 'all',
semi: false,
singleQuote: true,
quoteProps: 'as-needed',
bracketSpacing: true,
arrowParens: 'always',
}
break
case '.js':
parserName = "espree"
options = {
...options,
semi: false,
singleQuote: true,
}
break
case '.json':
parserName = 'json'
break
case '.yml':
parserName = 'yaml'
break
case '.yaml':
parserName = 'yaml'
break
}
try {
const content = await this.call('fileManager', 'readFile', file)
if (!content) return
let parserName = ''
let options: Options = {
}
switch (path.extname(file)) {
case '.sol':
parserName = 'solidity-parse'
break
case '.ts':
parserName = 'typescript'
options = {
...options,
trailingComma: 'all',
semi: false,
singleQuote: true,
quoteProps: 'as-needed',
bracketSpacing: true,
arrowParens: 'always',
}
break
case '.js':
parserName = "espree"
options = {
...options,
semi: false,
singleQuote: true,
}
break
case '.json':
parserName = 'json'
break
case '.yml':
parserName = 'yaml'
break
case '.yaml':
parserName = 'yaml'
break
}
if (file === '.prettierrc') {
parserName = 'json'
}
if (file === '.prettierrc') {
parserName = 'json'
}
const possibleFileNames = [
'.prettierrc',
'.prettierrc.json',
'.prettierrc.yaml',
'.prettierrc.yml',
'.prettierrc.toml',
'.prettierrc.js',
'.prettierrc.cjs',
'prettier.config.js',
'prettier.config.cjs',
'.prettierrc.json5',
]
const possibleFileNames = [
'.prettierrc',
'.prettierrc.json',
'.prettierrc.yaml',
'.prettierrc.yml',
'.prettierrc.toml',
'.prettierrc.js',
'.prettierrc.cjs',
'prettier.config.js',
'prettier.config.cjs',
'.prettierrc.json5',
]
const prettierConfigFile = await findAsync(possibleFileNames, async (fileName) => {
const exists = await this.call('fileManager', 'exists', fileName)
return exists
})
const prettierConfigFile = await findAsync(possibleFileNames, async (fileName) => {
const exists = await this.call('fileManager', 'exists', fileName)
return exists
})
let parsed = null
if (prettierConfigFile) {
let prettierConfig = await this.call('fileManager', 'readFile', prettierConfigFile)
if (prettierConfig) {
if (prettierConfigFile.endsWith('.yaml') || prettierConfigFile.endsWith('.yml')) {
try {
parsed = yaml.load(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile.endsWith('.toml')) {
try {
parsed = toml.parse(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile.endsWith('.json') || prettierConfigFile.endsWith('.json5')) {
try {
parsed = JSON.parse(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile === '.prettierrc') {
try {
parsed = JSON.parse(prettierConfig)
} catch (e) {
// do nothing
}
if (!parsed) {
try {
parsed = yaml.load(prettierConfig)
} catch (e) {
// do nothing
}
}
} else if (prettierConfigFile.endsWith('.js') || prettierConfigFile.endsWith('.cjs')) {
// remove any comments
prettierConfig = prettierConfig.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '')
// add quotes to keys
prettierConfig = prettierConfig.replace(/([a-zA-Z0-9_]+)(\s*):/g, '"$1"$2:')
// remove comma from last key
prettierConfig = prettierConfig.replace(/,(\s*})/g, '$1')
// remove any semi-colons
prettierConfig = prettierConfig.replace(/;/g, '')
// convert single quotes to double quotes
prettierConfig = prettierConfig.replace(/'/g, '"')
try {
parsed = JSON.parse(prettierConfig.replace('module.exports = ', '').replace('module.exports=', ''))
} catch (e) {
// do nothing
}
}
}
} else {
parsed = defaultOptions
await this.call('fileManager', 'writeFile', '.prettierrc.json', JSON.stringify(parsed, null, 2))
await this.call('notification', 'toast', 'A prettier config file has been created in the workspace.')
let parsed = null
if (prettierConfigFile) {
let prettierConfig = await this.call('fileManager', 'readFile', prettierConfigFile)
if (prettierConfig) {
if (prettierConfigFile.endsWith('.yaml') || prettierConfigFile.endsWith('.yml')) {
try {
parsed = yaml.load(prettierConfig)
} catch (e) {
// do nothing
}
if (!parsed && prettierConfigFile) {
this.call('notification', 'toast', `Error parsing prettier config file: ${prettierConfigFile}`)
} else if (prettierConfigFile.endsWith('.toml')) {
try {
parsed = toml.parse(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile.endsWith('.json') || prettierConfigFile.endsWith('.json5')) {
try {
parsed = JSON.parse(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile === '.prettierrc') {
try {
parsed = JSON.parse(prettierConfig)
} catch (e) {
// do nothing
}
if (!parsed) {
try {
parsed = yaml.load(prettierConfig)
} catch (e) {
// do nothing
}
}
} else if (prettierConfigFile.endsWith('.js') || prettierConfigFile.endsWith('.cjs')) {
// remove any comments
prettierConfig = prettierConfig.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '')
// add quotes to keys
prettierConfig = prettierConfig.replace(/([a-zA-Z0-9_]+)(\s*):/g, '"$1"$2:')
// remove comma from last key
prettierConfig = prettierConfig.replace(/,(\s*})/g, '$1')
// remove any semi-colons
prettierConfig = prettierConfig.replace(/;/g, '')
// convert single quotes to double quotes
prettierConfig = prettierConfig.replace(/'/g, '"')
try {
parsed = JSON.parse(prettierConfig.replace('module.exports = ', '').replace('module.exports=', ''))
} catch (e) {
// do nothing
}
}
}
} else {
parsed = defaultOptions
await this.call('fileManager', 'writeFile', '.prettierrc.json', JSON.stringify(parsed, null, 2))
await this.call('notification', 'toast', 'A prettier config file has been created in the workspace.')
}
if (!parsed && prettierConfigFile) {
this.call('notification', 'toast', `Error parsing prettier config file: ${prettierConfigFile}`)
}
// merge options
if (parsed) {
options = {
...options,
...parsed,
}
}
// search for overrides
if (parsed && parsed.overrides) {
const override = parsed.overrides.find((override) => {
if (override.files) {
const pathFilter: AnyFilter = {}
pathFilter.include = setGlobalExpression(override.files)
const filteredFiles = [file]
.filter(filePathFilter(pathFilter))
if (filteredFiles.length) {
return true
}
}
})
const validParsers = ['typescript', 'babel', 'espree', 'solidity-parse', 'json', 'yaml', 'solidity-parse']
if (override && override.options && override.options.parser) {
if (validParsers.includes(override.options.parser)) {
parserName = override.options.parser
} else {
this.call('notification', 'toast', `Invalid parser: ${override.options.parser}! Valid options are ${validParsers.join(', ')}`)
}
delete override.options.parser
}
// merge options
if (parsed) {
options = {
...options,
...parsed,
}
}
if (override) {
options = {
...options,
...override.options,
}
}
// search for overrides
if (parsed && parsed.overrides) {
const override = parsed.overrides.find((override) => {
if (override.files) {
const pathFilter: AnyFilter = {}
pathFilter.include = setGlobalExpression(override.files)
const filteredFiles = [file]
.filter(filePathFilter(pathFilter))
if (filteredFiles.length) {
return true
}
}
})
const validParsers = ['typescript', 'babel', 'espree', 'solidity-parse', 'json', 'yaml', 'solidity-parse']
if (override && override.options && override.options.parser) {
if (validParsers.includes(override.options.parser)) {
parserName = override.options.parser
} else {
this.call('notification', 'toast', `Invalid parser: ${override.options.parser}! Valid options are ${validParsers.join(', ')}`)
}
delete override.options.parser
}
const result = this.prettier.format(content, {
plugins: [sol as any, this.ts, this.babel, this.espree, this.yml],
parser: parserName,
...options
})
await this.call('fileManager', 'writeFile', file, result)
} catch (e) {
// do nothing
if (override) {
options = {
...options,
...override.options,
}
}
}
const result = this.prettier.format(content, {
plugins: [sol as any, this.ts, this.babel, this.espree, this.yml],
parser: parserName,
...options
})
await this.call('fileManager', 'writeFile', file, result)
} catch (e) {
// do nothing
}
}
}
//*.sol, **/*.txt, contracts/*
const setGlobalExpression = (paths: string) => {
const results = []
paths.split(',').forEach(path => {
path = path.trim()
if (path.startsWith('*.')) path = path.replace(/(\*\.)/g, '**/*.')
if (path.endsWith('/*') && !path.endsWith('/**/*'))
path = path.replace(/(\*)/g, '**/*.*')
results.push(path)
})
return results
const results = []
paths.split(',').forEach(path => {
path = path.trim()
if (path.startsWith('*.')) path = path.replace(/(\*\.)/g, '**/*.')
if (path.endsWith('/*') && !path.endsWith('/**/*'))
path = path.replace(/(\*)/g, '**/*.*')
results.push(path)
})
return results
}
async function findAsync(arr, asyncCallback) {
const promises = arr.map(asyncCallback);
const results = await Promise.all(promises);
const index = results.findIndex(result => result);
return arr[index];
const promises = arr.map(asyncCallback);
const results = await Promise.all(promises);
const index = results.findIndex(result => result);
return arr[index];
}

@ -8,54 +8,54 @@ import { parse } from './parser'
// https://prettier.io/docs/en/plugins.html#languages
// https://github.com/ikatyang/linguist-languages/blob/master/data/Solidity.json
const languages = [
{
linguistLanguageId: 237469032,
name: 'Solidity',
type: 'programming',
color: '#AA6746',
aceMode: 'text',
tmScope: 'source.solidity',
extensions: ['.sol'],
parsers: ['solidity-parse'],
vscodeLanguageIds: ['solidity']
}
{
linguistLanguageId: 237469032,
name: 'Solidity',
type: 'programming',
color: '#AA6746',
aceMode: 'text',
tmScope: 'source.solidity',
extensions: ['.sol'],
parsers: ['solidity-parse'],
vscodeLanguageIds: ['solidity']
}
];
// https://prettier.io/docs/en/plugins.html#parsers
const parser = { astFormat: 'solidity-ast', parse, ...loc };
const parsers = {
'solidity-parse': parser
'solidity-parse': parser
};
const canAttachComment = (node) =>
node.type && node.type !== 'BlockComment' && node.type !== 'LineComment';
node.type && node.type !== 'BlockComment' && node.type !== 'LineComment';
// https://prettier.io/docs/en/plugins.html#printers
const printers = {
'solidity-ast': {
canAttachComment,
handleComments: {
ownLine: handleComments.handleOwnLineComment,
endOfLine: handleComments.handleEndOfLineComment,
remaining: handleComments.handleRemainingComment
},
isBlockComment: handleComments.isBlockComment,
massageAstNode,
print,
printComment
}
'solidity-ast': {
canAttachComment,
handleComments: {
ownLine: handleComments.handleOwnLineComment,
endOfLine: handleComments.handleEndOfLineComment,
remaining: handleComments.handleRemainingComment
},
isBlockComment: handleComments.isBlockComment,
massageAstNode,
print,
printComment
}
};
// https://prettier.io/docs/en/plugins.html#defaultoptions
const defaultOptions = {
bracketSpacing: false,
tabWidth: 4
bracketSpacing: false,
tabWidth: 4
};
export default {
languages,
parsers,
printers,
options,
defaultOptions
languages,
parsers,
printers,
options,
defaultOptions
};

@ -5,193 +5,193 @@ const parser = (window as any).SolidityParser
import semver from 'semver';
const tryHug = (node, operators) => {
if (node.type === 'BinaryOperation' && operators.includes(node.operator))
return {
type: 'TupleExpression',
components: [node],
isArray: false
};
return node;
if (node.type === 'BinaryOperation' && operators.includes(node.operator))
return {
type: 'TupleExpression',
components: [node],
isArray: false
};
return node;
};
export function parse(text, _parsers, options) {
const compiler = semver.coerce(options.compiler);
const parsed = parser.parse(text, { loc: true, range: true });
parsed.comments = extractComments(text)
const compiler = semver.coerce(options.compiler);
const parsed = parser.parse(text, { loc: true, range: true });
parsed.comments = extractComments(text)
parser.visit(parsed, {
PragmaDirective(ctx) {
// if the pragma is not for solidity we leave.
if (ctx.name !== 'solidity') return;
// if the compiler option has not been provided we leave.
if (!compiler) return;
// we make a check against each pragma directive in the document.
if (!semver.satisfies(compiler, ctx.value)) {
// @TODO: investigate the best way to warn that would apply to
// different editors.
// eslint-disable-next-line no-console
console.warn(
`[prettier-solidity] The compiler option is set to '${options.compiler}', which does not satisfy 'pragma solidity ${ctx.value}'.`
);
}
},
ModifierDefinition(ctx) {
if (!ctx.parameters) {
ctx.parameters = [];
}
},
FunctionDefinition(ctx) {
if (!ctx.isConstructor) {
ctx.modifiers.forEach((modifier) => {
if (modifier.arguments && modifier.arguments.length === 0) {
// eslint-disable-next-line no-param-reassign
modifier.arguments = null;
}
});
}
},
ForStatement(ctx) {
if (ctx.initExpression) {
ctx.initExpression.omitSemicolon = true;
}
ctx.loopExpression.omitSemicolon = true;
},
HexLiteral(ctx) {
ctx.value = options.singleQuote
? `hex'${ctx.value.slice(4, -1)}'`
: `hex"${ctx.value.slice(4, -1)}"`;
},
ElementaryTypeName(ctx) {
// if the compiler is below 0.8.0 we will recognize the type 'byte' as an
// alias of 'bytes1'. Otherwise we will ignore this and enforce always
// 'bytes1'.
const pre080 = compiler && semver.satisfies(compiler, '<0.8.0');
if (!pre080 && ctx.name === 'byte') ctx.name = 'bytes1';
parser.visit(parsed, {
PragmaDirective(ctx) {
// if the pragma is not for solidity we leave.
if (ctx.name !== 'solidity') return;
// if the compiler option has not been provided we leave.
if (!compiler) return;
// we make a check against each pragma directive in the document.
if (!semver.satisfies(compiler, ctx.value)) {
// @TODO: investigate the best way to warn that would apply to
// different editors.
// eslint-disable-next-line no-console
console.warn(
`[prettier-solidity] The compiler option is set to '${options.compiler}', which does not satisfy 'pragma solidity ${ctx.value}'.`
);
}
},
ModifierDefinition(ctx) {
if (!ctx.parameters) {
ctx.parameters = [];
}
},
FunctionDefinition(ctx) {
if (!ctx.isConstructor) {
ctx.modifiers.forEach((modifier) => {
if (modifier.arguments && modifier.arguments.length === 0) {
// eslint-disable-next-line no-param-reassign
modifier.arguments = null;
}
});
}
},
ForStatement(ctx) {
if (ctx.initExpression) {
ctx.initExpression.omitSemicolon = true;
}
ctx.loopExpression.omitSemicolon = true;
},
HexLiteral(ctx) {
ctx.value = options.singleQuote
? `hex'${ctx.value.slice(4, -1)}'`
: `hex"${ctx.value.slice(4, -1)}"`;
},
ElementaryTypeName(ctx) {
// if the compiler is below 0.8.0 we will recognize the type 'byte' as an
// alias of 'bytes1'. Otherwise we will ignore this and enforce always
// 'bytes1'.
const pre080 = compiler && semver.satisfies(compiler, '<0.8.0');
if (!pre080 && ctx.name === 'byte') ctx.name = 'bytes1';
if (options.explicitTypes === 'always') {
if (ctx.name === 'uint') ctx.name = 'uint256';
if (ctx.name === 'int') ctx.name = 'int256';
if (pre080 && ctx.name === 'byte') ctx.name = 'bytes1';
} else if (options.explicitTypes === 'never') {
if (ctx.name === 'uint256') ctx.name = 'uint';
if (ctx.name === 'int256') ctx.name = 'int';
if (pre080 && ctx.name === 'bytes1') ctx.name = 'byte';
}
},
BinaryOperation(ctx) {
switch (ctx.operator) {
case '+':
case '-':
ctx.left = tryHug(ctx.left, ['%']);
ctx.right = tryHug(ctx.right, ['%']);
break;
case '*':
ctx.left = tryHug(ctx.left, ['/', '%']);
break;
case '/':
ctx.left = tryHug(ctx.left, ['*', '%']);
break;
case '%':
ctx.left = tryHug(ctx.left, ['*', '/', '%']);
break;
case '**':
// If the compiler has not been given as an option using we leave a**b**c.
if (!compiler) break;
if (options.explicitTypes === 'always') {
if (ctx.name === 'uint') ctx.name = 'uint256';
if (ctx.name === 'int') ctx.name = 'int256';
if (pre080 && ctx.name === 'byte') ctx.name = 'bytes1';
} else if (options.explicitTypes === 'never') {
if (ctx.name === 'uint256') ctx.name = 'uint';
if (ctx.name === 'int256') ctx.name = 'int';
if (pre080 && ctx.name === 'bytes1') ctx.name = 'byte';
}
},
BinaryOperation(ctx) {
switch (ctx.operator) {
case '+':
case '-':
ctx.left = tryHug(ctx.left, ['%']);
ctx.right = tryHug(ctx.right, ['%']);
break;
case '*':
ctx.left = tryHug(ctx.left, ['/', '%']);
break;
case '/':
ctx.left = tryHug(ctx.left, ['*', '%']);
break;
case '%':
ctx.left = tryHug(ctx.left, ['*', '/', '%']);
break;
case '**':
// If the compiler has not been given as an option using we leave a**b**c.
if (!compiler) break;
if (semver.satisfies(compiler, '<0.8.0')) {
// If the compiler is less than 0.8.0 then a**b**c is formatted as
// (a**b)**c.
ctx.left = tryHug(ctx.left, ['**']);
break;
}
if (
ctx.left.type === 'BinaryOperation' &&
if (semver.satisfies(compiler, '<0.8.0')) {
// If the compiler is less than 0.8.0 then a**b**c is formatted as
// (a**b)**c.
ctx.left = tryHug(ctx.left, ['**']);
break;
}
if (
ctx.left.type === 'BinaryOperation' &&
ctx.left.operator === '**'
) {
// the parser still organizes the a**b**c as (a**b)**c so we need
// to restructure it.
ctx.right = {
type: 'TupleExpression',
components: [
{
type: 'BinaryOperation',
operator: '**',
left: ctx.left.right,
right: ctx.right
}
],
isArray: false
};
ctx.left = ctx.left.left;
}
break;
case '<<':
case '>>':
ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']);
ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**']);
break;
case '&':
ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']);
ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**', '<<', '>>']);
break;
case '|':
ctx.left = tryHug(ctx.left, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&',
'^'
]);
ctx.right = tryHug(ctx.right, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&',
'^'
]);
break;
case '^':
ctx.left = tryHug(ctx.left, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&'
]);
ctx.right = tryHug(ctx.right, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&'
]);
break;
case '||':
ctx.left = tryHug(ctx.left, ['&&']);
ctx.right = tryHug(ctx.right, ['&&']);
break;
case '&&':
default:
break;
}
) {
// the parser still organizes the a**b**c as (a**b)**c so we need
// to restructure it.
ctx.right = {
type: 'TupleExpression',
components: [
{
type: 'BinaryOperation',
operator: '**',
left: ctx.left.right,
right: ctx.right
}
],
isArray: false
};
ctx.left = ctx.left.left;
}
});
break;
case '<<':
case '>>':
ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']);
ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**']);
break;
case '&':
ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']);
ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**', '<<', '>>']);
break;
case '|':
ctx.left = tryHug(ctx.left, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&',
'^'
]);
ctx.right = tryHug(ctx.right, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&',
'^'
]);
break;
case '^':
ctx.left = tryHug(ctx.left, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&'
]);
ctx.right = tryHug(ctx.right, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&'
]);
break;
case '||':
ctx.left = tryHug(ctx.left, ['&&']);
ctx.right = tryHug(ctx.right, ['&&']);
break;
case '&&':
default:
break;
}
}
});
return parsed;
return parsed;
}

@ -3,30 +3,30 @@ import { QueryParams } from '@remix-project/remix-lib'
import Registry from '../state/registry'
const profile = {
name: 'config',
displayName: 'Config',
description: 'Config',
methods: ['getAppParameter', 'setAppParameter'],
events: ['configChanged']
name: 'config',
displayName: 'Config',
description: 'Config',
methods: ['getAppParameter', 'setAppParameter'],
events: ['configChanged']
}
export class ConfigPlugin extends Plugin {
constructor () {
super(profile)
}
constructor () {
super(profile)
}
getAppParameter (name: string) {
const queryParams = new QueryParams()
const params = queryParams.get()
const config = Registry.getInstance().get('config').api
const param = params[name] || config.get(name) || config.get('settings/' + name)
if (param === 'true') return true
if (param === 'false') return false
return param
}
getAppParameter (name: string) {
const queryParams = new QueryParams()
const params = queryParams.get()
const config = Registry.getInstance().get('config').api
const param = params[name] || config.get(name) || config.get('settings/' + name)
if (param === 'true') return true
if (param === 'false') return false
return param
}
setAppParameter (name: string, value: any) {
const config = Registry.getInstance().get('config').api
config.set(name, value)
}
setAppParameter (name: string, value: any) {
const config = Registry.getInstance().get('config').api
config.set(name, value)
}
}

@ -6,74 +6,74 @@ import { concatSourceFiles, getDependencyGraph, normalizeContractPath } from '@r
const _paq = window._paq = window._paq || []
const profile = {
name: 'contractflattener',
displayName: 'Contract Flattener',
description: 'Flatten solidity contracts',
methods: ['flattenAContract', 'flattenContract'],
events: [],
maintainedBy: 'Remix',
name: 'contractflattener',
displayName: 'Contract Flattener',
description: 'Flatten solidity contracts',
methods: ['flattenAContract', 'flattenContract'],
events: [],
maintainedBy: 'Remix',
}
export class ContractFlattener extends Plugin {
triggerFlattenContract: boolean = false
constructor() {
super(profile)
}
triggerFlattenContract: boolean = false
constructor() {
super(profile)
}
onActivation(): void {
this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data, input, version) => {
if(data.sources && Object.keys(data.sources).length > 1) {
if(this.triggerFlattenContract) {
this.triggerFlattenContract = false
await this.flattenContract(source, file, data)
}
}
})
_paq.push(['trackEvent', 'plugin', 'activated', 'contractFlattener'])
}
onActivation(): void {
this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data, input, version) => {
if(data.sources && Object.keys(data.sources).length > 1) {
if(this.triggerFlattenContract) {
this.triggerFlattenContract = false
await this.flattenContract(source, file, data)
}
}
})
_paq.push(['trackEvent', 'plugin', 'activated', 'contractFlattener'])
}
onDeactivation(): void {
this.off('solidity', 'compilationFinished')
}
onDeactivation(): void {
this.off('solidity', 'compilationFinished')
}
async flattenAContract(action: customAction) {
this.triggerFlattenContract = true
await this.call('solidity', 'compile', action.path[0])
}
async flattenAContract(action: customAction) {
this.triggerFlattenContract = true
await this.call('solidity', 'compile', action.path[0])
}
/**
/**
* Takes currently compiled contract that has a bunch of imports at the top
* and flattens them ready, ideally for UML creation or for other purposes.
* Takes the flattened result, writes it to a file and returns the result.
* @returns {Promise<string>}
*/
async flattenContract (source: { sources: any, target: string },
filePath: string, data: { contracts: any, sources: any }): Promise<string> {
const appendage = '_flattened.sol'
const normalized = normalizeContractPath(filePath)
const path = `${normalized[normalized.length - 2]}${appendage}`
const ast = data.sources
let dependencyGraph
let sorted
let result
let sources
try{
dependencyGraph = getDependencyGraph(ast, filePath)
sorted = dependencyGraph.isEmpty()
? [filePath]
: dependencyGraph.sort().reverse()
sources = source.sources
result = concatSourceFiles(sorted, sources)
}catch(err){
console.warn(err)
}
await this.call('fileManager', 'writeFile', path , result)
_paq.push(['trackEvent', 'plugin', 'contractFlattener', 'flattenAContract'])
// clean up memory references & return result
sorted = null
sources = null
dependencyGraph = null
return result
async flattenContract (source: { sources: any, target: string },
filePath: string, data: { contracts: any, sources: any }): Promise<string> {
const appendage = '_flattened.sol'
const normalized = normalizeContractPath(filePath)
const path = `${normalized[normalized.length - 2]}${appendage}`
const ast = data.sources
let dependencyGraph
let sorted
let result
let sources
try{
dependencyGraph = getDependencyGraph(ast, filePath)
sorted = dependencyGraph.isEmpty()
? [filePath]
: dependencyGraph.sort().reverse()
sources = source.sources
result = concatSourceFiles(sorted, sources)
}catch(err){
console.warn(err)
}
await this.call('fileManager', 'writeFile', path , result)
_paq.push(['trackEvent', 'plugin', 'contractFlattener', 'flattenAContract'])
// clean up memory references & return result
sorted = null
sources = null
dependencyGraph = null
return result
}
}

@ -6,79 +6,79 @@ import { Plugin } from '@remixproject/engine'
import { fileDecoration } from '@remix-ui/file-decorators'
const profile = {
name: 'fileDecorator',
desciption: 'Keeps decorators of the files',
methods: ['setFileDecorators', 'clearFileDecorators', 'clearAllFileDecorators'],
events: ['fileDecoratorsChanged'],
version: '0.0.1'
name: 'fileDecorator',
desciption: 'Keeps decorators of the files',
methods: ['setFileDecorators', 'clearFileDecorators', 'clearAllFileDecorators'],
events: ['fileDecoratorsChanged'],
version: '0.0.1'
}
export class FileDecorator extends Plugin {
private _fileStates: fileDecoration[] = []
constructor() {
super(profile)
}
private _fileStates: fileDecoration[] = []
constructor() {
super(profile)
}
onActivation(): void {
this.on('filePanel', 'setWorkspace', async () => {
await this.clearAllFileDecorators()
})
}
onActivation(): void {
this.on('filePanel', 'setWorkspace', async () => {
await this.clearAllFileDecorators()
})
}
/**
/**
* @param fileStates Array of file states
*/
async setFileDecorators(fileStates: fileDecoration[] | fileDecoration) {
const { from } = this.currentRequest
const workspace = await this.call('filePanel', 'getCurrentWorkspace')
const fileStatesPayload = Array.isArray(fileStates) ? fileStates : [fileStates]
// clear all file states in the previous state of this owner on the files called
fileStatesPayload.forEach((state) => {
state.workspace = workspace
state.owner = from
})
const filteredState = this._fileStates.filter((state) => {
const index = fileStatesPayload.findIndex((payloadFileState: fileDecoration) => {
return from == state.owner && payloadFileState.path == state.path
})
return index == -1
})
const newState = [...filteredState, ...fileStatesPayload].sort(sortByPath)
async setFileDecorators(fileStates: fileDecoration[] | fileDecoration) {
const { from } = this.currentRequest
const workspace = await this.call('filePanel', 'getCurrentWorkspace')
const fileStatesPayload = Array.isArray(fileStates) ? fileStates : [fileStates]
// clear all file states in the previous state of this owner on the files called
fileStatesPayload.forEach((state) => {
state.workspace = workspace
state.owner = from
})
const filteredState = this._fileStates.filter((state) => {
const index = fileStatesPayload.findIndex((payloadFileState: fileDecoration) => {
return from == state.owner && payloadFileState.path == state.path
})
return index == -1
})
const newState = [...filteredState, ...fileStatesPayload].sort(sortByPath)
if (!deepequal(newState, this._fileStates)) {
this._fileStates = newState
this.emit('fileDecoratorsChanged', this._fileStates)
}
if (!deepequal(newState, this._fileStates)) {
this._fileStates = newState
this.emit('fileDecoratorsChanged', this._fileStates)
}
}
async clearFileDecorators(path?: string) {
const { from } = this.currentRequest
if (!from) return
async clearFileDecorators(path?: string) {
const { from } = this.currentRequest
if (!from) return
const filteredState = this._fileStates.filter((state) => {
if(state.owner != from) return true
if(path && state.path != path) return true
})
const newState = [...filteredState].sort(sortByPath)
if (!deepequal(newState, this._fileStates)) {
this._fileStates = newState
this.emit('fileDecoratorsChanged', this._fileStates)
}
const filteredState = this._fileStates.filter((state) => {
if(state.owner != from) return true
if(path && state.path != path) return true
})
const newState = [...filteredState].sort(sortByPath)
if (!deepequal(newState, this._fileStates)) {
this._fileStates = newState
this.emit('fileDecoratorsChanged', this._fileStates)
}
async clearAllFileDecorators() {
this._fileStates = []
this.emit('fileDecoratorsChanged', [])
}
}
async clearAllFileDecorators() {
this._fileStates = []
this.emit('fileDecoratorsChanged', [])
}
}
const sortByPath = (a: fileDecoration, b: fileDecoration) => {
if (a.path < b.path) {
return -1;
}
if (a.path > b.path) {
return 1;
}
return 0;
if (a.path < b.path) {
return -1;
}
if (a.path > b.path) {
return 1;
}
return 0;
}

@ -14,31 +14,31 @@ interface INotificationApi {
}
const profile:LibraryProfile<INotificationApi> = {
name: 'notification',
displayName: 'Notification',
description: 'Displays notifications',
methods: ['modal', 'alert', 'toast']
name: 'notification',
displayName: 'Notification',
description: 'Displays notifications',
methods: ['modal', 'alert', 'toast']
}
export class NotificationPlugin extends Plugin implements MethodApi<INotificationApi> {
dispatcher: dispatchModalInterface
constructor () {
super(profile)
}
dispatcher: dispatchModalInterface
constructor () {
super(profile)
}
setDispatcher (dispatcher: dispatchModalInterface) {
this.dispatcher = dispatcher
}
setDispatcher (dispatcher: dispatchModalInterface) {
this.dispatcher = dispatcher
}
async modal (args: AppModal) {
return this.dispatcher.modal(args)
}
async modal (args: AppModal) {
return this.dispatcher.modal(args)
}
async alert (args: AlertModal) {
return this.dispatcher.alert(args)
}
async alert (args: AlertModal) {
return this.dispatcher.alert(args)
}
async toast (message: string | JSX.Element) {
this.dispatcher.toast(message)
}
async toast (message: string | JSX.Element) {
this.dispatcher.toast(message)
}
}

File diff suppressed because it is too large Load Diff

@ -1,45 +1,45 @@
let parser: any
self.onmessage = (e: MessageEvent) => {
const data: any = e.data
switch (data.cmd) {
case 'load':
{
(self as any).importScripts(e.data.url)
// @ts-ignore
parser = SolidityParser as any;
const data: any = e.data
switch (data.cmd) {
case 'load':
{
(self as any).importScripts(e.data.url)
// @ts-ignore
parser = SolidityParser as any;
self.postMessage({
cmd: 'loaded',
})
break
}
self.postMessage({
cmd: 'loaded',
})
break
}
case 'parse':
if (data.text && parser) {
case 'parse':
if (data.text && parser) {
try {
let startTime = performance.now()
const blocks = parser.parseBlock(data.text, { loc: true, range: true, tolerant: true })
const blockDuration = performance.now() - startTime
startTime = performance.now()
const ast = parser.parse(data.text, { loc: true, range: true, tolerant: true })
const endTime = performance.now()
try {
let startTime = performance.now()
const blocks = parser.parseBlock(data.text, { loc: true, range: true, tolerant: true })
const blockDuration = performance.now() - startTime
startTime = performance.now()
const ast = parser.parse(data.text, { loc: true, range: true, tolerant: true })
const endTime = performance.now()
self.postMessage({
cmd: 'parsed',
timestamp: data.timestamp,
ast,
text: data.text,
file: data.file,
duration: endTime - startTime,
blockDuration,
blocks
})
} catch (e) {
// do nothing
}
self.postMessage({
cmd: 'parsed',
timestamp: data.timestamp,
ast,
text: data.text,
file: data.file,
duration: endTime - startTime,
blockDuration,
blocks
})
} catch (e) {
// do nothing
}
}
break
}
break
}
}

@ -20,13 +20,13 @@ interface BlockDefinition {
}
export default class CodeParserAntlrService {
plugin: CodeParser
worker: Worker
parserStartTime: number = 0
workerTimer: NodeJS.Timer
parserThreshold: number = 10
parserThresholdSampleAmount = 3
cache: {
plugin: CodeParser
worker: Worker
parserStartTime: number = 0
workerTimer: NodeJS.Timer
parserThreshold: number = 10
parserThresholdSampleAmount = 3
cache: {
[name: string]: {
text: string,
ast: antlr.ParseResult | null,
@ -36,253 +36,253 @@ export default class CodeParserAntlrService {
blockDurations?: number[]
}
} = {};
constructor(plugin: CodeParser) {
this.plugin = plugin
this.createWorker()
}
constructor(plugin: CodeParser) {
this.plugin = plugin
this.createWorker()
}
createWorker() {
this.worker = new Worker(new URL('./antlr-worker', import.meta.url))
this.worker.postMessage({
cmd: 'load',
url: document.location.protocol + '//' + document.location.host + '/assets/js/parser/antlr.js',
});
const self = this
createWorker() {
this.worker = new Worker(new URL('./antlr-worker', import.meta.url))
this.worker.postMessage({
cmd: 'load',
url: document.location.protocol + '//' + document.location.host + '/assets/js/parser/antlr.js',
});
const self = this
this.worker.addEventListener('message', function (ev) {
switch (ev.data.cmd) {
case 'parsed':
if (ev.data.ast && self.parserStartTime === ev.data.timestamp) {
self.cache[ev.data.file] = {
...self.cache[ev.data.file],
text: ev.data.text,
ast: ev.data.ast,
duration: ev.data.duration,
blocks: ev.data.blocks,
blockDurations: self.cache[ev.data.file].blockDurations? [...self.cache[ev.data.file].blockDurations.slice(-self.parserThresholdSampleAmount), ev.data.blockDuration]: [ev.data.blockDuration]
}
self.setFileParsingState(ev.data.file)
}
break;
}
this.worker.addEventListener('message', function (ev) {
switch (ev.data.cmd) {
case 'parsed':
if (ev.data.ast && self.parserStartTime === ev.data.timestamp) {
self.cache[ev.data.file] = {
...self.cache[ev.data.file],
text: ev.data.text,
ast: ev.data.ast,
duration: ev.data.duration,
blocks: ev.data.blocks,
blockDurations: self.cache[ev.data.file].blockDurations? [...self.cache[ev.data.file].blockDurations.slice(-self.parserThresholdSampleAmount), ev.data.blockDuration]: [ev.data.blockDuration]
}
self.setFileParsingState(ev.data.file)
}
break;
}
});
}
});
}
setFileParsingState(file: string) {
if (this.cache[file]) {
if (this.cache[file].blockDurations && this.cache[file].blockDurations.length > this.parserThresholdSampleAmount) {
// calculate average of durations to determine if the parsing should be disabled
const values = [...this.cache[file].blockDurations]
const average = values.reduce((a, b) => a + b, 0) / values.length
if (average > this.parserThreshold) {
this.cache[file].parsingEnabled = false
} else {
this.cache[file].parsingEnabled = true
}
}
}
setFileParsingState(file: string) {
if (this.cache[file]) {
if (this.cache[file].blockDurations && this.cache[file].blockDurations.length > this.parserThresholdSampleAmount) {
// calculate average of durations to determine if the parsing should be disabled
const values = [...this.cache[file].blockDurations]
const average = values.reduce((a, b) => a + b, 0) / values.length
if (average > this.parserThreshold) {
this.cache[file].parsingEnabled = false
} else {
this.cache[file].parsingEnabled = true
}
}
}
}
enableWorker() {
if (!this.workerTimer) {
this.workerTimer = setInterval(() => {
this.setCurrentFileAST()
}, 5000)
}
enableWorker() {
if (!this.workerTimer) {
this.workerTimer = setInterval(() => {
this.setCurrentFileAST()
}, 5000)
}
}
disableWorker() {
clearInterval(this.workerTimer)
}
disableWorker() {
clearInterval(this.workerTimer)
}
async parseWithWorker(text: string, file: string) {
this.parserStartTime = Date.now()
this.worker.postMessage({
cmd: 'parse',
text,
timestamp: this.parserStartTime,
file,
parsingEnabled: (this.cache[file] && this.cache[file].parsingEnabled) || true
});
async parseWithWorker(text: string, file: string) {
this.parserStartTime = Date.now()
this.worker.postMessage({
cmd: 'parse',
text,
timestamp: this.parserStartTime,
file,
parsingEnabled: (this.cache[file] && this.cache[file].parsingEnabled) || true
});
}
}
async parseSolidity(text: string) {
const ast: antlr.ParseResult = (SolidityParser as any).parse(text, { loc: true, range: true, tolerant: true })
return ast
}
async parseSolidity(text: string) {
const ast: antlr.ParseResult = (SolidityParser as any).parse(text, { loc: true, range: true, tolerant: true })
return ast
}
/**
/**
* Tries to parse the current file or the given text and returns the AST
* If the parsing fails it returns the last successful AST for this file
* @param text
* @returns
*/
async setCurrentFileAST(text: string | null = null) {
try {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) {
const fileContent = text || await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile)
if (!this.cache[this.plugin.currentFile]) {
this.cache[this.plugin.currentFile] = {
text: '',
ast: null,
parsingEnabled: true,
blockDurations: []
}
}
if (this.cache[this.plugin.currentFile] && this.cache[this.plugin.currentFile].text !== fileContent) {
try {
await this.parseWithWorker(fileContent, this.plugin.currentFile)
} catch (e) {
// do nothing
}
}
}
} catch (e) {
async setCurrentFileAST(text: string | null = null) {
try {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) {
const fileContent = text || await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile)
if (!this.cache[this.plugin.currentFile]) {
this.cache[this.plugin.currentFile] = {
text: '',
ast: null,
parsingEnabled: true,
blockDurations: []
}
}
if (this.cache[this.plugin.currentFile] && this.cache[this.plugin.currentFile].text !== fileContent) {
try {
await this.parseWithWorker(fileContent, this.plugin.currentFile)
} catch (e) {
// do nothing
}
}
}
} catch (e) {
// do nothing
}
}
/**
/**
* Lists the AST nodes from the current file parser
* These nodes need to be changed to match the node types returned by the compiler
* @returns
*/
async listAstNodes() {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (!this.cache[this.plugin.currentFile]) return
const nodes: AstNode[] = [];
(SolidityParser as any).visit(this.cache[this.plugin.currentFile].ast, {
StateVariableDeclaration: (node: antlr.StateVariableDeclaration) => {
if (node.variables) {
for (const variable of node.variables) {
nodes.push({ ...variable, nodeType: 'VariableDeclaration', id: null, src: null })
}
}
},
VariableDeclaration: (node: antlr.VariableDeclaration) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
UserDefinedTypeName: (node: antlr.UserDefinedTypeName) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
FunctionDefinition: (node: antlr.FunctionDefinition) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
ContractDefinition: (node: antlr.ContractDefinition) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
MemberAccess: function (node: antlr.MemberAccess) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
Identifier: function (node: antlr.Identifier) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
EventDefinition: function (node: antlr.EventDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
ModifierDefinition: function (node: antlr.ModifierDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
InvalidNode: function (node: antlr.InvalidNode) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
EnumDefinition: function (node: antlr.EnumDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
StructDefinition: function (node: antlr.StructDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
}
async listAstNodes() {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (!this.cache[this.plugin.currentFile]) return
const nodes: AstNode[] = [];
(SolidityParser as any).visit(this.cache[this.plugin.currentFile].ast, {
StateVariableDeclaration: (node: antlr.StateVariableDeclaration) => {
if (node.variables) {
for (const variable of node.variables) {
nodes.push({ ...variable, nodeType: 'VariableDeclaration', id: null, src: null })
}
}
},
VariableDeclaration: (node: antlr.VariableDeclaration) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
UserDefinedTypeName: (node: antlr.UserDefinedTypeName) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
FunctionDefinition: (node: antlr.FunctionDefinition) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
ContractDefinition: (node: antlr.ContractDefinition) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
MemberAccess: function (node: antlr.MemberAccess) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
Identifier: function (node: antlr.Identifier) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
EventDefinition: function (node: antlr.EventDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
ModifierDefinition: function (node: antlr.ModifierDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
InvalidNode: function (node: antlr.InvalidNode) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
EnumDefinition: function (node: antlr.EnumDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
StructDefinition: function (node: antlr.StructDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
}
})
return nodes
}
})
return nodes
}
/**
/**
*
* @param ast
* @returns
*/
async getLastNodeInLine(ast: string) {
let lastNode: any
const checkLastNode = (node: antlr.MemberAccess | antlr.Identifier) => {
if (lastNode && lastNode.range && lastNode.range[1]) {
if (node.range[1] > lastNode.range[1]) {
lastNode = node
}
} else {
lastNode = node
}
async getLastNodeInLine(ast: string) {
let lastNode: any
const checkLastNode = (node: antlr.MemberAccess | antlr.Identifier) => {
if (lastNode && lastNode.range && lastNode.range[1]) {
if (node.range[1] > lastNode.range[1]) {
lastNode = node
}
} else {
lastNode = node
}
}
(SolidityParser as any).visit(ast, {
MemberAccess: function (node: antlr.MemberAccess) {
checkLastNode(node)
},
Identifier: function (node: antlr.Identifier) {
checkLastNode(node)
}
})
if (lastNode && lastNode.expression) {
return lastNode.expression
}
return lastNode
(SolidityParser as any).visit(ast, {
MemberAccess: function (node: antlr.MemberAccess) {
checkLastNode(node)
},
Identifier: function (node: antlr.Identifier) {
checkLastNode(node)
}
})
if (lastNode && lastNode.expression) {
return lastNode.expression
}
/*
return lastNode
}
/*
* get the code blocks of the current file
*/
async getCurrentFileBlocks(text: string | null = null) {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (this.cache[this.plugin.currentFile]) {
if (!this.cache[this.plugin.currentFile].parsingEnabled) {
return
}
}
if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) {
const fileContent = text || await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile)
try {
const startTime = Date.now()
const blocks = (SolidityParser as any).parseBlock(fileContent, { loc: true, range: true, tolerant: true })
if(this.cache[this.plugin.currentFile] && this.cache[this.plugin.currentFile].blockDurations){
this.cache[this.plugin.currentFile].blockDurations = [...this.cache[this.plugin.currentFile].blockDurations.slice(-this.parserThresholdSampleAmount), Date.now() - startTime]
this.setFileParsingState(this.plugin.currentFile)
}
if (blocks) this.cache[this.plugin.currentFile].blocks = blocks
return blocks
} catch (e) {
// do nothing
}
async getCurrentFileBlocks(text: string | null = null) {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (this.cache[this.plugin.currentFile]) {
if (!this.cache[this.plugin.currentFile].parsingEnabled) {
return
}
}
if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) {
const fileContent = text || await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile)
try {
const startTime = Date.now()
const blocks = (SolidityParser as any).parseBlock(fileContent, { loc: true, range: true, tolerant: true })
if(this.cache[this.plugin.currentFile] && this.cache[this.plugin.currentFile].blockDurations){
this.cache[this.plugin.currentFile].blockDurations = [...this.cache[this.plugin.currentFile].blockDurations.slice(-this.parserThresholdSampleAmount), Date.now() - startTime]
this.setFileParsingState(this.plugin.currentFile)
}
if (blocks) this.cache[this.plugin.currentFile].blocks = blocks
return blocks
} catch (e) {
// do nothing
}
}
}
/**
/**
* Returns the block surrounding the given position
* For example if the position is in the middle of a function, it will return the function
* @param {position} position
* @param {string} text // optional
* @return {any}
* */
async getANTLRBlockAtPosition(position: any, text: string = null) {
const blocks: any[] = await this.getCurrentFileBlocks(text)
async getANTLRBlockAtPosition(position: any, text: string = null) {
const blocks: any[] = await this.getCurrentFileBlocks(text)
const walkAst = (blocks) => {
let nodeFound = null
for (const object of blocks) {
if (object.start <= position) {
nodeFound = object
break
}
}
return nodeFound
const walkAst = (blocks) => {
let nodeFound = null
for (const object of blocks) {
if (object.start <= position) {
nodeFound = object
break
}
if (!blocks) return
blocks.reverse()
const block = walkAst(blocks)
return block
}
return nodeFound
}
if (!blocks) return
blocks.reverse()
const block = walkAst(blocks)
return block
}
}

@ -34,253 +34,253 @@ type errorMarker = {
file: string
}
export default class CodeParserCompiler {
plugin: CodeParser
compiler: any // used to compile the current file seperately from the main compiler
onAstFinished: (success: any, data: CompilationResult, source: CompilationSourceCode, input: any, version: any) => Promise<void>;
errorState: boolean;
gastEstimateTimeOut: any
constructor(
plugin: CodeParser
compiler: any // used to compile the current file seperately from the main compiler
onAstFinished: (success: any, data: CompilationResult, source: CompilationSourceCode, input: any, version: any) => Promise<void>;
errorState: boolean;
gastEstimateTimeOut: any
constructor(
plugin: CodeParser
) {
this.plugin = plugin
}
init() {
) {
this.plugin = plugin
}
this.onAstFinished = async (success, data: CompilationResult, source: CompilationSourceCode, input: any, version) => {
this.plugin.call('editor', 'clearAnnotations')
this.errorState = true
const result = new CompilerAbstract('soljson', data, source, input)
let allErrors: errorMarker[] = []
if (data.errors || data.error) {
const file = await this.plugin.call('fileManager', 'getCurrentFile')
const currentFileContent = await this.plugin.call('fileManager', 'readFile', file)
const sources = result.getSourceCode().sources || []
if (data.error) {
if (data.error.formattedMessage) {
// mark this file as error
const errorMarker = await this.createErrorMarker(data.error, file, { start: { line: 0, column: 0 }, end: { line: 0, column: 100 } })
allErrors = [...allErrors, errorMarker]
}
} else {
for (const error of data.errors) {
if (!error.sourceLocation) {
// mark this file as error
const errorMarker = await this.createErrorMarker(error, file, { start: { line: 0, column: 0 }, end: { line: 0, column: 100 } })
allErrors = [...allErrors, errorMarker]
} else {
const lineBreaks = sourceMappingDecoder.getLinebreakPositions(sources[error.sourceLocation.file].content)
const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn({
start: error.sourceLocation.start,
length: error.sourceLocation.end - error.sourceLocation.start
}, lineBreaks)
init() {
this.onAstFinished = async (success, data: CompilationResult, source: CompilationSourceCode, input: any, version) => {
this.plugin.call('editor', 'clearAnnotations')
this.errorState = true
const result = new CompilerAbstract('soljson', data, source, input)
let allErrors: errorMarker[] = []
if (data.errors || data.error) {
const file = await this.plugin.call('fileManager', 'getCurrentFile')
const currentFileContent = await this.plugin.call('fileManager', 'readFile', file)
const sources = result.getSourceCode().sources || []
if (data.error) {
if (data.error.formattedMessage) {
// mark this file as error
const errorMarker = await this.createErrorMarker(data.error, file, { start: { line: 0, column: 0 }, end: { line: 0, column: 100 } })
allErrors = [...allErrors, errorMarker]
}
} else {
for (const error of data.errors) {
if (!error.sourceLocation) {
// mark this file as error
const errorMarker = await this.createErrorMarker(error, file, { start: { line: 0, column: 0 }, end: { line: 0, column: 100 } })
allErrors = [...allErrors, errorMarker]
} else {
const lineBreaks = sourceMappingDecoder.getLinebreakPositions(sources[error.sourceLocation.file].content)
const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn({
start: error.sourceLocation.start,
length: error.sourceLocation.end - error.sourceLocation.start
}, lineBreaks)
const filePath = error.sourceLocation.file
const fileTarget = await this.plugin.call('fileManager', 'getUrlFromPath', filePath)
const importFilePositions = await this.getPositionForImportErrors(fileTarget.file, currentFileContent)
for (const importFilePosition of importFilePositions) {
for (const line of importFilePosition.lines) {
allErrors = [...allErrors, await this.createErrorMarker(error, file, line.position)]
}
}
const filePath = error.sourceLocation.file
const fileTarget = await this.plugin.call('fileManager', 'getUrlFromPath', filePath)
allErrors = [...allErrors, await this.createErrorMarker(error, filePath, lineColumn)]
}
}
const importFilePositions = await this.getPositionForImportErrors(fileTarget.file, currentFileContent)
for (const importFilePosition of importFilePositions) {
for (const line of importFilePosition.lines) {
allErrors = [...allErrors, await this.createErrorMarker(error, file, line.position)]
}
}
const displayErrors = await this.plugin.call('config', 'getAppParameter', 'display-errors')
if (displayErrors) await this.plugin.call('editor', 'addErrorMarker', allErrors)
this.addDecorators(allErrors, sources)
} else {
await this.plugin.call('editor', 'clearErrorMarkers', result.getSourceCode().sources)
await this.clearDecorators(result.getSourceCode().sources)
allErrors = [...allErrors, await this.createErrorMarker(error, filePath, lineColumn)]
}
}
}
const displayErrors = await this.plugin.call('config', 'getAppParameter', 'display-errors')
if (displayErrors) await this.plugin.call('editor', 'addErrorMarker', allErrors)
this.addDecorators(allErrors, sources)
} else {
await this.plugin.call('editor', 'clearErrorMarkers', result.getSourceCode().sources)
await this.clearDecorators(result.getSourceCode().sources)
}
if (!data.sources) return
if (data.sources && Object.keys(data.sources).length === 0) return
this.plugin.compilerAbstract = new CompilerAbstract('soljson', data, source, input)
this.errorState = false
this.plugin.nodeIndex = {
declarations: {},
flatReferences: {},
nodesPerFile: {},
}
if (!data.sources) return
if (data.sources && Object.keys(data.sources).length === 0) return
this.plugin.compilerAbstract = new CompilerAbstract('soljson', data, source, input)
this.errorState = false
this.plugin.nodeIndex = {
declarations: {},
flatReferences: {},
nodesPerFile: {},
}
this.plugin._buildIndex(data, source)
// cast from the remix-plugin interface to the solidity one. Should be fixed when remix-plugin move to the remix-project repository
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult)
await this.plugin.gasService.showGasEstimates()
this.plugin.emit('astFinished')
}
this.compiler = new Compiler((url, cb) => this.plugin.call('contentImport', 'resolveAndSave', url, undefined).then((result) => cb(null, result)).catch((error) => cb(error.message)))
this.compiler.event.register('compilationFinished', this.onAstFinished)
this.plugin._buildIndex(data, source)
// cast from the remix-plugin interface to the solidity one. Should be fixed when remix-plugin move to the remix-project repository
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult)
await this.plugin.gasService.showGasEstimates()
this.plugin.emit('astFinished')
}
this.compiler = new Compiler((url, cb) => this.plugin.call('contentImport', 'resolveAndSave', url, undefined).then((result) => cb(null, result)).catch((error) => cb(error.message)))
this.compiler.event.register('compilationFinished', this.onAstFinished)
}
// COMPILER
// COMPILER
/**
/**
*
* @returns
*/
async compile() {
try {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) {
const state = await this.plugin.call('solidity', 'getCompilerState')
this.compiler.set('optimize', state.optimize)
this.compiler.set('evmVersion', state.evmVersion)
this.compiler.set('language', state.language)
this.compiler.set('runs', state.runs)
this.compiler.set('useFileConfiguration', true)
this.compiler.set('compilerRetriggerMode', CompilerRetriggerMode.retrigger)
const configFileContent = {
"language": "Solidity",
"settings": {
"optimizer": {
"enabled": state.optimize,
"runs": state.runs
},
"outputSelection": {
"*": {
"": ["ast"],
"*": ["evm.gasEstimates"]
}
},
"evmVersion": state.evmVersion && state.evmVersion.toString() || "berlin",
}
}
this.compiler.set('configFileContent', JSON.stringify(configFileContent))
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (!this.plugin.currentFile) return
const content = await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile)
const sources = { [this.plugin.currentFile]: { content } }
this.compiler.compile(sources, this.plugin.currentFile)
}
} catch (e) {
// do nothing
async compile() {
try {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) {
const state = await this.plugin.call('solidity', 'getCompilerState')
this.compiler.set('optimize', state.optimize)
this.compiler.set('evmVersion', state.evmVersion)
this.compiler.set('language', state.language)
this.compiler.set('runs', state.runs)
this.compiler.set('useFileConfiguration', true)
this.compiler.set('compilerRetriggerMode', CompilerRetriggerMode.retrigger)
const configFileContent = {
"language": "Solidity",
"settings": {
"optimizer": {
"enabled": state.optimize,
"runs": state.runs
},
"outputSelection": {
"*": {
"": ["ast"],
"*": ["evm.gasEstimates"]
}
},
"evmVersion": state.evmVersion && state.evmVersion.toString() || "berlin",
}
}
}
async addDecorators(allErrors: errorMarker[], sources: any) {
const displayErrors = await this.plugin.call('config', 'getAppParameter', 'display-errors')
if (!displayErrors) return
const errorsPerFiles: { [fileName: string]: errorMarker[] } = {}
for (const error of allErrors) {
if (!errorsPerFiles[error.file]) {
errorsPerFiles[error.file] = []
}
errorsPerFiles[error.file].push(error)
}
this.compiler.set('configFileContent', JSON.stringify(configFileContent))
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (!this.plugin.currentFile) return
const content = await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile)
const sources = { [this.plugin.currentFile]: { content } }
this.compiler.compile(sources, this.plugin.currentFile)
}
} catch (e) {
// do nothing
}
}
const errorPriority = {
'error': 0,
'warning': 1,
}
async addDecorators(allErrors: errorMarker[], sources: any) {
const displayErrors = await this.plugin.call('config', 'getAppParameter', 'display-errors')
if (!displayErrors) return
const errorsPerFiles: { [fileName: string]: errorMarker[] } = {}
for (const error of allErrors) {
if (!errorsPerFiles[error.file]) {
errorsPerFiles[error.file] = []
}
errorsPerFiles[error.file].push(error)
}
// sort errorPerFiles by error priority
const sortedErrorsPerFiles: { [fileName: string]: errorMarker[] } = {}
for (const fileName in errorsPerFiles) {
const errors = errorsPerFiles[fileName]
errors.sort((a, b) => {
return errorPriority[a.severity] - errorPriority[b.severity]
}
)
sortedErrorsPerFiles[fileName] = errors
}
const filesWithOutErrors = Object.keys(sources).filter((fileName) => !sortedErrorsPerFiles[fileName])
// add decorators
const decorators: fileDecoration[] = []
for (const fileName in sortedErrorsPerFiles) {
const errors = sortedErrorsPerFiles[fileName]
const fileTarget = await this.plugin.call('fileManager', 'getPathFromUrl', fileName)
const decorator: fileDecoration = {
path: fileTarget.file,
isDirectory: false,
fileStateType: errors[0].severity == MarkerSeverity.Error ? fileDecorationType.Error : fileDecorationType.Warning,
fileStateLabelClass: errors[0].severity == MarkerSeverity.Error ? 'text-danger' : 'text-warning',
fileStateIconClass: '',
fileStateIcon: '',
text: errors.length.toString(),
owner: 'code-parser',
bubble: true,
comment: errors.map((error) => error.message),
}
decorators.push(decorator)
}
for (const fileName of filesWithOutErrors) {
const fileTarget = await this.plugin.call('fileManager', 'getPathFromUrl', fileName)
const decorator: fileDecoration = {
path: fileTarget.file,
isDirectory: false,
fileStateType: fileDecorationType.None,
fileStateLabelClass: '',
fileStateIconClass: '',
fileStateIcon: '',
text: '',
owner: 'code-parser',
bubble: false
}
decorators.push(decorator)
}
await this.plugin.call('fileDecorator', 'setFileDecorators', decorators)
await this.plugin.call('editor', 'clearErrorMarkers', filesWithOutErrors)
const errorPriority = {
'error': 0,
'warning': 1,
}
// sort errorPerFiles by error priority
const sortedErrorsPerFiles: { [fileName: string]: errorMarker[] } = {}
for (const fileName in errorsPerFiles) {
const errors = errorsPerFiles[fileName]
errors.sort((a, b) => {
return errorPriority[a.severity] - errorPriority[b.severity]
}
)
sortedErrorsPerFiles[fileName] = errors
}
const filesWithOutErrors = Object.keys(sources).filter((fileName) => !sortedErrorsPerFiles[fileName])
// add decorators
const decorators: fileDecoration[] = []
for (const fileName in sortedErrorsPerFiles) {
const errors = sortedErrorsPerFiles[fileName]
const fileTarget = await this.plugin.call('fileManager', 'getPathFromUrl', fileName)
const decorator: fileDecoration = {
path: fileTarget.file,
isDirectory: false,
fileStateType: errors[0].severity == MarkerSeverity.Error ? fileDecorationType.Error : fileDecorationType.Warning,
fileStateLabelClass: errors[0].severity == MarkerSeverity.Error ? 'text-danger' : 'text-warning',
fileStateIconClass: '',
fileStateIcon: '',
text: errors.length.toString(),
owner: 'code-parser',
bubble: true,
comment: errors.map((error) => error.message),
}
decorators.push(decorator)
}
for (const fileName of filesWithOutErrors) {
const fileTarget = await this.plugin.call('fileManager', 'getPathFromUrl', fileName)
const decorator: fileDecoration = {
path: fileTarget.file,
isDirectory: false,
fileStateType: fileDecorationType.None,
fileStateLabelClass: '',
fileStateIconClass: '',
fileStateIcon: '',
text: '',
owner: 'code-parser',
bubble: false
}
decorators.push(decorator)
}
await this.plugin.call('fileDecorator', 'setFileDecorators', decorators)
await this.plugin.call('editor', 'clearErrorMarkers', filesWithOutErrors)
async createErrorMarker(error: any, filePath: string, lineColumn): Promise<errorMarker> {
return {
message: error.formattedMessage,
severity: error.severity === 'error' ? MarkerSeverity.Error : MarkerSeverity.Warning,
position: {
start: {
line: ((lineColumn.start && lineColumn.start.line) || 0) + 1,
column: ((lineColumn.start && lineColumn.start.column) || 0) + 1
},
end: {
line: ((lineColumn.end && lineColumn.end.line) || 0) + 1,
column: ((lineColumn.end && lineColumn.end.column) || 0) + 1
}
}
, file: filePath
}
async createErrorMarker(error: any, filePath: string, lineColumn): Promise<errorMarker> {
return {
message: error.formattedMessage,
severity: error.severity === 'error' ? MarkerSeverity.Error : MarkerSeverity.Warning,
position: {
start: {
line: ((lineColumn.start && lineColumn.start.line) || 0) + 1,
column: ((lineColumn.start && lineColumn.start.column) || 0) + 1
},
end: {
line: ((lineColumn.end && lineColumn.end.line) || 0) + 1,
column: ((lineColumn.end && lineColumn.end.column) || 0) + 1
}
}
, file: filePath
}
}
async clearDecorators(sources: any) {
const decorators: fileDecoration[] = []
if (!sources) return
for (const fileName of Object.keys(sources)) {
const decorator: fileDecoration = {
path: fileName,
isDirectory: false,
fileStateType: fileDecorationType.None,
fileStateLabelClass: '',
fileStateIconClass: '',
fileStateIcon: '',
text: '',
owner: 'code-parser',
bubble: false
}
decorators.push(decorator)
}
async clearDecorators(sources: any) {
const decorators: fileDecoration[] = []
if (!sources) return
for (const fileName of Object.keys(sources)) {
const decorator: fileDecoration = {
path: fileName,
isDirectory: false,
fileStateType: fileDecorationType.None,
fileStateLabelClass: '',
fileStateIconClass: '',
fileStateIcon: '',
text: '',
owner: 'code-parser',
bubble: false
}
decorators.push(decorator)
}
await this.plugin.call('fileDecorator', 'setFileDecorators', decorators)
}
await this.plugin.call('fileDecorator', 'setFileDecorators', decorators)
}
async getPositionForImportErrors(importedFileName: string, text: string) {
const re = new RegExp(importedFileName, 'gi')
const result: SearchResultLine[] = findLinesInStringWithMatch(
text,
re
)
return result
}
async getPositionForImportErrors(importedFileName: string, text: string) {
const re = new RegExp(importedFileName, 'gi')
const result: SearchResultLine[] = findLinesInStringWithMatch(
text,
re
)
return result
}
}

@ -3,76 +3,76 @@ import { lineText } from '@remix-ui/editor'
import { lastCompilationResult } from '@remixproject/plugin-api';
export default class CodeParserGasService {
plugin: CodeParser
plugin: CodeParser
constructor(plugin: CodeParser) {
this.plugin = plugin
}
constructor(plugin: CodeParser) {
this.plugin = plugin
}
async getGasEstimates(fileName: string) {
if (!fileName) {
fileName = await this.plugin.currentFile
}
if (this.plugin.nodeIndex.nodesPerFile && this.plugin.nodeIndex.nodesPerFile[fileName] && this.plugin.nodeIndex.nodesPerFile[fileName].contracts) {
const estimates: any = []
for (const contract in this.plugin.nodeIndex.nodesPerFile[fileName].contracts) {
if (this.plugin.nodeIndex.nodesPerFile[fileName].contracts[contract].contractNodes) {
const nodes = this.plugin.nodeIndex.nodesPerFile[fileName].contracts[contract].contractNodes
for (const node of Object.values(nodes) as any[]) {
if (node.gasEstimate) {
estimates.push({
node,
range: await this.plugin.getLineColumnOfNode(node)
})
}
}
}
async getGasEstimates(fileName: string) {
if (!fileName) {
fileName = await this.plugin.currentFile
}
if (this.plugin.nodeIndex.nodesPerFile && this.plugin.nodeIndex.nodesPerFile[fileName] && this.plugin.nodeIndex.nodesPerFile[fileName].contracts) {
const estimates: any = []
for (const contract in this.plugin.nodeIndex.nodesPerFile[fileName].contracts) {
if (this.plugin.nodeIndex.nodesPerFile[fileName].contracts[contract].contractNodes) {
const nodes = this.plugin.nodeIndex.nodesPerFile[fileName].contracts[contract].contractNodes
for (const node of Object.values(nodes) as any[]) {
if (node.gasEstimate) {
estimates.push({
node,
range: await this.plugin.getLineColumnOfNode(node)
})
}
return estimates
}
}
}
return estimates
}
}
async showGasEstimates() {
const showGasConfig = await this.plugin.call('config', 'getAppParameter', 'show-gas')
if(!showGasConfig) {
await this.plugin.call('editor', 'discardLineTexts')
return
}
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
// cast from the remix-plugin interface to the solidity one. Should be fixed when remix-plugin move to the remix-project repository
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = await this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult)
const gasEstimates = await this.getGasEstimates(this.plugin.currentFile)
async showGasEstimates() {
const showGasConfig = await this.plugin.call('config', 'getAppParameter', 'show-gas')
if(!showGasConfig) {
await this.plugin.call('editor', 'discardLineTexts')
return
}
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
// cast from the remix-plugin interface to the solidity one. Should be fixed when remix-plugin move to the remix-project repository
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = await this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult)
const gasEstimates = await this.getGasEstimates(this.plugin.currentFile)
const friendlyNames = {
'executionCost': 'Estimated execution cost',
'codeDepositCost': 'Estimated code deposit cost',
'creationCost': 'Estimated creation cost',
const friendlyNames = {
'executionCost': 'Estimated execution cost',
'codeDepositCost': 'Estimated code deposit cost',
'creationCost': 'Estimated creation cost',
}
await this.plugin.call('editor', 'discardLineTexts')
if (gasEstimates) {
for (const estimate of gasEstimates) {
const linetext: lineText = {
content: Object.entries(estimate.node.gasEstimate).map(([, value]) => `${value} gas`).join(' '),
position: estimate.range,
hide: false,
className: 'text-muted small',
afterContentClassName: 'text-muted small fas fa-gas-pump pl-4',
from: 'codeParser',
hoverMessage: [{
value: `${Object.entries(estimate.node.gasEstimate).map(([key, value]) => `${friendlyNames[key]}: ${value} gas`).join(' ')}`,
},
],
}
await this.plugin.call('editor', 'discardLineTexts')
if (gasEstimates) {
for (const estimate of gasEstimates) {
const linetext: lineText = {
content: Object.entries(estimate.node.gasEstimate).map(([, value]) => `${value} gas`).join(' '),
position: estimate.range,
hide: false,
className: 'text-muted small',
afterContentClassName: 'text-muted small fas fa-gas-pump pl-4',
from: 'codeParser',
hoverMessage: [{
value: `${Object.entries(estimate.node.gasEstimate).map(([key, value]) => `${friendlyNames[key]}: ${value} gas`).join(' ')}`,
},
],
}
this.plugin.call('editor', 'addLineText', linetext, estimate.range.fileName)
this.plugin.call('editor', 'addLineText', linetext, estimate.range.fileName)
}
}
}
}
}
}

@ -8,81 +8,81 @@ export type CodeParserImportsData= {
}
export default class CodeParserImports {
plugin: CodeParser
plugin: CodeParser
data: CodeParserImportsData = {}
constructor(plugin: CodeParser) {
this.plugin = plugin
this.init()
}
data: CodeParserImportsData = {}
constructor(plugin: CodeParser) {
this.plugin = plugin
this.init()
}
async getImports(){
return this.data
}
async getImports(){
return this.data
}
async init() {
// @ts-ignore
const txt = await import('raw-loader!libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt')
this.data.modules = txt.default.split('\n')
.filter(x => x !== '')
.map(x => x.replace('./node_modules/', ''))
.filter(x => {
if(x.includes('@openzeppelin')) {
return !x.includes('mock')
}else{
return true
}
})
async init() {
// @ts-ignore
const txt = await import('raw-loader!libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt')
this.data.modules = txt.default.split('\n')
.filter(x => x !== '')
.map(x => x.replace('./node_modules/', ''))
.filter(x => {
if(x.includes('@openzeppelin')) {
return !x.includes('mock')
}else{
return true
}
})
// get unique first words of the values in the array
this.data.packages = [...new Set(this.data.modules.map(x => x.split('/')[0]))]
}
// get unique first words of the values in the array
this.data.packages = [...new Set(this.data.modules.map(x => x.split('/')[0]))]
}
setFileTree = async () => {
this.data.files = await this.getDirectory('/')
this.data.files = this.data.files.filter(x => x.endsWith('.sol') && !x.startsWith('.deps') && !x.startsWith('.git'))
}
setFileTree = async () => {
this.data.files = await this.getDirectory('/')
this.data.files = this.data.files.filter(x => x.endsWith('.sol') && !x.startsWith('.deps') && !x.startsWith('.git'))
}
getDirectory = async (dir: string) => {
let result = []
let files = {}
try {
if (await this.plugin.call('fileManager', 'exists', dir)) {
files = await this.plugin.call('fileManager', 'readdir', dir)
}
} catch (e) {}
getDirectory = async (dir: string) => {
let result = []
let files = {}
try {
if (await this.plugin.call('fileManager', 'exists', dir)) {
files = await this.plugin.call('fileManager', 'readdir', dir)
}
} catch (e) {}
const fileArray = this.normalize(files)
for (const fi of fileArray) {
if (fi) {
const type = fi.data.isDirectory
if (type === true) {
result = [...result, ...(await this.getDirectory(`${fi.filename}`))]
} else {
result = [...result, fi.filename]
}
}
const fileArray = this.normalize(files)
for (const fi of fileArray) {
if (fi) {
const type = fi.data.isDirectory
if (type === true) {
result = [...result, ...(await this.getDirectory(`${fi.filename}`))]
} else {
result = [...result, fi.filename]
}
return result
}
}
return result
}
normalize = filesList => {
const folders = []
const files = []
Object.keys(filesList || {}).forEach(key => {
if (filesList[key].isDirectory) {
folders.push({
filename: key,
data: filesList[key]
})
} else {
files.push({
filename: key,
data: filesList[key]
})
}
normalize = filesList => {
const folders = []
const files = []
Object.keys(filesList || {}).forEach(key => {
if (filesList[key].isDirectory) {
folders.push({
filename: key,
data: filesList[key]
})
return [...folders, ...files]
}
} else {
files.push({
filename: key,
data: filesList[key]
})
}
})
return [...folders, ...files]
}
}

@ -596,132 +596,132 @@ type ASTTypeMap = ASTMap<ASTNode>
export const astNodeTypes = [
'SourceUnit',
'PragmaDirective',
'ImportDirective',
'ContractDefinition',
'InheritanceSpecifier',
'StateVariableDeclaration',
'UsingForDeclaration',
'StructDefinition',
'ModifierDefinition',
'ModifierInvocation',
'FunctionDefinition',
'EventDefinition',
'CustomErrorDefinition',
'RevertStatement',
'EnumValue',
'EnumDefinition',
'VariableDeclaration',
'UserDefinedTypeName',
'Mapping',
'ArrayTypeName',
'FunctionTypeName',
'Block',
'ExpressionStatement',
'IfStatement',
'WhileStatement',
'ForStatement',
'InlineAssemblyStatement',
'DoWhileStatement',
'ContinueStatement',
'Break',
'Continue',
'BreakStatement',
'ReturnStatement',
'EmitStatement',
'ThrowStatement',
'VariableDeclarationStatement',
'ElementaryTypeName',
'FunctionCall',
'AssemblyBlock',
'AssemblyCall',
'AssemblyLocalDefinition',
'AssemblyAssignment',
'AssemblyStackAssignment',
'LabelDefinition',
'AssemblySwitch',
'AssemblyCase',
'AssemblyFunctionDefinition',
'AssemblyFunctionReturns',
'AssemblyFor',
'AssemblyIf',
'SubAssembly',
'TupleExpression',
'NameValueExpression',
'BooleanLiteral',
'NumberLiteral',
'Identifier',
'BinaryOperation',
'UnaryOperation',
'NewExpression',
'Conditional',
'StringLiteral',
'HexLiteral',
'HexNumber',
'DecimalNumber',
'MemberAccess',
'IndexAccess',
'IndexRangeAccess',
'NameValueList',
'UncheckedStatement',
'TryStatement',
'CatchClause',
'FileLevelConstant',
'AssemblyMemberAccess',
'TypeDefinition',
'InvalidNode'
'SourceUnit',
'PragmaDirective',
'ImportDirective',
'ContractDefinition',
'InheritanceSpecifier',
'StateVariableDeclaration',
'UsingForDeclaration',
'StructDefinition',
'ModifierDefinition',
'ModifierInvocation',
'FunctionDefinition',
'EventDefinition',
'CustomErrorDefinition',
'RevertStatement',
'EnumValue',
'EnumDefinition',
'VariableDeclaration',
'UserDefinedTypeName',
'Mapping',
'ArrayTypeName',
'FunctionTypeName',
'Block',
'ExpressionStatement',
'IfStatement',
'WhileStatement',
'ForStatement',
'InlineAssemblyStatement',
'DoWhileStatement',
'ContinueStatement',
'Break',
'Continue',
'BreakStatement',
'ReturnStatement',
'EmitStatement',
'ThrowStatement',
'VariableDeclarationStatement',
'ElementaryTypeName',
'FunctionCall',
'AssemblyBlock',
'AssemblyCall',
'AssemblyLocalDefinition',
'AssemblyAssignment',
'AssemblyStackAssignment',
'LabelDefinition',
'AssemblySwitch',
'AssemblyCase',
'AssemblyFunctionDefinition',
'AssemblyFunctionReturns',
'AssemblyFor',
'AssemblyIf',
'SubAssembly',
'TupleExpression',
'NameValueExpression',
'BooleanLiteral',
'NumberLiteral',
'Identifier',
'BinaryOperation',
'UnaryOperation',
'NewExpression',
'Conditional',
'StringLiteral',
'HexLiteral',
'HexNumber',
'DecimalNumber',
'MemberAccess',
'IndexAccess',
'IndexRangeAccess',
'NameValueList',
'UncheckedStatement',
'TryStatement',
'CatchClause',
'FileLevelConstant',
'AssemblyMemberAccess',
'TypeDefinition',
'InvalidNode'
] as const
export const binaryOpValues = [
'+',
'-',
'*',
'/',
'**',
'%',
'<<',
'>>',
'&&',
'||',
',,',
'&',
',',
'^',
'<',
'>',
'<=',
'>=',
'==',
'!=',
'=',
',=',
'^=',
'&=',
'<<=',
'>>=',
'+=',
'-=',
'*=',
'/=',
'%=',
'|',
'|=',
'+',
'-',
'*',
'/',
'**',
'%',
'<<',
'>>',
'&&',
'||',
',,',
'&',
',',
'^',
'<',
'>',
'<=',
'>=',
'==',
'!=',
'=',
',=',
'^=',
'&=',
'<<=',
'>>=',
'+=',
'-=',
'*=',
'/=',
'%=',
'|',
'|=',
] as const
export type BinOp = typeof binaryOpValues[number]
export const unaryOpValues = [
'-',
'+',
'++',
'--',
'~',
'after',
'delete',
'!',
'-',
'+',
'++',
'--',
'~',
'after',
'delete',
'!',
] as const
export type UnaryOp = typeof unaryOpValues[number]

@ -6,73 +6,73 @@ import { PermissionHandlerDialog, PermissionHandlerValue } from '@remix-ui/permi
import { Profile } from '@remixproject/plugin-utils'
const profile = {
name: 'permissionhandler',
displayName: 'permissionhandler',
description: 'Plugin to handle permissions',
methods: ['askPermission']
name: 'permissionhandler',
displayName: 'permissionhandler',
description: 'Plugin to handle permissions',
methods: ['askPermission']
}
export class PermissionHandlerPlugin extends Plugin {
permissions: any
sessionPermissions: any
currentVersion: number
fallbackMemory: boolean
constructor() {
super(profile)
this.fallbackMemory = false
this.permissions = this._getFromLocal()
this.sessionPermissions = {}
this.currentVersion = 1
// here we remove the old permissions saved before adding 'permissionVersion'
// since with v1 the structure has been changed because of new engine ^0.2.0-alpha.6 changes
if (!localStorage.getItem('permissionVersion')) {
localStorage.setItem('plugins/permissions', '')
localStorage.setItem('permissionVersion', this.currentVersion.toString())
}
permissions: any
sessionPermissions: any
currentVersion: number
fallbackMemory: boolean
constructor() {
super(profile)
this.fallbackMemory = false
this.permissions = this._getFromLocal()
this.sessionPermissions = {}
this.currentVersion = 1
// here we remove the old permissions saved before adding 'permissionVersion'
// since with v1 the structure has been changed because of new engine ^0.2.0-alpha.6 changes
if (!localStorage.getItem('permissionVersion')) {
localStorage.setItem('plugins/permissions', '')
localStorage.setItem('permissionVersion', this.currentVersion.toString())
}
}
_getFromLocal() {
if (this.fallbackMemory) return this.permissions
const permission = localStorage.getItem('plugins/permissions')
return permission ? JSON.parse(permission) : {}
}
_getFromLocal() {
if (this.fallbackMemory) return this.permissions
const permission = localStorage.getItem('plugins/permissions')
return permission ? JSON.parse(permission) : {}
}
persistPermissions() {
const permissions = JSON.stringify(this.permissions)
try {
localStorage.setItem('plugins/permissions', permissions)
} catch (e) {
this.fallbackMemory = true
console.log(e)
}
persistPermissions() {
const permissions = JSON.stringify(this.permissions)
try {
localStorage.setItem('plugins/permissions', permissions)
} catch (e) {
this.fallbackMemory = true
console.log(e)
}
}
switchMode (from: Profile, to: Profile, method: string, set: boolean, sensitiveCall: boolean) {
if (sensitiveCall) {
set
? this.sessionPermissions[to.name][method][from.name] = {}
: delete this.sessionPermissions[to.name][method][from.name]
} else {
set
? this.permissions[to.name][method][from.name] = {}
: delete this.permissions[to.name][method][from.name]
}
}
switchMode (from: Profile, to: Profile, method: string, set: boolean, sensitiveCall: boolean) {
if (sensitiveCall) {
set
? this.sessionPermissions[to.name][method][from.name] = {}
: delete this.sessionPermissions[to.name][method][from.name]
} else {
set
? this.permissions[to.name][method][from.name] = {}
: delete this.permissions[to.name][method][from.name]
}
}
clear() {
localStorage.removeItem('plugins/permissions')
this.permissions = this._getFromLocal()
this.sessionPermissions = {}
}
clear() {
localStorage.removeItem('plugins/permissions')
this.permissions = this._getFromLocal()
this.sessionPermissions = {}
}
notAllowWarning(from: Profile, to: Profile, method: string) {
return `${from.displayName || from.name} is not allowed to call ${method} method of ${to.displayName || to.name}.`
}
notAllowWarning(from: Profile, to: Profile, method: string) {
return `${from.displayName || from.name} is not allowed to call ${method} method of ${to.displayName || to.name}.`
}
async getTheme() {
return (await this.call('theme', 'currentTheme')).quality
}
async getTheme() {
return (await this.call('theme', 'currentTheme')).quality
}
/**
/**
* Check if a plugin has the permission to call another plugin and askPermission if needed
* @param {PluginProfile} from the profile of the plugin that make the call
* @param {ModuleProfile} to The profile of the module that receive the call
@ -80,95 +80,95 @@ export class PermissionHandlerPlugin extends Plugin {
* @param {string} message from the caller plugin to add more details if needed
* @returns {Promise<boolean>}
*/
async askPermission(from: Profile, to: Profile, method: string, message: string, sensitiveCall: boolean) {
try {
if (sensitiveCall) {
if (!this.sessionPermissions[to.name]) this.sessionPermissions[to.name] = {}
if (!this.sessionPermissions[to.name][method]) this.sessionPermissions[to.name][method] = {}
if (!this.sessionPermissions[to.name][method][from.name]) return this.openPermission(from, to, method, message, sensitiveCall)
} else {
this.permissions = this._getFromLocal()
if (!this.permissions[to.name]) this.permissions[to.name] = {}
if (!this.permissions[to.name][method]) this.permissions[to.name][method] = {}
if (!this.permissions[to.name][method][from.name]) return this.openPermission(from, to, method, message, sensitiveCall)
}
async askPermission(from: Profile, to: Profile, method: string, message: string, sensitiveCall: boolean) {
try {
if (sensitiveCall) {
if (!this.sessionPermissions[to.name]) this.sessionPermissions[to.name] = {}
if (!this.sessionPermissions[to.name][method]) this.sessionPermissions[to.name][method] = {}
if (!this.sessionPermissions[to.name][method][from.name]) return this.openPermission(from, to, method, message, sensitiveCall)
} else {
this.permissions = this._getFromLocal()
if (!this.permissions[to.name]) this.permissions[to.name] = {}
if (!this.permissions[to.name][method]) this.permissions[to.name][method] = {}
if (!this.permissions[to.name][method][from.name]) return this.openPermission(from, to, method, message, sensitiveCall)
}
const { allow, hash } = sensitiveCall ? this.sessionPermissions[to.name][method][from.name] : this.permissions[to.name][method][from.name]
if (!allow) {
const warning = this.notAllowWarning(from, to, method)
this.call('notification', 'toast', warning)
return false
}
return hash === from.hash
? true // Allow
: await this.openPermission(from, to, method, message, sensitiveCall)
} catch (err) {
throw new Error(err)
}
const { allow, hash } = sensitiveCall ? this.sessionPermissions[to.name][method][from.name] : this.permissions[to.name][method][from.name]
if (!allow) {
const warning = this.notAllowWarning(from, to, method)
this.call('notification', 'toast', warning)
return false
}
return hash === from.hash
? true // Allow
: await this.openPermission(from, to, method, message, sensitiveCall)
} catch (err) {
throw new Error(err)
}
}
async openPermission(from: Profile, to: Profile, method: string, message: string, sensitiveCall: boolean) {
let remember
async openPermission(from: Profile, to: Profile, method: string, message: string, sensitiveCall: boolean) {
let remember
if (sensitiveCall) {
remember = this.sessionPermissions[to.name][method][from.name]
} else {
remember = this.permissions[to.name][method][from.name]
}
const value: PermissionHandlerValue = {
from,
to,
method,
message,
remember,
sensitiveCall
}
const modal: AppModal = {
id: 'PermissionHandler',
title: <FormattedMessage id='permissionHandler.permissionNeededFor' values={{ to: to.displayName || to.name }} />,
message: <PermissionHandlerDialog plugin={this} theme={await this.getTheme()} value={value}></PermissionHandlerDialog>,
okLabel: <FormattedMessage id='permissionHandler.accept' />,
cancelLabel: <FormattedMessage id='permissionHandler.decline' />
}
const result = await this.call('notification', 'modal', modal)
return new Promise((resolve, reject) => {
if (result) {
if (sensitiveCall) {
remember = this.sessionPermissions[to.name][method][from.name]
if (this.sessionPermissions[to.name][method][from.name]) {
this.sessionPermissions[to.name][method][from.name] = {
allow: true,
hash: from.hash
}
}
} else {
remember = this.permissions[to.name][method][from.name]
}
const value: PermissionHandlerValue = {
from,
to,
method,
message,
remember,
sensitiveCall
}
const modal: AppModal = {
id: 'PermissionHandler',
title: <FormattedMessage id='permissionHandler.permissionNeededFor' values={{ to: to.displayName || to.name }} />,
message: <PermissionHandlerDialog plugin={this} theme={await this.getTheme()} value={value}></PermissionHandlerDialog>,
okLabel: <FormattedMessage id='permissionHandler.accept' />,
cancelLabel: <FormattedMessage id='permissionHandler.decline' />
if (this.permissions[to.name][method][from.name]) {
this.permissions[to.name][method][from.name] = {
allow: true,
hash: from.hash
}
this.persistPermissions()
}
}
const result = await this.call('notification', 'modal', modal)
return new Promise((resolve, reject) => {
if (result) {
if (sensitiveCall) {
if (this.sessionPermissions[to.name][method][from.name]) {
this.sessionPermissions[to.name][method][from.name] = {
allow: true,
hash: from.hash
}
}
} else {
if (this.permissions[to.name][method][from.name]) {
this.permissions[to.name][method][from.name] = {
allow: true,
hash: from.hash
}
this.persistPermissions()
}
}
resolve(true)
} else {
if (sensitiveCall) {
if (this.sessionPermissions[to.name][method][from.name]) {
this.sessionPermissions[to.name][method][from.name] = {
allow: false,
hash: from.hash
}
}
} else {
if (this.permissions[to.name][method][from.name]) {
this.permissions[to.name][method][from.name] = {
allow: false,
hash: from.hash
}
this.persistPermissions()
}
}
reject(this.notAllowWarning(from, to, method))
resolve(true)
} else {
if (sensitiveCall) {
if (this.sessionPermissions[to.name][method][from.name]) {
this.sessionPermissions[to.name][method][from.name] = {
allow: false,
hash: from.hash
}
})
}
}
} else {
if (this.permissions[to.name][method][from.name]) {
this.permissions[to.name][method][from.name] = {
allow: false,
hash: from.hash
}
this.persistPermissions()
}
}
reject(this.notAllowWarning(from, to, method))
}
})
}
}

@ -11,179 +11,179 @@ import { AppModal, AlertModal } from '@remix-ui/app'
const LOCALHOST = ' - connect to localhost - '
const profile = {
name: 'remixd',
displayName: 'RemixD',
url: 'ws://127.0.0.1:65520',
methods: ['folderIsReadOnly', 'resolveDirectory', 'get', 'exists', 'isFile', 'set', 'rename', 'remove', 'isDirectory', 'list', 'createDir'],
events: [],
description: 'Using Remixd daemon, allow to access file system',
kind: 'other',
version: packageJson.version,
repo: "https://github.com/ethereum/remix-project/tree/master/libs/remixd",
maintainedBy: "Remix",
documentation: "https://remix-ide.readthedocs.io/en/latest/remixd.html",
authorContact: ""
name: 'remixd',
displayName: 'RemixD',
url: 'ws://127.0.0.1:65520',
methods: ['folderIsReadOnly', 'resolveDirectory', 'get', 'exists', 'isFile', 'set', 'rename', 'remove', 'isDirectory', 'list', 'createDir'],
events: [],
description: 'Using Remixd daemon, allow to access file system',
kind: 'other',
version: packageJson.version,
repo: "https://github.com/ethereum/remix-project/tree/master/libs/remixd",
maintainedBy: "Remix",
documentation: "https://remix-ide.readthedocs.io/en/latest/remixd.html",
authorContact: ""
}
export class RemixdHandle extends WebsocketPlugin {
localhostProvider: any
appManager: PluginManager
dependentPlugins: Array<string>
constructor(localhostProvider, appManager) {
super(profile)
this.localhostProvider = localhostProvider
this.appManager = appManager
this.dependentPlugins = ['hardhat', 'truffle', 'slither', 'foundry']
}
localhostProvider: any
appManager: PluginManager
dependentPlugins: Array<string>
constructor(localhostProvider, appManager) {
super(profile)
this.localhostProvider = localhostProvider
this.appManager = appManager
this.dependentPlugins = ['hardhat', 'truffle', 'slither', 'foundry']
}
async deactivate() {
for (const plugin of this.dependentPlugins) {
if (await this.appManager.isActive(plugin)) {
await this.appManager.deactivatePlugin(plugin)
}
}
if (super.socket) super.deactivate()
// this.appManager.deactivatePlugin('git') // plugin call doesn't work.. see issue https://github.com/ethereum/remix-plugin/issues/342
this.localhostProvider.close((error) => {
if (error) console.log(error)
})
async deactivate() {
for (const plugin of this.dependentPlugins) {
if (await this.appManager.isActive(plugin)) {
await this.appManager.deactivatePlugin(plugin)
}
}
if (super.socket) super.deactivate()
// this.appManager.deactivatePlugin('git') // plugin call doesn't work.. see issue https://github.com/ethereum/remix-plugin/issues/342
this.localhostProvider.close((error) => {
if (error) console.log(error)
})
}
async activate() {
this.connectToLocalhost()
return true
}
async activate() {
this.connectToLocalhost()
return true
async canceled() {
for (const plugin of this.dependentPlugins) {
if (await this.appManager.isActive(plugin)) {
await this.appManager.deactivatePlugin(plugin)
}
}
async canceled() {
for (const plugin of this.dependentPlugins) {
if (await this.appManager.isActive(plugin)) {
await this.appManager.deactivatePlugin(plugin)
}
}
await this.appManager.deactivatePlugin('remixd')
await this.appManager.deactivatePlugin('remixd')
}
async callPluginMethod(key: string, payload?: any[]) {
if (this.socket.readyState !== this.socket.OPEN) {
console.log(`${this.profile.name} connection is not opened.`)
return false
}
return super.callPluginMethod(key, payload)
}
async callPluginMethod(key: string, payload?: any[]) {
if (this.socket.readyState !== this.socket.OPEN) {
console.log(`${this.profile.name} connection is not opened.`)
return false
}
return super.callPluginMethod(key, payload)
}
/**
/**
* connect to localhost if no connection and render the explorer
* disconnect from localhost if connected and remove the explorer
*
* @param {String} txHash - hash of the transaction
*/
async connectToLocalhost() {
const connection = async (error?: any) => {
if (error) {
console.log(error)
const alert: AlertModal = {
id: 'connectionAlert',
message: 'Cannot connect to the remixd daemon. Please make sure you have the remixd running in the background.'
}
this.call('notification', 'alert', alert)
this.canceled()
} else {
const intervalId = setInterval(() => {
if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed
clearInterval(intervalId)
const alert: AlertModal = {
id: 'connectionAlert',
message: 'Connection to remixd terminated. Please make sure remixd is still running in the background.'
}
this.call('notification', 'alert', alert)
this.canceled()
}
}, 3000)
this.localhostProvider.init(() => {
this.call('filePanel', 'setWorkspace', { name: LOCALHOST, isLocalhost: true }, true)
});
for (const plugin of this.dependentPlugins) {
await this.appManager.activatePlugin(plugin)
}
}
async connectToLocalhost() {
const connection = async (error?: any) => {
if (error) {
console.log(error)
const alert: AlertModal = {
id: 'connectionAlert',
message: 'Cannot connect to the remixd daemon. Please make sure you have the remixd running in the background.'
}
if (this.localhostProvider.isConnected()) {
this.deactivate()
} else if (!isElectron()) {
// warn the user only if he/she is in the browser context
const mod: AppModal = {
id: 'remixdConnect',
title: 'Access file system using remixd',
message: remixdDialog(),
okLabel: 'Connect',
cancelLabel: 'Cancel',
}
const result = await this.call('notification', 'modal', mod)
if (result) {
try {
this.localhostProvider.preInit()
super.activate()
setTimeout(() => {
if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed
connection(new Error('Connection with daemon failed.'))
} else {
connection()
}
}, 3000)
} catch (error) {
connection(error)
}
}
else {
await this.canceled()
this.call('notification', 'alert', alert)
this.canceled()
} else {
const intervalId = setInterval(() => {
if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed
clearInterval(intervalId)
const alert: AlertModal = {
id: 'connectionAlert',
message: 'Connection to remixd terminated. Please make sure remixd is still running in the background.'
}
} else {
try {
super.activate()
setTimeout(() => { connection() }, 2000)
} catch (error) {
connection(error)
this.call('notification', 'alert', alert)
this.canceled()
}
}, 3000)
this.localhostProvider.init(() => {
this.call('filePanel', 'setWorkspace', { name: LOCALHOST, isLocalhost: true }, true)
});
for (const plugin of this.dependentPlugins) {
await this.appManager.activatePlugin(plugin)
}
}
}
if (this.localhostProvider.isConnected()) {
this.deactivate()
} else if (!isElectron()) {
// warn the user only if he/she is in the browser context
const mod: AppModal = {
id: 'remixdConnect',
title: 'Access file system using remixd',
message: remixdDialog(),
okLabel: 'Connect',
cancelLabel: 'Cancel',
}
const result = await this.call('notification', 'modal', mod)
if (result) {
try {
this.localhostProvider.preInit()
super.activate()
setTimeout(() => {
if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed
connection(new Error('Connection with daemon failed.'))
} else {
connection()
}
}, 3000)
} catch (error) {
connection(error)
}
}
else {
await this.canceled()
}
} else {
try {
super.activate()
setTimeout(() => { connection() }, 2000)
} catch (error) {
connection(error)
}
}
}
}
function remixdDialog() {
const commandText = 'remixd'
const fullCommandText = 'remixd -s <path-to-the-shared-folder> -u <remix-ide-instance-URL>'
return (<>
<div className=''>
<div className='mb-2 text-break'>
const commandText = 'remixd'
const fullCommandText = 'remixd -s <path-to-the-shared-folder> -u <remix-ide-instance-URL>'
return (<>
<div className=''>
<div className='mb-2 text-break'>
Access your local file system from Remix IDE using <a target="_blank" href="https://www.npmjs.com/package/@remix-project/remixd">Remixd NPM package</a>.
</div>
<div className='mb-2 text-break'>
</div>
<div className='mb-2 text-break'>
Remixd <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">documentation</a>.
</div>
<div className='mb-2 text-break'>
</div>
<div className='mb-2 text-break'>
The remixd command is:
<br /><b>{commandText}</b>
</div>
<div className='mb-2 text-break'>
<br /><b>{commandText}</b>
</div>
<div className='mb-2 text-break'>
The remixd command without options uses the terminal's current directory as the shared directory and the shared Remix domain can only be https://remix.ethereum.org, https://remix-alpha.ethereum.org, or https://remix-beta.ethereum.org
</div>
<div className='mb-2 text-break'>
</div>
<div className='mb-2 text-break'>
Example command with flags: <br />
<b>{fullCommandText}</b>
</div>
<div className='mb-2 text-break'>
<b>{fullCommandText}</b>
</div>
<div className='mb-2 text-break'>
For info about ports, see <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#ports-usage">Remixd ports usage</a>
</div>
<div className='mb-2 text-break'>
</div>
<div className='mb-2 text-break'>
This feature is still in Alpha. We recommend to keep a backup of the shared folder.
</div>
<div className='mb-2 text-break'>
<h6 className="text-danger">
</div>
<div className='mb-2 text-break'>
<h6 className="text-danger">
Before using, make sure remixd version is latest i.e. <b>v{remixdVersion}</b>
<br></br><a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#update-to-the-latest-remixd">Read here how to update it</a>
</h6>
</div>
</div>
</>)
<br></br><a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#update-to-the-latest-remixd">Read here how to update it</a>
</h6>
</div>
</div>
</>)
}

@ -6,25 +6,25 @@ import { TransactionConfig } from 'web3-core'
const _paq = window._paq = window._paq || [] //eslint-disable-line
const profile = {
name: 'solidity-script',
displayName: 'solidity-script',
description: 'solidity-script',
methods: ['execute']
name: 'solidity-script',
displayName: 'solidity-script',
description: 'solidity-script',
methods: ['execute']
}
export class SolidityScript extends Plugin {
constructor () {
super(profile)
}
constructor () {
super(profile)
}
async execute (path: string, functionName: string = 'run') {
_paq.push(['trackEvent', 'SolidityScript', 'execute', 'script'])
this.call('terminal', 'log', `Running free function '${functionName}' from ${path}...`)
let content = await this.call('fileManager', 'readFile', path)
const params = await this.call('solidity', 'getCompilerParameters')
async execute (path: string, functionName: string = 'run') {
_paq.push(['trackEvent', 'SolidityScript', 'execute', 'script'])
this.call('terminal', 'log', `Running free function '${functionName}' from ${path}...`)
let content = await this.call('fileManager', 'readFile', path)
const params = await this.call('solidity', 'getCompilerParameters')
content = `
content = `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1;
@ -38,69 +38,69 @@ export class SolidityScript extends Plugin {
${functionName}();
}
}`
const targets = { 'script.sol': { content } }
const targets = { 'script.sol': { content } }
// compile
const compilation = await compile(targets, params, async (url, cb) => {
await this.call('contentImport', 'resolveAndSave', url).then((result) => cb(null, result)).catch((error) => cb(error.message))
})
// compile
const compilation = await compile(targets, params, async (url, cb) => {
await this.call('contentImport', 'resolveAndSave', url).then((result) => cb(null, result)).catch((error) => cb(error.message))
})
if (compilation.data.error) {
this.call('terminal', 'log', compilation.data.error.formattedMessage)
}
if (compilation.data.errors && compilation.data.errors.length > 0) {
compilation.data.errors.map((error) => {
this.call('terminal', 'log', error.formattedMessage)
})
}
if (compilation.data.error) {
this.call('terminal', 'log', compilation.data.error.formattedMessage)
}
if (compilation.data.errors && compilation.data.errors.length > 0) {
compilation.data.errors.map((error) => {
this.call('terminal', 'log', error.formattedMessage)
})
}
// get the contract
const contract = compilation.getContract('SolidityScript')
if (!contract) {
console.log('compilation failed')
return
}
const bytecode = '0x' + contract.object.evm.bytecode.object
const web3 = await this.call('blockchain', 'web3VM')
const accounts = await this.call('blockchain', 'getAccounts')
if (!accounts || accounts.length === 0) {
throw new Error('no account available')
}
// get the contract
const contract = compilation.getContract('SolidityScript')
if (!contract) {
console.log('compilation failed')
return
}
const bytecode = '0x' + contract.object.evm.bytecode.object
const web3 = await this.call('blockchain', 'web3VM')
const accounts = await this.call('blockchain', 'getAccounts')
if (!accounts || accounts.length === 0) {
throw new Error('no account available')
}
// deploy the contract
let tx: TransactionConfig = {
from: accounts[0],
data: bytecode
}
const receipt = await web3.eth.sendTransaction(tx)
tx = {
from: accounts[0],
to: receipt.contractAddress,
data: '0x69d4394b' // function remixRun() public
}
const receiptCall = await web3.eth.sendTransaction(tx)
// deploy the contract
let tx: TransactionConfig = {
from: accounts[0],
data: bytecode
}
const receipt = await web3.eth.sendTransaction(tx)
tx = {
from: accounts[0],
to: receipt.contractAddress,
data: '0x69d4394b' // function remixRun() public
}
const receiptCall = await web3.eth.sendTransaction(tx)
const hhlogs = await web3.eth.getHHLogsForTx(receiptCall.transactionHash)
const hhlogs = await web3.eth.getHHLogsForTx(receiptCall.transactionHash)
if (hhlogs && hhlogs.length) {
const finalLogs = <div><div><b>console.log:</b></div>
{
hhlogs.map((log) => {
let formattedLog
// Hardhat implements the same formatting options that can be found in Node.js' console.log,
// which in turn uses util.format: https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args
// For example: console.log("Name: %s, Age: %d", remix, 6) will log 'Name: remix, Age: 6'
// We check first arg to determine if 'util.format' is needed
if (typeof log[0] === 'string' && (log[0].includes('%s') || log[0].includes('%d'))) {
formattedLog = format(log[0], ...log.slice(1))
} else {
formattedLog = log.join(' ')
}
return <div>{formattedLog}</div>
})}
</div>
_paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log'])
this.call('terminal', 'logHtml', finalLogs)
}
}
if (hhlogs && hhlogs.length) {
const finalLogs = <div><div><b>console.log:</b></div>
{
hhlogs.map((log) => {
let formattedLog
// Hardhat implements the same formatting options that can be found in Node.js' console.log,
// which in turn uses util.format: https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args
// For example: console.log("Name: %s, Age: %d", remix, 6) will log 'Name: remix, Age: 6'
// We check first arg to determine if 'util.format' is needed
if (typeof log[0] === 'string' && (log[0].includes('%s') || log[0].includes('%d'))) {
formattedLog = format(log[0], ...log.slice(1))
} else {
formattedLog = log.join(' ')
}
return <div>{formattedLog}</div>
})}
</div>
_paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log'])
this.call('terminal', 'logHtml', finalLogs)
}
}
}

@ -16,39 +16,39 @@ const parser = (window as any).SolidityParser
const _paq = window._paq = window._paq || []
const profile = {
name: 'solidityumlgen',
displayName: 'Solidity UML Generator',
description: 'Generates UML diagram in svg format from last compiled contract',
location: 'mainPanel',
methods: ['showUmlDiagram', 'generateUml', 'generateCustomAction'],
events: [],
name: 'solidityumlgen',
displayName: 'Solidity UML Generator',
description: 'Generates UML diagram in svg format from last compiled contract',
location: 'mainPanel',
methods: ['showUmlDiagram', 'generateUml', 'generateCustomAction'],
events: [],
}
const themeCollection = [
{ themeName: 'HackerOwl', backgroundColor: '#011628', textColor: '#babbcc',
shapeColor: '#8694a1',fillColor: '#011C32'},
{ themeName: 'Cerulean', backgroundColor: '#ffffff', textColor: '#343a40',
shapeColor: '#343a40',fillColor: '#f8f9fa'},
{ themeName: 'Cyborg', backgroundColor: '#060606', textColor: '#adafae',
shapeColor: '#adafae', fillColor: '#222222'},
{ themeName: 'Dark', backgroundColor: '#222336', textColor: '#babbcc',
shapeColor: '#babbcc',fillColor: '#2a2c3f'},
{ themeName: 'Flatly', backgroundColor: '#ffffff', textColor: '#343a40',
shapeColor: '#7b8a8b',fillColor: '#ffffff'},
{ themeName: 'Black', backgroundColor: '#1a1a1a', textColor: '#babbcc',
shapeColor: '#b5b4bc',fillColor: '#1f2020'},
{ themeName: 'Light', backgroundColor: '#eef1f6', textColor: '#3b445e',
shapeColor: '#343a40',fillColor: '#ffffff'},
{ themeName: 'Midcentury', backgroundColor: '#DBE2E0', textColor: '#11556c',
shapeColor: '#343a40',fillColor: '#eeede9'},
{ themeName: 'Spacelab', backgroundColor: '#ffffff', textColor: '#343a40',
shapeColor: '#333333', fillColor: '#eeeeee'},
{ themeName: 'Candy', backgroundColor: '#d5efff', textColor: '#11556c',
shapeColor: '#343a40',fillColor: '#fbe7f8' },
{ themeName: 'Violet', backgroundColor: '#f1eef6', textColor: '#3b445e',
shapeColor: '#343a40',fillColor: '#f8fafe' },
{ themeName: 'Unicorn', backgroundColor: '#f1eef6', textColor: '#343a40',
shapeColor: '#343a40',fillColor: '#f8fafe' },
{ themeName: 'HackerOwl', backgroundColor: '#011628', textColor: '#babbcc',
shapeColor: '#8694a1',fillColor: '#011C32'},
{ themeName: 'Cerulean', backgroundColor: '#ffffff', textColor: '#343a40',
shapeColor: '#343a40',fillColor: '#f8f9fa'},
{ themeName: 'Cyborg', backgroundColor: '#060606', textColor: '#adafae',
shapeColor: '#adafae', fillColor: '#222222'},
{ themeName: 'Dark', backgroundColor: '#222336', textColor: '#babbcc',
shapeColor: '#babbcc',fillColor: '#2a2c3f'},
{ themeName: 'Flatly', backgroundColor: '#ffffff', textColor: '#343a40',
shapeColor: '#7b8a8b',fillColor: '#ffffff'},
{ themeName: 'Black', backgroundColor: '#1a1a1a', textColor: '#babbcc',
shapeColor: '#b5b4bc',fillColor: '#1f2020'},
{ themeName: 'Light', backgroundColor: '#eef1f6', textColor: '#3b445e',
shapeColor: '#343a40',fillColor: '#ffffff'},
{ themeName: 'Midcentury', backgroundColor: '#DBE2E0', textColor: '#11556c',
shapeColor: '#343a40',fillColor: '#eeede9'},
{ themeName: 'Spacelab', backgroundColor: '#ffffff', textColor: '#343a40',
shapeColor: '#333333', fillColor: '#eeeeee'},
{ themeName: 'Candy', backgroundColor: '#d5efff', textColor: '#11556c',
shapeColor: '#343a40',fillColor: '#fbe7f8' },
{ themeName: 'Violet', backgroundColor: '#f1eef6', textColor: '#3b445e',
shapeColor: '#343a40',fillColor: '#f8fafe' },
{ themeName: 'Unicorn', backgroundColor: '#f1eef6', textColor: '#343a40',
shapeColor: '#343a40',fillColor: '#f8fafe' },
]
/**
@ -57,182 +57,182 @@ const themeCollection = [
*/
export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
element: HTMLDivElement
currentFile: string
svgPayload: string
updatedSvg: string
currentlySelectedTheme: string
themeName: string
themeDark: string
loading: boolean
themeCollection: ThemeSummary[]
activeTheme: ThemeSummary
triggerGenerateUml: boolean
umlClasses: UmlClass[] = []
appManager: RemixAppManager
dispatch: React.Dispatch<any> = () => {}
constructor(appManager: RemixAppManager) {
super(profile)
this.currentFile = ''
this.svgPayload = ''
this.updatedSvg = ''
this.loading = false
this.currentlySelectedTheme = ''
this.themeName = ''
this.themeCollection = themeCollection
this.activeTheme = themeCollection.find(t => t.themeName === 'Dark')
this.appManager = appManager
this.element = document.createElement('div')
this.element.setAttribute('id', 'sol-uml-gen')
}
onActivation(): void {
this.handleThemeChange()
this.on('solidity', 'compilationFinished', async (file: string, source, languageVersion, data, input, version) => {
if(!this.triggerGenerateUml) return
this.triggerGenerateUml = false
const currentTheme: ThemeQualityType = await this.call('theme', 'currentTheme')
this.currentlySelectedTheme = currentTheme.quality
this.themeName = currentTheme.name
let result = ''
const normalized = normalizeContractPath(file)
this.currentFile = normalized[normalized.length - 1]
try {
if (data.sources && Object.keys(data.sources).length > 1) { // we should flatten first as there are multiple asts
result = await this.flattenContract(source, file, data)
}
const ast = result.length > 1 ? parser.parse(result) : parser.parse(source.sources[file].content)
this.umlClasses = convertAST2UmlClasses(ast, this.currentFile)
let umlDot = ''
this.activeTheme = themeCollection.find(theme => theme.themeName === currentTheme.name)
umlDot = convertUmlClasses2Dot(this.umlClasses, false, { backColor: this.activeTheme.backgroundColor, textColor: this.activeTheme.textColor, shapeColor: this.activeTheme.shapeColor, fillColor: this.activeTheme.fillColor })
const payload = vizRenderStringSync(umlDot)
this.updatedSvg = payload
_paq.push(['trackEvent', 'solidityumlgen', 'umlgenerated'])
this.renderComponent()
await this.call('tabs', 'focus', 'solidityumlgen')
} catch (error) {
console.log('error', error)
}
})
}
getThemeCssVariables(cssVars: string) {
return window.getComputedStyle(document.documentElement)
.getPropertyValue(cssVars)
}
private handleThemeChange() {
this.on('theme', 'themeChanged', async (theme) => {
this.currentlySelectedTheme = theme.quality
const themeQuality: ThemeQualityType = await this.call('theme', 'currentTheme')
themeCollection.forEach((theme) => {
if (theme.themeName === themeQuality.name) {
this.themeDark = theme.backgroundColor
this.activeTheme = theme
const umlDot = convertUmlClasses2Dot(this.umlClasses, false, { backColor: this.activeTheme.backgroundColor, textColor: this.activeTheme.textColor, shapeColor: this.activeTheme.shapeColor, fillColor: this.activeTheme.fillColor })
this.updatedSvg = vizRenderStringSync(umlDot)
this.renderComponent()
}
})
await this.call('tabs', 'focus', 'solidityumlgen')
})
}
async mangleSvgPayload(svgPayload: string) : Promise<string> {
const parser = new DOMParser()
const themeQuality: ThemeQualityType = await this.call('theme', 'currentTheme')
const parsedDocument = parser.parseFromString(svgPayload, 'image/svg+xml')
const element = parsedDocument.getElementsByTagName('svg')
themeCollection.forEach((theme) => {
if (theme.themeName === themeQuality.name) {
parsedDocument.documentElement.setAttribute('style', `background-color: var(${this.getThemeCssVariables('--body-bg')})`)
element[0].setAttribute('fill', theme.backgroundColor)
}
})
const stringifiedSvg = new XMLSerializer().serializeToString(parsedDocument)
return stringifiedSvg
}
onDeactivation(): void {
this.off('solidity', 'compilationFinished')
}
generateCustomAction = async (action: customAction) => {
this.triggerGenerateUml = true
this.updatedSvg = this.updatedSvg.startsWith('<?xml') ? '' : this.updatedSvg
_paq.push(['trackEvent', 'solidityumlgen', 'activated'])
await this.generateUml(action.path[0])
}
async generateUml(currentFile: string) {
await this.call('solidity', 'compile', currentFile)
await this.call('tabs', 'focus', 'solidityumlgen')
this.loading = true
element: HTMLDivElement
currentFile: string
svgPayload: string
updatedSvg: string
currentlySelectedTheme: string
themeName: string
themeDark: string
loading: boolean
themeCollection: ThemeSummary[]
activeTheme: ThemeSummary
triggerGenerateUml: boolean
umlClasses: UmlClass[] = []
appManager: RemixAppManager
dispatch: React.Dispatch<any> = () => {}
constructor(appManager: RemixAppManager) {
super(profile)
this.currentFile = ''
this.svgPayload = ''
this.updatedSvg = ''
this.loading = false
this.currentlySelectedTheme = ''
this.themeName = ''
this.themeCollection = themeCollection
this.activeTheme = themeCollection.find(t => t.themeName === 'Dark')
this.appManager = appManager
this.element = document.createElement('div')
this.element.setAttribute('id', 'sol-uml-gen')
}
onActivation(): void {
this.handleThemeChange()
this.on('solidity', 'compilationFinished', async (file: string, source, languageVersion, data, input, version) => {
if(!this.triggerGenerateUml) return
this.triggerGenerateUml = false
const currentTheme: ThemeQualityType = await this.call('theme', 'currentTheme')
this.currentlySelectedTheme = currentTheme.quality
this.themeName = currentTheme.name
let result = ''
const normalized = normalizeContractPath(file)
this.currentFile = normalized[normalized.length - 1]
try {
if (data.sources && Object.keys(data.sources).length > 1) { // we should flatten first as there are multiple asts
result = await this.flattenContract(source, file, data)
}
const ast = result.length > 1 ? parser.parse(result) : parser.parse(source.sources[file].content)
this.umlClasses = convertAST2UmlClasses(ast, this.currentFile)
let umlDot = ''
this.activeTheme = themeCollection.find(theme => theme.themeName === currentTheme.name)
umlDot = convertUmlClasses2Dot(this.umlClasses, false, { backColor: this.activeTheme.backgroundColor, textColor: this.activeTheme.textColor, shapeColor: this.activeTheme.shapeColor, fillColor: this.activeTheme.fillColor })
const payload = vizRenderStringSync(umlDot)
this.updatedSvg = payload
_paq.push(['trackEvent', 'solidityumlgen', 'umlgenerated'])
this.renderComponent()
}
/**
await this.call('tabs', 'focus', 'solidityumlgen')
} catch (error) {
console.log('error', error)
}
})
}
getThemeCssVariables(cssVars: string) {
return window.getComputedStyle(document.documentElement)
.getPropertyValue(cssVars)
}
private handleThemeChange() {
this.on('theme', 'themeChanged', async (theme) => {
this.currentlySelectedTheme = theme.quality
const themeQuality: ThemeQualityType = await this.call('theme', 'currentTheme')
themeCollection.forEach((theme) => {
if (theme.themeName === themeQuality.name) {
this.themeDark = theme.backgroundColor
this.activeTheme = theme
const umlDot = convertUmlClasses2Dot(this.umlClasses, false, { backColor: this.activeTheme.backgroundColor, textColor: this.activeTheme.textColor, shapeColor: this.activeTheme.shapeColor, fillColor: this.activeTheme.fillColor })
this.updatedSvg = vizRenderStringSync(umlDot)
this.renderComponent()
}
})
await this.call('tabs', 'focus', 'solidityumlgen')
})
}
async mangleSvgPayload(svgPayload: string) : Promise<string> {
const parser = new DOMParser()
const themeQuality: ThemeQualityType = await this.call('theme', 'currentTheme')
const parsedDocument = parser.parseFromString(svgPayload, 'image/svg+xml')
const element = parsedDocument.getElementsByTagName('svg')
themeCollection.forEach((theme) => {
if (theme.themeName === themeQuality.name) {
parsedDocument.documentElement.setAttribute('style', `background-color: var(${this.getThemeCssVariables('--body-bg')})`)
element[0].setAttribute('fill', theme.backgroundColor)
}
})
const stringifiedSvg = new XMLSerializer().serializeToString(parsedDocument)
return stringifiedSvg
}
onDeactivation(): void {
this.off('solidity', 'compilationFinished')
}
generateCustomAction = async (action: customAction) => {
this.triggerGenerateUml = true
this.updatedSvg = this.updatedSvg.startsWith('<?xml') ? '' : this.updatedSvg
_paq.push(['trackEvent', 'solidityumlgen', 'activated'])
await this.generateUml(action.path[0])
}
async generateUml(currentFile: string) {
await this.call('solidity', 'compile', currentFile)
await this.call('tabs', 'focus', 'solidityumlgen')
this.loading = true
this.renderComponent()
}
/**
* Takes currently compiled contract that has a bunch of imports at the top
* and flattens them ready for UML creation. Takes the flattened result
* and assigns to a local property
* @returns {Promise<string>}
*/
async flattenContract (source: any, filePath: string, data: any) {
const result = await this.call('contractflattener', 'flattenContract', source, filePath, data)
return result
}
async showUmlDiagram(svgPayload: string) {
this.updatedSvg = svgPayload
this.renderComponent()
}
hideSpinner() {
this.loading = false
this.renderComponent()
}
setDispatch (dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
this.renderComponent()
}
render() {
return <div id='sol-uml-gen'>
<PluginViewWrapper plugin={this} />
</div>
}
renderComponent () {
this.dispatch({
...this,
updatedSvg: this.updatedSvg,
loading: this.loading,
themeSelected: this.currentlySelectedTheme,
themeName: this.themeName,
themeDark: this.themeDark,
fileName: this.currentFile,
themeCollection: this.themeCollection,
activeTheme: this.activeTheme,
})
}
updateComponent(state: any) {
return <RemixUiSolidityUmlGen
updatedSvg={state.updatedSvg}
loading={state.loading}
themeSelected={state.currentlySelectedTheme}
themeName={state.themeName}
fileName={state.fileName}
themeCollection={state.themeCollection}
themeDark={state.themeDark}
/>
}
async flattenContract (source: any, filePath: string, data: any) {
const result = await this.call('contractflattener', 'flattenContract', source, filePath, data)
return result
}
async showUmlDiagram(svgPayload: string) {
this.updatedSvg = svgPayload
this.renderComponent()
}
hideSpinner() {
this.loading = false
this.renderComponent()
}
setDispatch (dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
this.renderComponent()
}
render() {
return <div id='sol-uml-gen'>
<PluginViewWrapper plugin={this} />
</div>
}
renderComponent () {
this.dispatch({
...this,
updatedSvg: this.updatedSvg,
loading: this.loading,
themeSelected: this.currentlySelectedTheme,
themeName: this.themeName,
themeDark: this.themeDark,
fileName: this.currentFile,
themeCollection: this.themeCollection,
activeTheme: this.activeTheme,
})
}
updateComponent(state: any) {
return <RemixUiSolidityUmlGen
updatedSvg={state.updatedSvg}
loading={state.loading}
themeSelected={state.currentlySelectedTheme}
themeName={state.themeName}
fileName={state.fileName}
themeCollection={state.themeCollection}
themeDark={state.themeDark}
/>
}
}
@ -246,10 +246,10 @@ interface Sol2umlClassOptions extends ClassOptions {
import { dirname } from 'path'
import { convertClass2Dot } from 'sol2uml/lib/converterClass2Dot'
import {
Association,
ClassStereotype,
ReferenceType,
UmlClass,
Association,
ClassStereotype,
ReferenceType,
UmlClass,
} from 'sol2uml/lib/umlClass'
import { findAssociatedClass } from 'sol2uml/lib/associations'
@ -264,11 +264,11 @@ import { findAssociatedClass } from 'sol2uml/lib/associations'
* @return dotString Graphviz's DOT format for defining nodes, edges and clusters.
*/
export function convertUmlClasses2Dot(
umlClasses: UmlClass[],
clusterFolders: boolean = false,
classOptions: Sol2umlClassOptions = {}
umlClasses: UmlClass[],
clusterFolders: boolean = false,
classOptions: Sol2umlClassOptions = {}
): string {
let dotString: string = `
let dotString: string = `
digraph UmlClassDiagram {
rankdir=BT
arrowhead=open
@ -276,124 +276,124 @@ bgcolor="${classOptions.backColor}"
edge [color="${classOptions.shapeColor}"]
node [shape=record, style=filled, color="${classOptions.shapeColor}", fillcolor="${classOptions.fillColor}", fontcolor="${classOptions.textColor}"]`
// Sort UML Classes by folder of source file
const umlClassesSortedByCodePath = sortUmlClassesByCodePath(umlClasses)
// Sort UML Classes by folder of source file
const umlClassesSortedByCodePath = sortUmlClassesByCodePath(umlClasses)
let currentCodeFolder = ''
for (const umlClass of umlClassesSortedByCodePath) {
const codeFolder = dirname(umlClass.relativePath)
if (currentCodeFolder !== codeFolder) {
// Need to close off the last subgraph if not the first
if (currentCodeFolder != '') {
dotString += '\n}'
}
let currentCodeFolder = ''
for (const umlClass of umlClassesSortedByCodePath) {
const codeFolder = dirname(umlClass.relativePath)
if (currentCodeFolder !== codeFolder) {
// Need to close off the last subgraph if not the first
if (currentCodeFolder != '') {
dotString += '\n}'
}
dotString += `
dotString += `
subgraph ${getSubGraphName(clusterFolders)} {
label="${codeFolder}"`
currentCodeFolder = codeFolder
}
dotString += convertClass2Dot(umlClass, classOptions)
currentCodeFolder = codeFolder
}
dotString += convertClass2Dot(umlClass, classOptions)
}
// Need to close off the last subgraph if not the first
if (currentCodeFolder != '') {
dotString += '\n}'
}
// Need to close off the last subgraph if not the first
if (currentCodeFolder != '') {
dotString += '\n}'
}
dotString += addAssociationsToDot(umlClasses, classOptions)
dotString += addAssociationsToDot(umlClasses, classOptions)
// Need to close off the last the digraph
dotString += '\n}'
// Need to close off the last the digraph
dotString += '\n}'
// debug(dotString)
// debug(dotString)
return dotString
return dotString
}
let subGraphCount = 0
function getSubGraphName(clusterFolders: boolean = false) {
if (clusterFolders) {
return ` cluster_${subGraphCount++}`
}
return ` graph_${subGraphCount++}`
if (clusterFolders) {
return ` cluster_${subGraphCount++}`
}
return ` graph_${subGraphCount++}`
}
function sortUmlClassesByCodePath(umlClasses: UmlClass[]): UmlClass[] {
return umlClasses.sort((a, b) => {
if (a.relativePath < b.relativePath) {
return -1
}
if (a.relativePath > b.relativePath) {
return 1
}
return 0
})
return umlClasses.sort((a, b) => {
if (a.relativePath < b.relativePath) {
return -1
}
if (a.relativePath > b.relativePath) {
return 1
}
return 0
})
}
export function addAssociationsToDot(
umlClasses: UmlClass[],
classOptions: ClassOptions = {}
umlClasses: UmlClass[],
classOptions: ClassOptions = {}
): string {
let dotString: string = ''
// for each class
for (const sourceUmlClass of umlClasses) {
if (!classOptions.hideEnums) {
// for each enum in the class
sourceUmlClass.enums.forEach((enumId) => {
// Has the enum been filtered out? eg depth limited
const targetUmlClass = umlClasses.find((c) => c.id === enumId)
if (targetUmlClass) {
// Draw aggregated link from contract to contract level Enum
dotString += `\n${enumId} -> ${sourceUmlClass.id} [arrowhead=diamond, weight=2]`
}
})
let dotString: string = ''
// for each class
for (const sourceUmlClass of umlClasses) {
if (!classOptions.hideEnums) {
// for each enum in the class
sourceUmlClass.enums.forEach((enumId) => {
// Has the enum been filtered out? eg depth limited
const targetUmlClass = umlClasses.find((c) => c.id === enumId)
if (targetUmlClass) {
// Draw aggregated link from contract to contract level Enum
dotString += `\n${enumId} -> ${sourceUmlClass.id} [arrowhead=diamond, weight=2]`
}
if (!classOptions.hideStructs) {
// for each struct in the class
sourceUmlClass.structs.forEach((structId) => {
// Has the struct been filtered out? eg depth limited
const targetUmlClass = umlClasses.find((c) => c.id === structId)
if (targetUmlClass) {
// Draw aggregated link from contract to contract level Struct
dotString += `\n${structId} -> ${sourceUmlClass.id} [arrowhead=diamond, weight=2]`
}
})
})
}
if (!classOptions.hideStructs) {
// for each struct in the class
sourceUmlClass.structs.forEach((structId) => {
// Has the struct been filtered out? eg depth limited
const targetUmlClass = umlClasses.find((c) => c.id === structId)
if (targetUmlClass) {
// Draw aggregated link from contract to contract level Struct
dotString += `\n${structId} -> ${sourceUmlClass.id} [arrowhead=diamond, weight=2]`
}
})
}
// for each association in that class
for (const association of Object.values(sourceUmlClass.associations)) {
const targetUmlClass = findAssociatedClass(
association,
sourceUmlClass,
umlClasses
)
if (targetUmlClass) {
dotString += addAssociationToDot(
sourceUmlClass,
targetUmlClass,
association,
classOptions
)
}
}
// for each association in that class
for (const association of Object.values(sourceUmlClass.associations)) {
const targetUmlClass = findAssociatedClass(
association,
sourceUmlClass,
umlClasses
)
if (targetUmlClass) {
dotString += addAssociationToDot(
sourceUmlClass,
targetUmlClass,
association,
classOptions
)
}
}
}
return dotString
return dotString
}
function addAssociationToDot(
sourceUmlClass: UmlClass,
targetUmlClass: UmlClass,
association: Association,
classOptions: ClassOptions = {}
sourceUmlClass: UmlClass,
targetUmlClass: UmlClass,
association: Association,
classOptions: ClassOptions = {}
): string {
// do not include library or interface associations if hidden
// Or associations to Structs, Enums or Constants if they are hidden
if (
(classOptions.hideLibraries &&
// do not include library or interface associations if hidden
// Or associations to Structs, Enums or Constants if they are hidden
if (
(classOptions.hideLibraries &&
(sourceUmlClass.stereotype === ClassStereotype.Library ||
targetUmlClass.stereotype === ClassStereotype.Library)) ||
(classOptions.hideInterfaces &&
@ -408,28 +408,28 @@ function addAssociationToDot(
targetUmlClass.stereotype === ClassStereotype.Enum) ||
(classOptions.hideConstants &&
targetUmlClass.stereotype === ClassStereotype.Constant)
) {
return ''
}
) {
return ''
}
let dotString = `\n${sourceUmlClass.id} -> ${targetUmlClass.id} [`
let dotString = `\n${sourceUmlClass.id} -> ${targetUmlClass.id} [`
if (
association.referenceType == ReferenceType.Memory ||
if (
association.referenceType == ReferenceType.Memory ||
(association.realization &&
targetUmlClass.stereotype === ClassStereotype.Interface)
) {
dotString += 'style=dashed, '
}
if (association.realization) {
dotString += 'arrowhead=empty, arrowsize=3, '
if (!targetUmlClass.stereotype) {
dotString += 'weight=4, '
} else {
dotString += 'weight=3, '
}
) {
dotString += 'style=dashed, '
}
if (association.realization) {
dotString += 'arrowhead=empty, arrowsize=3, '
if (!targetUmlClass.stereotype) {
dotString += 'weight=4, '
} else {
dotString += 'weight=3, '
}
}
return dotString + ']'
return dotString + ']'
}

@ -1,59 +1,59 @@
import { Plugin } from '@remixproject/engine';
const profile = {
name: 'storage',
displayName: 'Storage',
description: 'Storage',
methods: ['getStorage', 'formatString']
name: 'storage',
displayName: 'Storage',
description: 'Storage',
methods: ['getStorage', 'formatString']
};
export class StoragePlugin extends Plugin {
constructor() {
super(profile);
constructor() {
super(profile);
}
async getStorage() {
let storage = null
if ('storage' in navigator && 'estimate' in navigator.storage && (window as any).remixFileSystem.name !== 'localstorage') {
storage = await navigator.storage.estimate()
} else {
storage ={
usage: parseFloat(this.calculateLocalStorage()) * 1000,
quota: 5000000,
}
}
async getStorage() {
let storage = null
if ('storage' in navigator && 'estimate' in navigator.storage && (window as any).remixFileSystem.name !== 'localstorage') {
storage = await navigator.storage.estimate()
} else {
storage ={
usage: parseFloat(this.calculateLocalStorage()) * 1000,
quota: 5000000,
}
}
const _paq = (window as any)._paq = (window as any)._paq || []
_paq.push(['trackEvent', 'Storage', 'used', this.formatString(storage)]);
return storage
}
formatString(storage) {
return `${this.formatBytes(storage.usage)} / ${this.formatBytes(storage.quota)}`;
const _paq = (window as any)._paq = (window as any)._paq || []
_paq.push(['trackEvent', 'Storage', 'used', this.formatString(storage)]);
return storage
}
formatString(storage) {
return `${this.formatBytes(storage.usage)} / ${this.formatBytes(storage.quota)}`;
}
calculateLocalStorage() {
let _lsTotal = 0
let _xLen; let _x
for (_x in localStorage) {
// eslint-disable-next-line no-prototype-builtins
if (!localStorage.hasOwnProperty(_x)) {
continue
}
_xLen = ((localStorage[_x].length + _x.length))
_lsTotal += _xLen
}
return (_lsTotal / 1024).toFixed(2)
}
calculateLocalStorage() {
let _lsTotal = 0
let _xLen; let _x
for (_x in localStorage) {
// eslint-disable-next-line no-prototype-builtins
if (!localStorage.hasOwnProperty(_x)) {
continue
}
_xLen = ((localStorage[_x].length + _x.length))
_lsTotal += _xLen
}
return (_lsTotal / 1024).toFixed(2)
}
formatBytes(bytes: number, decimals = 2) {
if (bytes === 0) return '0 Bytes';
formatBytes(bytes: number, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
}

@ -28,110 +28,110 @@ export interface IProvider {
}
export abstract class AbstractProvider extends Plugin implements IProvider {
provider: ethers.providers.JsonRpcProvider
blockchain: Blockchain
defaultUrl: string
connected: boolean
nodeUrl: string
options: { [id: string] : any } = {}
provider: ethers.providers.JsonRpcProvider
blockchain: Blockchain
defaultUrl: string
connected: boolean
nodeUrl: string
options: { [id: string] : any } = {}
constructor (profile, blockchain, defaultUrl) {
super(profile)
this.defaultUrl = defaultUrl
this.provider = null
this.connected = false
this.blockchain = blockchain
this.nodeUrl = 'http://localhost:8545'
}
constructor (profile, blockchain, defaultUrl) {
super(profile)
this.defaultUrl = defaultUrl
this.provider = null
this.connected = false
this.blockchain = blockchain
this.nodeUrl = 'http://localhost:8545'
}
abstract body(): JSX.Element
onDeactivation () {
this.provider = null
this.provider = null
}
async init () {
this.nodeUrl = await ((): Promise<string> => {
return new Promise((resolve, reject) => {
const modalContent: AppModal = {
id: this.profile.name,
title: this.profile.displayName,
message: this.body(),
modalType: ModalTypes.prompt,
okLabel: 'OK',
cancelLabel: 'Cancel',
validationFn: (value) => {
if (!value) return { valid: false, message: "value is empty" }
if (value.startsWith('https://') || value.startsWith('http://')) {
return {
valid: true,
message: ''
}
} else {
return {
valid: false,
message: 'the provided value should contain the protocol ( e.g starts with http:// or https:// )'
}
}
},
okFn: (value: string) => {
setTimeout(() => resolve(value), 0)
},
cancelFn: () => {
setTimeout(() => reject(new Error('Canceled')), 0)
},
hideFn: () => {
setTimeout(() => reject(new Error('Hide')), 0)
},
defaultValue: this.defaultUrl
this.nodeUrl = await ((): Promise<string> => {
return new Promise((resolve, reject) => {
const modalContent: AppModal = {
id: this.profile.name,
title: this.profile.displayName,
message: this.body(),
modalType: ModalTypes.prompt,
okLabel: 'OK',
cancelLabel: 'Cancel',
validationFn: (value) => {
if (!value) return { valid: false, message: "value is empty" }
if (value.startsWith('https://') || value.startsWith('http://')) {
return {
valid: true,
message: ''
}
this.call('notification', 'modal', modalContent)
})
})()
this.provider = new ethers.providers.JsonRpcProvider(this.nodeUrl)
return {
nodeUrl: this.nodeUrl
}
} else {
return {
valid: false,
message: 'the provided value should contain the protocol ( e.g starts with http:// or https:// )'
}
}
},
okFn: (value: string) => {
setTimeout(() => resolve(value), 0)
},
cancelFn: () => {
setTimeout(() => reject(new Error('Canceled')), 0)
},
hideFn: () => {
setTimeout(() => reject(new Error('Hide')), 0)
},
defaultValue: this.defaultUrl
}
this.call('notification', 'modal', modalContent)
})
})()
this.provider = new ethers.providers.JsonRpcProvider(this.nodeUrl)
return {
nodeUrl: this.nodeUrl
}
}
sendAsync (data: JsonDataRequest): Promise<JsonDataResult> {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
if (!this.provider) return reject(new Error('provider node set'))
this.sendAsyncInternal(data, resolve, reject)
})
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
if (!this.provider) return reject(new Error('provider node set'))
this.sendAsyncInternal(data, resolve, reject)
})
}
private async switchAway (showError) {
if (!this.provider) return
this.provider = null
this.connected = false
if (showError) {
const modalContent: AlertModal = {
id: this.profile.name,
title: this.profile.displayName,
message: `Error while connecting to the provider, provider not connected`,
}
this.call('notification', 'alert', modalContent)
if (!this.provider) return
this.provider = null
this.connected = false
if (showError) {
const modalContent: AlertModal = {
id: this.profile.name,
title: this.profile.displayName,
message: `Error while connecting to the provider, provider not connected`,
}
await this.call('udapp', 'setEnvironmentMode', { context: 'vm-merge'})
return
this.call('notification', 'alert', modalContent)
}
await this.call('udapp', 'setEnvironmentMode', { context: 'vm-merge'})
return
}
private async sendAsyncInternal (data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise<void> {
if (this.provider) {
try {
const result = await this.provider.send(data.method, data.params)
resolve({ jsonrpc: '2.0', result, id: data.id })
} catch (error) {
if (error && error.message && error.message.includes('net_version') && error.message.includes('SERVER_ERROR')) {
this.switchAway(true)
}
reject(error)
}
} else {
const result = data.method === 'net_listening' ? 'canceled' : []
resolve({ jsonrpc: '2.0', result: result, id: data.id })
if (this.provider) {
try {
const result = await this.provider.send(data.method, data.params)
resolve({ jsonrpc: '2.0', result, id: data.id })
} catch (error) {
if (error && error.message && error.message.includes('net_version') && error.message.includes('SERVER_ERROR')) {
this.switchAway(true)
}
reject(error)
}
} else {
const result = data.method === 'net_listening' ? 'canceled' : []
resolve({ jsonrpc: '2.0', result: result, id: data.id })
}
}
}

@ -5,102 +5,102 @@ import { BasicVMProvider } from './vm-provider'
import { Hardfork } from '@ethereumjs/common'
export class CustomForkVMProvider extends BasicVMProvider {
nodeUrl: string
blockNumber: number | 'latest'
inputs: any
nodeUrl: string
blockNumber: number | 'latest'
inputs: any
constructor (blockchain) {
super({
name: 'vm-custom-fork',
displayName: 'Custom fork - Remix VM',
kind: 'provider',
description: 'Custom fork - Remix VM',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = ''
this.nodeUrl = ''
this.blockNumber = 'latest'
this.inputs = {}
}
constructor (blockchain) {
super({
name: 'vm-custom-fork',
displayName: 'Custom fork - Remix VM',
kind: 'provider',
description: 'Custom fork - Remix VM',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = ''
this.nodeUrl = ''
this.blockNumber = 'latest'
this.inputs = {}
}
async init () {
const body = () => {
return <div>
<span>Please provide information about the custom fork. If the node URL is not provided, the VM will start with an empty state.</span>
<div>
<label className="mt-3 mb-1">Node URL</label>
<input data-id="CustomForkNodeUrl" name="nodeUrl" type="text" className="border form-control border-right-0" />
</div>
<div>
<label className="mt-3 mb-1">Block number (or "latest")</label>
<input data-id="CustomForkBlockNumber" name="blockNumber" type="text" defaultValue="latest" placeholder='block number or "latest"' className="border form-control border-right-0" />
</div>
<div>
<label className="mt-3 mb-1">EVM</label>
<select data-id="CustomForkEvmType" name="evmType" defaultValue="merge" className="border form-control border-right-0">
{Object.keys(Hardfork).map((value, index) => {
return <option value={Hardfork[value]} key={index}>{value}</option>
})}
</select>
</div>
</div>
}
const result = await ((): Promise<any> => {
return new Promise((resolve, reject) => {
const modalContent: AppModal = {
id: this.profile.name,
title: this.profile.displayName,
message: body(),
validationFn: (data: any) => {
if(data.nodeUrl !== '' && !data.nodeUrl.startsWith("http")) {
return {
valid: false,
message: 'node URL should be a valid URL'
}
}
if (data.blockNumber !== 'latest' && isNaN(data.blockNumber)) {
return {
valid: false,
message: 'blockNumber should be a number or "latest"'
}
}
return {
valid: true,
message: ''
}
},
modalType: ModalTypes.form,
okLabel: 'Connect',
cancelLabel: 'Cancel',
okFn: (value: string) => {
setTimeout(() => resolve(value), 0)
},
cancelFn: () => {
setTimeout(() => reject(new Error('Canceled')), 0)
},
hideFn: () => {
setTimeout(() => reject(new Error('Hide')), 0)
}
}
return this.call('notification', 'modal', modalContent)
})
})()
this.fork = result.evmType
this.nodeUrl = result.nodeUrl
if (this.nodeUrl) {
const block = result.blockNumber
this.blockNumber = block === 'latest' ? 'latest' : parseInt(block)
} else {
this.nodeUrl = undefined
this.blockNumber = undefined
async init () {
const body = () => {
return <div>
<span>Please provide information about the custom fork. If the node URL is not provided, the VM will start with an empty state.</span>
<div>
<label className="mt-3 mb-1">Node URL</label>
<input data-id="CustomForkNodeUrl" name="nodeUrl" type="text" className="border form-control border-right-0" />
</div>
<div>
<label className="mt-3 mb-1">Block number (or "latest")</label>
<input data-id="CustomForkBlockNumber" name="blockNumber" type="text" defaultValue="latest" placeholder='block number or "latest"' className="border form-control border-right-0" />
</div>
<div>
<label className="mt-3 mb-1">EVM</label>
<select data-id="CustomForkEvmType" name="evmType" defaultValue="merge" className="border form-control border-right-0">
{Object.keys(Hardfork).map((value, index) => {
return <option value={Hardfork[value]} key={index}>{value}</option>
})}
</select>
</div>
</div>
}
const result = await ((): Promise<any> => {
return new Promise((resolve, reject) => {
const modalContent: AppModal = {
id: this.profile.name,
title: this.profile.displayName,
message: body(),
validationFn: (data: any) => {
if(data.nodeUrl !== '' && !data.nodeUrl.startsWith("http")) {
return {
valid: false,
message: 'node URL should be a valid URL'
}
}
if (data.blockNumber !== 'latest' && isNaN(data.blockNumber)) {
return {
valid: false,
message: 'blockNumber should be a number or "latest"'
}
}
return {
valid: true,
message: ''
}
},
modalType: ModalTypes.form,
okLabel: 'Connect',
cancelLabel: 'Cancel',
okFn: (value: string) => {
setTimeout(() => resolve(value), 0)
},
cancelFn: () => {
setTimeout(() => reject(new Error('Canceled')), 0)
},
hideFn: () => {
setTimeout(() => reject(new Error('Hide')), 0)
}
}
return this.call('notification', 'modal', modalContent)
})
})()
this.fork = result.evmType
this.nodeUrl = result.nodeUrl
if (this.nodeUrl) {
const block = result.blockNumber
this.blockNumber = block === 'latest' ? 'latest' : parseInt(block)
} else {
this.nodeUrl = undefined
this.blockNumber = undefined
}
return {
'fork': this.fork,
'nodeUrl': this.nodeUrl,
'blockNumber': this.blockNumber
}
return {
'fork': this.fork,
'nodeUrl': this.nodeUrl,
'blockNumber': this.blockNumber
}
}
}

@ -3,39 +3,39 @@ import React from 'react' // eslint-disable-line
import { AbstractProvider } from './abstract-provider'
const profile = {
name: 'basic-http-provider',
displayName: 'External Http Provider',
kind: 'provider',
description: 'External Http Provider',
methods: ['sendAsync', 'init'],
version: packageJson.version
name: 'basic-http-provider',
displayName: 'External Http Provider',
kind: 'provider',
description: 'External Http Provider',
methods: ['sendAsync', 'init'],
version: packageJson.version
}
export class ExternalHttpProvider extends AbstractProvider {
constructor (blockchain) {
super(profile, blockchain, 'http://127.0.0.1:8545')
}
constructor (blockchain) {
super(profile, blockchain, 'http://127.0.0.1:8545')
}
body (): JSX.Element {
const thePath = '<path/to/local/folder/for/test/chain>'
return (
<>
<div className="">
body (): JSX.Element {
const thePath = '<path/to/local/folder/for/test/chain>'
return (
<>
<div className="">
Note: To use Geth & https://remix.ethereum.org, configure it to allow requests from Remix:(see <a href="https://geth.ethereum.org/docs/rpc/server" target="_blank" rel="noreferrer">Geth Docs on rpc server</a>)
<div className="border p-1">geth --http --http.corsdomain https://remix.ethereum.org</div>
<br />
<div className="border p-1">geth --http --http.corsdomain https://remix.ethereum.org</div>
<br />
To run Remix & a local Geth test node, use this command: (see <a href="https://geth.ethereum.org/getting-started/dev-mode" target="_blank" rel="noreferrer">Geth Docs on Dev mode</a>)
<div className="border p-1">geth --http --http.corsdomain="{window.origin}" --http.api web3,eth,debug,personal,net --vmdebug --datadir {thePath} --dev console</div>
<br />
<br />
<b>WARNING:</b> It is not safe to use the --http.corsdomain flag with a wildcard: <b>--http.corsdomain *</b>
<br />
<br />For more info: <a href="https://remix-ide.readthedocs.io/en/latest/run.html#more-about-web3-provider" target="_blank" rel="noreferrer">Remix Docs on External HTTP Provider</a>
<br />
<br />
<div className="border p-1">geth --http --http.corsdomain="{window.origin}" --http.api web3,eth,debug,personal,net --vmdebug --datadir {thePath} --dev console</div>
<br />
<br />
<b>WARNING:</b> It is not safe to use the --http.corsdomain flag with a wildcard: <b>--http.corsdomain *</b>
<br />
<br />For more info: <a href="https://remix-ide.readthedocs.io/en/latest/run.html#more-about-web3-provider" target="_blank" rel="noreferrer">Remix Docs on External HTTP Provider</a>
<br />
<br />
External HTTP Provider Endpoint
</div>
</>
)
}
</div>
</>
)
}
}

@ -3,29 +3,29 @@ import React from 'react' // eslint-disable-line
import { AbstractProvider } from './abstract-provider'
const profile = {
name: 'foundry-provider',
displayName: 'Foundry Provider',
kind: 'provider',
description: 'Foundry Anvil provider',
methods: ['sendAsync', 'init'],
version: packageJson.version
name: 'foundry-provider',
displayName: 'Foundry Provider',
kind: 'provider',
description: 'Foundry Anvil provider',
methods: ['sendAsync', 'init'],
version: packageJson.version
}
export class FoundryProvider extends AbstractProvider {
constructor (blockchain) {
super(profile, blockchain, 'http://127.0.0.1:8545')
}
constructor (blockchain) {
super(profile, blockchain, 'http://127.0.0.1:8545')
}
body (): JSX.Element {
return (
<div> Note: To run Anvil on your system, run:
<div className="p-1 pl-3"><b>curl -L https://foundry.paradigm.xyz | bash</b></div>
<div className="p-1 pl-3"><b>anvil</b></div>
<div className="pt-2 pb-4">
body (): JSX.Element {
return (
<div> Note: To run Anvil on your system, run:
<div className="p-1 pl-3"><b>curl -L https://foundry.paradigm.xyz | bash</b></div>
<div className="p-1 pl-3"><b>anvil</b></div>
<div className="pt-2 pb-4">
For more info, visit: <a href="https://github.com/foundry-rs/foundry" target="_blank">Foundry Documentation</a>
</div>
<div>Anvil JSON-RPC Endpoint:</div>
</div>
)
}
</div>
<div>Anvil JSON-RPC Endpoint:</div>
</div>
)
}
}

@ -3,29 +3,29 @@ import React from 'react' // eslint-disable-line
import { AbstractProvider } from './abstract-provider'
const profile = {
name: 'ganache-provider',
displayName: 'Ganache Provider',
kind: 'provider',
description: 'Truffle Ganache provider',
methods: ['sendAsync', 'init'],
version: packageJson.version
name: 'ganache-provider',
displayName: 'Ganache Provider',
kind: 'provider',
description: 'Truffle Ganache provider',
methods: ['sendAsync', 'init'],
version: packageJson.version
}
export class GanacheProvider extends AbstractProvider {
constructor (blockchain) {
super(profile, blockchain, 'http://127.0.0.1:8545')
}
constructor (blockchain) {
super(profile, blockchain, 'http://127.0.0.1:8545')
}
body (): JSX.Element {
return (
<div> Note: To run Ganache on your system, run:
<div className="p-1 pl-3"><b>yarn global add ganache</b></div>
<div className="p-1 pl-3"><b>ganache</b></div>
<div className="pt-2 pb-4">
body (): JSX.Element {
return (
<div> Note: To run Ganache on your system, run:
<div className="p-1 pl-3"><b>yarn global add ganache</b></div>
<div className="p-1 pl-3"><b>ganache</b></div>
<div className="pt-2 pb-4">
For more info, visit: <a href="https://github.com/trufflesuite/ganache" target="_blank">Ganache Documentation</a>
</div>
<div>Ganache JSON-RPC Endpoint:</div>
</div>
)
}
</div>
<div>Ganache JSON-RPC Endpoint:</div>
</div>
)
}
}

@ -2,28 +2,28 @@ import * as packageJson from '../../../../../package.json'
import { BasicVMProvider } from './vm-provider'
export class GoerliForkVMProvider extends BasicVMProvider {
nodeUrl: string
blockNumber: number | 'latest'
constructor (blockchain) {
super({
name: 'vm-goerli-fork',
displayName: 'Goerli fork - Remix VM (London)',
kind: 'provider',
description: 'Remix VM (London)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'shanghai'
this.nodeUrl = 'https://remix-sepolia.ethdevops.io'
this.blockNumber = 'latest'
}
nodeUrl: string
blockNumber: number | 'latest'
constructor (blockchain) {
super({
name: 'vm-goerli-fork',
displayName: 'Goerli fork - Remix VM (London)',
kind: 'provider',
description: 'Remix VM (London)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'shanghai'
this.nodeUrl = 'https://remix-sepolia.ethdevops.io'
this.blockNumber = 'latest'
}
async init () {
return {
'fork': this.fork,
'nodeUrl': this.nodeUrl,
'blockNumber': this.blockNumber
}
async init () {
return {
'fork': this.fork,
'nodeUrl': this.nodeUrl,
'blockNumber': this.blockNumber
}
}
}

@ -3,28 +3,28 @@ import React from 'react' // eslint-disable-line
import { AbstractProvider } from './abstract-provider'
const profile = {
name: 'hardhat-provider',
displayName: 'Hardhat Provider',
kind: 'provider',
description: 'Hardhat provider',
methods: ['sendAsync', 'init'],
version: packageJson.version
name: 'hardhat-provider',
displayName: 'Hardhat Provider',
kind: 'provider',
description: 'Hardhat provider',
methods: ['sendAsync', 'init'],
version: packageJson.version
}
export class HardhatProvider extends AbstractProvider {
constructor (blockchain) {
super(profile, blockchain, 'http://127.0.0.1:8545')
}
constructor (blockchain) {
super(profile, blockchain, 'http://127.0.0.1:8545')
}
body (): JSX.Element {
return (
<div> Note: To run Hardhat network node on your system, go to hardhat project folder and run command:
<div className="p-1 pl-3"><b>npx hardhat node</b></div>
<div className="pt-2 pb-4">
body (): JSX.Element {
return (
<div> Note: To run Hardhat network node on your system, go to hardhat project folder and run command:
<div className="p-1 pl-3"><b>npx hardhat node</b></div>
<div className="pt-2 pb-4">
For more info, visit: <a href="https://hardhat.org/getting-started/#connecting-a-wallet-or-dapp-to-hardhat-network" target="_blank">Hardhat Documentation</a>
</div>
<div>Hardhat JSON-RPC Endpoint:</div>
</div>
)
}
</div>
<div>Hardhat JSON-RPC Endpoint:</div>
</div>
)
}
}

@ -1,55 +1,55 @@
import { InjectedProviderDefaultBase } from './injected-provider-default'
export class InjectedL2Provider extends InjectedProviderDefaultBase {
chainName: string
chainId: string
rpcUrls: Array<string>
chainName: string
chainId: string
rpcUrls: Array<string>
constructor (profile: any, chainName: string, chainId: string, rpcUrls: Array<string>) {
super(profile)
this.chainName = chainName
this.chainId = chainId
this.rpcUrls = rpcUrls
}
constructor (profile: any, chainName: string, chainId: string, rpcUrls: Array<string>) {
super(profile)
this.chainName = chainName
this.chainId = chainId
this.rpcUrls = rpcUrls
}
async init () {
await super.init()
if (this.chainName && this.rpcUrls && this.rpcUrls.length > 0) await addL2Network(this.chainName, this.chainId, this.rpcUrls)
else
throw new Error('Cannot add the L2 network to main injected provider')
return {}
}
async init () {
await super.init()
if (this.chainName && this.rpcUrls && this.rpcUrls.length > 0) await addL2Network(this.chainName, this.chainId, this.rpcUrls)
else
throw new Error('Cannot add the L2 network to main injected provider')
return {}
}
}
export const addL2Network = async (chainName: string, chainId: string, rpcUrls: Array<string>) => {
try {
try {
await (window as any).ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: chainId }],
});
} catch (switchError) {
// This error code indicates that the chain has not been added to MetaMask.
if (switchError.code === 4902) {
try {
await (window as any).ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: chainId }],
method: 'wallet_addEthereumChain',
params: [
{
chainId: chainId,
chainName: chainName,
rpcUrls: rpcUrls,
},
],
});
} catch (switchError) {
// This error code indicates that the chain has not been added to MetaMask.
if (switchError.code === 4902) {
try {
await (window as any).ethereum.request({
method: 'wallet_addEthereumChain',
params: [
{
chainId: chainId,
chainName: chainName,
rpcUrls: rpcUrls,
},
],
});
await (window as any).ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: chainId }],
});
} catch (addError) {
// handle "add" error
}
}
// handle other "switch" errors
await (window as any).ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: chainId }],
});
} catch (addError) {
// handle "add" error
}
}
// handle other "switch" errors
}
}

@ -2,17 +2,17 @@ import * as packageJson from '../../../../../package.json'
import { InjectedL2Provider } from './injected-L2-provider'
const profile = {
name: 'injected-arbitrum-one-provider',
displayName: 'Injected Arbitrum One Provider',
kind: 'provider',
description: 'injected Arbitrum One Provider',
methods: ['sendAsync', 'init'],
version: packageJson.version
name: 'injected-arbitrum-one-provider',
displayName: 'Injected Arbitrum One Provider',
kind: 'provider',
description: 'injected Arbitrum One Provider',
methods: ['sendAsync', 'init'],
version: packageJson.version
}
export class InjectedArbitrumOneProvider extends InjectedL2Provider {
constructor () {
super(profile, 'Arbitrum One', '0xa4b1', ['https://arb1.arbitrum.io/rpc'])
}
constructor () {
super(profile, 'Arbitrum One', '0xa4b1', ['https://arb1.arbitrum.io/rpc'])
}
}

@ -2,17 +2,17 @@ import * as packageJson from '../../../../../package.json'
import { InjectedL2Provider } from './injected-L2-provider'
const profile = {
name: 'injected-optimism-provider',
displayName: 'Injected Optimism Provider',
kind: 'provider',
description: 'injected Optimism Provider',
methods: ['sendAsync', 'init'],
version: packageJson.version
name: 'injected-optimism-provider',
displayName: 'Injected Optimism Provider',
kind: 'provider',
description: 'injected Optimism Provider',
methods: ['sendAsync', 'init'],
version: packageJson.version
}
export class Injected0ptimismProvider extends InjectedL2Provider {
constructor () {
super(profile, 'Optimism', '0xa', ['https://mainnet.optimism.io'])
}
constructor () {
super(profile, 'Optimism', '0xa', ['https://mainnet.optimism.io'])
}
}

@ -3,38 +3,38 @@ import * as packageJson from '../../../../../package.json'
import { InjectedProvider } from './injected-provider'
export class InjectedProviderDefaultBase extends InjectedProvider {
constructor (profile) {
super(profile)
}
constructor (profile) {
super(profile)
}
async init () {
const injectedProvider = this.getInjectedProvider()
if (injectedProvider && injectedProvider._metamask && injectedProvider._metamask.isUnlocked) {
if (!await injectedProvider._metamask.isUnlocked()) this.call('notification', 'toast', 'Please make sure the injected provider is unlocked (e.g Metamask).')
}
return super.init()
async init () {
const injectedProvider = this.getInjectedProvider()
if (injectedProvider && injectedProvider._metamask && injectedProvider._metamask.isUnlocked) {
if (!await injectedProvider._metamask.isUnlocked()) this.call('notification', 'toast', 'Please make sure the injected provider is unlocked (e.g Metamask).')
}
return super.init()
}
getInjectedProvider () {
return (window as any).ethereum
}
getInjectedProvider () {
return (window as any).ethereum
}
notFound () {
return 'No injected provider found. Make sure your provider (e.g. MetaMask, ...) is active and running (when recently activated you may have to reload the page).'
}
notFound () {
return 'No injected provider found. Make sure your provider (e.g. MetaMask, ...) is active and running (when recently activated you may have to reload the page).'
}
}
const profile = {
name: 'injected',
displayName: 'Injected Provider',
kind: 'provider',
description: 'injected Provider',
methods: ['sendAsync', 'init'],
version: packageJson.version
name: 'injected',
displayName: 'Injected Provider',
kind: 'provider',
description: 'injected Provider',
methods: ['sendAsync', 'init'],
version: packageJson.version
}
export class InjectedProviderDefault extends InjectedProviderDefaultBase {
constructor () {
super(profile)
}
constructor () {
super(profile)
}
}

@ -3,24 +3,24 @@ import * as packageJson from '../../../../../package.json'
import { InjectedProvider } from './injected-provider'
const profile = {
name: 'injected-trustwallet',
displayName: 'Trust wallet',
kind: 'provider',
description: 'Trust wallet',
methods: ['sendAsync', 'init'],
version: packageJson.version
name: 'injected-trustwallet',
displayName: 'Trust wallet',
kind: 'provider',
description: 'Trust wallet',
methods: ['sendAsync', 'init'],
version: packageJson.version
}
export class InjectedProviderTrustWallet extends InjectedProvider {
constructor () {
super(profile)
}
constructor () {
super(profile)
}
getInjectedProvider () {
return (window as any).trustwallet
}
getInjectedProvider () {
return (window as any).trustwallet
}
notFound () {
return 'Could not find Trust Wallet provider. Please make sure the Trust Wallet extension is active. Download the latest version from https://trustwallet.com/browser-extension'
}
notFound () {
return 'Could not find Trust Wallet provider. Please make sure the Trust Wallet extension is active. Download the latest version from https://trustwallet.com/browser-extension'
}
}

@ -5,101 +5,101 @@ import { JsonDataRequest, RejectRequest, SuccessRequest } from '../providers/abs
import { IProvider } from './abstract-provider'
export abstract class InjectedProvider extends Plugin implements IProvider {
options: { [id: string] : any } = {}
listenerAccountsChanged: (accounts: Array<string>) => void
listenerChainChanged: (chainId: number) => void
options: { [id: string] : any } = {}
listenerAccountsChanged: (accounts: Array<string>) => void
listenerChainChanged: (chainId: number) => void
constructor (profile) {
super(profile)
this.listenerAccountsChanged = (accounts: Array<string>) => {
this.emit('accountsChanged', accounts)
}
this.listenerChainChanged = (chainId: number) => {
this.emit('chainChanged', chainId)
}
constructor (profile) {
super(profile)
this.listenerAccountsChanged = (accounts: Array<string>) => {
this.emit('accountsChanged', accounts)
}
this.listenerChainChanged = (chainId: number) => {
this.emit('chainChanged', chainId)
}
}
abstract getInjectedProvider(): any
abstract notFound(): string
onActivation(): void {
try {
const web3Provider = this.getInjectedProvider()
web3Provider.on('accountsChanged', this.listenerAccountsChanged);
web3Provider.on('chainChanged', this.listenerChainChanged);
} catch (error) {
console.log('unable to listen on context changed')
}
try {
const web3Provider = this.getInjectedProvider()
web3Provider.on('accountsChanged', this.listenerAccountsChanged);
web3Provider.on('chainChanged', this.listenerChainChanged);
} catch (error) {
console.log('unable to listen on context changed')
}
}
onDeactivation(): void {
try {
const web3Provider = this.getInjectedProvider()
web3Provider.removeListener('accountsChanged', this.listenerAccountsChanged)
web3Provider.removeListener('chainChanged', this.listenerChainChanged)
} catch (error) {
console.log('unable to remove listener on context changed')
}
try {
const web3Provider = this.getInjectedProvider()
web3Provider.removeListener('accountsChanged', this.listenerAccountsChanged)
web3Provider.removeListener('chainChanged', this.listenerChainChanged)
} catch (error) {
console.log('unable to remove listener on context changed')
}
}
askPermission (throwIfNoInjectedProvider) {
const web3Provider = this.getInjectedProvider()
if (typeof web3Provider !== "undefined" && typeof web3Provider.request === "function") {
web3Provider.request({ method: "eth_requestAccounts" })
} else if (throwIfNoInjectedProvider) {
throw new Error(this.notFound())
}
const web3Provider = this.getInjectedProvider()
if (typeof web3Provider !== "undefined" && typeof web3Provider.request === "function") {
web3Provider.request({ method: "eth_requestAccounts" })
} else if (throwIfNoInjectedProvider) {
throw new Error(this.notFound())
}
}
body (): JSX.Element {
return (
<div></div>
)
return (
<div></div>
)
}
async init () {
const injectedProvider = this.getInjectedProvider()
if (injectedProvider === undefined) {
this.call('notification', 'toast', this.notFound())
throw new Error(this.notFound())
} else {
this.askPermission(true)
}
return {}
const injectedProvider = this.getInjectedProvider()
if (injectedProvider === undefined) {
this.call('notification', 'toast', this.notFound())
throw new Error(this.notFound())
} else {
this.askPermission(true)
}
return {}
}
sendAsync (data: JsonDataRequest): Promise<any> {
return new Promise((resolve, reject) => {
this.sendAsyncInternal(data, resolve, reject)
})
return new Promise((resolve, reject) => {
this.sendAsyncInternal(data, resolve, reject)
})
}
private async sendAsyncInternal (data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise<void> {
// Check the case where current environment is VM on UI and it still sends RPC requests
// This will be displayed on UI tooltip as 'cannot get account list: Environment Updated !!'
const web3Provider = this.getInjectedProvider()
if (!web3Provider) {
this.call('notification', 'toast', 'No injected provider (e.g Metamask) has been found.')
return resolve({ jsonrpc: '2.0', error: 'no injected provider found', id: data.id })
// Check the case where current environment is VM on UI and it still sends RPC requests
// This will be displayed on UI tooltip as 'cannot get account list: Environment Updated !!'
const web3Provider = this.getInjectedProvider()
if (!web3Provider) {
this.call('notification', 'toast', 'No injected provider (e.g Metamask) has been found.')
return resolve({ jsonrpc: '2.0', error: 'no injected provider found', id: data.id })
}
try {
let resultData
if (web3Provider.send) resultData = await web3Provider.send(data.method, data.params)
else if (web3Provider.request) resultData = await web3Provider.request({ method: data.method, params: data.params})
else {
resolve({ jsonrpc: '2.0', error: 'provider not valid', id: data.id })
return
}
try {
let resultData
if (web3Provider.send) resultData = await web3Provider.send(data.method, data.params)
else if (web3Provider.request) resultData = await web3Provider.request({ method: data.method, params: data.params})
else {
resolve({ jsonrpc: '2.0', error: 'provider not valid', id: data.id })
return
}
if (resultData) {
if (resultData.jsonrpc && resultData.jsonrpc === '2.0') {
resultData = resultData.result
}
resolve({ jsonrpc: '2.0', result: resultData, id: data.id })
} else {
resolve({ jsonrpc: '2.0', error: 'no return data provided', id: data.id })
}
} catch (error) {
resolve({ jsonrpc: '2.0', error: error.data && error.data.message ? error.data.message : error.message, id: data.id })
if (resultData) {
if (resultData.jsonrpc && resultData.jsonrpc === '2.0') {
resultData = resultData.result
}
resolve({ jsonrpc: '2.0', result: resultData, id: data.id })
} else {
resolve({ jsonrpc: '2.0', error: 'no return data provided', id: data.id })
}
} catch (error) {
resolve({ jsonrpc: '2.0', error: error.data && error.data.message ? error.data.message : error.message, id: data.id })
}
}
}

@ -2,28 +2,28 @@ import * as packageJson from '../../../../../package.json'
import { BasicVMProvider } from './vm-provider'
export class MainnetForkVMProvider extends BasicVMProvider {
nodeUrl: string
blockNumber: number | 'latest'
constructor (blockchain) {
super({
name: 'vm-mainnet-fork',
displayName: 'Mainet fork -Remix VM (London)',
kind: 'provider',
description: 'Remix VM (London)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'shanghai'
this.nodeUrl = 'https://mainnet.infura.io/v3/08b2a484451e4635a28b3d8234f24332'
this.blockNumber = 'latest'
}
nodeUrl: string
blockNumber: number | 'latest'
constructor (blockchain) {
super({
name: 'vm-mainnet-fork',
displayName: 'Mainet fork -Remix VM (London)',
kind: 'provider',
description: 'Remix VM (London)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'shanghai'
this.nodeUrl = 'https://mainnet.infura.io/v3/08b2a484451e4635a28b3d8234f24332'
this.blockNumber = 'latest'
}
async init () {
return {
'fork': this.fork,
'nodeUrl': this.nodeUrl,
'blockNumber': this.blockNumber
}
async init () {
return {
'fork': this.fork,
'nodeUrl': this.nodeUrl,
'blockNumber': this.blockNumber
}
}
}

@ -2,28 +2,28 @@ import * as packageJson from '../../../../../package.json'
import { BasicVMProvider } from './vm-provider'
export class SepoliaForkVMProvider extends BasicVMProvider {
nodeUrl: string
blockNumber: number | 'latest'
constructor (blockchain) {
super({
name: 'vm-sepolia-fork',
displayName: 'Sepolia fork - Remix VM (London)',
kind: 'provider',
description: 'Remix VM (London)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'shanghai'
this.nodeUrl = 'https://remix-sepolia.ethdevops.io'
this.blockNumber = 'latest'
}
nodeUrl: string
blockNumber: number | 'latest'
constructor (blockchain) {
super({
name: 'vm-sepolia-fork',
displayName: 'Sepolia fork - Remix VM (London)',
kind: 'provider',
description: 'Remix VM (London)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'shanghai'
this.nodeUrl = 'https://remix-sepolia.ethdevops.io'
this.blockNumber = 'latest'
}
async init () {
return {
'fork': this.fork,
'nodeUrl': this.nodeUrl,
'blockNumber': this.blockNumber
}
async init () {
return {
'fork': this.fork,
'nodeUrl': this.nodeUrl,
'blockNumber': this.blockNumber
}
}
}

@ -5,99 +5,99 @@ import { Plugin } from '@remixproject/engine'
import { IProvider } from './abstract-provider'
export class BasicVMProvider extends Plugin implements IProvider {
blockchain
fork: string
options: { [id: string] : any } = {}
constructor (profile, blockchain) {
super(profile)
this.blockchain = blockchain
this.fork = ''
}
blockchain
fork: string
options: { [id: string] : any } = {}
constructor (profile, blockchain) {
super(profile)
this.blockchain = blockchain
this.fork = ''
}
async init (): Promise<{ [id: string] : any }> { return {} }
async init (): Promise<{ [id: string] : any }> { return {} }
body (): JSX.Element {
return (
<div></div>
)
}
body (): JSX.Element {
return (
<div></div>
)
}
sendAsync (data: JsonDataRequest): Promise<any> {
return new Promise((resolve, reject) => {
this.sendAsyncInternal(data, resolve, reject)
})
}
sendAsync (data: JsonDataRequest): Promise<any> {
return new Promise((resolve, reject) => {
this.sendAsyncInternal(data, resolve, reject)
})
}
private async sendAsyncInternal (data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise<void> {
try {
await this.blockchain.providers.vm.provider.sendAsync(data, (error, result) => {
if (error) return reject(error)
else {
resolve({ jsonrpc: '2.0', result, id: data.id })
}
})
} catch (error) {
reject(error)
private async sendAsyncInternal (data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise<void> {
try {
await this.blockchain.providers.vm.provider.sendAsync(data, (error, result) => {
if (error) return reject(error)
else {
resolve({ jsonrpc: '2.0', result, id: data.id })
}
})
} catch (error) {
reject(error)
}
}
}
export class MergeVMProvider extends BasicVMProvider {
constructor (blockchain) {
super({
name: 'vm-merge',
displayName: 'Remix VM (Merge)',
kind: 'provider',
description: 'Remix VM (Merge)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'merge'
}
constructor (blockchain) {
super({
name: 'vm-merge',
displayName: 'Remix VM (Merge)',
kind: 'provider',
description: 'Remix VM (Merge)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'merge'
}
}
export class LondonVMProvider extends BasicVMProvider {
constructor (blockchain) {
super({
name: 'vm-london',
displayName: 'Remix VM (London)',
kind: 'provider',
description: 'Remix VM (London)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'london'
}
constructor (blockchain) {
super({
name: 'vm-london',
displayName: 'Remix VM (London)',
kind: 'provider',
description: 'Remix VM (London)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'london'
}
}
export class BerlinVMProvider extends BasicVMProvider {
constructor (blockchain) {
super({
name: 'vm-berlin',
displayName: 'Remix VM (Berlin)',
kind: 'provider',
description: 'Remix VM (Berlin)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'berlin'
}
constructor (blockchain) {
super({
name: 'vm-berlin',
displayName: 'Remix VM (Berlin)',
kind: 'provider',
description: 'Remix VM (Berlin)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'berlin'
}
}
export class ShanghaiVMProvider extends BasicVMProvider {
constructor (blockchain) {
super({
name: 'vm-shanghai',
displayName: 'Remix VM (Shanghai)',
kind: 'provider',
description: 'Remix VM (Shanghai)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'shanghai'
}
constructor (blockchain) {
super({
name: 'vm-shanghai',
displayName: 'Remix VM (Shanghai)',
kind: 'provider',
description: 'Remix VM (Shanghai)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'shanghai'
}
}

@ -4,35 +4,35 @@ type registryEntry = {
}
export default class Registry {
private static instance: Registry;
private state: any
private static instance: Registry;
private state: any
private constructor () {
this.state = {}
}
public static getInstance (): Registry {
if (!Registry.instance) {
Registry.instance = new Registry()
}
private constructor () {
this.state = {}
}
return Registry.instance
public static getInstance (): Registry {
if (!Registry.instance) {
Registry.instance = new Registry()
}
public put (entry: registryEntry) {
if (this.state[entry.name]) return this.state[entry.name]
const server = {
// uid: serveruid,
api: entry.api
}
this.state[entry.name] = { server }
return server
}
return Registry.instance
}
public get (name: string) {
const state = this.state[name]
if (!state) return
const server = state.server
return server
public put (entry: registryEntry) {
if (this.state[entry.name]) return this.state[entry.name]
const server = {
// uid: serveruid,
api: entry.api
}
this.state[entry.name] = { server }
return server
}
public get (name: string) {
const state = this.state[name]
if (!state) return
const server = state.server
return server
}
}

@ -9,91 +9,91 @@ import { PluginViewWrapper } from '@remix-ui/helper'
var EventManager = require('../../lib/events')
const profile = {
name: 'solidityStaticAnalysis',
displayName: 'Solidity Analyzers',
methods: [],
events: [],
icon: 'assets/img/staticAnalysis.webp',
description: 'Analyze your code using Remix, Solhint and Slither.',
kind: 'analysis',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/static_analysis.html',
version: packageJson.version,
maintainedBy: 'Remix'
name: 'solidityStaticAnalysis',
displayName: 'Solidity Analyzers',
methods: [],
events: [],
icon: 'assets/img/staticAnalysis.webp',
description: 'Analyze your code using Remix, Solhint and Slither.',
kind: 'analysis',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/static_analysis.html',
version: packageJson.version,
maintainedBy: 'Remix'
}
class AnalysisTab extends ViewPlugin {
constructor () {
super(profile)
this.event = new EventManager()
this.events = new EventEmitter()
this.registry = Registry.getInstance()
this.element = document.createElement('div')
this.element.setAttribute('id', 'staticAnalyserView')
this._components = {}
this._components.registry = this.registry
this._deps = {
offsetToLineColumnConverter: this.registry.get(
'offsettolinecolumnconverter').api
}
this.dispatch = null
this.hints = []
this.basicEnabled = false
this.solhintEnabled = false
this.slitherEnabled = false
constructor () {
super(profile)
this.event = new EventManager()
this.events = new EventEmitter()
this.registry = Registry.getInstance()
this.element = document.createElement('div')
this.element.setAttribute('id', 'staticAnalyserView')
this._components = {}
this._components.registry = this.registry
this._deps = {
offsetToLineColumnConverter: this.registry.get(
'offsettolinecolumnconverter').api
}
this.dispatch = null
this.hints = []
this.basicEnabled = false
this.solhintEnabled = false
this.slitherEnabled = false
}
async onActivation () {
this.renderComponent()
const isSolidityActive = await this.call('manager', 'isActive', 'solidity')
if (!isSolidityActive) {
await this.call('manager', 'activatePlugin', 'solidity')
}
async onActivation () {
this.renderComponent()
const isSolidityActive = await this.call('manager', 'isActive', 'solidity')
if (!isSolidityActive) {
await this.call('manager', 'activatePlugin', 'solidity')
}
this.event.register('staticAnaysisWarning', (count) => {
let payloadType = ''
const error = this.hints?.find(hint => hint.type === 'error')
if (error && this.solhintEnabled) {
payloadType = 'error'
} else {
payloadType = 'warning'
}
this.event.register('staticAnaysisWarning', (count) => {
let payloadType = ''
const error = this.hints?.find(hint => hint.type === 'error')
if (error && this.solhintEnabled) {
payloadType = 'error'
} else {
payloadType = 'warning'
}
if (count > 0) {
this.emit('statusChanged', { key: count, title: payloadType === 'error' ? `You have ${count} problem${count === 1 ? '' : 's'}` : `You have ${count} warnings`, type: payloadType })
} else if (count === 0) {
this.emit('statusChanged', { key: 'succeed', title: 'no warnings or errors', type: 'success' })
} else {
// count ==-1 no compilation result
this.emit('statusChanged', { key: 'none' })
}
})
}
if (count > 0) {
this.emit('statusChanged', { key: count, title: payloadType === 'error' ? `You have ${count} problem${count === 1 ? '' : 's'}` : `You have ${count} warnings`, type: payloadType })
} else if (count === 0) {
this.emit('statusChanged', { key: 'succeed', title: 'no warnings or errors', type: 'success' })
} else {
// count ==-1 no compilation result
this.emit('statusChanged', { key: 'none' })
}
})
}
setDispatch (dispatch) {
this.dispatch = dispatch
this.renderComponent()
}
setDispatch (dispatch) {
this.dispatch = dispatch
this.renderComponent()
}
render () {
return <div id='staticAnalyserView'><PluginViewWrapper plugin={this} /></div>
}
render () {
return <div id='staticAnalyserView'><PluginViewWrapper plugin={this} /></div>
}
updateComponent(state) {
return <RemixUiStaticAnalyser
registry={state.registry}
analysisModule={state.analysisModule}
event={state.event}
/>
}
updateComponent(state) {
return <RemixUiStaticAnalyser
registry={state.registry}
analysisModule={state.analysisModule}
event={state.event}
/>
}
renderComponent () {
this.dispatch && this.dispatch({
registry: this.registry,
analysisModule: this,
event: this.event
})
}
renderComponent () {
this.dispatch && this.dispatch({
registry: this.registry,
analysisModule: this,
event: this.event
})
}
}
module.exports = AnalysisTab

@ -8,89 +8,89 @@ declare global {
const _paq = window._paq = window._paq || []
export const profile = {
name: 'compileAndRun',
displayName: 'Compile and Run',
description: 'After each compilation, run the script defined in Natspec.',
methods: ['runScriptAfterCompilation'],
version: packageJson.version,
kind: 'none'
name: 'compileAndRun',
displayName: 'Compile and Run',
description: 'After each compilation, run the script defined in Natspec.',
methods: ['runScriptAfterCompilation'],
version: packageJson.version,
kind: 'none'
}
type listener = (event: KeyboardEvent) => void
export class CompileAndRun extends Plugin {
executionListener: listener
targetFileName: string
executionListener: listener
targetFileName: string
constructor () {
super(profile)
this.executionListener = async (e) => {
// ctrl+e or command+e
constructor () {
super(profile)
this.executionListener = async (e) => {
// ctrl+e or command+e
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.keyCode === 83) {
const file = await this.call('fileManager', 'file')
if (file) {
if (file.endsWith('.sol')) {
e.preventDefault()
this.targetFileName = file
await this.call('solidity', 'compile', file)
_paq.push(['trackEvent', 'ScriptExecutor', 'CompileAndRun', 'compile_solidity'])
} else if (file.endsWith('.js') || file.endsWith('.ts')) {
e.preventDefault()
this.runScript(file, false)
_paq.push(['trackEvent', 'ScriptExecutor', 'CompileAndRun', 'run_script'])
}
}
}
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.keyCode === 83) {
const file = await this.call('fileManager', 'file')
if (file) {
if (file.endsWith('.sol')) {
e.preventDefault()
this.targetFileName = file
await this.call('solidity', 'compile', file)
_paq.push(['trackEvent', 'ScriptExecutor', 'CompileAndRun', 'compile_solidity'])
} else if (file.endsWith('.js') || file.endsWith('.ts')) {
e.preventDefault()
this.runScript(file, false)
_paq.push(['trackEvent', 'ScriptExecutor', 'CompileAndRun', 'run_script'])
}
}
}
}
}
runScriptAfterCompilation (fileName: string) {
this.targetFileName = fileName
_paq.push(['trackEvent', 'ScriptExecutor', 'CompileAndRun', 'request_run_script'])
}
runScriptAfterCompilation (fileName: string) {
this.targetFileName = fileName
_paq.push(['trackEvent', 'ScriptExecutor', 'CompileAndRun', 'request_run_script'])
}
async runScript (fileName, clearAllInstances) {
await this.call('terminal', 'log', { value: `running ${fileName} ...`, type: 'info' })
try {
const exists = await this.call('fileManager', 'exists', fileName)
if (!exists) {
await this.call('terminal', 'log', { value: `${fileName} does not exist.`, type: 'info' } )
return
}
const content = await this.call('fileManager', 'readFile', fileName)
if (clearAllInstances) {
await this.call('udapp', 'clearAllInstances')
}
await this.call('scriptRunner', 'execute', content, fileName)
} catch (e) {
this.call('notification', 'toast', e.message || e)
}
}
async runScript (fileName, clearAllInstances) {
await this.call('terminal', 'log', { value: `running ${fileName} ...`, type: 'info' })
try {
const exists = await this.call('fileManager', 'exists', fileName)
if (!exists) {
await this.call('terminal', 'log', { value: `${fileName} does not exist.`, type: 'info' } )
return
}
const content = await this.call('fileManager', 'readFile', fileName)
if (clearAllInstances) {
await this.call('udapp', 'clearAllInstances')
}
await this.call('scriptRunner', 'execute', content, fileName)
} catch (e) {
this.call('notification', 'toast', e.message || e)
}
}
onActivation () {
window.document.addEventListener('keydown', this.executionListener)
onActivation () {
window.document.addEventListener('keydown', this.executionListener)
this.on('compilerMetadata', 'artefactsUpdated', async (fileName, contract) => {
if (this.targetFileName === contract.file) {
this.targetFileName = null
if (contract.object && contract.object.devdoc['custom:dev-run-script']) {
const file = contract.object.devdoc['custom:dev-run-script']
if (file) {
this.runScript(file, true)
_paq.push(['trackEvent', 'ScriptExecutor', 'CompileAndRun', 'run_script_after_compile'])
} else {
this.call('notification', 'toast', 'You have not set a script to run. Set it with @custom:dev-run-script NatSpec tag.')
}
} else {
this.call('notification', 'toast', 'You have not set a script to run. Set it with @custom:dev-run-script NatSpec tag.')
}
}
})
}
this.on('compilerMetadata', 'artefactsUpdated', async (fileName, contract) => {
if (this.targetFileName === contract.file) {
this.targetFileName = null
if (contract.object && contract.object.devdoc['custom:dev-run-script']) {
const file = contract.object.devdoc['custom:dev-run-script']
if (file) {
this.runScript(file, true)
_paq.push(['trackEvent', 'ScriptExecutor', 'CompileAndRun', 'run_script_after_compile'])
} else {
this.call('notification', 'toast', 'You have not set a script to run. Set it with @custom:dev-run-script NatSpec tag.')
}
} else {
this.call('notification', 'toast', 'You have not set a script to run. Set it with @custom:dev-run-script NatSpec tag.')
}
}
})
}
onDeactivation () {
window.document.removeEventListener('keydown', this.executionListener)
this.off('compilerMetadata', 'artefactsUpdated')
}
onDeactivation () {
window.document.removeEventListener('keydown', this.executionListener)
this.off('compilerMetadata', 'artefactsUpdated')
}
}

@ -11,17 +11,17 @@ import { compilerConfigChangedToastMsg, compileToastMsg } from '@remix-ui/helper
import { isNative } from '../../remixAppManager'
const profile = {
name: 'solidity',
displayName: 'Solidity compiler',
icon: 'assets/img/solidity.webp',
description: 'Compile solidity contracts',
kind: 'compiler',
permission: true,
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/compile.html',
version: packageJson.version,
maintainedBy: 'Remix',
methods: ['getCompilationResult', 'compile', 'compileWithParameters', 'setCompilerConfig', 'compileFile', 'getCompilerState', 'getCompilerParameters', 'getCompiler']
name: 'solidity',
displayName: 'Solidity compiler',
icon: 'assets/img/solidity.webp',
description: 'Compile solidity contracts',
kind: 'compiler',
permission: true,
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/compile.html',
version: packageJson.version,
maintainedBy: 'Remix',
methods: ['getCompilationResult', 'compile', 'compileWithParameters', 'setCompilerConfig', 'compileFile', 'getCompilerState', 'getCompilerParameters', 'getCompiler']
}
// EditorApi:
@ -29,134 +29,134 @@ const profile = {
// - methods: ['getCompilationResult']
class CompileTab extends CompilerApiMixin(ViewPlugin) { // implements ICompilerApi
constructor (config, fileManager) {
super(profile)
this.fileManager = fileManager
this.config = config
this.queryParams = new QueryParams()
this.compileTabLogic = new CompileTabLogic(this, this.contentImport)
this.compiler = this.compileTabLogic.compiler
this.compileTabLogic.init()
this.initCompilerApi()
this.el = document.createElement('div')
this.el.setAttribute('id', 'compileTabView')
}
renderComponent () {
// empty method, is a state update needed?
}
onCurrentFileChanged () {
this.renderComponent()
}
// onResetResults () {
// this.renderComponent()
// }
onSetWorkspace () {
this.renderComponent()
}
onFileRemoved () {
this.renderComponent()
}
onNoFileSelected () {
this.renderComponent()
}
onFileClosed () {
this.renderComponent()
}
onCompilationFinished () {
this.renderComponent()
}
render () {
return <div id='compileTabView'><SolidityCompiler api={this}/></div>
}
async compileWithParameters (compilationTargets, settings) {
return await super.compileWithParameters(compilationTargets, settings)
}
getCompilationResult () {
return super.getCompilationResult()
}
getFileManagerMode () {
return this.fileManager.mode
}
/**
constructor (config, fileManager) {
super(profile)
this.fileManager = fileManager
this.config = config
this.queryParams = new QueryParams()
this.compileTabLogic = new CompileTabLogic(this, this.contentImport)
this.compiler = this.compileTabLogic.compiler
this.compileTabLogic.init()
this.initCompilerApi()
this.el = document.createElement('div')
this.el.setAttribute('id', 'compileTabView')
}
renderComponent () {
// empty method, is a state update needed?
}
onCurrentFileChanged () {
this.renderComponent()
}
// onResetResults () {
// this.renderComponent()
// }
onSetWorkspace () {
this.renderComponent()
}
onFileRemoved () {
this.renderComponent()
}
onNoFileSelected () {
this.renderComponent()
}
onFileClosed () {
this.renderComponent()
}
onCompilationFinished () {
this.renderComponent()
}
render () {
return <div id='compileTabView'><SolidityCompiler api={this}/></div>
}
async compileWithParameters (compilationTargets, settings) {
return await super.compileWithParameters(compilationTargets, settings)
}
getCompilationResult () {
return super.getCompilationResult()
}
getFileManagerMode () {
return this.fileManager.mode
}
/**
* set the compiler configuration
* This function is used by remix-plugin compiler API.
* @param {object} settings {evmVersion, optimize, runs, version, language}
*/
setCompilerConfig (settings) {
super.setCompilerConfig(settings)
this.renderComponent()
// @todo(#2875) should use loading compiler return value to check whether the compiler is loaded instead of "setInterval"
const value = JSON.stringify(settings, null, '\t')
this.call('notification', 'toast', compilerConfigChangedToastMsg(this.currentRequest.from, value))
}
compile (fileName) {
if (!isNative(this.currentRequest.from)) this.call('notification', 'toast', compileToastMsg(this.currentRequest.from, fileName))
super.compile(fileName)
}
compileFile (event) {
return super.compileFile(event)
}
async onActivation () {
super.onActivation()
this.on('filePanel', 'workspaceInitializationCompleted', () => {
this.call('filePanel', 'registerContextMenuItem', {
id: 'solidity',
name: 'compileFile',
label: 'Compile',
type: [],
extension: ['.sol'],
path: [],
pattern: [],
group: 6
})
})
try {
this.currentFile = await this.call('fileManager', 'file')
} catch (error) {
if (error.message !== 'Error: No such file or directory No file selected') throw error
}
}
getCompiler () {
return this.compileTabLogic.compiler
}
getCompilerParameters () {
const params = this.queryParams.get()
params.evmVersion = params.evmVersion === 'null' || params.evmVersion === 'undefined' ? null : params.evmVersion
params.optimize = (params.optimize === 'false' || params.optimize === null || params.optimize === undefined) ? false : params.optimize
params.optimize = params.optimize === 'true' ? true : params.optimize
return params
}
setCompilerParameters (params) {
this.queryParams.update(params)
}
async getAppParameter (name) {
return await this.call('config', 'getAppParameter', name)
}
async setAppParameter (name, value) {
await this.call('config', 'setAppParameter', name, value)
}
setCompilerConfig (settings) {
super.setCompilerConfig(settings)
this.renderComponent()
// @todo(#2875) should use loading compiler return value to check whether the compiler is loaded instead of "setInterval"
const value = JSON.stringify(settings, null, '\t')
this.call('notification', 'toast', compilerConfigChangedToastMsg(this.currentRequest.from, value))
}
compile (fileName) {
if (!isNative(this.currentRequest.from)) this.call('notification', 'toast', compileToastMsg(this.currentRequest.from, fileName))
super.compile(fileName)
}
compileFile (event) {
return super.compileFile(event)
}
async onActivation () {
super.onActivation()
this.on('filePanel', 'workspaceInitializationCompleted', () => {
this.call('filePanel', 'registerContextMenuItem', {
id: 'solidity',
name: 'compileFile',
label: 'Compile',
type: [],
extension: ['.sol'],
path: [],
pattern: [],
group: 6
})
})
try {
this.currentFile = await this.call('fileManager', 'file')
} catch (error) {
if (error.message !== 'Error: No such file or directory No file selected') throw error
}
}
getCompiler () {
return this.compileTabLogic.compiler
}
getCompilerParameters () {
const params = this.queryParams.get()
params.evmVersion = params.evmVersion === 'null' || params.evmVersion === 'undefined' ? null : params.evmVersion
params.optimize = (params.optimize === 'false' || params.optimize === null || params.optimize === undefined) ? false : params.optimize
params.optimize = params.optimize === 'true' ? true : params.optimize
return params
}
setCompilerParameters (params) {
this.queryParams.update(params)
}
async getAppParameter (name) {
return await this.call('config', 'getAppParameter', name)
}
async setAppParameter (name, value) {
await this.call('config', 'setAppParameter', name, value)
}
}
module.exports = CompileTab

@ -9,106 +9,106 @@ import { compilationFinishedToastMsg, compilingToastMsg, localCompilationToastMs
const css = require('./styles/debugger-tab-styles')
const profile = {
name: 'debugger',
displayName: 'Debugger',
methods: ['debug', 'getTrace', 'decodeLocalVariable', 'decodeStateVariable', 'globalContext'],
events: [],
icon: 'assets/img/debuggerLogo.webp',
description: 'Debug transactions',
kind: 'debugging',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/debugger.html',
version: packageJson.version,
maintainedBy: 'Remix'
name: 'debugger',
displayName: 'Debugger',
methods: ['debug', 'getTrace', 'decodeLocalVariable', 'decodeStateVariable', 'globalContext'],
events: [],
icon: 'assets/img/debuggerLogo.webp',
description: 'Debug transactions',
kind: 'debugging',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/debugger.html',
version: packageJson.version,
maintainedBy: 'Remix'
}
export class DebuggerTab extends DebuggerApiMixin(ViewPlugin) {
constructor () {
super(profile)
this.el = document.createElement('div')
this.el.setAttribute('id', 'debugView')
this.el.classList.add(css.debuggerTabView)
this.initDebuggerApi()
}
constructor () {
super(profile)
this.el = document.createElement('div')
this.el.setAttribute('id', 'debugView')
this.el.classList.add(css.debuggerTabView)
this.initDebuggerApi()
}
render () {
this.on('fetchAndCompile', 'compiling', (settings) => {
settings = JSON.stringify(settings, null, '\t')
this.call('notification', 'toast', compilingToastMsg(settings))
})
render () {
this.on('fetchAndCompile', 'compiling', (settings) => {
settings = JSON.stringify(settings, null, '\t')
this.call('notification', 'toast', compilingToastMsg(settings))
})
this.on('fetchAndCompile', 'compilationFailed', (data) => {
this.call('notification', 'toast', compilationFinishedToastMsg())
})
this.on('fetchAndCompile', 'compilationFailed', (data) => {
this.call('notification', 'toast', compilationFinishedToastMsg())
})
this.on('fetchAndCompile', 'notFound', (contractAddress) => {
this.call('notification', 'toast', notFoundToastMsg(contractAddress))
})
this.on('fetchAndCompile', 'notFound', (contractAddress) => {
this.call('notification', 'toast', notFoundToastMsg(contractAddress))
})
this.on('fetchAndCompile', 'sourceVerificationNotAvailable', () => {
this.call('notification', 'toast', sourceVerificationNotAvailableToastMsg())
})
const onReady = (api) => { this.api = api }
return <div className="overflow-hidden px-1" id='debugView'><DebuggerUI debuggerAPI={this} onReady={onReady} /></div>
}
this.on('fetchAndCompile', 'sourceVerificationNotAvailable', () => {
this.call('notification', 'toast', sourceVerificationNotAvailableToastMsg())
})
const onReady = (api) => { this.api = api }
return <div className="overflow-hidden px-1" id='debugView'><DebuggerUI debuggerAPI={this} onReady={onReady} /></div>
}
showMessage (title, message) {
try {
this.call('notification', 'alert', {
id: 'debuggerTabShowMessage',
title,
message: bleach.sanitize(message)
})
} catch (e) {
console.log(e)
}
showMessage (title, message) {
try {
this.call('notification', 'alert', {
id: 'debuggerTabShowMessage',
title,
message: bleach.sanitize(message)
})
} catch (e) {
console.log(e)
}
}
async decodeLocalVariable (variableId) {
if (!this.debuggerBackend) return null
return await this.debuggerBackend.debugger.decodeLocalVariableByIdAtCurrentStep(this.debuggerBackend.step_manager.currentStepIndex, variableId)
}
async decodeLocalVariable (variableId) {
if (!this.debuggerBackend) return null
return await this.debuggerBackend.debugger.decodeLocalVariableByIdAtCurrentStep(this.debuggerBackend.step_manager.currentStepIndex, variableId)
}
async decodeStateVariable (variableId) {
if (!this.debuggerBackend) return null
return await this.debuggerBackend.debugger.decodeStateVariableByIdAtCurrentStep(this.debuggerBackend.step_manager.currentStepIndex, variableId)
}
async decodeStateVariable (variableId) {
if (!this.debuggerBackend) return null
return await this.debuggerBackend.debugger.decodeStateVariableByIdAtCurrentStep(this.debuggerBackend.step_manager.currentStepIndex, variableId)
}
async globalContext () {
if (this.api?.globalContext) {
const { tx, block } = await this.api.globalContext()
const blockContext = {
'chainid': tx.chainId,
'coinbase': block.miner,
'difficulty': block.difficulty,
'gaslimit': block.gasLimit,
'number': block.number,
'timestamp': block.timestamp,
}
if (block.baseFeePerGas) {
blockContext['basefee'] = Web3.utils.toBN(block.baseFeePerGas).toString(10) + ` Wei (${block.baseFeePerGas})`
}
const msg = {
'sender': tx.from,
'sig': tx.input.substring(0, 10),
'value': tx.value + ' Wei'
}
async globalContext () {
if (this.api?.globalContext) {
const { tx, block } = await this.api.globalContext()
const blockContext = {
'chainid': tx.chainId,
'coinbase': block.miner,
'difficulty': block.difficulty,
'gaslimit': block.gasLimit,
'number': block.number,
'timestamp': block.timestamp,
}
if (block.baseFeePerGas) {
blockContext['basefee'] = Web3.utils.toBN(block.baseFeePerGas).toString(10) + ` Wei (${block.baseFeePerGas})`
}
const msg = {
'sender': tx.from,
'sig': tx.input.substring(0, 10),
'value': tx.value + ' Wei'
}
const txOrigin = {
'origin': tx.from
}
const txOrigin = {
'origin': tx.from
}
return {
block: blockContext,
msg,
tx: txOrigin
}
} else {
return {
block: null,
msg: null,
tx: null
}
}
return {
block: blockContext,
msg,
tx: txOrigin
}
} else {
return {
block: null,
msg: null,
tx: null
}
}
}
}

@ -8,71 +8,71 @@ import zhJson from './locales/zh'
const _paq = window._paq = window._paq || []
const locales = [
{ code: 'en', name: 'English', localeName: 'English', messages: enJson },
{ code: 'zh', name: 'Chinese Simplified', localeName: '简体中文', messages: zhJson },
{ code: 'en', name: 'English', localeName: 'English', messages: enJson },
{ code: 'zh', name: 'Chinese Simplified', localeName: '简体中文', messages: zhJson },
]
const profile = {
name: 'locale',
events: ['localeChanged'],
methods: ['switchLocale', 'getLocales', 'currentLocale'],
version: packageJson.version,
kind: 'locale'
name: 'locale',
events: ['localeChanged'],
methods: ['switchLocale', 'getLocales', 'currentLocale'],
version: packageJson.version,
kind: 'locale'
}
export class LocaleModule extends Plugin {
constructor () {
super(profile)
this.events = new EventEmitter()
this._deps = {
config: Registry.getInstance().get('config') && Registry.getInstance().get('config').api
}
this.locales = {}
locales.map((locale) => {
this.locales[locale.code.toLocaleLowerCase()] = locale
})
this._paq = _paq
this.queryParams = new QueryParams()
let queryLocale = this.queryParams.get().lang
queryLocale = queryLocale && queryLocale.toLocaleLowerCase()
queryLocale = this.locales[queryLocale] ? queryLocale : null
let currentLocale = (this._deps.config && this._deps.config.get('settings/locale')) || null
currentLocale = currentLocale && currentLocale.toLocaleLowerCase()
currentLocale = this.locales[currentLocale] ? currentLocale : null
this.currentLocaleState = { queryLocale, currentLocale }
this.active = queryLocale || currentLocale || 'en'
this.forced = !!queryLocale
this.queryParams.update({ lang: this.active })
constructor () {
super(profile)
this.events = new EventEmitter()
this._deps = {
config: Registry.getInstance().get('config') && Registry.getInstance().get('config').api
}
this.locales = {}
locales.map((locale) => {
this.locales[locale.code.toLocaleLowerCase()] = locale
})
this._paq = _paq
this.queryParams = new QueryParams()
let queryLocale = this.queryParams.get().lang
queryLocale = queryLocale && queryLocale.toLocaleLowerCase()
queryLocale = this.locales[queryLocale] ? queryLocale : null
let currentLocale = (this._deps.config && this._deps.config.get('settings/locale')) || null
currentLocale = currentLocale && currentLocale.toLocaleLowerCase()
currentLocale = this.locales[currentLocale] ? currentLocale : null
this.currentLocaleState = { queryLocale, currentLocale }
this.active = queryLocale || currentLocale || 'en'
this.forced = !!queryLocale
this.queryParams.update({ lang: this.active })
}
/** Return the active locale */
currentLocale () {
return this.locales[this.active]
}
/** Return the active locale */
currentLocale () {
return this.locales[this.active]
}
/** Returns all locales as an array */
getLocales () {
return Object.keys(this.locales).map(key => this.locales[key])
}
/** Returns all locales as an array */
getLocales () {
return Object.keys(this.locales).map(key => this.locales[key])
}
/**
/**
* Change the current locale
* @param {string} [localeCode] - The code of the locale
*/
switchLocale (localeCode) {
localeCode = localeCode && localeCode.toLocaleLowerCase()
if (localeCode && !Object.keys(this.locales).includes(localeCode)) {
throw new Error(`Locale ${localeCode} doesn't exist`)
}
const next = localeCode || this.active // Name
if (next === this.active) return // --> exit out of this method
_paq.push(['trackEvent', 'localeModule', 'switchTo', next])
const nextLocale = this.locales[next] // Locale
if (!this.forced) this._deps.config.set('settings/locale', next)
if (localeCode) this.active = localeCode
this.queryParams.update({ lang: localeCode })
this.emit('localeChanged', nextLocale)
this.events.emit('localeChanged', nextLocale)
switchLocale (localeCode) {
localeCode = localeCode && localeCode.toLocaleLowerCase()
if (localeCode && !Object.keys(this.locales).includes(localeCode)) {
throw new Error(`Locale ${localeCode} doesn't exist`)
}
const next = localeCode || this.active // Name
if (next === this.active) return // --> exit out of this method
_paq.push(['trackEvent', 'localeModule', 'switchTo', next])
const nextLocale = this.locales[next] // Locale
if (!this.forced) this._deps.config.set('settings/locale', next)
if (localeCode) this.active = localeCode
this.queryParams.update({ lang: localeCode })
this.emit('localeChanged', nextLocale)
this.events.emit('localeChanged', nextLocale)
}
}

@ -12,16 +12,16 @@ import solidityUnitTestingJson from './solidityUnitTesting.json';
import permissionHandlerJson from './permissionHandler.json';
export default {
...debuggerJson,
...filePanelJson,
...homeJson,
...panelJson,
...pluginManagerJson,
...searchJson,
...settingsJson,
...solidityJson,
...terminalJson,
...udappJson,
...solidityUnitTestingJson,
...permissionHandlerJson,
...debuggerJson,
...filePanelJson,
...homeJson,
...panelJson,
...pluginManagerJson,
...searchJson,
...settingsJson,
...solidityJson,
...terminalJson,
...udappJson,
...solidityUnitTestingJson,
...permissionHandlerJson,
}

@ -15,16 +15,16 @@ import enJson from '../en';
// There may have some un-translated content. Always fill in the gaps with EN JSON.
// No need for a defaultMessage prop when render a FormattedMessage component.
export default Object.assign({}, enJson, {
...debuggerJson,
...filePanelJson,
...homeJson,
...panelJson,
...pluginManagerJson,
...searchJson,
...settingsJson,
...solidityJson,
...terminalJson,
...udappJson,
...solidityUnitTestingJson,
...permissionHandlerJson,
...debuggerJson,
...filePanelJson,
...homeJson,
...panelJson,
...pluginManagerJson,
...searchJson,
...settingsJson,
...solidityJson,
...terminalJson,
...udappJson,
...solidityUnitTestingJson,
...permissionHandlerJson,
})

@ -3,11 +3,11 @@ import * as packageJson from '../../../../../package.json'
import { Web3 } from 'web3'
export const profile = {
name: 'network',
description: 'Manage the network (mainnet, ropsten, goerli...) and the provider (web3, vm, injected)',
methods: ['getNetworkProvider', 'getEndpoint', 'detectNetwork', 'addNetwork', 'removeNetwork'],
version: packageJson.version,
kind: 'network'
name: 'network',
description: 'Manage the network (mainnet, ropsten, goerli...) and the provider (web3, vm, injected)',
methods: ['getNetworkProvider', 'getEndpoint', 'detectNetwork', 'addNetwork', 'removeNetwork'],
version: packageJson.version,
kind: 'network'
}
// Network API has :
@ -15,46 +15,46 @@ export const profile = {
// - methods: ['getNetworkProvider', 'getEndpoint', 'detectNetwork', 'addNetwork', 'removeNetwork']
export class NetworkModule extends Plugin {
constructor (blockchain) {
super(profile)
this.blockchain = blockchain
// TODO: See with remix-lib to make sementic coherent
this.blockchain.event.register('contextChanged', (provider) => {
this.emit('providerChanged', provider)
})
}
/** Return the current network provider (web3, vm, injected) */
getNetworkProvider () {
return this.blockchain.getProvider()
}
/** Return the current network */
detectNetwork () {
return new Promise((resolve, reject) => {
this.blockchain.detectNetwork((error, network) => {
error ? reject(error) : resolve(network)
})
})
}
/** Return the url only if network provider is 'web3' */
getEndpoint () {
const provider = this.blockchain.getProvider()
if (provider !== 'web3') {
throw new Error('no endpoint: current provider is either injected or vm')
}
return this.blockchain.web3().currentProvider.host
}
/** Add a custom network to the list of available networks */
addNetwork (network) { // { name, url }
const provider = network.url === 'ipc' ? new Web3.providers.IpcProvider() : new Web3.providers.HttpProvider(network.url)
this.blockchain.addProvider({ name: network.name, provider })
}
/** Remove a network to the list of availble networks */
removeNetwork (name) {
this.blockchain.removeProvider(name)
constructor (blockchain) {
super(profile)
this.blockchain = blockchain
// TODO: See with remix-lib to make sementic coherent
this.blockchain.event.register('contextChanged', (provider) => {
this.emit('providerChanged', provider)
})
}
/** Return the current network provider (web3, vm, injected) */
getNetworkProvider () {
return this.blockchain.getProvider()
}
/** Return the current network */
detectNetwork () {
return new Promise((resolve, reject) => {
this.blockchain.detectNetwork((error, network) => {
error ? reject(error) : resolve(network)
})
})
}
/** Return the url only if network provider is 'web3' */
getEndpoint () {
const provider = this.blockchain.getProvider()
if (provider !== 'web3') {
throw new Error('no endpoint: current provider is either injected or vm')
}
return this.blockchain.web3().currentProvider.host
}
/** Add a custom network to the list of available networks */
addNetwork (network) { // { name, url }
const provider = network.url === 'ipc' ? new Web3.providers.IpcProvider() : new Web3.providers.HttpProvider(network.url)
this.blockchain.addProvider({ name: network.name, provider })
}
/** Remove a network to the list of availble networks */
removeNetwork (name) {
this.blockchain.removeProvider(name)
}
}

@ -12,110 +12,110 @@ import { addressToString } from '@remix-ui/helper'
const _paq = window._paq = window._paq || [] //eslint-disable-line
const profile = {
name: 'recorder',
displayName: 'Recorder',
description: 'Records transactions to save and run',
version: packageJson.version,
methods: [ ]
name: 'recorder',
displayName: 'Recorder',
description: 'Records transactions to save and run',
version: packageJson.version,
methods: [ ]
}
/**
* Record transaction as long as the user create them.
*/
class Recorder extends Plugin {
constructor (blockchain) {
super(profile)
this.event = new EventManager()
this.blockchain = blockchain
this.data = { _listen: true, _replay: false, journal: [], _createdContracts: {}, _createdContractsReverse: {}, _usedAccounts: {}, _abis: {}, _contractABIReferences: {}, _linkReferences: {} }
constructor (blockchain) {
super(profile)
this.event = new EventManager()
this.blockchain = blockchain
this.data = { _listen: true, _replay: false, journal: [], _createdContracts: {}, _createdContractsReverse: {}, _usedAccounts: {}, _abis: {}, _contractABIReferences: {}, _linkReferences: {} }
this.blockchain.event.register('initiatingTransaction', (timestamp, tx, payLoad) => {
if (tx.useCall) return
var { from, to, value } = tx
this.blockchain.event.register('initiatingTransaction', (timestamp, tx, payLoad) => {
if (tx.useCall) return
var { from, to, value } = tx
// convert to and from to tokens
if (this.data._listen) {
var record = {
value,
inputs: txHelper.serializeInputs(payLoad.funAbi),
parameters: payLoad.funArgs,
name: payLoad.funAbi.name,
type: payLoad.funAbi.type
}
if (!to) {
var abi = payLoad.contractABI
var keccak = bufferToHex(hash.keccakFromString(JSON.stringify(abi)))
record.abi = keccak
record.contractName = payLoad.contractName
record.bytecode = payLoad.contractBytecode
record.linkReferences = payLoad.linkReferences
if (record.linkReferences && Object.keys(record.linkReferences).length) {
for (var file in record.linkReferences) {
for (var lib in record.linkReferences[file]) {
this.data._linkReferences[lib] = '<address>'
}
}
}
this.data._abis[keccak] = abi
// convert to and from to tokens
if (this.data._listen) {
var record = {
value,
inputs: txHelper.serializeInputs(payLoad.funAbi),
parameters: payLoad.funArgs,
name: payLoad.funAbi.name,
type: payLoad.funAbi.type
}
if (!to) {
var abi = payLoad.contractABI
var keccak = bufferToHex(hash.keccakFromString(JSON.stringify(abi)))
record.abi = keccak
record.contractName = payLoad.contractName
record.bytecode = payLoad.contractBytecode
record.linkReferences = payLoad.linkReferences
if (record.linkReferences && Object.keys(record.linkReferences).length) {
for (var file in record.linkReferences) {
for (var lib in record.linkReferences[file]) {
this.data._linkReferences[lib] = '<address>'
}
}
}
this.data._abis[keccak] = abi
this.data._contractABIReferences[timestamp] = keccak
} else {
var creationTimestamp = this.data._createdContracts[to]
record.to = `created{${creationTimestamp}}`
record.abi = this.data._contractABIReferences[creationTimestamp]
}
for (var p in record.parameters) {
var thisarg = record.parameters[p]
var thistimestamp = this.data._createdContracts[thisarg]
if (thistimestamp) record.parameters[p] = `created{${thistimestamp}}`
}
this.data._contractABIReferences[timestamp] = keccak
} else {
var creationTimestamp = this.data._createdContracts[to]
record.to = `created{${creationTimestamp}}`
record.abi = this.data._contractABIReferences[creationTimestamp]
}
for (var p in record.parameters) {
var thisarg = record.parameters[p]
var thistimestamp = this.data._createdContracts[thisarg]
if (thistimestamp) record.parameters[p] = `created{${thistimestamp}}`
}
this.blockchain.getAccounts((error, accounts) => {
if (error) return console.log(error)
record.from = `account{${accounts.indexOf(from)}}`
this.data._usedAccounts[record.from] = from
this.append(timestamp, record)
})
}
this.blockchain.getAccounts((error, accounts) => {
if (error) return console.log(error)
record.from = `account{${accounts.indexOf(from)}}`
this.data._usedAccounts[record.from] = from
this.append(timestamp, record)
})
}
})
this.blockchain.event.register('transactionExecuted', (error, from, to, data, call, txResult, timestamp, _payload) => {
if (error) return console.log(error)
if (call) return
const rawAddress = txResult.receipt.contractAddress
if (!rawAddress) return // not a contract creation
const address = addressToString(rawAddress)
// save back created addresses for the convertion from tokens to real adresses
this.data._createdContracts[address] = timestamp
this.data._createdContractsReverse[timestamp] = address
})
this.blockchain.event.register('contextChanged', this.clearAll.bind(this))
this.event.register('newTxRecorded', (count) => {
this.event.trigger('recorderCountChange', [count])
})
this.event.register('cleared', () => {
this.event.trigger('recorderCountChange', [0])
})
}
this.blockchain.event.register('transactionExecuted', (error, from, to, data, call, txResult, timestamp, _payload) => {
if (error) return console.log(error)
if (call) return
const rawAddress = txResult.receipt.contractAddress
if (!rawAddress) return // not a contract creation
const address = addressToString(rawAddress)
// save back created addresses for the convertion from tokens to real adresses
this.data._createdContracts[address] = timestamp
this.data._createdContractsReverse[timestamp] = address
})
this.blockchain.event.register('contextChanged', this.clearAll.bind(this))
this.event.register('newTxRecorded', (count) => {
this.event.trigger('recorderCountChange', [count])
})
this.event.register('cleared', () => {
this.event.trigger('recorderCountChange', [0])
})
}
/**
/**
* stop/start saving txs. If not listenning, is basically in replay mode
*
* @param {Bool} listen
*/
setListen (listen) {
this.data._listen = listen
this.data._replay = !listen
}
setListen (listen) {
this.data._listen = listen
this.data._replay = !listen
}
extractTimestamp (value) {
var stamp = /created{(.*)}/g.exec(value)
if (stamp) {
return stamp[1]
}
return null
extractTimestamp (value) {
var stamp = /created{(.*)}/g.exec(value)
if (stamp) {
return stamp[1]
}
return null
}
/**
/**
* convert back from/to from tokens to real addresses
*
* @param {Object} record
@ -123,66 +123,66 @@ class Recorder extends Plugin {
* @param {Object} options
*
*/
resolveAddress (record, accounts, options) {
if (record.to) {
var stamp = this.extractTimestamp(record.to)
if (stamp) {
record.to = this.data._createdContractsReverse[stamp]
}
}
record.from = accounts[record.from]
// @TODO: writing browser test
return record
resolveAddress (record, accounts, options) {
if (record.to) {
var stamp = this.extractTimestamp(record.to)
if (stamp) {
record.to = this.data._createdContractsReverse[stamp]
}
}
record.from = accounts[record.from]
// @TODO: writing browser test
return record
}
/**
/**
* save the given @arg record
*
* @param {Number/String} timestamp
* @param {Object} record
*
*/
append (timestamp, record) {
this.data.journal.push({ timestamp, record })
this.event.trigger('newTxRecorded', [this.data.journal.length])
}
append (timestamp, record) {
this.data.journal.push({ timestamp, record })
this.event.trigger('newTxRecorded', [this.data.journal.length])
}
/**
/**
* basically return the records + associate values (like abis / accounts)
*
*/
getAll () {
var records = [].concat(this.data.journal)
return {
accounts: this.data._usedAccounts,
linkReferences: this.data._linkReferences,
transactions: records.sort((A, B) => {
var stampA = A.timestamp
var stampB = B.timestamp
return stampA - stampB
}),
abis: this.data._abis
}
getAll () {
var records = [].concat(this.data.journal)
return {
accounts: this.data._usedAccounts,
linkReferences: this.data._linkReferences,
transactions: records.sort((A, B) => {
var stampA = A.timestamp
var stampB = B.timestamp
return stampA - stampB
}),
abis: this.data._abis
}
}
/**
/**
* delete the seen transactions
*
*/
clearAll () {
this.data._listen = true
this.data._replay = false
this.data.journal = []
this.data._createdContracts = {}
this.data._createdContractsReverse = {}
this.data._usedAccounts = {}
this.data._abis = {}
this.data._contractABIReferences = {}
this.data._linkReferences = {}
this.event.trigger('cleared', [])
}
clearAll () {
this.data._listen = true
this.data._replay = false
this.data.journal = []
this.data._createdContracts = {}
this.data._createdContractsReverse = {}
this.data._usedAccounts = {}
this.data._abis = {}
this.data._contractABIReferences = {}
this.data._linkReferences = {}
this.event.trigger('cleared', [])
}
/**
/**
* run the list of records
*
* @param {Object} records
@ -199,134 +199,134 @@ class Recorder extends Plugin {
* @param {Function} newContractFn
*
*/
run (records, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, liveMode, newContractFn) {
this.setListen(false)
const liveMsg = liveMode ? ' with updated contracts' : ''
logCallBack(`Running ${records.length} transaction(s)${liveMsg} ...`)
async.eachOfSeries(records, async (tx, index, cb) => {
if (liveMode && tx.record.type === 'constructor') {
// resolve the bytecode and ABI using the contract name, this ensure getting the last compiled one.
const data = await this.call('compilerArtefacts', 'getArtefactsByContractName', tx.record.contractName)
tx.record.bytecode = data.artefact.evm.bytecode.object
const updatedABIKeccak = bufferToHex(hash.keccakFromString(JSON.stringify(data.artefact.abi)))
abis[updatedABIKeccak] = data.artefact.abi
tx.record.abi = updatedABIKeccak
}
var record = this.resolveAddress(tx.record, accounts, options)
var abi = abis[tx.record.abi]
if (!abi) {
return alertCb('cannot find ABI for ' + tx.record.abi + '. Execution stopped at ' + index)
}
/* Resolve Library */
if (record.linkReferences && Object.keys(record.linkReferences).length) {
for (var k in linkReferences) {
var link = linkReferences[k]
var timestamp = this.extractTimestamp(link)
if (timestamp && this.data._createdContractsReverse[timestamp]) {
link = this.data._createdContractsReverse[timestamp]
}
tx.record.bytecode = format.linkLibraryStandardFromlinkReferences(k, link.replace('0x', ''), tx.record.bytecode, tx.record.linkReferences)
}
}
/* Encode params */
var fnABI
if (tx.record.type === 'constructor') {
fnABI = txHelper.getConstructorInterface(abi)
} else if (tx.record.type === 'fallback') {
fnABI = txHelper.getFallbackInterface(abi)
} else if (tx.record.type === 'receive') {
fnABI = txHelper.getReceiveInterface(abi)
} else {
fnABI = txHelper.getFunction(abi, record.name + record.inputs)
}
if (!fnABI) {
alertCb('cannot resolve abi of ' + JSON.stringify(record, null, '\t') + '. Execution stopped at ' + index)
return cb('cannot resolve abi')
}
if (tx.record.parameters) {
/* check if we have some params to resolve */
try {
tx.record.parameters.forEach((value, index) => {
var isString = true
if (typeof value !== 'string') {
isString = false
value = JSON.stringify(value)
}
for (var timestamp in this.data._createdContractsReverse) {
value = value.replace(new RegExp('created\\{' + timestamp + '\\}', 'g'), this.data._createdContractsReverse[timestamp])
}
if (!isString) value = JSON.parse(value)
tx.record.parameters[index] = value
})
} catch (e) {
return alertCb('cannot resolve input parameters ' + JSON.stringify(tx.record.parameters) + '. Execution stopped at ' + index)
}
}
var data = format.encodeData(fnABI, tx.record.parameters, tx.record.bytecode)
if (data.error) {
alertCb(data.error + '. Record:' + JSON.stringify(record, null, '\t') + '. Execution stopped at ' + index)
return cb(data.error)
}
logCallBack(`(${index}) ${JSON.stringify(record, null, '\t')}`)
logCallBack(`(${index}) data: ${data.data}`)
record.data = { dataHex: data.data, funArgs: tx.record.parameters, funAbi: fnABI, contractBytecode: tx.record.bytecode, contractName: tx.record.contractName, timestamp: tx.timestamp }
this.blockchain.runTx(record, confirmationCb, continueCb, promptCb,
(err, txResult, rawAddress) => {
if (err) {
console.error(err)
return logCallBack(err + '. Execution failed at ' + index)
}
if (rawAddress) {
const address = addressToString(rawAddress)
// save back created addresses for the convertion from tokens to real adresses
this.data._createdContracts[address] = tx.timestamp
this.data._createdContractsReverse[tx.timestamp] = address
newContractFn(abi, address, record.contractName)
}
cb(err)
}
)
}, () => { this.setListen(true) })
}
runScenario (liveMode, json, continueCb, promptCb, alertCb, confirmationCb, logCallBack, cb) {
_paq.push(['trackEvent', 'run', 'recorder', 'start'])
if (!json) {
_paq.push(['trackEvent', 'run', 'recorder', 'wrong-json'])
return cb('a json content must be provided')
}
if (typeof json === 'string') {
try {
json = JSON.parse(json)
} catch (e) {
return cb('A scenario file is required. It must be json formatted')
}
run (records, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, liveMode, newContractFn) {
this.setListen(false)
const liveMsg = liveMode ? ' with updated contracts' : ''
logCallBack(`Running ${records.length} transaction(s)${liveMsg} ...`)
async.eachOfSeries(records, async (tx, index, cb) => {
if (liveMode && tx.record.type === 'constructor') {
// resolve the bytecode and ABI using the contract name, this ensure getting the last compiled one.
const data = await this.call('compilerArtefacts', 'getArtefactsByContractName', tx.record.contractName)
tx.record.bytecode = data.artefact.evm.bytecode.object
const updatedABIKeccak = bufferToHex(hash.keccakFromString(JSON.stringify(data.artefact.abi)))
abis[updatedABIKeccak] = data.artefact.abi
tx.record.abi = updatedABIKeccak
}
var record = this.resolveAddress(tx.record, accounts, options)
var abi = abis[tx.record.abi]
if (!abi) {
return alertCb('cannot find ABI for ' + tx.record.abi + '. Execution stopped at ' + index)
}
/* Resolve Library */
if (record.linkReferences && Object.keys(record.linkReferences).length) {
for (var k in linkReferences) {
var link = linkReferences[k]
var timestamp = this.extractTimestamp(link)
if (timestamp && this.data._createdContractsReverse[timestamp]) {
link = this.data._createdContractsReverse[timestamp]
}
tx.record.bytecode = format.linkLibraryStandardFromlinkReferences(k, link.replace('0x', ''), tx.record.bytecode, tx.record.linkReferences)
}
let txArray
let accounts
let options
let abis
let linkReferences
}
/* Encode params */
var fnABI
if (tx.record.type === 'constructor') {
fnABI = txHelper.getConstructorInterface(abi)
} else if (tx.record.type === 'fallback') {
fnABI = txHelper.getFallbackInterface(abi)
} else if (tx.record.type === 'receive') {
fnABI = txHelper.getReceiveInterface(abi)
} else {
fnABI = txHelper.getFunction(abi, record.name + record.inputs)
}
if (!fnABI) {
alertCb('cannot resolve abi of ' + JSON.stringify(record, null, '\t') + '. Execution stopped at ' + index)
return cb('cannot resolve abi')
}
if (tx.record.parameters) {
/* check if we have some params to resolve */
try {
txArray = json.transactions || []
accounts = json.accounts || []
options = json.options || {}
abis = json.abis || {}
linkReferences = json.linkReferences || {}
tx.record.parameters.forEach((value, index) => {
var isString = true
if (typeof value !== 'string') {
isString = false
value = JSON.stringify(value)
}
for (var timestamp in this.data._createdContractsReverse) {
value = value.replace(new RegExp('created\\{' + timestamp + '\\}', 'g'), this.data._createdContractsReverse[timestamp])
}
if (!isString) value = JSON.parse(value)
tx.record.parameters[index] = value
})
} catch (e) {
return cb('Invalid scenario file. Please try again')
return alertCb('cannot resolve input parameters ' + JSON.stringify(tx.record.parameters) + '. Execution stopped at ' + index)
}
}
var data = format.encodeData(fnABI, tx.record.parameters, tx.record.bytecode)
if (data.error) {
alertCb(data.error + '. Record:' + JSON.stringify(record, null, '\t') + '. Execution stopped at ' + index)
return cb(data.error)
}
logCallBack(`(${index}) ${JSON.stringify(record, null, '\t')}`)
logCallBack(`(${index}) data: ${data.data}`)
record.data = { dataHex: data.data, funArgs: tx.record.parameters, funAbi: fnABI, contractBytecode: tx.record.bytecode, contractName: tx.record.contractName, timestamp: tx.timestamp }
if (!txArray.length) {
return cb('No transactions found in scenario file')
this.blockchain.runTx(record, confirmationCb, continueCb, promptCb,
(err, txResult, rawAddress) => {
if (err) {
console.error(err)
return logCallBack(err + '. Execution failed at ' + index)
}
if (rawAddress) {
const address = addressToString(rawAddress)
// save back created addresses for the convertion from tokens to real adresses
this.data._createdContracts[address] = tx.timestamp
this.data._createdContractsReverse[tx.timestamp] = address
newContractFn(abi, address, record.contractName)
}
cb(err)
}
)
}, () => { this.setListen(true) })
}
this.run(txArray, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, liveMode, (abi, address, contractName) => {
cb(null, abi, address, contractName)
})
runScenario (liveMode, json, continueCb, promptCb, alertCb, confirmationCb, logCallBack, cb) {
_paq.push(['trackEvent', 'run', 'recorder', 'start'])
if (!json) {
_paq.push(['trackEvent', 'run', 'recorder', 'wrong-json'])
return cb('a json content must be provided')
}
if (typeof json === 'string') {
try {
json = JSON.parse(json)
} catch (e) {
return cb('A scenario file is required. It must be json formatted')
}
}
let txArray
let accounts
let options
let abis
let linkReferences
try {
txArray = json.transactions || []
accounts = json.accounts || []
options = json.options || {}
abis = json.abis || {}
linkReferences = json.linkReferences || {}
} catch (e) {
return cb('Invalid scenario file. Please try again')
}
if (!txArray.length) {
return cb('No transactions found in scenario file')
}
this.run(txArray, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, liveMode, (abi, address, contractName) => {
cb(null, abi, address, contractName)
})
}
}
module.exports = Recorder

@ -3,31 +3,31 @@ import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line
import { SearchTab } from '@remix-ui/search'
const profile = {
name: 'search',
displayName: 'Search in files',
methods: [''],
events: [],
icon: 'assets/img/search_icon.webp',
description: 'Find and replace in file explorer',
kind: '',
location: 'sidePanel',
documentation: '',
version: packageJson.version,
maintainedBy: 'Remix'
name: 'search',
displayName: 'Search in files',
methods: [''],
events: [],
icon: 'assets/img/search_icon.webp',
description: 'Find and replace in file explorer',
kind: '',
location: 'sidePanel',
documentation: '',
version: packageJson.version,
maintainedBy: 'Remix'
}
export class SearchPlugin extends ViewPlugin {
constructor () {
super(profile)
}
constructor () {
super(profile)
}
render() {
return (
<div id='searchTab'>
<SearchTab plugin={this}></SearchTab>
</div>
);
}
render() {
return (
<div id='searchTab'>
<SearchTab plugin={this}></SearchTab>
</div>
);
}
}

@ -7,81 +7,81 @@ import Registry from '../state/registry'
import { PluginViewWrapper } from '@remix-ui/helper'
const profile = {
name: 'settings',
displayName: 'Settings',
methods: ['get'],
events: [],
icon: 'assets/img/settings.webp',
description: 'Remix-IDE settings',
kind: 'settings',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/settings.html',
version: packageJson.version,
permission: true,
maintainedBy: "Remix"
name: 'settings',
displayName: 'Settings',
methods: ['get'],
events: [],
icon: 'assets/img/settings.webp',
description: 'Remix-IDE settings',
kind: 'settings',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/settings.html',
version: packageJson.version,
permission: true,
maintainedBy: "Remix"
}
module.exports = class SettingsTab extends ViewPlugin {
config: any = {}
editor: any
private _deps: {
config: any = {}
editor: any
private _deps: {
themeModule: any
localeModule: any
}
element: HTMLDivElement
public useMatomoAnalytics: any
dispatch: React.Dispatch<any> = () => {}
constructor (config, editor) {
super(profile)
this.config = config
this.config.events.on('configChanged', (changedConfig) => {
this.emit('configChanged', changedConfig)
})
this.editor = editor
this._deps = {
themeModule: Registry.getInstance().get('themeModule').api,
localeModule: Registry.getInstance().get('localeModule').api
}
this.element = document.createElement('div')
this.element.setAttribute('id', 'settingsTab')
this.useMatomoAnalytics = null
element: HTMLDivElement
public useMatomoAnalytics: any
dispatch: React.Dispatch<any> = () => {}
constructor (config, editor) {
super(profile)
this.config = config
this.config.events.on('configChanged', (changedConfig) => {
this.emit('configChanged', changedConfig)
})
this.editor = editor
this._deps = {
themeModule: Registry.getInstance().get('themeModule').api,
localeModule: Registry.getInstance().get('localeModule').api
}
this.element = document.createElement('div')
this.element.setAttribute('id', 'settingsTab')
this.useMatomoAnalytics = null
}
setDispatch (dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
this.renderComponent()
}
setDispatch (dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
this.renderComponent()
}
render() {
return <div id='settingsTab'>
<PluginViewWrapper plugin={this} />
</div>
}
render() {
return <div id='settingsTab'>
<PluginViewWrapper plugin={this} />
</div>
}
updateComponent(state: any){
return <RemixUiSettings
config={state.config}
editor={state.editor}
_deps={state._deps}
useMatomoAnalytics={state.useMatomoAnalytics}
themeModule = {state._deps.themeModule}
localeModule={state._deps.localeModule}
/>
}
updateComponent(state: any){
return <RemixUiSettings
config={state.config}
editor={state.editor}
_deps={state._deps}
useMatomoAnalytics={state.useMatomoAnalytics}
themeModule = {state._deps.themeModule}
localeModule={state._deps.localeModule}
/>
}
renderComponent () {
this.dispatch(this)
}
renderComponent () {
this.dispatch(this)
}
get (key) {
return this.config.get(key)
}
get (key) {
return this.config.get(key)
}
updateMatomoAnalyticsChoice (isChecked) {
this.config.set('settings/matomo-analytics', isChecked)
this.useMatomoAnalytics = isChecked
this.dispatch({
...this
})
}
updateMatomoAnalyticsChoice (isChecked) {
this.config.set('settings/matomo-analytics', isChecked)
this.useMatomoAnalytics = isChecked
this.dispatch({
...this
})
}
}

@ -11,147 +11,147 @@ import { PluginViewWrapper } from '@remix-ui/helper'
var { UnitTestRunner, assertLibCode } = require('@remix-project/remix-tests')
const profile = {
name: 'solidityUnitTesting',
displayName: 'Solidity unit testing',
methods: ['testFromPath', 'testFromSource', 'setTestFolderPath', 'getTestlibs', 'createTestLibs'],
events: [],
icon: 'assets/img/unitTesting.webp',
description: 'Write and run unit tests for your contracts in Solidity',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/unittesting.html',
maintainedBy: 'Remix'
name: 'solidityUnitTesting',
displayName: 'Solidity unit testing',
methods: ['testFromPath', 'testFromSource', 'setTestFolderPath', 'getTestlibs', 'createTestLibs'],
events: [],
icon: 'assets/img/unitTesting.webp',
description: 'Write and run unit tests for your contracts in Solidity',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/unittesting.html',
maintainedBy: 'Remix'
}
module.exports = class TestTab extends ViewPlugin {
constructor (fileManager, offsetToLineColumnConverter, filePanel, compileTab, appManager, contentImport) {
super(profile)
this.compileTab = compileTab
this.contentImport = contentImport
this.fileManager = fileManager
this.filePanel = filePanel
this.appManager = appManager
this.testRunner = new UnitTestRunner()
this.testTabLogic = new TestTabLogic(this.fileManager, helper)
this.offsetToLineColumnConverter = offsetToLineColumnConverter
this.allFilesInvolved = ['.deps/remix-tests/remix_tests.sol', '.deps/remix-tests/remix_accounts.sol']
this.element = document.createElement('div')
this.dispatch = null
constructor (fileManager, offsetToLineColumnConverter, filePanel, compileTab, appManager, contentImport) {
super(profile)
this.compileTab = compileTab
this.contentImport = contentImport
this.fileManager = fileManager
this.filePanel = filePanel
this.appManager = appManager
this.testRunner = new UnitTestRunner()
this.testTabLogic = new TestTabLogic(this.fileManager, helper)
this.offsetToLineColumnConverter = offsetToLineColumnConverter
this.allFilesInvolved = ['.deps/remix-tests/remix_tests.sol', '.deps/remix-tests/remix_accounts.sol']
this.element = document.createElement('div')
this.dispatch = null
}
onActivationInternal () {
this.listenToEvents()
this.call('filePanel', 'registerContextMenuItem', {
id: 'solidityUnitTesting',
name: 'setTestFolderPath',
label: 'Set path for Unit Testing',
type: ['folder'],
extension: [],
path: [],
pattern: []
})
}
async setTestFolderPath (event) {
if (event.path.length > 0) {
this.renderComponent(event.path[0])
}
}
onActivationInternal () {
this.listenToEvents()
this.call('filePanel', 'registerContextMenuItem', {
id: 'solidityUnitTesting',
name: 'setTestFolderPath',
label: 'Set path for Unit Testing',
type: ['folder'],
extension: [],
path: [],
pattern: []
})
}
async setTestFolderPath (event) {
if (event.path.length > 0) {
this.renderComponent(event.path[0])
}
}
getTestlibs () {
return { assertLibCode, accountsLibCode: this.testRunner.accountsLibCode }
}
async createTestLibs () {
const provider = await this.fileManager.currentFileProvider()
if (provider) {
await provider.addExternal('.deps/remix-tests/remix_tests.sol', assertLibCode, 'remix_tests.sol')
await provider.addExternal('.deps/remix-tests/remix_accounts.sol', this.testRunner.accountsLibCode, 'remix_accounts.sol')
}
}
async onActivation () {
const isSolidityActive = await this.call('manager', 'isActive', 'solidity')
if (!isSolidityActive) {
await this.call('manager', 'activatePlugin', 'solidity')
}
await this.testRunner.init(await this.call('blockchain', 'web3VM'))
await this.createTestLibs()
}
onDeactivation () {
this.off('filePanel', 'newTestFileCreated')
this.off('filePanel', 'setWorkspace')
// 'currentFileChanged' event is added more than once
this.fileManager.events.removeAllListeners('currentFileChanged')
}
getTestlibs () {
return { assertLibCode, accountsLibCode: this.testRunner.accountsLibCode }
}
listenToEvents () {
this.on('filePanel', 'workspaceCreated', async () => {
this.createTestLibs()
})
this.testRunner.event.on('compilationFinished', (success, data, source, input, version) => {
if (success) {
this.allFilesInvolved.push(...Object.keys(data.sources))
// forwarding the event to the appManager infra
// This is listened by compilerArtefacts to show data while debugging
this.emit('compilationFinished', source.target, source, 'soljson', data, input, version)
}
})
async createTestLibs () {
const provider = await this.fileManager.currentFileProvider()
if (provider) {
await provider.addExternal('.deps/remix-tests/remix_tests.sol', assertLibCode, 'remix_tests.sol')
await provider.addExternal('.deps/remix-tests/remix_accounts.sol', this.testRunner.accountsLibCode, 'remix_accounts.sol')
}
}
async testFromPath (path) {
const fileContent = await this.fileManager.readFile(path)
return this.testFromSource(fileContent, path)
async onActivation () {
const isSolidityActive = await this.call('manager', 'isActive', 'solidity')
if (!isSolidityActive) {
await this.call('manager', 'activatePlugin', 'solidity')
}
/*
await this.testRunner.init(await this.call('blockchain', 'web3VM'))
await this.createTestLibs()
}
onDeactivation () {
this.off('filePanel', 'newTestFileCreated')
this.off('filePanel', 'setWorkspace')
// 'currentFileChanged' event is added more than once
this.fileManager.events.removeAllListeners('currentFileChanged')
}
listenToEvents () {
this.on('filePanel', 'workspaceCreated', async () => {
this.createTestLibs()
})
this.testRunner.event.on('compilationFinished', (success, data, source, input, version) => {
if (success) {
this.allFilesInvolved.push(...Object.keys(data.sources))
// forwarding the event to the appManager infra
// This is listened by compilerArtefacts to show data while debugging
this.emit('compilationFinished', source.target, source, 'soljson', data, input, version)
}
})
}
async testFromPath (path) {
const fileContent = await this.fileManager.readFile(path)
return this.testFromSource(fileContent, path)
}
/*
Test is not associated with the UI
*/
async testFromSource (content, path = 'browser/unit_test.sol') {
const web3 = await this.call('blockchain', 'web3VM')
await this.testRunner.init(web3)
await this.createTestLibs()
return new Promise((resolve, reject) => {
const runningTest = {}
runningTest[path] = { content }
const { currentVersion, evmVersion, optimize, runs } = this.compileTab.getCurrentCompilerConfig()
const currentCompilerUrl = urlFromVersion(currentVersion)
const compilerConfig = {
currentCompilerUrl,
evmVersion,
optimize,
usingWorker: canUseWorker(currentVersion),
runs
}
this.testRunner.runTestSources(runningTest, compilerConfig, () => { /* Do nothing. */ }, () => { /* Do nothing. */ }, null, (error, result) => {
if (error) return reject(error)
resolve(result)
}, (url, cb) => {
return this.contentImport.resolveAndSave(url).then((result) => cb(null, result)).catch((error) => cb(error.message))
}, {})
})
}
setDispatch (dispatch) {
this.dispatch = dispatch
this.renderComponent('tests')
}
render () {
this.onActivationInternal()
return <div><PluginViewWrapper plugin={this} /></div>
}
updateComponent(state) {
return <SolidityUnitTesting testTab={state.testTab} helper={state.helper} initialPath={state.testDirPath} />
}
renderComponent (testDirPath) {
this.dispatch({
testTab: this,
helper: helper,
testDirPath: testDirPath
})
}
async testFromSource (content, path = 'browser/unit_test.sol') {
const web3 = await this.call('blockchain', 'web3VM')
await this.testRunner.init(web3)
await this.createTestLibs()
return new Promise((resolve, reject) => {
const runningTest = {}
runningTest[path] = { content }
const { currentVersion, evmVersion, optimize, runs } = this.compileTab.getCurrentCompilerConfig()
const currentCompilerUrl = urlFromVersion(currentVersion)
const compilerConfig = {
currentCompilerUrl,
evmVersion,
optimize,
usingWorker: canUseWorker(currentVersion),
runs
}
this.testRunner.runTestSources(runningTest, compilerConfig, () => { /* Do nothing. */ }, () => { /* Do nothing. */ }, null, (error, result) => {
if (error) return reject(error)
resolve(result)
}, (url, cb) => {
return this.contentImport.resolveAndSave(url).then((result) => cb(null, result)).catch((error) => cb(error.message))
}, {})
})
}
setDispatch (dispatch) {
this.dispatch = dispatch
this.renderComponent('tests')
}
render () {
this.onActivationInternal()
return <div><PluginViewWrapper plugin={this} /></div>
}
updateComponent(state) {
return <SolidityUnitTesting testTab={state.testTab} helper={state.helper} initialPath={state.testDirPath} />
}
renderComponent (testDirPath) {
this.dispatch({
testTab: this,
helper: helper,
testDirPath: testDirPath
})
}
}

@ -6,128 +6,128 @@ import Registry from '../state/registry'
const _paq = window._paq = window._paq || []
const themes = [
{ name: 'Dark', quality: 'dark', url: 'assets/css/themes/remix-dark_tvx1s2.css' },
{ name: 'Light', quality: 'light', url: 'assets/css/themes/remix-light_powaqg.css' },
{ name: 'Violet', quality: 'light', url: 'assets/css/themes/remix-violet.css' },
{ name: 'Unicorn', quality: 'light', url: 'assets/css/themes/remix-unicorn.css' },
{ name: 'Midcentury', quality: 'light', url: 'assets/css/themes/remix-midcentury_hrzph3.css' },
{ name: 'Black', quality: 'dark', url: 'assets/css/themes/remix-black_undtds.css' },
{ name: 'Candy', quality: 'light', url: 'assets/css/themes/remix-candy_ikhg4m.css' },
{ name: 'HackerOwl', quality: 'dark', url: 'assets/css/themes/remix-hacker_owl.css' },
{ name: 'Dark', quality: 'dark', url: 'assets/css/themes/remix-dark_tvx1s2.css' },
{ name: 'Light', quality: 'light', url: 'assets/css/themes/remix-light_powaqg.css' },
{ name: 'Violet', quality: 'light', url: 'assets/css/themes/remix-violet.css' },
{ name: 'Unicorn', quality: 'light', url: 'assets/css/themes/remix-unicorn.css' },
{ name: 'Midcentury', quality: 'light', url: 'assets/css/themes/remix-midcentury_hrzph3.css' },
{ name: 'Black', quality: 'dark', url: 'assets/css/themes/remix-black_undtds.css' },
{ name: 'Candy', quality: 'light', url: 'assets/css/themes/remix-candy_ikhg4m.css' },
{ name: 'HackerOwl', quality: 'dark', url: 'assets/css/themes/remix-hacker_owl.css' },
{ name: 'Cerulean', quality: 'light', url: 'assets/css/themes/bootstrap-cerulean.min.css' },
{ name: 'Flatly', quality: 'light', url: 'assets/css/themes/bootstrap-flatly.min.css' },
{ name: 'Spacelab', quality: 'light', url: 'assets/css/themes/bootstrap-spacelab.min.css' },
{ name: 'Cyborg', quality: 'dark', url: 'assets/css/themes/bootstrap-cyborg.min.css' }
{ name: 'Cerulean', quality: 'light', url: 'assets/css/themes/bootstrap-cerulean.min.css' },
{ name: 'Flatly', quality: 'light', url: 'assets/css/themes/bootstrap-flatly.min.css' },
{ name: 'Spacelab', quality: 'light', url: 'assets/css/themes/bootstrap-spacelab.min.css' },
{ name: 'Cyborg', quality: 'dark', url: 'assets/css/themes/bootstrap-cyborg.min.css' }
]
const profile = {
name: 'theme',
events: ['themeChanged'],
methods: ['switchTheme', 'getThemes', 'currentTheme', 'fixInvert'],
version: packageJson.version,
kind: 'theme'
name: 'theme',
events: ['themeChanged'],
methods: ['switchTheme', 'getThemes', 'currentTheme', 'fixInvert'],
version: packageJson.version,
kind: 'theme'
}
export class ThemeModule extends Plugin {
constructor () {
super(profile)
this.events = new EventEmitter()
this._deps = {
config: Registry.getInstance().get('config') && Registry.getInstance().get('config').api
}
this.themes = {}
themes.map((theme) => {
this.themes[theme.name.toLocaleLowerCase()] = {
...theme,
url: window.location.origin + ( window.location.pathname.startsWith('/address/') || window.location.pathname.endsWith('.sol') ? '/' : window.location.pathname ) + theme.url
}
})
this._paq = _paq
let queryTheme = (new QueryParams()).get().theme
queryTheme = queryTheme && queryTheme.toLocaleLowerCase()
queryTheme = this.themes[queryTheme] ? queryTheme : null
let currentTheme = (this._deps.config && this._deps.config.get('settings/theme')) || null
currentTheme = currentTheme && currentTheme.toLocaleLowerCase()
currentTheme = this.themes[currentTheme] ? currentTheme : null
this.currentThemeState = { queryTheme, currentTheme }
this.active = queryTheme || currentTheme || 'dark'
this.forced = !!queryTheme
constructor () {
super(profile)
this.events = new EventEmitter()
this._deps = {
config: Registry.getInstance().get('config') && Registry.getInstance().get('config').api
}
this.themes = {}
themes.map((theme) => {
this.themes[theme.name.toLocaleLowerCase()] = {
...theme,
url: window.location.origin + ( window.location.pathname.startsWith('/address/') || window.location.pathname.endsWith('.sol') ? '/' : window.location.pathname ) + theme.url
}
})
this._paq = _paq
let queryTheme = (new QueryParams()).get().theme
queryTheme = queryTheme && queryTheme.toLocaleLowerCase()
queryTheme = this.themes[queryTheme] ? queryTheme : null
let currentTheme = (this._deps.config && this._deps.config.get('settings/theme')) || null
currentTheme = currentTheme && currentTheme.toLocaleLowerCase()
currentTheme = this.themes[currentTheme] ? currentTheme : null
this.currentThemeState = { queryTheme, currentTheme }
this.active = queryTheme || currentTheme || 'dark'
this.forced = !!queryTheme
}
/** Return the active theme
/** Return the active theme
* @return {{ name: string, quality: string, url: string }} - The active theme
*/
currentTheme () {
return this.themes[this.active]
}
currentTheme () {
return this.themes[this.active]
}
/** Returns all themes as an array */
getThemes () {
return Object.keys(this.themes).map(key => this.themes[key])
}
/** Returns all themes as an array */
getThemes () {
return Object.keys(this.themes).map(key => this.themes[key])
}
/**
/**
* Init the theme
*/
initTheme (callback) { // callback is setTimeOut in app.js which is always passed
if (callback) this.initCallback = callback
if (this.active) {
document.getElementById('theme-link') ? document.getElementById('theme-link').remove():null
const nextTheme = this.themes[this.active] // Theme
document.documentElement.style.setProperty('--theme', nextTheme.quality)
initTheme (callback) { // callback is setTimeOut in app.js which is always passed
if (callback) this.initCallback = callback
if (this.active) {
document.getElementById('theme-link') ? document.getElementById('theme-link').remove():null
const nextTheme = this.themes[this.active] // Theme
document.documentElement.style.setProperty('--theme', nextTheme.quality)
const theme = document.createElement('link')
theme.setAttribute('rel', 'stylesheet')
theme.setAttribute('href', nextTheme.url)
theme.setAttribute('id', 'theme-link')
theme.addEventListener('load', () => {
if (callback) callback()
})
document.head.insertBefore(theme, document.head.firstChild)
}
const theme = document.createElement('link')
theme.setAttribute('rel', 'stylesheet')
theme.setAttribute('href', nextTheme.url)
theme.setAttribute('id', 'theme-link')
theme.addEventListener('load', () => {
if (callback) callback()
})
document.head.insertBefore(theme, document.head.firstChild)
}
}
/**
/**
* Change the current theme
* @param {string} [themeName] - The name of the theme
*/
switchTheme (themeName) {
themeName = themeName && themeName.toLocaleLowerCase()
if (themeName && !Object.keys(this.themes).includes(themeName)) {
throw new Error(`Theme ${themeName} doesn't exist`)
}
const next = themeName || this.active // Name
if (next === this.active) return // --> exit out of this method
_paq.push(['trackEvent', 'themeModule', 'switchTo', next])
const nextTheme = this.themes[next] // Theme
if (!this.forced) this._deps.config.set('settings/theme', next)
document.getElementById('theme-link') ? document.getElementById('theme-link').remove():null
const theme = document.createElement('link')
theme.setAttribute('rel', 'stylesheet')
theme.setAttribute('href', nextTheme.url)
theme.setAttribute('id', 'theme-link')
theme.addEventListener('load', () => {
this.emit('themeLoaded', nextTheme)
this.events.emit('themeLoaded', nextTheme)
})
document.head.insertBefore(theme, document.head.firstChild)
document.documentElement.style.setProperty('--theme', nextTheme.quality)
if (themeName) this.active = themeName
// TODO: Only keep `this.emit` (issue#2210)
this.emit('themeChanged', nextTheme)
this.events.emit('themeChanged', nextTheme)
switchTheme (themeName) {
themeName = themeName && themeName.toLocaleLowerCase()
if (themeName && !Object.keys(this.themes).includes(themeName)) {
throw new Error(`Theme ${themeName} doesn't exist`)
}
const next = themeName || this.active // Name
if (next === this.active) return // --> exit out of this method
_paq.push(['trackEvent', 'themeModule', 'switchTo', next])
const nextTheme = this.themes[next] // Theme
if (!this.forced) this._deps.config.set('settings/theme', next)
document.getElementById('theme-link') ? document.getElementById('theme-link').remove():null
const theme = document.createElement('link')
theme.setAttribute('rel', 'stylesheet')
theme.setAttribute('href', nextTheme.url)
theme.setAttribute('id', 'theme-link')
theme.addEventListener('load', () => {
this.emit('themeLoaded', nextTheme)
this.events.emit('themeLoaded', nextTheme)
})
document.head.insertBefore(theme, document.head.firstChild)
document.documentElement.style.setProperty('--theme', nextTheme.quality)
if (themeName) this.active = themeName
// TODO: Only keep `this.emit` (issue#2210)
this.emit('themeChanged', nextTheme)
this.events.emit('themeChanged', nextTheme)
}
/**
/**
* fixes the invertion for images since this should be adjusted when we switch between dark/light qualified themes
* @param {element} [image] - the dom element which invert should be fixed to increase visibility
*/
fixInvert (image) {
const invert = this.currentTheme().quality === 'dark' ? 1 : 0
if (image) {
image.style.filter = `invert(${invert})`
}
fixInvert (image) {
const invert = this.currentTheme().quality === 'dark' ? 1 : 0
if (image) {
image.style.filter = `invert(${invert})`
}
}
}

@ -2,83 +2,83 @@ import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json'
export const profile = {
name: 'web3Provider',
displayName: 'Global Web3 Provider',
description: 'Represent the current web3 provider used by the app at global scope',
methods: ['sendAsync'],
version: packageJson.version,
kind: 'provider'
name: 'web3Provider',
displayName: 'Global Web3 Provider',
description: 'Represent the current web3 provider used by the app at global scope',
methods: ['sendAsync'],
version: packageJson.version,
kind: 'provider'
}
export class Web3ProviderModule extends Plugin {
constructor(blockchain) {
super(profile)
this.blockchain = blockchain
}
constructor(blockchain) {
super(profile)
this.blockchain = blockchain
}
/*
/*
that is used by plugins to call the current ethereum provider.
Should be taken carefully and probably not be release as it is now.
*/
sendAsync(payload) {
sendAsync(payload) {
return new Promise((resolve, reject) => {
this.askUserPermission('sendAsync', `Calling ${payload.method} with parameters ${JSON.stringify(payload.params, null, '\t')}`).then(
async (result) => {
if (result) {
const provider = this.blockchain.web3().currentProvider
// see https://github.com/ethereum/web3.js/pull/1018/files#diff-d25786686c1053b786cc2626dc6e048675050593c0ebaafbf0814e1996f22022R129
provider[provider.sendAsync ? 'sendAsync' : 'send'](payload, async (error, message) => {
if (error) {
// Handle 'The method "debug_traceTransaction" does not exist / is not available.' error
if(error.message && error.code && error.code === -32601) {
this.call('terminal', 'log', { value: error.message, type: 'error' } )
return reject(error.message)
} else {
const errorData = error.data || error.message || error
// See: https://github.com/ethers-io/ethers.js/issues/901
if (!(typeof errorData === 'string' && errorData.includes("unknown method eth_chainId"))) this.call('terminal', 'log', { value: error.data || error.message, type: 'error' } )
return reject(errorData)
}
}
if (payload.method === 'eth_sendTransaction') {
if (payload.params.length && !payload.params[0].to && message.result) {
setTimeout(async () => {
const receipt = await this.tryTillReceiptAvailable(message.result)
if (!receipt.contractAddress) {
console.log('receipt available but contract address not present', receipt)
return
}
const contractData = await this.call('compilerArtefacts', 'getContractDataFromAddress', receipt.contractAddress)
if (contractData) this.call('udapp', 'addInstance', receipt.contractAddress, contractData.contract.abi, contractData.name)
}, 50)
}
}
resolve(message)
})
} else {
reject(new Error('User denied permission'))
return new Promise((resolve, reject) => {
this.askUserPermission('sendAsync', `Calling ${payload.method} with parameters ${JSON.stringify(payload.params, null, '\t')}`).then(
async (result) => {
if (result) {
const provider = this.blockchain.web3().currentProvider
// see https://github.com/ethereum/web3.js/pull/1018/files#diff-d25786686c1053b786cc2626dc6e048675050593c0ebaafbf0814e1996f22022R129
provider[provider.sendAsync ? 'sendAsync' : 'send'](payload, async (error, message) => {
if (error) {
// Handle 'The method "debug_traceTransaction" does not exist / is not available.' error
if(error.message && error.code && error.code === -32601) {
this.call('terminal', 'log', { value: error.message, type: 'error' } )
return reject(error.message)
} else {
const errorData = error.data || error.message || error
// See: https://github.com/ethers-io/ethers.js/issues/901
if (!(typeof errorData === 'string' && errorData.includes("unknown method eth_chainId"))) this.call('terminal', 'log', { value: error.data || error.message, type: 'error' } )
return reject(errorData)
}
}
if (payload.method === 'eth_sendTransaction') {
if (payload.params.length && !payload.params[0].to && message.result) {
setTimeout(async () => {
const receipt = await this.tryTillReceiptAvailable(message.result)
if (!receipt.contractAddress) {
console.log('receipt available but contract address not present', receipt)
return
}
}).catch((e) => {
reject(e)
const contractData = await this.call('compilerArtefacts', 'getContractDataFromAddress', receipt.contractAddress)
if (contractData) this.call('udapp', 'addInstance', receipt.contractAddress, contractData.contract.abi, contractData.name)
}, 50)
}
}
resolve(message)
})
})
}
} else {
reject(new Error('User denied permission'))
}
}).catch((e) => {
reject(e)
})
})
}
async tryTillReceiptAvailable(txhash) {
try {
const receipt = await this.call('blockchain', 'getTransactionReceipt', txhash)
if (receipt) return receipt
} catch (e) {
// do nothing
}
await this.pause()
return await this.tryTillReceiptAvailable(txhash)
async tryTillReceiptAvailable(txhash) {
try {
const receipt = await this.call('blockchain', 'getTransactionReceipt', txhash)
if (receipt) return receipt
} catch (e) {
// do nothing
}
await this.pause()
return await this.tryTillReceiptAvailable(txhash)
}
async pause() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 500)
})
}
async pause() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 500)
})
}
}

@ -4,37 +4,37 @@ var remixLib = require('@remix-project/remix-lib')
var EventsDecoder = remixLib.execution.EventsDecoder
export function makeUdapp (blockchain, compilersArtefacts, logHtmlCallback) {
// ----------------- Tx listener -----------------
const _transactionReceipts = {}
const transactionReceiptResolver = (tx, cb) => {
if (_transactionReceipts[tx.hash]) {
return cb(null, _transactionReceipts[tx.hash])
}
blockchain.web3().eth.getTransactionReceipt(tx.hash, (error, receipt) => {
if (error) {
return cb(error)
}
_transactionReceipts[tx.hash] = receipt
cb(null, receipt)
})
// ----------------- Tx listener -----------------
const _transactionReceipts = {}
const transactionReceiptResolver = (tx, cb) => {
if (_transactionReceipts[tx.hash]) {
return cb(null, _transactionReceipts[tx.hash])
}
const txlistener = blockchain.getTxListener({
api: {
contracts: function () {
if (compilersArtefacts.__last) return compilersArtefacts.getAllContractDatas()
return null
},
resolveReceipt: transactionReceiptResolver
}
blockchain.web3().eth.getTransactionReceipt(tx.hash, (error, receipt) => {
if (error) {
return cb(error)
}
_transactionReceipts[tx.hash] = receipt
cb(null, receipt)
})
}
const txlistener = blockchain.getTxListener({
api: {
contracts: function () {
if (compilersArtefacts.__last) return compilersArtefacts.getAllContractDatas()
return null
},
resolveReceipt: transactionReceiptResolver
}
})
Registry.getInstance().put({ api: txlistener, name: 'txlistener' })
blockchain.startListening(txlistener)
Registry.getInstance().put({ api: txlistener, name: 'txlistener' })
blockchain.startListening(txlistener)
const eventsDecoder = new EventsDecoder({
resolveReceipt: transactionReceiptResolver
})
txlistener.startListening()
Registry.getInstance().put({ api: eventsDecoder, name: 'eventsDecoder' })
const eventsDecoder = new EventsDecoder({
resolveReceipt: transactionReceiptResolver
})
txlistener.startListening()
Registry.getInstance().put({ api: eventsDecoder, name: 'eventsDecoder' })
}

@ -9,183 +9,183 @@ const Recorder = require('../tabs/runTab/model/recorder.js')
const _paq = window._paq = window._paq || []
const profile = {
name: 'udapp',
displayName: 'Deploy & run transactions',
icon: 'assets/img/deployAndRun.webp',
description: 'Execute, save and replay transactions',
kind: 'udapp',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/run.html',
version: packageJson.version,
maintainedBy: 'Remix',
permission: true,
events: ['newTransaction'],
methods: ['createVMAccount', 'sendTransaction', 'getAccounts', 'pendingTransactionsCount', 'getSettings', 'setEnvironmentMode', 'clearAllInstances', 'addInstance', 'resolveContractAndAddInstance']
name: 'udapp',
displayName: 'Deploy & run transactions',
icon: 'assets/img/deployAndRun.webp',
description: 'Execute, save and replay transactions',
kind: 'udapp',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/run.html',
version: packageJson.version,
maintainedBy: 'Remix',
permission: true,
events: ['newTransaction'],
methods: ['createVMAccount', 'sendTransaction', 'getAccounts', 'pendingTransactionsCount', 'getSettings', 'setEnvironmentMode', 'clearAllInstances', 'addInstance', 'resolveContractAndAddInstance']
}
export class RunTab extends ViewPlugin {
constructor (blockchain, config, fileManager, editor, filePanel, compilersArtefacts, networkModule, fileProvider) {
super(profile)
this.event = new EventManager()
this.config = config
this.blockchain = blockchain
this.fileManager = fileManager
this.editor = editor
this.filePanel = filePanel
this.compilersArtefacts = compilersArtefacts
this.networkModule = networkModule
this.fileProvider = fileProvider
this.recorder = new Recorder(blockchain)
this.REACT_API = {}
this.setupEvents()
this.el = document.createElement('div')
}
setupEvents () {
this.blockchain.events.on('newTransaction', (tx, receipt) => {
this.emit('newTransaction', tx, receipt)
})
}
getSettings () {
return new Promise((resolve, reject) => {
resolve({
selectedAccount: this.REACT_API.accounts.selectedAccount,
selectedEnvMode: this.REACT_API.selectExEnv,
networkEnvironment: this.REACT_API.networkName
})
})
}
constructor (blockchain, config, fileManager, editor, filePanel, compilersArtefacts, networkModule, fileProvider) {
super(profile)
this.event = new EventManager()
this.config = config
this.blockchain = blockchain
this.fileManager = fileManager
this.editor = editor
this.filePanel = filePanel
this.compilersArtefacts = compilersArtefacts
this.networkModule = networkModule
this.fileProvider = fileProvider
this.recorder = new Recorder(blockchain)
this.REACT_API = {}
this.setupEvents()
this.el = document.createElement('div')
}
setupEvents () {
this.blockchain.events.on('newTransaction', (tx, receipt) => {
this.emit('newTransaction', tx, receipt)
})
}
getSettings () {
return new Promise((resolve, reject) => {
resolve({
selectedAccount: this.REACT_API.accounts.selectedAccount,
selectedEnvMode: this.REACT_API.selectExEnv,
networkEnvironment: this.REACT_API.networkName
})
})
}
async setEnvironmentMode (env) {
const canCall = await this.askUserPermission('setEnvironmentMode', 'change the environment used')
if (canCall) {
env = typeof env === 'string' ? { context: env } : env
this.emit('setEnvironmentModeReducer', env, this.currentRequest.from)
}
}
clearAllInstances () {
this.emit('clearAllInstancesReducer')
}
addInstance (address, abi, name) {
this.emit('addInstanceReducer', address, abi, name)
}
createVMAccount (newAccount) {
return this.blockchain.createVMAccount(newAccount)
}
sendTransaction (tx) {
_paq.push(['trackEvent', 'udapp', 'sendTx', 'udappTransaction'])
return this.blockchain.sendTransaction(tx)
}
getAccounts (cb) {
return this.blockchain.getAccounts(cb)
}
pendingTransactionsCount () {
return this.blockchain.pendingTransactionsCount()
}
render () {
return <div><RunTabUI plugin={this} /></div>
}
onReady (api) {
this.REACT_API = api
}
async onInitDone () {
const udapp = this // eslint-disable-line
async setEnvironmentMode (env) {
const canCall = await this.askUserPermission('setEnvironmentMode', 'change the environment used')
if (canCall) {
env = typeof env === 'string' ? { context: env } : env
this.emit('setEnvironmentModeReducer', env, this.currentRequest.from)
const addProvider = async (name, displayName, isInjected, isVM, fork = '', dataId = '', title = '') => {
await this.call('blockchain', 'addProvider', {
options: {},
dataId,
name,
displayName,
fork,
isInjected,
isVM,
title,
init: async function () {
const options = await udapp.call(name, 'init')
if (options) {
this.options = options
if (options['fork']) this.fork = options['fork']
}
},
provider: {
async sendAsync (payload, callback) {
try {
const result = await udapp.call(name, 'sendAsync', payload)
callback(null, result)
} catch (e) {
callback(e)
}
}
}
})
}
clearAllInstances () {
this.emit('clearAllInstancesReducer')
}
addInstance (address, abi, name) {
this.emit('addInstanceReducer', address, abi, name)
}
createVMAccount (newAccount) {
return this.blockchain.createVMAccount(newAccount)
}
sendTransaction (tx) {
_paq.push(['trackEvent', 'udapp', 'sendTx', 'udappTransaction'])
return this.blockchain.sendTransaction(tx)
}
getAccounts (cb) {
return this.blockchain.getAccounts(cb)
}
pendingTransactionsCount () {
return this.blockchain.pendingTransactionsCount()
}
render () {
return <div><RunTabUI plugin={this} /></div>
// basic injected
// if it's the trust wallet provider, we have a specific provider for that, see below
if (window && window.ethereum && !(window.ethereum.isTrustWallet || window.ethereum.selectedProvider?.isTrustWallet)) {
const displayNameInjected = `Injected Provider${(window && window.ethereum && !(window.ethereum.providers && !window.ethereum.selectedProvider)) ?
window.ethereum.isCoinbaseWallet || window.ethereum.selectedProvider?.isCoinbaseWallet ? ' - Coinbase' :
window.ethereum.isBraveWallet || window.ethereum.selectedProvider?.isBraveWallet ? ' - Brave' :
window.ethereum.isMetaMask || window.ethereum.selectedProvider?.isMetaMask ? ' - MetaMask' : '' : ''}`
await addProvider('injected', displayNameInjected, true, false)
} else if (window && !window.ethereum) {
// we still add "injected" if there's no provider (just so it's visible to the user).
await addProvider('injected', 'Injected Provider', true, false)
}
onReady (api) {
this.REACT_API = api
if (window && window.trustwallet) {
const displayNameInjected = `Injected Provider - TrustWallet`
await addProvider('injected-trustwallet', displayNameInjected, true, false)
}
async onInitDone () {
const udapp = this // eslint-disable-line
const addProvider = async (name, displayName, isInjected, isVM, fork = '', dataId = '', title = '') => {
await this.call('blockchain', 'addProvider', {
options: {},
dataId,
name,
displayName,
fork,
isInjected,
isVM,
title,
init: async function () {
const options = await udapp.call(name, 'init')
if (options) {
this.options = options
if (options['fork']) this.fork = options['fork']
}
},
provider: {
async sendAsync (payload, callback) {
try {
const result = await udapp.call(name, 'sendAsync', payload)
callback(null, result)
} catch (e) {
callback(e)
}
}
}
})
}
// basic injected
// if it's the trust wallet provider, we have a specific provider for that, see below
if (window && window.ethereum && !(window.ethereum.isTrustWallet || window.ethereum.selectedProvider?.isTrustWallet)) {
const displayNameInjected = `Injected Provider${(window && window.ethereum && !(window.ethereum.providers && !window.ethereum.selectedProvider)) ?
window.ethereum.isCoinbaseWallet || window.ethereum.selectedProvider?.isCoinbaseWallet ? ' - Coinbase' :
window.ethereum.isBraveWallet || window.ethereum.selectedProvider?.isBraveWallet ? ' - Brave' :
window.ethereum.isMetaMask || window.ethereum.selectedProvider?.isMetaMask ? ' - MetaMask' : '' : ''}`
await addProvider('injected', displayNameInjected, true, false)
} else if (window && !window.ethereum) {
// we still add "injected" if there's no provider (just so it's visible to the user).
await addProvider('injected', 'Injected Provider', true, false)
}
if (window && window.trustwallet) {
const displayNameInjected = `Injected Provider - TrustWallet`
await addProvider('injected-trustwallet', displayNameInjected, true, false)
}
// VM
const titleVM = 'Execution environment is local to Remix. Data is only saved to browser memory and will vanish upon reload.'
await addProvider('vm-shanghai', 'Remix VM (Shanghai)', false, true, 'shanghai', 'settingsVMShanghaiMode', titleVM)
await addProvider('vm-merge', 'Remix VM (Merge)', false, true, 'merge', 'settingsVMMergeMode', titleVM)
await addProvider('vm-london', 'Remix VM (London)', false, true, 'london', 'settingsVMLondonMode', titleVM)
await addProvider('vm-berlin', 'Remix VM (Berlin)', false, true, 'berlin', 'settingsVMBerlinMode', titleVM)
await addProvider('vm-mainnet-fork', 'Remix VM - Mainnet fork', false, true, 'merge', 'settingsVMMainnetMode', titleVM)
await addProvider('vm-sepolia-fork', 'Remix VM - Sepolia fork', false, true, 'merge', 'settingsVMSepoliaMode', titleVM)
await addProvider('vm-goerli-fork', 'Remix VM - Goerli fork', false, true, 'merge', 'settingsVMGoerliMode', titleVM)
await addProvider('vm-custom-fork', 'Remix VM - Custom fork', false, true, '', 'settingsVMCustomMode', titleVM)
// wallet connect
await addProvider('walletconnect', 'WalletConnect', false, false)
// external provider
await addProvider('basic-http-provider', 'Custom - External Http Provider', false, false)
await addProvider('hardhat-provider', 'Dev - Hardhat Provider', false, false)
await addProvider('ganache-provider', 'Dev - Ganache Provider', false, false)
await addProvider('foundry-provider', 'Dev - Foundry Provider', false, false)
// VM
const titleVM = 'Execution environment is local to Remix. Data is only saved to browser memory and will vanish upon reload.'
await addProvider('vm-shanghai', 'Remix VM (Shanghai)', false, true, 'shanghai', 'settingsVMShanghaiMode', titleVM)
await addProvider('vm-merge', 'Remix VM (Merge)', false, true, 'merge', 'settingsVMMergeMode', titleVM)
await addProvider('vm-london', 'Remix VM (London)', false, true, 'london', 'settingsVMLondonMode', titleVM)
await addProvider('vm-berlin', 'Remix VM (Berlin)', false, true, 'berlin', 'settingsVMBerlinMode', titleVM)
await addProvider('vm-mainnet-fork', 'Remix VM - Mainnet fork', false, true, 'merge', 'settingsVMMainnetMode', titleVM)
await addProvider('vm-sepolia-fork', 'Remix VM - Sepolia fork', false, true, 'merge', 'settingsVMSepoliaMode', titleVM)
await addProvider('vm-goerli-fork', 'Remix VM - Goerli fork', false, true, 'merge', 'settingsVMGoerliMode', titleVM)
await addProvider('vm-custom-fork', 'Remix VM - Custom fork', false, true, '', 'settingsVMCustomMode', titleVM)
// wallet connect
await addProvider('walletconnect', 'WalletConnect', false, false)
// external provider
await addProvider('basic-http-provider', 'Custom - External Http Provider', false, false)
await addProvider('hardhat-provider', 'Dev - Hardhat Provider', false, false)
await addProvider('ganache-provider', 'Dev - Ganache Provider', false, false)
await addProvider('foundry-provider', 'Dev - Foundry Provider', false, false)
// injected provider
await addProvider('injected-optimism-provider', 'L2 - Optimism Provider', true, false)
await addProvider('injected-arbitrum-one-provider', 'L2 - Arbitrum One Provider', true, false)
}
// injected provider
await addProvider('injected-optimism-provider', 'L2 - Optimism Provider', true, false)
await addProvider('injected-arbitrum-one-provider', 'L2 - Arbitrum One Provider', true, false)
}
writeFile (fileName, content) {
return this.call('fileManager', 'writeFile', fileName, content)
}
writeFile (fileName, content) {
return this.call('fileManager', 'writeFile', fileName, content)
}
readFile (fileName) {
return this.call('fileManager', 'readFile', fileName)
}
readFile (fileName) {
return this.call('fileManager', 'readFile', fileName)
}
resolveContractAndAddInstance (contractObject, address) {
const data = this.compilersArtefacts.getCompilerAbstract(contractObject.contract.file)
resolveContractAndAddInstance (contractObject, address) {
const data = this.compilersArtefacts.getCompilerAbstract(contractObject.contract.file)
this.compilersArtefacts.addResolvedContract(addressToString(address), data)
this.addInstance(address, contractObject.abi, contractObject.name)
}
this.compilersArtefacts.addResolvedContract(addressToString(address), data)
this.addInstance(address, contractObject.abi, contractObject.name)
}
}

@ -5,33 +5,33 @@ import { ViewPlugin } from '@remixproject/engine-web'
import { RemixUiHomeTab } from '@remix-ui/home-tab' // eslint-disable-line
const profile = {
name: 'home',
displayName: 'Home',
methods: [],
events: [],
description: 'Remix Home',
icon: 'assets/img/home.webp',
location: 'mainPanel',
version: packageJson.version
name: 'home',
displayName: 'Home',
methods: [],
events: [],
description: 'Remix Home',
icon: 'assets/img/home.webp',
location: 'mainPanel',
version: packageJson.version
}
export class LandingPage extends ViewPlugin {
constructor (appManager, verticalIcons, fileManager, filePanel, contentImport) {
super(profile)
this.profile = profile
this.fileManager = fileManager
this.filePanel = filePanel
this.contentImport = contentImport
this.appManager = appManager
this.verticalIcons = verticalIcons
this.el = document.createElement('div')
this.el.setAttribute('id', 'landingPageHomeContainer')
this.el.setAttribute('class', 'remixui_homeContainer justify-content-between bg-light d-flex')
this.el.setAttribute('data-id', 'landingPageHomeContainer')
}
constructor (appManager, verticalIcons, fileManager, filePanel, contentImport) {
super(profile)
this.profile = profile
this.fileManager = fileManager
this.filePanel = filePanel
this.contentImport = contentImport
this.appManager = appManager
this.verticalIcons = verticalIcons
this.el = document.createElement('div')
this.el.setAttribute('id', 'landingPageHomeContainer')
this.el.setAttribute('class', 'remixui_homeContainer justify-content-between bg-light d-flex')
this.el.setAttribute('data-id', 'landingPageHomeContainer')
}
render () {
return <div id='landingPageHomeContainer' className='remixui_homeContainer justify-content-between bg-light d-flex' data-id='landingPageHomeContainer'>
<RemixUiHomeTab plugin={this} />
</div>
}
render () {
return <div id='landingPageHomeContainer' className='remixui_homeContainer justify-content-between bg-light d-flex' data-id='landingPageHomeContainer'>
<RemixUiHomeTab plugin={this} />
</div>
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

@ -8,188 +8,188 @@ const _paq = window._paq = window._paq || []
let web3
if (typeof window !== 'undefined' && typeof window.ethereum !== 'undefined') {
var injectedProvider = window.ethereum
web3 = new Web3(injectedProvider)
var injectedProvider = window.ethereum
web3 = new Web3(injectedProvider)
} else {
web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
}
/*
trigger contextChanged, web3EndpointChanged
*/
export class ExecutionContext {
constructor () {
this.event = new EventManager()
this.executionContext = 'vm-shanghai'
this.lastBlock = null
this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault
this.currentFork = 'shanghai'
this.mainNetGenesisHash = '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3'
this.customNetWorks = {}
this.blocks = {}
this.latestBlockNumber = 0
this.txs = {}
this.customWeb3 = {} // mapping between a context name and a web3.js instance
}
init (config) {
this.executionContext = 'vm-shanghai'
this.event.trigger('contextChanged', [this.executionContext])
}
getProvider () {
return this.executionContext
}
getProviderObject () {
return this.customNetWorks[this.executionContext]
}
getSelectedAddress () {
return injectedProvider ? injectedProvider.selectedAddress : null
}
getCurrentFork () {
return this.currentFork
}
isVM () {
return this.executionContext.startsWith('vm')
}
setWeb3 (context, web3) {
this.customWeb3[context] = web3
}
web3 () {
if (this.customWeb3[this.executionContext]) return this.customWeb3[this.executionContext]
return web3
}
detectNetwork (callback) {
if (this.isVM()) {
callback(null, { id: '-', name: 'VM' })
constructor () {
this.event = new EventManager()
this.executionContext = 'vm-shanghai'
this.lastBlock = null
this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault
this.currentFork = 'shanghai'
this.mainNetGenesisHash = '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3'
this.customNetWorks = {}
this.blocks = {}
this.latestBlockNumber = 0
this.txs = {}
this.customWeb3 = {} // mapping between a context name and a web3.js instance
}
init (config) {
this.executionContext = 'vm-shanghai'
this.event.trigger('contextChanged', [this.executionContext])
}
getProvider () {
return this.executionContext
}
getProviderObject () {
return this.customNetWorks[this.executionContext]
}
getSelectedAddress () {
return injectedProvider ? injectedProvider.selectedAddress : null
}
getCurrentFork () {
return this.currentFork
}
isVM () {
return this.executionContext.startsWith('vm')
}
setWeb3 (context, web3) {
this.customWeb3[context] = web3
}
web3 () {
if (this.customWeb3[this.executionContext]) return this.customWeb3[this.executionContext]
return web3
}
detectNetwork (callback) {
if (this.isVM()) {
callback(null, { id: '-', name: 'VM' })
} else {
if (!web3.currentProvider) {
return callback('No provider set')
}
web3.eth.net.getId((err, id) => {
let name = null
if (err) name = 'Unknown'
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
else if (id === 1) name = 'Main'
else if (id === 3) name = 'Ropsten'
else if (id === 4) name = 'Rinkeby'
else if (id === 5) name = 'Goerli'
else if (id === 42) name = 'Kovan'
else if (id === 11155111) name = 'Sepolia'
else name = 'Custom'
if (id === '1') {
web3.eth.getBlock(0, (error, block) => {
if (error) console.log('cant query first block')
if (block && block.hash !== this.mainNetGenesisHash) name = 'Custom'
callback(err, { id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
})
} else {
if (!web3.currentProvider) {
return callback('No provider set')
}
web3.eth.net.getId((err, id) => {
let name = null
if (err) name = 'Unknown'
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
else if (id === 1) name = 'Main'
else if (id === 3) name = 'Ropsten'
else if (id === 4) name = 'Rinkeby'
else if (id === 5) name = 'Goerli'
else if (id === 42) name = 'Kovan'
else if (id === 11155111) name = 'Sepolia'
else name = 'Custom'
if (id === '1') {
web3.eth.getBlock(0, (error, block) => {
if (error) console.log('cant query first block')
if (block && block.hash !== this.mainNetGenesisHash) name = 'Custom'
callback(err, { id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
})
} else {
callback(err, { id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
}
})
callback(err, { id, name, lastBlock: this.lastBlock, currentFork: this.currentFork })
}
})
}
}
removeProvider (name) {
if (name && this.customNetWorks[name]) {
if (this.executionContext === name) this.setContext('vm-merge', null, null, null)
delete this.customNetWorks[name]
this.event.trigger('removeProvider', [name])
}
removeProvider (name) {
if (name && this.customNetWorks[name]) {
if (this.executionContext === name) this.setContext('vm-merge', null, null, null)
delete this.customNetWorks[name]
this.event.trigger('removeProvider', [name])
}
}
addProvider (network) {
if (network && network.name && !this.customNetWorks[network.name]) {
this.customNetWorks[network.name] = network
this.event.trigger('addProvider', [network])
}
addProvider (network) {
if (network && network.name && !this.customNetWorks[network.name]) {
this.customNetWorks[network.name] = network
this.event.trigger('addProvider', [network])
}
}
internalWeb3 () {
return web3
}
internalWeb3 () {
return web3
}
setContext (context, endPointUrl, confirmCb, infoCb) {
this.executionContext = context
this.executionContextChange(context, endPointUrl, confirmCb, infoCb, null)
}
async executionContextChange (value, endPointUrl, confirmCb, infoCb, cb) {
_paq.push(['trackEvent', 'udapp', 'providerChanged', value.context])
const context = value.context
if (!cb) cb = () => { /* Do nothing. */ }
if (!confirmCb) confirmCb = () => { /* Do nothing. */ }
if (!infoCb) infoCb = () => { /* Do nothing. */ }
if (this.customNetWorks[context]) {
var network = this.customNetWorks[context]
await network.init()
this.currentFork = network.fork
this.executionContext = context
// injected
web3.setProvider(network.provider)
await this._updateChainContext()
this.event.trigger('contextChanged', [context])
cb()
setContext (context, endPointUrl, confirmCb, infoCb) {
this.executionContext = context
this.executionContextChange(context, endPointUrl, confirmCb, infoCb, null)
}
async executionContextChange (value, endPointUrl, confirmCb, infoCb, cb) {
_paq.push(['trackEvent', 'udapp', 'providerChanged', value.context])
const context = value.context
if (!cb) cb = () => { /* Do nothing. */ }
if (!confirmCb) confirmCb = () => { /* Do nothing. */ }
if (!infoCb) infoCb = () => { /* Do nothing. */ }
if (this.customNetWorks[context]) {
var network = this.customNetWorks[context]
await network.init()
this.currentFork = network.fork
this.executionContext = context
// injected
web3.setProvider(network.provider)
await this._updateChainContext()
this.event.trigger('contextChanged', [context])
cb()
}
}
currentblockGasLimit () {
return this.blockGasLimit
}
stopListenOnLastBlock () {
if (this.listenOnLastBlockId) clearInterval(this.listenOnLastBlockId)
this.listenOnLastBlockId = null
}
async _updateChainContext () {
if (!this.isVM()) {
try {
const block = await web3.eth.getBlock('latest')
// we can't use the blockGasLimit cause the next blocks could have a lower limit : https://github.com/ethereum/remix/issues/506
this.blockGasLimit = (block && block.gasLimit) ? Math.floor(block.gasLimit - (5 * block.gasLimit) / 1024) : this.blockGasLimitDefault
this.lastBlock = block
try {
this.currentFork = execution.forkAt(await web3.eth.net.getId(), block.number)
} catch (e) {
this.currentFork = 'merge'
console.log(`unable to detect fork, defaulting to ${this.currentFork}..`)
console.error(e)
}
} catch (e) {
console.error(e)
this.blockGasLimit = this.blockGasLimitDefault
}
}
}
currentblockGasLimit () {
return this.blockGasLimit
}
stopListenOnLastBlock () {
if (this.listenOnLastBlockId) clearInterval(this.listenOnLastBlockId)
this.listenOnLastBlockId = null
}
async _updateChainContext () {
if (!this.isVM()) {
try {
const block = await web3.eth.getBlock('latest')
// we can't use the blockGasLimit cause the next blocks could have a lower limit : https://github.com/ethereum/remix/issues/506
this.blockGasLimit = (block && block.gasLimit) ? Math.floor(block.gasLimit - (5 * block.gasLimit) / 1024) : this.blockGasLimitDefault
this.lastBlock = block
try {
this.currentFork = execution.forkAt(await web3.eth.net.getId(), block.number)
} catch (e) {
this.currentFork = 'merge'
console.log(`unable to detect fork, defaulting to ${this.currentFork}..`)
console.error(e)
}
} catch (e) {
console.error(e)
this.blockGasLimit = this.blockGasLimitDefault
}
}
}
listenOnLastBlock () {
this.listenOnLastBlockId = setInterval(() => {
this._updateChainContext()
}, 15000)
}
listenOnLastBlock () {
this.listenOnLastBlockId = setInterval(() => {
this._updateChainContext()
}, 15000)
txDetailsLink (network, hash) {
const transactionDetailsLinks = {
Main: 'https://www.etherscan.io/tx/',
Rinkeby: 'https://rinkeby.etherscan.io/tx/',
Ropsten: 'https://ropsten.etherscan.io/tx/',
Sepolia: 'https://sepolia.etherscan.io/tx/',
Kovan: 'https://kovan.etherscan.io/tx/',
Goerli: 'https://goerli.etherscan.io/tx/'
}
txDetailsLink (network, hash) {
const transactionDetailsLinks = {
Main: 'https://www.etherscan.io/tx/',
Rinkeby: 'https://rinkeby.etherscan.io/tx/',
Ropsten: 'https://ropsten.etherscan.io/tx/',
Sepolia: 'https://sepolia.etherscan.io/tx/',
Kovan: 'https://kovan.etherscan.io/tx/',
Goerli: 'https://goerli.etherscan.io/tx/'
}
if (transactionDetailsLinks[network]) {
return transactionDetailsLinks[network] + hash
}
if (transactionDetailsLinks[network]) {
return transactionDetailsLinks[network] + hash
}
}
}

@ -1,14 +1,14 @@
const transactionDetailsLinks = {
Main: 'https://www.etherscan.io/tx/',
Rinkeby: 'https://rinkeby.etherscan.io/tx/',
Ropsten: 'https://ropsten.etherscan.io/tx/',
Kovan: 'https://kovan.etherscan.io/tx/',
Goerli: 'https://goerli.etherscan.io/tx/',
Sepolia: 'https://sepolia.etherscan.io/tx/'
Main: 'https://www.etherscan.io/tx/',
Rinkeby: 'https://rinkeby.etherscan.io/tx/',
Ropsten: 'https://ropsten.etherscan.io/tx/',
Kovan: 'https://kovan.etherscan.io/tx/',
Goerli: 'https://goerli.etherscan.io/tx/',
Sepolia: 'https://sepolia.etherscan.io/tx/'
}
export function etherScanLink (network: string, hash: string): string {
if (transactionDetailsLinks[network]) {
return transactionDetailsLinks[network] + hash
}
if (transactionDetailsLinks[network]) {
return transactionDetailsLinks[network] + hash
}
}

@ -3,47 +3,47 @@ import { hashPersonalMessage } from '@ethereumjs/util'
import { ExecutionContext } from '../execution-context'
export class InjectedProvider {
executionContext: ExecutionContext
constructor (executionContext) {
this.executionContext = executionContext
}
getAccounts (cb) {
return this.executionContext.web3().eth.getAccounts(cb)
}
newAccount (passwordPromptCb, cb) {
passwordPromptCb((passphrase) => {
this.executionContext.web3().eth.personal.newAccount(passphrase, cb)
})
}
async resetEnvironment () {
/* Do nothing. */
}
async getBalanceInEther (address) {
const balance = await this.executionContext.web3().eth.getBalance(address)
return Web3.utils.fromWei(balance.toString(10), 'ether')
}
getGasPrice (cb) {
this.executionContext.web3().eth.getGasPrice(cb)
}
signMessage (message, account, _passphrase, cb) {
const messageHash = hashPersonalMessage(Buffer.from(message))
try {
this.executionContext.web3().eth.personal.sign(message, account, (error, signedData) => {
cb(error, '0x' + messageHash.toString('hex'), signedData)
})
} catch (e) {
cb(e.message)
}
}
getProvider () {
return 'injected'
}
executionContext: ExecutionContext
constructor (executionContext) {
this.executionContext = executionContext
}
getAccounts (cb) {
return this.executionContext.web3().eth.getAccounts(cb)
}
newAccount (passwordPromptCb, cb) {
passwordPromptCb((passphrase) => {
this.executionContext.web3().eth.personal.newAccount(passphrase, cb)
})
}
async resetEnvironment () {
/* Do nothing. */
}
async getBalanceInEther (address) {
const balance = await this.executionContext.web3().eth.getBalance(address)
return Web3.utils.fromWei(balance.toString(10), 'ether')
}
getGasPrice (cb) {
this.executionContext.web3().eth.getGasPrice(cb)
}
signMessage (message, account, _passphrase, cb) {
const messageHash = hashPersonalMessage(Buffer.from(message))
try {
this.executionContext.web3().eth.personal.sign(message, account, (error, signedData) => {
cb(error, '0x' + messageHash.toString('hex'), signedData)
})
} catch (e) {
cb(e.message)
}
}
getProvider () {
return 'injected'
}
}

@ -5,56 +5,56 @@ import { ExecutionContext } from '../execution-context'
import Config from '../../config'
export class NodeProvider {
executionContext: ExecutionContext
config: Config
constructor (executionContext: ExecutionContext, config: Config) {
this.executionContext = executionContext
this.config = config
}
getAccounts (cb) {
if (this.config.get('settings/personal-mode')) {
return this.executionContext.web3().eth.personal.getAccounts(cb)
}
return this.executionContext.web3().eth.getAccounts(cb)
}
newAccount (passwordPromptCb, cb) {
if (!this.config.get('settings/personal-mode')) {
return cb('Not running in personal mode')
}
passwordPromptCb((passphrase) => {
this.executionContext.web3().eth.personal.newAccount(passphrase, cb)
})
}
async resetEnvironment () {
/* Do nothing. */
}
async getBalanceInEther (address) {
const balance = await this.executionContext.web3().eth.getBalance(address)
return Web3.utils.fromWei(balance.toString(10), 'ether')
}
getGasPrice (cb) {
this.executionContext.web3().eth.getGasPrice(cb)
}
signMessage (message, account, passphrase, cb) {
const messageHash = hashPersonalMessage(Buffer.from(message))
try {
const personal = new Personal(this.executionContext.web3().currentProvider)
personal.sign(message, account, passphrase, (error, signedData) => {
cb(error, '0x' + messageHash.toString('hex'), signedData)
})
} catch (e) {
cb(e.message)
}
}
getProvider () {
return this.executionContext.getProvider()
}
executionContext: ExecutionContext
config: Config
constructor (executionContext: ExecutionContext, config: Config) {
this.executionContext = executionContext
this.config = config
}
getAccounts (cb) {
if (this.config.get('settings/personal-mode')) {
return this.executionContext.web3().eth.personal.getAccounts(cb)
}
return this.executionContext.web3().eth.getAccounts(cb)
}
newAccount (passwordPromptCb, cb) {
if (!this.config.get('settings/personal-mode')) {
return cb('Not running in personal mode')
}
passwordPromptCb((passphrase) => {
this.executionContext.web3().eth.personal.newAccount(passphrase, cb)
})
}
async resetEnvironment () {
/* Do nothing. */
}
async getBalanceInEther (address) {
const balance = await this.executionContext.web3().eth.getBalance(address)
return Web3.utils.fromWei(balance.toString(10), 'ether')
}
getGasPrice (cb) {
this.executionContext.web3().eth.getGasPrice(cb)
}
signMessage (message, account, passphrase, cb) {
const messageHash = hashPersonalMessage(Buffer.from(message))
try {
const personal = new Personal(this.executionContext.web3().currentProvider)
personal.sign(message, account, passphrase, (error, signedData) => {
cb(error, '0x' + messageHash.toString('hex'), signedData)
})
} catch (e) {
cb(e.message)
}
}
getProvider () {
return this.executionContext.getProvider()
}
}

@ -5,105 +5,105 @@ import { extend, JSONRPCRequestPayload, JSONRPCResponseCallback } from '@remix-p
import { ExecutionContext } from '../execution-context'
export class VMProvider {
executionContext: ExecutionContext
web3: Web3
worker: Worker
provider: {
executionContext: ExecutionContext
web3: Web3
worker: Worker
provider: {
sendAsync: (query: JSONRPCRequestPayload, callback: JSONRPCResponseCallback) => void
}
newAccountCallback: {[stamp: number]: (error: Error, address: string) => void}
newAccountCallback: {[stamp: number]: (error: Error, address: string) => void}
constructor (executionContext: ExecutionContext) {
this.executionContext = executionContext
this.worker = null
this.provider = null
this.newAccountCallback = {}
}
constructor (executionContext: ExecutionContext) {
this.executionContext = executionContext
this.worker = null
this.provider = null
this.newAccountCallback = {}
}
getAccounts (cb) {
this.web3.eth.getAccounts((err, accounts) => {
if (err) {
return cb('No accounts?')
}
return cb(null, accounts)
})
}
getAccounts (cb) {
this.web3.eth.getAccounts((err, accounts) => {
if (err) {
return cb('No accounts?')
}
return cb(null, accounts)
})
}
async resetEnvironment () {
if (this.worker) this.worker.terminate()
this.worker = new Worker(new URL('./worker-vm', import.meta.url))
const provider = this.executionContext.getProviderObject()
async resetEnvironment () {
if (this.worker) this.worker.terminate()
this.worker = new Worker(new URL('./worker-vm', import.meta.url))
const provider = this.executionContext.getProviderObject()
let incr = 0
const stamps = {}
let incr = 0
const stamps = {}
return new Promise((resolve, reject) => {
this.worker.addEventListener('message', (msg) => {
if (msg.data.cmd === 'sendAsyncResult' && stamps[msg.data.stamp]) {
stamps[msg.data.stamp](msg.data.error, msg.data.result)
} else if (msg.data.cmd === 'initiateResult') {
if (!msg.data.error) {
this.provider = {
sendAsync: (query, callback) => {
const stamp = Date.now() + incr
incr++
stamps[stamp] = callback
this.worker.postMessage({ cmd: 'sendAsync', query, stamp })
}
}
this.web3 = new Web3(this.provider)
extend(this.web3)
this.executionContext.setWeb3(this.executionContext.getProvider(), this.web3)
resolve({})
} else {
reject(new Error(msg.data.error))
}
} else if (msg.data.cmd === 'newAccountResult') {
if (this.newAccountCallback[msg.data.stamp]) {
this.newAccountCallback[msg.data.stamp](msg.data.error, msg.data.result)
delete this.newAccountCallback[msg.data.stamp]
}
}
})
this.worker.postMessage({ cmd: 'init', fork: this.executionContext.getCurrentFork(), nodeUrl: provider?.options['nodeUrl'], blockNumber: provider?.options['blockNumber']})
})
}
return new Promise((resolve, reject) => {
this.worker.addEventListener('message', (msg) => {
if (msg.data.cmd === 'sendAsyncResult' && stamps[msg.data.stamp]) {
stamps[msg.data.stamp](msg.data.error, msg.data.result)
} else if (msg.data.cmd === 'initiateResult') {
if (!msg.data.error) {
this.provider = {
sendAsync: (query, callback) => {
const stamp = Date.now() + incr
incr++
stamps[stamp] = callback
this.worker.postMessage({ cmd: 'sendAsync', query, stamp })
}
}
this.web3 = new Web3(this.provider)
extend(this.web3)
this.executionContext.setWeb3(this.executionContext.getProvider(), this.web3)
resolve({})
} else {
reject(new Error(msg.data.error))
}
} else if (msg.data.cmd === 'newAccountResult') {
if (this.newAccountCallback[msg.data.stamp]) {
this.newAccountCallback[msg.data.stamp](msg.data.error, msg.data.result)
delete this.newAccountCallback[msg.data.stamp]
}
}
})
this.worker.postMessage({ cmd: 'init', fork: this.executionContext.getCurrentFork(), nodeUrl: provider?.options['nodeUrl'], blockNumber: provider?.options['blockNumber']})
})
}
// TODO: is still here because of the plugin API
// can be removed later when we update the API
createVMAccount (newAccount) {
const { privateKey, balance } = newAccount
this.worker.postMessage({ cmd: 'addAccount', privateKey: privateKey, balance })
const privKey = Buffer.from(privateKey, 'hex')
return '0x' + privateToAddress(privKey).toString('hex')
}
// TODO: is still here because of the plugin API
// can be removed later when we update the API
createVMAccount (newAccount) {
const { privateKey, balance } = newAccount
this.worker.postMessage({ cmd: 'addAccount', privateKey: privateKey, balance })
const privKey = Buffer.from(privateKey, 'hex')
return '0x' + privateToAddress(privKey).toString('hex')
}
newAccount (_passwordPromptCb, cb) {
const stamp = Date.now()
this.newAccountCallback[stamp] = cb
this.worker.postMessage({ cmd: 'newAccount', stamp })
}
newAccount (_passwordPromptCb, cb) {
const stamp = Date.now()
this.newAccountCallback[stamp] = cb
this.worker.postMessage({ cmd: 'newAccount', stamp })
}
async getBalanceInEther (address) {
const balance = await this.web3.eth.getBalance(address)
return Web3.utils.fromWei(new BN(balance).toString(10), 'ether')
}
async getBalanceInEther (address) {
const balance = await this.web3.eth.getBalance(address)
return Web3.utils.fromWei(new BN(balance).toString(10), 'ether')
}
getGasPrice (cb) {
this.web3.eth.getGasPrice(cb)
}
getGasPrice (cb) {
this.web3.eth.getGasPrice(cb)
}
signMessage (message, account, _passphrase, cb) {
const messageHash = hashPersonalMessage(Buffer.from(message))
this.web3.eth.sign(message, account, (error, signedData) => {
if (error) {
return cb(error)
}
cb(null, '0x' + messageHash.toString('hex'), signedData)
})
}
signMessage (message, account, _passphrase, cb) {
const messageHash = hashPersonalMessage(Buffer.from(message))
this.web3.eth.sign(message, account, (error, signedData) => {
if (error) {
return cb(error)
}
cb(null, '0x' + messageHash.toString('hex'), signedData)
})
}
getProvider () {
return this.executionContext.getProvider()
}
getProvider () {
return this.executionContext.getProvider()
}
}

@ -2,76 +2,76 @@ import { Provider } from '@remix-project/remix-simulator'
let provider: Provider = null
self.onmessage = (e: MessageEvent) => {
const data = e.data
switch (data.cmd) {
case 'init':
{
provider = new Provider({ fork: data.fork, nodeUrl: data.nodeUrl, blockNumber: data.blockNumber })
provider.init().then(() => {
self.postMessage({
cmd: 'initiateResult',
stamp: data.stamp
})
}).catch((error) => {
self.postMessage({
cmd: 'initiateResult',
error,
stamp: data.stamp
})
const data = e.data
switch (data.cmd) {
case 'init':
{
provider = new Provider({ fork: data.fork, nodeUrl: data.nodeUrl, blockNumber: data.blockNumber })
provider.init().then(() => {
self.postMessage({
cmd: 'initiateResult',
stamp: data.stamp
})
}).catch((error) => {
self.postMessage({
cmd: 'initiateResult',
error,
stamp: data.stamp
})
})
break
}
case 'sendAsync':
{
if (provider) {
provider.sendAsync(data.query, (error, result) => {
self.postMessage({
cmd: 'sendAsyncResult',
error,
result,
stamp: data.stamp
})
break
})
} else {
self.postMessage({
cmd: 'sendAsyncResult',
error: 'Provider not instantiated',
result: null,
stamp: data.stamp
})
}
case 'sendAsync':
{
if (provider) {
provider.sendAsync(data.query, (error, result) => {
self.postMessage({
cmd: 'sendAsyncResult',
error,
result,
stamp: data.stamp
})
})
} else {
self.postMessage({
cmd: 'sendAsyncResult',
error: 'Provider not instantiated',
result: null,
stamp: data.stamp
})
}
break
break
}
case 'addAccount':
{
if (provider) {
provider.Accounts._addAccount(data.privateKey, data.balance)
}
case 'addAccount':
{
if (provider) {
provider.Accounts._addAccount(data.privateKey, data.balance)
}
break
break
}
case 'newAccount':
{
if (provider) {
provider.Accounts.newAccount((error, address: string) => {
if (error) {
self.postMessage({
cmd: 'newAccountResult',
error,
stamp: data.stamp
})
} else {
self.postMessage({
cmd: 'newAccountResult',
result: address,
stamp: data.stamp
})
}
})
}
case 'newAccount':
{
if (provider) {
provider.Accounts.newAccount((error, address: string) => {
if (error) {
self.postMessage({
cmd: 'newAccountResult',
error,
stamp: data.stamp
})
} else {
self.postMessage({
cmd: 'newAccountResult',
result: address,
stamp: data.stamp
})
}
})
}
break
}
}
break
}
}
}

@ -4,53 +4,53 @@ var CONFIG_FILE = '.remix.config'
const EventEmitter = require('events')
function Config(storage) {
this.items = {}
this.unpersistedItems = {}
this.events = new EventEmitter()
// load on instantiation
try {
var config = storage.get(CONFIG_FILE)
if (config) {
this.items = JSON.parse(config)
}
} catch (exception) {
/* Do nothing. */
}
this.exists = function (key) {
return this.items[key] !== undefined
}
this.get = function (key) {
return this.items[key]
this.items = {}
this.unpersistedItems = {}
this.events = new EventEmitter()
// load on instantiation
try {
var config = storage.get(CONFIG_FILE)
if (config) {
this.items = JSON.parse(config)
}
} catch (exception) {
/* Do nothing. */
}
this.set = function (key, content) {
this.items[key] = content
try {
storage.set(CONFIG_FILE, JSON.stringify(this.items))
this.events.emit('configChanged', { key, content })
this.events.emit(key + '_changed', content)
} catch (exception) {
/* Do nothing. */
}
}
this.exists = function (key) {
return this.items[key] !== undefined
}
this.clear = function () {
this.items = {}
storage.remove(CONFIG_FILE)
}
this.get = function (key) {
return this.items[key]
}
this.getUnpersistedProperty = function (key) {
return this.unpersistedItems[key]
this.set = function (key, content) {
this.items[key] = content
try {
storage.set(CONFIG_FILE, JSON.stringify(this.items))
this.events.emit('configChanged', { key, content })
this.events.emit(key + '_changed', content)
} catch (exception) {
/* Do nothing. */
}
}
// TODO: this only used for *one* property "doNotShowTransactionConfirmationAgain"
// and can be removed once it's refactored away in txRunner
this.setUnpersistedProperty = function (key, value) {
this.unpersistedItems[key] = value
}
this.clear = function () {
this.items = {}
storage.remove(CONFIG_FILE)
}
this.getUnpersistedProperty = function (key) {
return this.unpersistedItems[key]
}
// TODO: this only used for *one* property "doNotShowTransactionConfirmationAgain"
// and can be removed once it's refactored away in txRunner
this.setUnpersistedProperty = function (key, value) {
this.unpersistedItems[key] = value
}
}
module.exports = Config

@ -9,20 +9,20 @@ import Registry from './app/state/registry'
import { Storage } from '@remix-project/remix-lib'
(async function () {
try {
const configStorage = new Storage('config-v0.8:')
const config = new Config(configStorage);
Registry.getInstance().put({ api: config, name: 'config' })
} catch (e) { }
const theme = new ThemeModule()
theme.initTheme()
try {
const configStorage = new Storage('config-v0.8:')
const config = new Config(configStorage);
Registry.getInstance().put({ api: config, name: 'config' })
} catch (e) { }
const theme = new ThemeModule()
theme.initTheme()
render(
<React.StrictMode>
<Preload></Preload>
</React.StrictMode>,
document.getElementById('root')
)
render(
<React.StrictMode>
<Preload></Preload>
</React.StrictMode>,
document.getElementById('root')
)
})()

@ -2,153 +2,153 @@ var async = require('async')
import { toChecksumAddress } from '@ethereumjs/util'
export default {
shortenAddress: function (address, etherBalance) {
var len = address.length
return address.slice(0, 5) + '...' + address.slice(len - 5, len) + (etherBalance ? ' (' + etherBalance.toString() + ' ether)' : '')
},
addressToString: function (address) {
if (!address) return null
if (typeof address !== 'string') {
address = address.toString('hex')
}
if (address.indexOf('0x') === -1) {
address = '0x' + address
}
return toChecksumAddress(address)
},
shortenHexData: function (data) {
if (!data) return ''
if (data.length < 5) return data
var len = data.length
return data.slice(0, 5) + '...' + data.slice(len - 5, len)
},
createNonClashingNameWithPrefix (name, fileProvider, prefix, cb) {
if (!name) name = 'Undefined'
var counter = ''
var ext = 'sol'
var reg = /(.*)\.([^.]+)/g
var split = reg.exec(name)
if (split) {
name = split[1]
ext = split[2]
}
var exist = true
async.whilst(
() => { return exist },
(callback) => {
fileProvider.exists(name + counter + prefix + '.' + ext).then(currentExist => {
exist = currentExist
if (exist) counter = (counter | 0) + 1
callback()
}).catch(error => {
if (error) console.log(error)
})
},
(error) => { cb(error, name + counter + prefix + '.' + ext) }
)
},
createNonClashingName (name, fileProvider, cb) {
this.createNonClashingNameWithPrefix(name, fileProvider, '', cb)
},
async createNonClashingNameAsync (name, fileManager, prefix = '') {
if (!name) name = 'Undefined'
let counter = ''
let ext = 'sol'
const reg = /(.*)\.([^.]+)/g
const split = reg.exec(name)
if (split) {
name = split[1]
ext = split[2]
}
let exist = true
shortenAddress: function (address, etherBalance) {
var len = address.length
return address.slice(0, 5) + '...' + address.slice(len - 5, len) + (etherBalance ? ' (' + etherBalance.toString() + ' ether)' : '')
},
addressToString: function (address) {
if (!address) return null
if (typeof address !== 'string') {
address = address.toString('hex')
}
if (address.indexOf('0x') === -1) {
address = '0x' + address
}
return toChecksumAddress(address)
},
shortenHexData: function (data) {
if (!data) return ''
if (data.length < 5) return data
var len = data.length
return data.slice(0, 5) + '...' + data.slice(len - 5, len)
},
createNonClashingNameWithPrefix (name, fileProvider, prefix, cb) {
if (!name) name = 'Undefined'
var counter = ''
var ext = 'sol'
var reg = /(.*)\.([^.]+)/g
var split = reg.exec(name)
if (split) {
name = split[1]
ext = split[2]
}
var exist = true
async.whilst(
() => { return exist },
(callback) => {
fileProvider.exists(name + counter + prefix + '.' + ext).then(currentExist => {
exist = currentExist
if (exist) counter = (counter | 0) + 1
callback()
}).catch(error => {
if (error) console.log(error)
})
},
(error) => { cb(error, name + counter + prefix + '.' + ext) }
)
},
createNonClashingName (name, fileProvider, cb) {
this.createNonClashingNameWithPrefix(name, fileProvider, '', cb)
},
async createNonClashingNameAsync (name, fileManager, prefix = '') {
if (!name) name = 'Undefined'
let counter = ''
let ext = 'sol'
const reg = /(.*)\.([^.]+)/g
const split = reg.exec(name)
if (split) {
name = split[1]
ext = split[2]
}
let exist = true
do {
const isDuplicate = await fileManager.exists(name + counter + prefix + '.' + ext)
if (isDuplicate) counter = (counter | 0) + 1
else exist = false
} while (exist)
do {
const isDuplicate = await fileManager.exists(name + counter + prefix + '.' + ext)
if (isDuplicate) counter = (counter | 0) + 1
else exist = false
} while (exist)
return name + counter + prefix + '.' + ext
},
async createNonClashingDirNameAsync (name, fileManager) {
if (!name) name = 'Undefined'
let counter = ''
let exist = true
return name + counter + prefix + '.' + ext
},
async createNonClashingDirNameAsync (name, fileManager) {
if (!name) name = 'Undefined'
let counter = ''
let exist = true
do {
const isDuplicate = await fileManager.exists(name + counter)
do {
const isDuplicate = await fileManager.exists(name + counter)
if (isDuplicate) counter = (counter | 0) + 1
else exist = false
} while (exist)
if (isDuplicate) counter = (counter | 0) + 1
else exist = false
} while (exist)
return name + counter
},
checkSpecialChars (name) {
return name.match(/[:*?"<>\\'|]/) != null
},
checkSlash (name) {
return name.match(/\//) != null
},
isHexadecimal (value) {
return /^[0-9a-fA-F]+$/.test(value) && (value.length % 2 === 0)
},
is0XPrefixed (value) {
return value.substr(0, 2) === '0x'
},
isNumeric (value) {
return /^\+?(0|[1-9]\d*)$/.test(value)
},
isValidHash (hash) { // 0x prefixed, hexadecimal, 64digit
const hexValue = hash.slice(2, hash.length)
return this.is0XPrefixed(hash) && /^[0-9a-fA-F]{64}$/.test(hexValue)
},
removeTrailingSlashes (text) {
// Remove single or consecutive trailing slashes
return text.replace(/\/+$/g, '')
},
removeMultipleSlashes (text) {
// Replace consecutive slashes with '/'
return text.replace(/\/+/g, '/')
},
find: find,
joinPath (...paths) {
paths = paths.filter((value) => value !== '').map((path) => path.replace(/^\/|\/$/g, '')) // remove first and last slash)
if (paths.length === 1) return paths[0]
return paths.join('/')
},
extractNameFromKey (key) {
const keyPath = key.split('/')
return name + counter
},
checkSpecialChars (name) {
return name.match(/[:*?"<>\\'|]/) != null
},
checkSlash (name) {
return name.match(/\//) != null
},
isHexadecimal (value) {
return /^[0-9a-fA-F]+$/.test(value) && (value.length % 2 === 0)
},
is0XPrefixed (value) {
return value.substr(0, 2) === '0x'
},
isNumeric (value) {
return /^\+?(0|[1-9]\d*)$/.test(value)
},
isValidHash (hash) { // 0x prefixed, hexadecimal, 64digit
const hexValue = hash.slice(2, hash.length)
return this.is0XPrefixed(hash) && /^[0-9a-fA-F]{64}$/.test(hexValue)
},
removeTrailingSlashes (text) {
// Remove single or consecutive trailing slashes
return text.replace(/\/+$/g, '')
},
removeMultipleSlashes (text) {
// Replace consecutive slashes with '/'
return text.replace(/\/+/g, '/')
},
find: find,
joinPath (...paths) {
paths = paths.filter((value) => value !== '').map((path) => path.replace(/^\/|\/$/g, '')) // remove first and last slash)
if (paths.length === 1) return paths[0]
return paths.join('/')
},
extractNameFromKey (key) {
const keyPath = key.split('/')
return keyPath[keyPath.length - 1]
}
return keyPath[keyPath.length - 1]
}
}
function findDeep (object, fn, found = { break: false, value: undefined }) {
if (typeof object !== 'object' || object === null) return
for (var i in object) {
if (found.break) break
var el = object[i]
if (el && el.innerText !== undefined && el.innerText !== null) el = el.innerText
if (fn(el, i, object)) {
found.value = el
found.break = true
break
} else {
findDeep(el, fn, found)
}
if (typeof object !== 'object' || object === null) return
for (var i in object) {
if (found.break) break
var el = object[i]
if (el && el.innerText !== undefined && el.innerText !== null) el = el.innerText
if (fn(el, i, object)) {
found.value = el
found.break = true
break
} else {
findDeep(el, fn, found)
}
return found.value
}
return found.value
}
function find (args, query) {
query = query.trim()
var isMatch = !!findDeep(args, function check (value, key) {
if (value === undefined || value === null) return false
if (typeof value === 'function') return false
if (typeof value === 'object') return false
var contains = String(value).indexOf(query.trim()) !== -1
return contains
})
return isMatch
query = query.trim()
var isMatch = !!findDeep(args, function check (value, key) {
if (value === undefined || value === null) return false
if (typeof value === 'function') return false
if (typeof value === 'object') return false
var contains = String(value).indexOf(query.trim()) !== -1
return contains
})
return isMatch
}

@ -1,26 +1,26 @@
// const moduleID = require('./module-id.js')
module.exports = class registry {
constructor () {
this.state = {}
}
constructor () {
this.state = {}
}
put ({ api, name }) {
// const serveruid = moduleID() + '.' + (name || '')
if (this.state[name]) return this.state[name]
const server = {
// uid: serveruid,
api
}
this.state[name] = { server }
return server
put ({ api, name }) {
// const serveruid = moduleID() + '.' + (name || '')
if (this.state[name]) return this.state[name]
const server = {
// uid: serveruid,
api
}
this.state[name] = { server }
return server
}
get (name) {
// const clientuid = moduleID()
const state = this.state[name]
if (!state) return
const server = state.server
return server
}
get (name) {
// const clientuid = moduleID()
const state = this.state[name]
if (!state) return
const server = state.server
return server
}
}

@ -6,13 +6,13 @@ const _paq = window._paq = window._paq || []
// requiredModule removes the plugin from the plugin manager list on UI
const requiredModules = [ // services + layout views + system views
'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme', 'locale',
'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons',
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity', 'solidity-logic', 'gistHandler', 'layout',
'notification', 'permissionhandler', 'walkthrough', 'storage', 'restorebackupzip', 'link-libraries', 'deploy-libraries', 'openzeppelin-proxy',
'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected', 'injected-trustwallet', 'injected-optimism-provider', 'injected-arbitrum-one-provider', 'vm-custom-fork', 'vm-goerli-fork', 'vm-mainnet-fork', 'vm-sepolia-fork', 'vm-merge', 'vm-london', 'vm-berlin',
'vm-shanghai',
'compileAndRun', 'search', 'recorder', 'fileDecorator', 'codeParser', 'codeFormatter', 'solidityumlgen', 'contractflattener', 'solidity-script']
'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme', 'locale',
'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons',
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity', 'solidity-logic', 'gistHandler', 'layout',
'notification', 'permissionhandler', 'walkthrough', 'storage', 'restorebackupzip', 'link-libraries', 'deploy-libraries', 'openzeppelin-proxy',
'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected', 'injected-trustwallet', 'injected-optimism-provider', 'injected-arbitrum-one-provider', 'vm-custom-fork', 'vm-goerli-fork', 'vm-mainnet-fork', 'vm-sepolia-fork', 'vm-merge', 'vm-london', 'vm-berlin',
'vm-shanghai',
'compileAndRun', 'search', 'recorder', 'fileDecorator', 'codeParser', 'codeFormatter', 'solidityumlgen', 'contractflattener', 'solidity-script']
// dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd)
const dependentModules = ['foundry', 'hardhat', 'truffle', 'slither']
@ -20,17 +20,17 @@ const dependentModules = ['foundry', 'hardhat', 'truffle', 'slither']
const loadLocalPlugins = ["doc-gen", "doc-viewer", "etherscan", "vyper", 'solhint', 'walletconnect']
const sensitiveCalls = {
'fileManager': ['writeFile', 'copyFile', 'rename', 'copyDir'],
'contentImport': ['resolveAndSave'],
'web3Provider': ['sendAsync'],
'fileManager': ['writeFile', 'copyFile', 'rename', 'copyDir'],
'contentImport': ['resolveAndSave'],
'web3Provider': ['sendAsync'],
}
export function isNative(name) {
// nativePlugin allows to bypass the permission request
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic', 'solidityStaticAnalysis', 'solidityUnitTesting',
'layout', 'notification', 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider',
'tabs', 'injected-arbitrum-one-provider', 'injected', 'doc-gen', 'doc-viewer']
return nativePlugins.includes(name) || requiredModules.includes(name)
// nativePlugin allows to bypass the permission request
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic', 'solidityStaticAnalysis', 'solidityUnitTesting',
'layout', 'notification', 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider',
'tabs', 'injected-arbitrum-one-provider', 'injected', 'doc-gen', 'doc-viewer']
return nativePlugins.includes(name) || requiredModules.includes(name)
}
/**
@ -44,191 +44,191 @@ export function isNative(name) {
* @returns {boolean}
*/
export function canActivate(from, to) {
return ['ethdoc'].includes(from.name) ||
return ['ethdoc'].includes(from.name) ||
isNative(from.name) ||
(to && from && from.canActivate && from.canActivate.includes(to.name))
}
export class RemixAppManager extends PluginManager {
constructor() {
super()
this.event = new EventEmitter()
this.pluginsDirectory = 'https://raw.githubusercontent.com/ethereum/remix-plugins-directory/master/build/metadata.json'
this.pluginLoader = new PluginLoader()
}
constructor() {
super()
this.event = new EventEmitter()
this.pluginsDirectory = 'https://raw.githubusercontent.com/ethereum/remix-plugins-directory/master/build/metadata.json'
this.pluginLoader = new PluginLoader()
}
async canActivatePlugin(from, to) {
return canActivate(from, to)
}
async canActivatePlugin(from, to) {
return canActivate(from, to)
}
async canDeactivatePlugin(from, to) {
if (requiredModules.includes(to.name)) return false
return isNative(from.name)
}
async canDeactivatePlugin(from, to) {
if (requiredModules.includes(to.name)) return false
return isNative(from.name)
}
async canDeactivate(from, to) {
return this.canDeactivatePlugin(from, to)
}
async canDeactivate(from, to) {
return this.canDeactivatePlugin(from, to)
}
async deactivatePlugin(name) {
const profile = await this.getProfile(name)
const [to, from] = [
profile,
await this.getProfile(this.requestFrom)
]
if (this.canDeactivatePlugin(from, to)) {
if (profile.methods.includes('deactivate')) {
try {
await this.call(name, 'deactivate')
} catch (e) {
console.log(e)
}
}
await this.toggleActive(name)
async deactivatePlugin(name) {
const profile = await this.getProfile(name)
const [to, from] = [
profile,
await this.getProfile(this.requestFrom)
]
if (this.canDeactivatePlugin(from, to)) {
if (profile.methods.includes('deactivate')) {
try {
await this.call(name, 'deactivate')
} catch (e) {
console.log(e)
}
}
await this.toggleActive(name)
}
}
async canCall(from, to, method, message) {
const isSensitiveCall = sensitiveCalls[to] && sensitiveCalls[to].includes(method)
// Make sure the caller of this methods is the target plugin
if (to !== this.currentRequest.from) {
return false
}
// skipping native plugins' requests
if (isNative(from)) {
return true
}
// ask the user for permission
return await this.call('permissionhandler', 'askPermission', this.profiles[from], this.profiles[to], method, message, isSensitiveCall)
async canCall(from, to, method, message) {
const isSensitiveCall = sensitiveCalls[to] && sensitiveCalls[to].includes(method)
// Make sure the caller of this methods is the target plugin
if (to !== this.currentRequest.from) {
return false
}
onPluginActivated(plugin) {
this.pluginLoader.set(plugin, this.actives.filter((plugin) => !this.isDependent(plugin)))
this.event.emit('activate', plugin)
this.emit('activate', plugin)
if (!requiredModules.includes(plugin.name)) _paq.push(['trackEvent', 'pluginManager', 'activate', plugin.name])
// skipping native plugins' requests
if (isNative(from)) {
return true
}
getAll() {
return Object.keys(this.profiles).map((p) => {
return this.profiles[p]
})
}
// ask the user for permission
return await this.call('permissionhandler', 'askPermission', this.profiles[from], this.profiles[to], method, message, isSensitiveCall)
}
getIds() {
return Object.keys(this.profiles)
}
onPluginActivated(plugin) {
this.pluginLoader.set(plugin, this.actives.filter((plugin) => !this.isDependent(plugin)))
this.event.emit('activate', plugin)
this.emit('activate', plugin)
if (!requiredModules.includes(plugin.name)) _paq.push(['trackEvent', 'pluginManager', 'activate', plugin.name])
}
onPluginDeactivated(plugin) {
this.pluginLoader.set(plugin, this.actives.filter((plugin) => !this.isDependent(plugin)))
this.event.emit('deactivate', plugin)
_paq.push(['trackEvent', 'pluginManager', 'deactivate', plugin.name])
}
getAll() {
return Object.keys(this.profiles).map((p) => {
return this.profiles[p]
})
}
isDependent(name) {
return dependentModules.includes(name)
}
getIds() {
return Object.keys(this.profiles)
}
isRequired(name) {
// excluding internal use plugins
return requiredModules.includes(name)
}
onPluginDeactivated(plugin) {
this.pluginLoader.set(plugin, this.actives.filter((plugin) => !this.isDependent(plugin)))
this.event.emit('deactivate', plugin)
_paq.push(['trackEvent', 'pluginManager', 'deactivate', plugin.name])
}
isDependent(name) {
return dependentModules.includes(name)
}
async registeredPlugins() {
let plugins
isRequired(name) {
// excluding internal use plugins
return requiredModules.includes(name)
}
async registeredPlugins() {
let plugins
try {
const res = await fetch(this.pluginsDirectory)
plugins = await res.json()
plugins = plugins.filter((plugin) => {
if (plugin.targets && Array.isArray(plugin.targets) && plugin.targets.length > 0) {
return (plugin.targets.includes('remix'))
}
return true
})
localStorage.setItem('plugins-directory', JSON.stringify(plugins))
} catch (e) {
console.log('getting plugins list from localstorage...')
const savedPlugins = localStorage.getItem('plugins-directory')
if (savedPlugins) {
try {
const res = await fetch(this.pluginsDirectory)
plugins = await res.json()
plugins = plugins.filter((plugin) => {
if (plugin.targets && Array.isArray(plugin.targets) && plugin.targets.length > 0) {
return (plugin.targets.includes('remix'))
}
return true
})
localStorage.setItem('plugins-directory', JSON.stringify(plugins))
plugins = JSON.parse(savedPlugins)
} catch (e) {
console.log('getting plugins list from localstorage...')
const savedPlugins = localStorage.getItem('plugins-directory')
if (savedPlugins) {
try {
plugins = JSON.parse(savedPlugins)
} catch (e) {
console.error(e)
}
}
}
const testPluginName = localStorage.getItem('test-plugin-name')
const testPluginUrl = localStorage.getItem('test-plugin-url')
for (let plugin of loadLocalPlugins) {
// fetch the profile from the local plugin
try {
const profile = await fetch(`plugins/${plugin}/profile.json`)
const profileJson = await profile.json()
// remove duplicates
plugins = plugins.filter((p) => p.name !== profileJson.name && p.displayName !== profileJson.displayName)
// change url
profileJson.url = `plugins/${plugin}/index.html`
// add the local plugin
plugins.push(profileJson)
} catch (e) {
console.log(e)
}
console.error(e)
}
return plugins.map(plugin => {
if (plugin.name === testPluginName) plugin.url = testPluginUrl
return new IframePlugin(plugin)
})
}
}
const testPluginName = localStorage.getItem('test-plugin-name')
const testPluginUrl = localStorage.getItem('test-plugin-url')
async registerContextMenuItems() {
await this.call('filePanel', 'registerContextMenuItem', {
id: 'contractflattener',
name: 'flattenAContract',
label: 'Flatten',
type: [],
extension: ['.sol'],
path: [],
pattern: [],
sticky: true,
group: 5
})
await this.call('filePanel', 'registerContextMenuItem', {
id: 'nahmii-compiler',
name: 'compileCustomAction',
label: 'Compile for Nahmii',
type: [],
extension: ['.sol'],
path: [],
pattern: [],
sticky: true,
group: 6
})
await this.call('filePanel', 'registerContextMenuItem', {
id: 'solidityumlgen',
name: 'generateCustomAction',
label: 'Generate UML',
type: [],
extension: ['.sol'],
path: [],
pattern: [],
sticky: true,
group: 7
})
await this.call('filePanel', 'registerContextMenuItem', {
id: 'doc-gen',
name: 'generateDocsCustomAction',
label: 'Generate Docs',
type: [],
extension: ['.sol'],
path: [],
pattern: [],
sticky: true,
group: 7
})
for (let plugin of loadLocalPlugins) {
// fetch the profile from the local plugin
try {
const profile = await fetch(`plugins/${plugin}/profile.json`)
const profileJson = await profile.json()
// remove duplicates
plugins = plugins.filter((p) => p.name !== profileJson.name && p.displayName !== profileJson.displayName)
// change url
profileJson.url = `plugins/${plugin}/index.html`
// add the local plugin
plugins.push(profileJson)
} catch (e) {
console.log(e)
}
}
return plugins.map(plugin => {
if (plugin.name === testPluginName) plugin.url = testPluginUrl
return new IframePlugin(plugin)
})
}
async registerContextMenuItems() {
await this.call('filePanel', 'registerContextMenuItem', {
id: 'contractflattener',
name: 'flattenAContract',
label: 'Flatten',
type: [],
extension: ['.sol'],
path: [],
pattern: [],
sticky: true,
group: 5
})
await this.call('filePanel', 'registerContextMenuItem', {
id: 'nahmii-compiler',
name: 'compileCustomAction',
label: 'Compile for Nahmii',
type: [],
extension: ['.sol'],
path: [],
pattern: [],
sticky: true,
group: 6
})
await this.call('filePanel', 'registerContextMenuItem', {
id: 'solidityumlgen',
name: 'generateCustomAction',
label: 'Generate UML',
type: [],
extension: ['.sol'],
path: [],
pattern: [],
sticky: true,
group: 7
})
await this.call('filePanel', 'registerContextMenuItem', {
id: 'doc-gen',
name: 'generateDocsCustomAction',
label: 'Generate Docs',
type: [],
extension: ['.sol'],
path: [],
pattern: [],
sticky: true,
group: 7
})
}
}
/** @class Reference loaders.
@ -236,39 +236,39 @@ export class RemixAppManager extends PluginManager {
* (localStorage, queryParams)
**/
class PluginLoader {
get currentLoader() {
return this.loaders[this.current]
}
get currentLoader() {
return this.loaders[this.current]
}
constructor() {
const queryParams = new QueryParams()
this.donotAutoReload = ['remixd'] // that would be a bad practice to force loading some plugins at page load.
this.loaders = {}
this.loaders.localStorage = {
set: (plugin, actives) => {
const saved = actives.filter((name) => !this.donotAutoReload.includes(name))
localStorage.setItem('workspace', JSON.stringify(saved))
},
get: () => { return JSON.parse(localStorage.getItem('workspace')) }
}
this.loaders.queryParams = {
set: () => { /* Do nothing. */ },
get: () => {
const { activate } = queryParams.get()
if (!activate) return []
return activate.split(',')
}
}
this.current = queryParams.get().activate ? 'queryParams' : 'localStorage'
constructor() {
const queryParams = new QueryParams()
this.donotAutoReload = ['remixd'] // that would be a bad practice to force loading some plugins at page load.
this.loaders = {}
this.loaders.localStorage = {
set: (plugin, actives) => {
const saved = actives.filter((name) => !this.donotAutoReload.includes(name))
localStorage.setItem('workspace', JSON.stringify(saved))
},
get: () => { return JSON.parse(localStorage.getItem('workspace')) }
}
set(plugin, actives) {
this.currentLoader.set(plugin, actives)
this.loaders.queryParams = {
set: () => { /* Do nothing. */ },
get: () => {
const { activate } = queryParams.get()
if (!activate) return []
return activate.split(',')
}
}
get() {
return this.currentLoader.get()
}
this.current = queryParams.get().activate ? 'queryParams' : 'localStorage'
}
set(plugin, actives) {
this.currentLoader.set(plugin, actives)
}
get() {
return this.currentLoader.get()
}
}

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

Loading…
Cancel
Save