use prettier to format all code files in repo. adjust eslint rules. fix text in vyper

pull/4018/head
Joseph Izang 1 year ago
parent aa7bb9aeaf
commit 665eb5389f
  1. 7
      .eslintrc.json
  2. 3
      .prettierrc.json
  3. 14
      apps/debugger/src/app/app.tsx
  4. 11
      apps/debugger/src/main.tsx
  5. 67
      apps/debugger/webpack.config.js
  6. 30
      apps/doc-gen/src/app/App.tsx
  7. 25
      apps/doc-gen/src/app/hooks/useLocalStorage.tsx
  8. 23
      apps/doc-gen/src/app/views/ErrorView.tsx
  9. 10
      apps/doc-gen/src/main.tsx
  10. 25
      apps/doc-gen/webpack.config.js
  11. 9
      apps/doc-viewer/src/app/App.tsx
  12. 4
      apps/doc-viewer/src/main.tsx
  13. 32
      apps/doc-viewer/webpack.config.js
  14. 20
      apps/etherscan/src/app/AppContext.tsx
  15. 108
      apps/etherscan/src/app/RemixPlugin.tsx
  16. 83
      apps/etherscan/src/app/app.tsx
  17. 87
      apps/etherscan/src/app/components/HeaderWithSettings.tsx
  18. 14
      apps/etherscan/src/app/components/SubmitButton.tsx
  19. 2
      apps/etherscan/src/app/hooks/useLocalStorage.tsx
  20. 4
      apps/etherscan/src/app/layouts/Default.tsx
  21. 18
      apps/etherscan/src/app/routes.tsx
  22. 124
      apps/etherscan/src/app/views/CaptureKeyView.tsx
  23. 4
      apps/etherscan/src/app/views/ErrorView.tsx
  24. 17
      apps/etherscan/src/app/views/HomeView.tsx
  25. 138
      apps/etherscan/src/app/views/ReceiptsView.tsx
  26. 391
      apps/etherscan/src/app/views/VerifyView.tsx
  27. 14
      apps/etherscan/src/main.tsx
  28. 64
      apps/etherscan/webpack.config.js
  29. 77
      apps/remix-ide-e2e/nightwatch.ts
  30. 109
      apps/remix-ide-e2e/src/local-plugin/src/app/app.tsx
  31. 8
      apps/remix-ide-e2e/src/local-plugin/src/app/logger.tsx
  32. 11
      apps/remix-ide/background.js
  33. 28
      apps/remix-ide/src/app/components/hidden-panel.tsx
  34. 37
      apps/remix-ide/src/app/components/main-panel.tsx
  35. 255
      apps/remix-ide/src/app/components/preload.tsx
  36. 31
      apps/remix-ide/src/app/components/side-panel.tsx
  37. 99
      apps/remix-ide/src/app/components/vertical-icons.tsx
  38. 46
      apps/remix-ide/src/app/plugins/contractFlattener.tsx
  39. 33
      apps/remix-ide/src/app/plugins/notification.tsx
  40. 566
      apps/remix-ide/src/app/plugins/parser/code-parser.tsx
  41. 87
      apps/remix-ide/src/app/plugins/permission-handler-plugin.tsx
  42. 174
      apps/remix-ide/src/app/plugins/remixd-handle.tsx
  43. 50
      apps/remix-ide/src/app/plugins/solidity-script.tsx
  44. 193
      apps/remix-ide/src/app/plugins/solidity-umlgen.tsx
  45. 70
      apps/remix-ide/src/app/providers/abstract-provider.tsx
  46. 103
      apps/remix-ide/src/app/providers/custom-vm-fork-provider.tsx
  47. 52
      apps/remix-ide/src/app/providers/external-http-provider.tsx
  48. 25
      apps/remix-ide/src/app/providers/foundry-provider.tsx
  49. 25
      apps/remix-ide/src/app/providers/ganache-provider.tsx
  50. 31
      apps/remix-ide/src/app/providers/goerli-vm-fork-provider.tsx
  51. 25
      apps/remix-ide/src/app/providers/hardhat-provider.tsx
  52. 43
      apps/remix-ide/src/app/providers/injected-L2-provider.tsx
  53. 7
      apps/remix-ide/src/app/providers/injected-arbitrum-one-provider.tsx
  54. 7
      apps/remix-ide/src/app/providers/injected-optimism-provider.tsx
  55. 27
      apps/remix-ide/src/app/providers/injected-provider-default.tsx
  56. 10
      apps/remix-ide/src/app/providers/injected-provider-trustwallet.tsx
  57. 86
      apps/remix-ide/src/app/providers/injected-provider.tsx
  58. 34
      apps/remix-ide/src/app/providers/mainnet-vm-fork-provider.tsx
  59. 31
      apps/remix-ide/src/app/providers/sepolia-vm-fork-provider.tsx
  60. 131
      apps/remix-ide/src/app/providers/vm-provider.tsx
  61. 12
      apps/remix-ide/src/app/tabs/search.tsx
  62. 46
      apps/remix-ide/src/app/tabs/settings-tab.tsx
  63. 904
      apps/remix-ide/src/blockchain/blockchain.tsx
  64. 19
      apps/remix-ide/src/index.tsx
  65. 109
      apps/remix-ide/webpack.config.js
  66. 8
      apps/solhint/src/app/App.tsx
  67. 38
      apps/solhint/webpack.config.js
  68. 4
      apps/solidity-compiler/src/app/app.tsx
  69. 73
      apps/solidity-compiler/webpack.config.js
  70. 38
      apps/vyper/src/app/app.tsx
  71. 44
      apps/vyper/src/app/components/CompilerButton.tsx
  72. 20
      apps/vyper/src/app/components/LocalUrl.tsx
  73. 72
      apps/vyper/src/app/components/VyperResult.tsx
  74. 10
      apps/vyper/src/app/components/WarnRemote.tsx
  75. 59
      apps/vyper/src/app/utils/compiler.tsx
  76. 68
      apps/vyper/src/app/utils/remix-client.tsx
  77. 14
      apps/vyper/src/main.tsx
  78. 67
      apps/vyper/webpack.config.js
  79. 24
      apps/walletconnect/src/app/app.tsx
  80. 33
      apps/walletconnect/src/app/walletConnectUI.tsx
  81. 5
      apps/walletconnect/src/main.tsx
  82. 68
      apps/walletconnect/webpack.config.js
  83. 28
      libs/remix-debug/test.ts
  84. 42
      libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx
  85. 10
      libs/remix-ui/app/src/lib/remix-app/components/modals/dialogViewPlugin.tsx
  86. 17
      libs/remix-ui/app/src/lib/remix-app/components/modals/dialogs.tsx
  87. 77
      libs/remix-ui/app/src/lib/remix-app/components/modals/matomo.tsx
  88. 68
      libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx
  89. 37
      libs/remix-ui/app/src/lib/remix-app/components/modals/origin-warning.tsx
  90. 31
      libs/remix-ui/app/src/lib/remix-app/components/splashscreen.tsx
  91. 22
      libs/remix-ui/app/src/lib/remix-app/context/context.tsx
  92. 95
      libs/remix-ui/app/src/lib/remix-app/context/provider.tsx
  93. 68
      libs/remix-ui/app/src/lib/remix-app/remix-app.tsx
  94. 55
      libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx
  95. 44
      libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx
  96. 37
      libs/remix-ui/debugger-ui/src/hooks/extract-data.tsx
  97. 311
      libs/remix-ui/debugger-ui/src/lib/button-navigator/button-navigator.tsx
  98. 415
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  99. 15
      libs/remix-ui/debugger-ui/src/lib/slider/slider.tsx
  100. 34
      libs/remix-ui/debugger-ui/src/lib/step-manager/step-manager.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

@ -48,6 +48,11 @@
"no-empty": "off", "no-empty": "off",
"jsx-a11y/anchor-is-valid": "off", "jsx-a11y/anchor-is-valid": "off",
"@typescript-eslint/no-inferrable-types": "off", "@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "off",
"react-hooks/exhaustive-deps": "off",
"array-callback-return": "off",
"prefer-spread": "off",
"indent": ["error", 2] "indent": ["error", 2]
} }
}, },
@ -67,4 +72,4 @@
"globals": { "globals": {
"JSX": true "JSX": true
} }
} }

@ -9,6 +9,5 @@
"trailingComma": "none", "trailingComma": "none",
"jsxBracketSameLine": false, "jsxBracketSameLine": false,
"arrowParens": "always", "arrowParens": "always",
"singleAttributePerLine": false, "singleAttributePerLine": false
"ignorePath": ".prettierignore"
} }

@ -1,17 +1,17 @@
import React, { useState, useEffect } from 'react'; import React, {useState, useEffect} from 'react'
import { DebuggerUI } from '@remix-ui/debugger-ui' // eslint-disable-line import {DebuggerUI} from '@remix-ui/debugger-ui' // eslint-disable-line
import { DebuggerClientApi } from './debugger' import {DebuggerClientApi} from './debugger'
const remix = new DebuggerClientApi() const remix = new DebuggerClientApi()
export const App = () => { export const App = () => {
return ( return (
<div className="debugger"> <div className="debugger">
<DebuggerUI debuggerAPI={remix} /> <DebuggerUI debuggerAPI={remix} />
</div> </div>
); )
}; }
export default App; export default App

@ -1,9 +1,6 @@
import React from 'react'; import React from 'react'
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom'
import App from './app/app'; import App from './app/app'
ReactDOM.render( ReactDOM.render(<App />, document.getElementById('root'))
<App />,
document.getElementById('root')
);

@ -1,8 +1,7 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack. // Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => { module.exports = composePlugins(withNx(), (config) => {
@ -12,56 +11,52 @@ module.exports = composePlugins(withNx(), (config) => {
// add fallback for node modules // add fallback for node modules
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"), crypto: require.resolve('crypto-browserify'),
"stream": require.resolve("stream-browserify"), stream: require.resolve('stream-browserify'),
"path": require.resolve("path-browserify"), path: require.resolve('path-browserify'),
"http": require.resolve("stream-http"), http: require.resolve('stream-http'),
"https": require.resolve("https-browserify"), https: require.resolve('https-browserify'),
"constants": require.resolve("constants-browserify"), constants: require.resolve('constants-browserify'),
"os": false, //require.resolve("os-browserify/browser"), os: false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"), timers: false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"), zlib: require.resolve('browserify-zlib'),
"fs": false, fs: false,
"module": false, module: false,
"tls": false, tls: false,
"net": false, net: false,
"readline": false, readline: false,
"child_process": false, child_process: false,
"buffer": require.resolve("buffer/"), buffer: require.resolve('buffer/'),
"vm": require.resolve('vm-browserify'), vm: require.resolve('vm-browserify')
} }
// add externals // add externals
config.externals = { config.externals = {
...config.externals, ...config.externals,
solc: 'solc', solc: 'solc'
} }
// add public path // add public path
config.output.publicPath = '/' config.output.publicPath = '/'
// add copy & provide plugin // add copy & provide plugin
config.plugins.push( config.plugins.push(
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}) })
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer // set minimizer
config.optimization.minimizer = [ config.optimization.minimizer = [
new TerserPlugin({ new TerserPlugin({
@ -71,17 +66,17 @@ module.exports = composePlugins(withNx(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
config.watchOptions = { config.watchOptions = {
ignored: /node_modules/ ignored: /node_modules/
} }
return config; return config
}); })

@ -4,13 +4,13 @@ import './App.css'
import { DocGenClient } from './docgen-client' import { DocGenClient } from './docgen-client'
import { Build } from './docgen/site' import { Build } from './docgen/site'
export const client = new DocGenClient() export const client = new DocGenClient()
const App = () => { const App = () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [themeType, setThemeType] = useState<string>('dark'); const [themeType, setThemeType] = useState<string>('dark')
const [hasBuild, setHasBuild] = useState<boolean>(false); const [hasBuild, setHasBuild] = useState<boolean>(false)
const [fileName, setFileName] = useState<string>(''); const [fileName, setFileName] = useState<string>('')
useEffect(() => { useEffect(() => {
const watchThemeSwitch = async () => { const watchThemeSwitch = async () => {
@ -21,19 +21,27 @@ const App = () => {
setHasBuild(true) setHasBuild(true)
setFileName(fileName) setFileName(fileName)
}) })
client.eventEmitter.on('docsGenerated', (docs: string[]) => { client.eventEmitter.on('docsGenerated', (docs: string[]) => {})
})
} }
watchThemeSwitch() watchThemeSwitch()
}, []) }, [])
return ( return (
<div className="p-3"> <div className="p-3">
<h5 className="h-5 mb-3">Compile a Solidity contract and generate its documentation as Markdown. (Right-click on a contract in the File Explorer and select "Generate Docs" from the context menu.).</h5> <h5 className="h-5 mb-3">
{fileName && <div className="border-bottom border-top px-2 py-3 justify-center align-items-center d-flex"> Compile a Solidity contract and generate its documentation as Markdown. (Right-click on a contract in the File
<h6>File: {fileName}</h6> Explorer and select "Generate Docs" from the context menu.).
</div>} </h5>
{hasBuild && <button className="btn btn-primary btn-block mt-4" onClick={() => client.generateDocs()}>Generate Docs</button>} {fileName && (
<div className="border-bottom border-top px-2 py-3 justify-center align-items-center d-flex">
<h6>File: {fileName}</h6>
</div>
)}
{hasBuild && (
<button className="btn btn-primary btn-block mt-4" onClick={() => client.generateDocs()}>
Generate Docs
</button>
)}
</div> </div>
) )
} }

@ -1,4 +1,4 @@
import { useState } from "react"; import { useState } from 'react'
export function useLocalStorage(key: string, initialValue: any) { export function useLocalStorage(key: string, initialValue: any) {
// State to store our value // State to store our value
@ -6,32 +6,31 @@ export function useLocalStorage(key: string, initialValue: any) {
const [storedValue, setStoredValue] = useState(() => { const [storedValue, setStoredValue] = useState(() => {
try { try {
// Get from local storage by key // Get from local storage by key
const item = window.localStorage.getItem(key); const item = window.localStorage.getItem(key)
// Parse stored json or if none return initialValue // Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue; return item ? JSON.parse(item) : initialValue
} catch (error) { } catch (error) {
// If error also return initialValue // If error also return initialValue
console.log(error); console.log(error)
return initialValue; return initialValue
} }
}); })
// Return a wrapped version of useState's setter function that ... // Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage. // ... persists the new value to localStorage.
const setValue = (value: any) => { const setValue = (value: any) => {
try { try {
// Allow value to be a function so we have same API as useState // Allow value to be a function so we have same API as useState
const valueToStore = const valueToStore = value instanceof Function ? value(storedValue) : value
value instanceof Function ? value(storedValue) : value;
// Save state // Save state
setStoredValue(valueToStore); setStoredValue(valueToStore)
// Save to local storage // Save to local storage
window.localStorage.setItem(key, JSON.stringify(valueToStore)); window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) { } catch (error) {
// A more advanced implementation would handle the error case // A more advanced implementation would handle the error case
console.log(error); console.log(error)
} }
}; }
return [storedValue, setValue]; return [storedValue, setValue]
} }

@ -1,31 +1,28 @@
import React from "react"; import React from 'react'
export const ErrorView: React.FC = () => { export const ErrorView: React.FC = () => {
return ( return (
<div <div
style={{ style={{
width: "100%", width: '100%',
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
alignItems: "center", alignItems: 'center',
}} }}
> >
<img <img
style={{ paddingBottom: "2em" }} style={{ paddingBottom: '2em' }}
width="250" width="250"
src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png" src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png"
alt="Error page" alt="Error page"
/> />
<h5>Sorry, something unexpected happened. </h5> <h5>Sorry, something unexpected happened. </h5>
<h5> <h5>
Please raise an issue:{" "} Please raise an issue:{' '}
<a <a style={{ color: 'red' }} href="https://github.com/Machinalabs/remix-ethdoc-plugin/issues">
style={{ color: "red" }}
href="https://github.com/Machinalabs/remix-ethdoc-plugin/issues"
>
Here Here
</a> </a>
</h5> </h5>
</div> </div>
); )
}; }

@ -1,11 +1,11 @@
import React from "react"; import React from 'react'
import ReactDOM from "react-dom"; import ReactDOM from 'react-dom'
import App from "./app/App"; import App from './app/App'
// import { Routes } from "./routes"; // import { Routes } from "./routes";
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>,
document.getElementById("root") document.getElementById('root'),
); )

@ -1,19 +1,19 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const { composePlugins, withNx } = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react') const { withReact } = require('@nrwl/react')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack. // Nx plugins for webpack.
module.exports = composePlugins(withNx(), withReact(), (config) => { module.exports = composePlugins(withNx(), withReact(), config => {
// Update the webpack config as needed here. // Update the webpack config as needed here.
// e.g. `config.plugins.push(new MyPlugin())` // e.g. `config.plugins.push(new MyPlugin())`
// add fallback for node modules // add fallback for node modules
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"path": require.resolve("path-browserify"), path: require.resolve('path-browserify'),
"fs": false, fs: false,
} }
// add externals // add externals
@ -24,10 +24,9 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
config.module.rules.push({ config.module.rules.push({
test: /\.hbs$/, test: /\.hbs$/,
type: 'asset/source' type: 'asset/source',
}) })
// add public path // add public path
config.output.publicPath = '/' config.output.publicPath = '/'
@ -38,16 +37,14 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser',
}), }),
new webpack.DefinePlugin({ new webpack.DefinePlugin({}),
}),
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre',
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
@ -67,7 +64,7 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
extractComments: false, extractComments: false,
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin(),
]; ]
return config; return config
}) })

@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react" import React, {useEffect, useState} from 'react'
import { DocViewer } from "./docviewer" import {DocViewer} from './docviewer'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm' import remarkGfm from 'remark-gfm'
@ -11,13 +11,12 @@ export default function App() {
client.eventEmitter.on('contentsReady', (fileContents: string) => { client.eventEmitter.on('contentsReady', (fileContents: string) => {
setContents(fileContents) setContents(fileContents)
}) })
}, []) }, [])
return ( return (
<> <>
<div className="m-5 p-2"> <div className="m-5 p-2">
<ReactMarkdown children={contents} remarkPlugins={[remarkGfm]}/> <ReactMarkdown children={contents} remarkPlugins={[remarkGfm]} />
</div> </div>
</> </>
) )
} }

@ -6,5 +6,5 @@ ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>,
document.getElementById("root") document.getElementById('root')
); )

@ -1,8 +1,8 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react') const {withReact} = require('@nrwl/react')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack. // Nx plugins for webpack.
module.exports = composePlugins(withNx(), withReact(), (config) => { module.exports = composePlugins(withNx(), withReact(), (config) => {
@ -12,7 +12,7 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
// add externals // add externals
config.externals = { config.externals = {
...config.externals, ...config.externals,
solc: 'solc', solc: 'solc'
} }
// add public path // add public path
@ -23,18 +23,16 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}),
new webpack.DefinePlugin({
}), }),
new webpack.DefinePlugin({})
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
@ -48,13 +46,13 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
return config; return config
}) })

@ -1,24 +1,24 @@
import React from "react" import React from 'react'
import { PluginClient } from "@remixproject/plugin" import {PluginClient} from '@remixproject/plugin'
import { Receipt, ThemeType } from "./types" import {Receipt, ThemeType} from './types'
export const AppContext = React.createContext({ export const AppContext = React.createContext({
apiKey: "", apiKey: '',
setAPIKey: (value: string) => { setAPIKey: (value: string) => {
console.log("Set API Key from Context") console.log('Set API Key from Context')
}, },
clientInstance: {} as PluginClient, clientInstance: {} as PluginClient,
receipts: [] as Receipt[], receipts: [] as Receipt[],
setReceipts: (receipts: Receipt[]) => { setReceipts: (receipts: Receipt[]) => {
console.log("Calling Set Receipts") console.log('Calling Set Receipts')
}, },
contracts: [] as string[], contracts: [] as string[],
setContracts: (contracts: string[]) => { setContracts: (contracts: string[]) => {
console.log("Calling Set Contract Names") console.log('Calling Set Contract Names')
}, },
themeType: "dark" as ThemeType, themeType: 'dark' as ThemeType,
setThemeType: (themeType: ThemeType) => { setThemeType: (themeType: ThemeType) => {
console.log("Calling Set Theme Type") console.log('Calling Set Theme Type')
}, }
}) })

@ -1,40 +1,80 @@
import { PluginClient } from '@remixproject/plugin'; import {PluginClient} from '@remixproject/plugin'
import { verify, EtherScanReturn } from './utils/verify'; import {verify, EtherScanReturn} from './utils/verify'
import { getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus } from './utils'; import {
getReceiptStatus,
getEtherScanApi,
getNetworkName,
getProxyContractReceiptStatus
} from './utils'
export class RemixClient extends PluginClient { export class RemixClient extends PluginClient {
loaded() {
return this.onload()
}
loaded() { async verify(
return this.onload() apiKey: string,
} contractAddress: string,
contractArguments: string,
async verify (apiKey: string, contractAddress: string, contractArguments: string, contractName: string, compilationResultParam: any, chainRef?: number | string, isProxyContract?: boolean, expectedImplAddress?: string) { contractName: string,
const result = await verify(apiKey, contractAddress, contractArguments, contractName, compilationResultParam, chainRef, isProxyContract, expectedImplAddress, this, compilationResultParam: any,
(value: EtherScanReturn) => {}, (value: string) => {}) chainRef?: number | string,
return result isProxyContract?: boolean,
} expectedImplAddress?: string
) {
const result = await verify(
apiKey,
contractAddress,
contractArguments,
contractName,
compilationResultParam,
chainRef,
isProxyContract,
expectedImplAddress,
this,
(value: EtherScanReturn) => {},
(value: string) => {}
)
return result
}
async receiptStatus(
receiptGuid: string,
apiKey: string,
isProxyContract: boolean
) {
try {
const {network, networkId} = await getNetworkName(this)
if (network === 'vm') {
throw new Error(
'Cannot check the receipt status in the selected network'
)
}
const etherscanApi = getEtherScanApi(networkId)
let receiptStatus
async receiptStatus (receiptGuid: string, apiKey: string, isProxyContract: boolean) { if (isProxyContract)
try { receiptStatus = await getProxyContractReceiptStatus(
const { network, networkId } = await getNetworkName(this) receiptGuid,
if (network === "vm") { apiKey,
throw new Error("Cannot check the receipt status in the selected network") etherscanApi
} )
const etherscanApi = getEtherScanApi(networkId) else
let receiptStatus receiptStatus = await getReceiptStatus(
receiptGuid,
if (isProxyContract) receiptStatus = await getProxyContractReceiptStatus(receiptGuid, apiKey, etherscanApi) apiKey,
else receiptStatus = await getReceiptStatus(receiptGuid, apiKey, etherscanApi) etherscanApi
return { )
message: receiptStatus.result, return {
succeed: receiptStatus.status === '0' ? false : true message: receiptStatus.result,
} succeed: receiptStatus.status === '0' ? false : true
} catch (e: any){ }
return { } catch (e: any) {
status: 'error', return {
message: e.message, status: 'error',
succeed: false message: e.message,
} succeed: false
} }
} }
}
} }

@ -1,22 +1,27 @@
import React, { useState, useEffect, useRef } from "react" import React, {useState, useEffect, useRef} from 'react'
import { import {
CompilationFileSources, CompilationFileSources,
CompilationResult, CompilationResult
} from "@remixproject/plugin-api" } from '@remixproject/plugin-api'
import { RemixClient } from "./RemixPlugin"; import {RemixClient} from './RemixPlugin'
import { createClient } from "@remixproject/plugin-webview"; import {createClient} from '@remixproject/plugin-webview'
import { AppContext } from "./AppContext" import {AppContext} from './AppContext'
import { DisplayRoutes } from "./routes" import {DisplayRoutes} from './routes'
import { useLocalStorage } from "./hooks/useLocalStorage" import {useLocalStorage} from './hooks/useLocalStorage'
import { getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus } from "./utils" import {
import { Receipt, ThemeType } from "./types" getReceiptStatus,
getEtherScanApi,
getNetworkName,
getProxyContractReceiptStatus
} from './utils'
import {Receipt, ThemeType} from './types'
import "./App.css" import './App.css'
export const getNewContractNames = (compilationResult: CompilationResult) => { export const getNewContractNames = (compilationResult: CompilationResult) => {
const compiledContracts = compilationResult.contracts const compiledContracts = compilationResult.contracts
@ -31,11 +36,11 @@ export const getNewContractNames = (compilationResult: CompilationResult) => {
} }
const App = () => { const App = () => {
const [apiKey, setAPIKey] = useLocalStorage("apiKey", "") const [apiKey, setAPIKey] = useLocalStorage('apiKey', '')
const [clientInstance, setClientInstance] = useState(undefined as any) const [clientInstance, setClientInstance] = useState(undefined as any)
const [receipts, setReceipts] = useLocalStorage("receipts", []) const [receipts, setReceipts] = useLocalStorage('receipts', [])
const [contracts, setContracts] = useState([] as string[]) const [contracts, setContracts] = useState([] as string[])
const [themeType, setThemeType] = useState("dark" as ThemeType) const [themeType, setThemeType] = useState('dark' as ThemeType)
const timer = useRef(null) const timer = useRef(null)
const clientInstanceRef = useRef(clientInstance) const clientInstanceRef = useRef(clientInstance)
@ -49,8 +54,9 @@ const App = () => {
const loadClient = async () => { const loadClient = async () => {
await client.onload() await client.onload()
setClientInstance(client) setClientInstance(client)
client.on("solidity", client.on(
"compilationFinished", 'solidity',
'compilationFinished',
( (
fileName: string, fileName: string,
source: CompilationFileSources, source: CompilationFileSources,
@ -61,7 +67,7 @@ const App = () => {
const newContractsToSave: string[] = [ const newContractsToSave: string[] = [
...contractsRef.current, ...contractsRef.current,
...newContractsNames, ...newContractsNames
] ]
const uniqueContracts: string[] = [...new Set(newContractsToSave)] const uniqueContracts: string[] = [...new Set(newContractsToSave)]
@ -82,7 +88,10 @@ const App = () => {
useEffect(() => { useEffect(() => {
let receiptsNotVerified: Receipt[] = receipts.filter((item: Receipt) => { let receiptsNotVerified: Receipt[] = receipts.filter((item: Receipt) => {
return item.status === "Pending in queue" || item.status === "Max rate limit reached" return (
item.status === 'Pending in queue' ||
item.status === 'Max rate limit reached'
)
}) })
if (receiptsNotVerified.length > 0) { if (receiptsNotVerified.length > 0) {
@ -91,17 +100,19 @@ const App = () => {
timer.current = null timer.current = null
} }
timer.current = setInterval(async () => { timer.current = setInterval(async () => {
const { network, networkId } = await getNetworkName(clientInstanceRef.current) const {network, networkId} = await getNetworkName(
clientInstanceRef.current
)
if (!clientInstanceRef.current) { if (!clientInstanceRef.current) {
return return
} }
if (network === "vm") { if (network === 'vm') {
return return
} }
let newReceipts = receipts let newReceipts = receipts
for (const item of receiptsNotVerified) { for (const item of receiptsNotVerified) {
await new Promise(r => setTimeout(r, 500)) // avoid api rate limit exceed. await new Promise((r) => setTimeout(r, 500)) // avoid api rate limit exceed.
let status let status
if (item.isProxyContract) { if (item.isProxyContract) {
status = await getProxyContractReceiptStatus( status = await getProxyContractReceiptStatus(
@ -113,29 +124,35 @@ const App = () => {
status.message = status.result status.message = status.result
status.result = 'Successfully Updated' status.result = 'Successfully Updated'
} }
} else } else
status = await getReceiptStatus( status = await getReceiptStatus(
item.guid, item.guid,
apiKey, apiKey,
getEtherScanApi(networkId) getEtherScanApi(networkId)
) )
if (status.result === "Pass - Verified" || status.result === "Already Verified" || if (
status.result === "Successfully Updated") { status.result === 'Pass - Verified' ||
status.result === 'Already Verified' ||
status.result === 'Successfully Updated'
) {
newReceipts = newReceipts.map((currentReceipt: Receipt) => { newReceipts = newReceipts.map((currentReceipt: Receipt) => {
if (currentReceipt.guid === item.guid) { if (currentReceipt.guid === item.guid) {
let res = { const res = {
...currentReceipt, ...currentReceipt,
status: status.result, status: status.result
} }
if (currentReceipt.isProxyContract) res.message = status.message if (currentReceipt.isProxyContract) res.message = status.message
return res return res
} }
return currentReceipt return currentReceipt
}) })
} }
} }
receiptsNotVerified = newReceipts.filter((item: Receipt) => { receiptsNotVerified = newReceipts.filter((item: Receipt) => {
return item.status === "Pending in queue" || item.status === "Max rate limit reached" return (
item.status === 'Pending in queue' ||
item.status === 'Max rate limit reached'
)
}) })
if (timer.current && receiptsNotVerified.length === 0) { if (timer.current && receiptsNotVerified.length === 0) {
clearInterval(timer.current) clearInterval(timer.current)
@ -157,7 +174,7 @@ const App = () => {
contracts, contracts,
setContracts, setContracts,
themeType, themeType,
setThemeType, setThemeType
}} }}
> >
<DisplayRoutes /> <DisplayRoutes />
@ -165,4 +182,4 @@ const App = () => {
) )
} }
export default App export default App

@ -1,8 +1,8 @@
import React from "react" import React from 'react'
import { NavLink } from "react-router-dom" import {NavLink} from 'react-router-dom'
import { CustomTooltip } from '@remix-ui/helper' import {CustomTooltip} from '@remix-ui/helper'
import { AppContext } from "../AppContext" import {AppContext} from '../AppContext'
interface Props { interface Props {
title?: string title?: string
@ -13,21 +13,29 @@ interface IconProps {
from: string from: string
} }
const HomeIcon: React.FC<IconProps> = ({ from }: IconProps) => { const HomeIcon: React.FC<IconProps> = ({from}: IconProps) => {
return ( return (
<NavLink <NavLink
data-id="home" data-id="home"
to={{ to={{
pathname: "/" pathname: '/'
}} }}
className={({ isActive }) => isActive ? "border border-secondary shadow-none btn p-1 m-0" : "border-0 shadow-none btn p-1 m-0"} className={({isActive}) =>
style={ ({ isActive }) => !isActive ? { width: "1.8rem", filter: "contrast(0.5)"} : {width: "1.8rem"}} isActive
state={ from } ? 'border border-secondary shadow-none btn p-1 m-0'
: 'border-0 shadow-none btn p-1 m-0'
}
style={({isActive}) =>
!isActive
? {width: '1.8rem', filter: 'contrast(0.5)'}
: {width: '1.8rem'}
}
state={from}
> >
<CustomTooltip <CustomTooltip
tooltipText='Home' tooltipText="Home"
tooltipId='etherscan-nav-home' tooltipId="etherscan-nav-home"
placement='bottom' placement="bottom"
> >
<i className="fas fa-home"></i> <i className="fas fa-home"></i>
</CustomTooltip> </CustomTooltip>
@ -35,21 +43,29 @@ const HomeIcon: React.FC<IconProps> = ({ from }: IconProps) => {
) )
} }
const ReceiptsIcon: React.FC<IconProps> = ({ from }: IconProps) => { const ReceiptsIcon: React.FC<IconProps> = ({from}: IconProps) => {
return ( return (
<NavLink <NavLink
data-id="receipts" data-id="receipts"
to={{ to={{
pathname: "/receipts" pathname: '/receipts'
}} }}
className={({ isActive }) => isActive ? "border border-secondary shadow-none btn p-1 m-0" : "border-0 shadow-none btn p-1 m-0"} className={({isActive}) =>
style={ ({ isActive }) => !isActive ? { width: "1.8rem", filter: "contrast(0.5)"} : {width: "1.8rem"}} isActive
state={ from } ? 'border border-secondary shadow-none btn p-1 m-0'
: 'border-0 shadow-none btn p-1 m-0'
}
style={({isActive}) =>
!isActive
? {width: '1.8rem', filter: 'contrast(0.5)'}
: {width: '1.8rem'}
}
state={from}
> >
<CustomTooltip <CustomTooltip
tooltipText='Receipts' tooltipText="Receipts"
tooltipId='etherscan-nav-receipts' tooltipId="etherscan-nav-receipts"
placement='bottom' placement="bottom"
> >
<i className="fas fa-receipt"></i> <i className="fas fa-receipt"></i>
</CustomTooltip> </CustomTooltip>
@ -57,21 +73,29 @@ const ReceiptsIcon: React.FC<IconProps> = ({ from }: IconProps) => {
) )
} }
const SettingsIcon: React.FC<IconProps> = ({ from }: IconProps) => { const SettingsIcon: React.FC<IconProps> = ({from}: IconProps) => {
return ( return (
<NavLink <NavLink
data-id="settings" data-id="settings"
to={{ to={{
pathname: "/settings" pathname: '/settings'
}} }}
className={({ isActive }) => isActive ? "border border-secondary shadow-none btn p-1 m-0" : "border-0 shadow-none btn p-1 m-0"} className={({isActive}) =>
style={ ({ isActive }) => !isActive ? { width: "1.8rem", filter: "contrast(0.5)"} : {width: "1.8rem"}} isActive
state= {from} ? 'border border-secondary shadow-none btn p-1 m-0'
: 'border-0 shadow-none btn p-1 m-0'
}
style={({isActive}) =>
!isActive
? {width: '1.8rem', filter: 'contrast(0.5)'}
: {width: '1.8rem'}
}
state={from}
> >
<CustomTooltip <CustomTooltip
tooltipText='Settings' tooltipText="Settings"
tooltipId='etherscan-nav-settings' tooltipId="etherscan-nav-settings"
placement='bottom' placement="bottom"
> >
<i className="fas fa-cog"></i> <i className="fas fa-cog"></i>
</CustomTooltip> </CustomTooltip>
@ -79,10 +103,7 @@ const SettingsIcon: React.FC<IconProps> = ({ from }: IconProps) => {
) )
} }
export const HeaderWithSettings: React.FC<Props> = ({ export const HeaderWithSettings: React.FC<Props> = ({title = '', from}) => {
title = "",
from,
}) => {
return ( return (
<AppContext.Consumer> <AppContext.Consumer>
{() => ( {() => (

@ -1,5 +1,5 @@
import React from "react" import React from 'react'
import { CustomTooltip } from '@remix-ui/helper' import {CustomTooltip} from '@remix-ui/helper'
interface Props { interface Props {
text: string text: string
@ -23,10 +23,14 @@ export const SubmitButton: React.FC<Props> = ({
disabled={disable} disabled={disable}
> >
<CustomTooltip <CustomTooltip
tooltipText={disable ? "Fill in the valid value(s) and select a supported network" : "Click to proceed"} tooltipText={
tooltipId={'etherscan-submit-button-'+ dataId} disable
? 'Fill in the valid value(s) and select a supported network'
: 'Click to proceed'
}
tooltipId={'etherscan-submit-button-' + dataId}
tooltipTextClasses="border bg-light text-dark p-1 pr-3" tooltipTextClasses="border bg-light text-dark p-1 pr-3"
placement='bottom' placement="bottom"
> >
<div> <div>
{!isSubmitting && text} {!isSubmitting && text}

@ -1,4 +1,4 @@
import { useState } from "react" import {useState} from 'react'
export function useLocalStorage(key: string, initialValue: any) { export function useLocalStorage(key: string, initialValue: any) {
// State to store our value // State to store our value

@ -1,6 +1,6 @@
import React, { PropsWithChildren } from "react" import React, {PropsWithChildren} from 'react'
import { HeaderWithSettings } from "../components" import {HeaderWithSettings} from '../components'
interface Props { interface Props {
from: string from: string

@ -1,13 +1,8 @@
import React from "react" import React from 'react'
import { import {HashRouter as Router, Route, Routes, RouteProps} from 'react-router-dom'
HashRouter as Router,
Route,
Routes,
RouteProps,
} from "react-router-dom"
import { ErrorView, HomeView, ReceiptsView, CaptureKeyView } from "./views" import {ErrorView, HomeView, ReceiptsView, CaptureKeyView} from './views'
import { DefaultLayout } from "./layouts" import {DefaultLayout} from './layouts'
interface Props extends RouteProps { interface Props extends RouteProps {
component: any // TODO: new (props: any) => React.Component component: any // TODO: new (props: any) => React.Component
@ -16,7 +11,7 @@ interface Props extends RouteProps {
export const DisplayRoutes = () => ( export const DisplayRoutes = () => (
<Router> <Router>
<Routes> <Routes>
<Route <Route
path="/" path="/"
element={ element={
@ -25,8 +20,7 @@ export const DisplayRoutes = () => (
</DefaultLayout> </DefaultLayout>
} }
/> />
<Route path="/error" <Route path="/error" element={<ErrorView />} />
element={<ErrorView />} />
<Route <Route
path="/receipts" path="/receipts"
element={ element={

@ -1,75 +1,79 @@
import React, { useState } from "react" import React, {useState} from 'react'
import { Formik, ErrorMessage, Field } from "formik" import {Formik, ErrorMessage, Field} from 'formik'
import { useNavigate, useLocation } from "react-router-dom" import {useNavigate, useLocation} from 'react-router-dom'
import { AppContext } from "../AppContext" import {AppContext} from '../AppContext'
import { SubmitButton } from "../components" import {SubmitButton} from '../components'
export const CaptureKeyView: React.FC = () => { export const CaptureKeyView: React.FC = () => {
const location = useLocation() const location = useLocation()
const navigate = useNavigate() const navigate = useNavigate()
const [msg, setMsg] = useState("") const [msg, setMsg] = useState('')
return ( return (
<AppContext.Consumer> <AppContext.Consumer>
{({ apiKey, clientInstance, setAPIKey }) => { {({apiKey, clientInstance, setAPIKey}) => {
if (!apiKey) setMsg('Please provide a 34-character API key to continue') if (!apiKey) setMsg('Please provide a 34-character API key to continue')
return ( return (
<div> <div>
<Formik <Formik
initialValues={{ apiKey }} initialValues={{apiKey}}
validate={(values) => { validate={(values) => {
const errors = {} as any const errors = {} as any
if (!values.apiKey) { if (!values.apiKey) {
errors.apiKey = "Required" errors.apiKey = 'Required'
} else if (values.apiKey.length !== 34) { } else if (values.apiKey.length !== 34) {
errors.apiKey = "API key should be 34 characters long" errors.apiKey = 'API key should be 34 characters long'
} }
return errors return errors
}} }}
onSubmit={(values) => { onSubmit={(values) => {
const apiKey = values.apiKey const apiKey = values.apiKey
if (apiKey.length === 34) { if (apiKey.length === 34) {
setAPIKey(values.apiKey) setAPIKey(values.apiKey)
navigate((location && location.state ? location.state : '/')) navigate(location && location.state ? location.state : '/')
} }
}} }}
> >
{({ errors, touched, handleSubmit }) => ( {({errors, touched, handleSubmit}) => (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="form-group mb-2"> <div className="form-group mb-2">
<label htmlFor="apikey">API Key</label> <label htmlFor="apikey">API Key</label>
<Field <Field
className={ className={
errors.apiKey && touched.apiKey errors.apiKey && touched.apiKey
? "form-control form-control-sm is-invalid" ? 'form-control form-control-sm is-invalid'
: "form-control form-control-sm" : 'form-control form-control-sm'
} }
type="password" type="password"
name="apiKey" name="apiKey"
placeholder="e.g. GM1T20XY6JGSAPWKDCYZ7B2FJXKTJRFVGZ" placeholder="e.g. GM1T20XY6JGSAPWKDCYZ7B2FJXKTJRFVGZ"
/> />
<ErrorMessage <ErrorMessage
className="invalid-feedback" className="invalid-feedback"
name="apiKey" name="apiKey"
component="div" component="div"
/> />
</div> </div>
<div> <div>
<SubmitButton text="Save" dataId="save-api-key" disable={errors && errors.apiKey ? true : false } /> <SubmitButton
</div> text="Save"
</form> dataId="save-api-key"
)} disable={errors && errors.apiKey ? true : false}
</Formik> />
</div>
</form>
)}
</Formik>
<div <div
data-id="api-key-result" data-id="api-key-result"
className="text-primary mt-4 text-center" className="text-primary mt-4 text-center"
style={{fontSize: "0.8em"}} style={{fontSize: '0.8em'}}
dangerouslySetInnerHTML={{ __html: msg }} dangerouslySetInnerHTML={{__html: msg}}
/> />
</div> </div>
) )
}} }}
</AppContext.Consumer> </AppContext.Consumer>

@ -1,4 +1,4 @@
import React from "react" import React from 'react'
export const ErrorView: React.FC = () => { export const ErrorView: React.FC = () => {
return ( return (
@ -11,7 +11,7 @@ export const ErrorView: React.FC = () => {
/> />
<h5>Sorry, something unexpected happened.</h5> <h5>Sorry, something unexpected happened.</h5>
<h5> <h5>
Please raise an issue:{" "} Please raise an issue:{' '}
<a <a
className="text-danger" className="text-danger"
href="https://github.com/ethereum/remix-project/issues" href="https://github.com/ethereum/remix-project/issues"

@ -1,20 +1,20 @@
import React from "react" import React from 'react'
import { Navigate } from "react-router-dom" import {Navigate} from 'react-router-dom'
import { AppContext } from "../AppContext" import {AppContext} from '../AppContext'
import { Receipt } from "../types" import {Receipt} from '../types'
import { VerifyView } from "./VerifyView" import {VerifyView} from './VerifyView'
export const HomeView: React.FC = () => { export const HomeView: React.FC = () => {
return ( return (
<AppContext.Consumer> <AppContext.Consumer>
{({ apiKey, clientInstance, setReceipts, receipts, contracts }) => { {({apiKey, clientInstance, setReceipts, receipts, contracts}) => {
return !apiKey ? ( return !apiKey ? (
<Navigate <Navigate
to={{ to={{
pathname: "/settings" pathname: '/settings'
}} }}
/> />
) : ( ) : (
@ -28,8 +28,7 @@ export const HomeView: React.FC = () => {
}} }}
/> />
) )
} }}
}
</AppContext.Consumer> </AppContext.Consumer>
) )
} }

@ -1,13 +1,18 @@
import React, { useState } from "react" import React, {useState} from 'react'
import { Formik, ErrorMessage, Field } from "formik" import {Formik, ErrorMessage, Field} from 'formik'
import { getEtherScanApi, getNetworkName, getReceiptStatus, getProxyContractReceiptStatus } from "../utils" import {
import { Receipt } from "../types" getEtherScanApi,
import { AppContext } from "../AppContext" getNetworkName,
import { SubmitButton } from "../components" getReceiptStatus,
import { Navigate } from "react-router-dom" getProxyContractReceiptStatus
import { Button } from "react-bootstrap" } from '../utils'
import { CustomTooltip } from '@remix-ui/helper' import {Receipt} from '../types'
import {AppContext} from '../AppContext'
import {SubmitButton} from '../components'
import {Navigate} from 'react-router-dom'
import {Button} from 'react-bootstrap'
import {CustomTooltip} from '@remix-ui/helper'
interface FormValues { interface FormValues {
receiptGuid: string receiptGuid: string
@ -23,11 +28,11 @@ export const ReceiptsView: React.FC = () => {
apiKey: string apiKey: string
) => { ) => {
try { try {
const { network, networkId } = await getNetworkName(clientInstance) const {network, networkId} = await getNetworkName(clientInstance)
if (network === "vm") { if (network === 'vm') {
setResults({ setResults({
succeed: false, succeed: false,
message: "Cannot verify in the selected network" message: 'Cannot verify in the selected network'
}) })
return return
} }
@ -51,7 +56,9 @@ export const ReceiptsView: React.FC = () => {
) )
setResults({ setResults({
succeed: result.status === '1' ? true : false, succeed: result.status === '1' ? true : false,
message: result.result || (result.status === '0' ? 'Verification failed' : result.message) message:
result.result ||
(result.status === '0' ? 'Verification failed' : result.message)
}) })
} catch (error: any) { } catch (error: any) {
setResults({ setResults({
@ -63,21 +70,21 @@ export const ReceiptsView: React.FC = () => {
return ( return (
<AppContext.Consumer> <AppContext.Consumer>
{({ apiKey, clientInstance, receipts, setReceipts }) => { {({apiKey, clientInstance, receipts, setReceipts}) => {
return !apiKey ? ( return !apiKey ? (
<Navigate <Navigate
to={{ to={{
pathname: "/settings" pathname: '/settings'
}} }}
/> />
) : ( ) : (
<div> <div>
<Formik <Formik
initialValues={{ receiptGuid: "" }} initialValues={{receiptGuid: ''}}
validate={(values) => { validate={(values) => {
const errors = {} as any const errors = {} as any
if (!values.receiptGuid) { if (!values.receiptGuid) {
errors.receiptGuid = "Required" errors.receiptGuid = 'Required'
} }
return errors return errors
}} }}
@ -85,15 +92,15 @@ export const ReceiptsView: React.FC = () => {
onGetReceiptStatus(values, clientInstance, apiKey) onGetReceiptStatus(values, clientInstance, apiKey)
} }
> >
{({ errors, touched, handleSubmit, handleChange }) => ( {({errors, touched, handleSubmit, handleChange}) => (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="form-group mb-2"> <div className="form-group mb-2">
<label htmlFor="receiptGuid">Receipt GUID</label> <label htmlFor="receiptGuid">Receipt GUID</label>
<Field <Field
className={ className={
errors.receiptGuid && touched.receiptGuid errors.receiptGuid && touched.receiptGuid
? "form-control form-control-sm is-invalid" ? 'form-control form-control-sm is-invalid'
: "form-control form-control-sm" : 'form-control form-control-sm'
} }
type="text" type="text"
name="receiptGuid" name="receiptGuid"
@ -115,37 +122,63 @@ export const ReceiptsView: React.FC = () => {
handleChange(e) handleChange(e)
if (e.target.checked) setIsProxyContractReceipt(true) if (e.target.checked) setIsProxyContractReceipt(true)
else setIsProxyContractReceipt(false) else setIsProxyContractReceipt(false)
}} }}
/> />
<label className="form-check-label custom-control-label" htmlFor="isProxyReceipt">It's a proxy contract GUID</label> <label
className="form-check-label custom-control-label"
htmlFor="isProxyReceipt"
>
It's a proxy contract GUID
</label>
</div> </div>
<SubmitButton text="Check" disable = {!touched.receiptGuid || (touched.receiptGuid && errors.receiptGuid) ? true : false} /> <SubmitButton
text="Check"
disable={
!touched.receiptGuid ||
(touched.receiptGuid && errors.receiptGuid)
? true
: false
}
/>
</form> </form>
)} )}
</Formik> </Formik>
<div <div
className={results['succeed'] ? "text-success mt-3 text-center" : "text-danger mt-3 text-center"} className={
dangerouslySetInnerHTML={{ __html: results.message ? results.message : '' }} results['succeed']
? 'text-success mt-3 text-center'
: 'text-danger mt-3 text-center'
}
dangerouslySetInnerHTML={{
__html: results.message ? results.message : ''
}}
/> />
<ReceiptsTable receipts={receipts} /><br/> <ReceiptsTable receipts={receipts} />
<br />
<CustomTooltip <CustomTooltip
tooltipText="Clear the list of receipts" tooltipText="Clear the list of receipts"
tooltipId='etherscan-clear-receipts' tooltipId="etherscan-clear-receipts"
placement='bottom' placement="bottom"
> >
<Button className="btn-sm" onClick={() => { setReceipts([]) }} >Clear</Button> <Button
className="btn-sm"
onClick={() => {
setReceipts([])
}}
>
Clear
</Button>
</CustomTooltip> </CustomTooltip>
</div> </div>
) )
} }}
}
</AppContext.Consumer> </AppContext.Consumer>
) )
} }
const ReceiptsTable: React.FC<{ receipts: Receipt[] }> = ({ receipts }) => { const ReceiptsTable: React.FC<{receipts: Receipt[]}> = ({receipts}) => {
return ( return (
<div className="table-responsive"> <div className="table-responsive">
<h6>Receipts</h6> <h6>Receipts</h6>
@ -162,20 +195,35 @@ const ReceiptsTable: React.FC<{ receipts: Receipt[] }> = ({ receipts }) => {
receipts.map((item: Receipt, index) => { receipts.map((item: Receipt, index) => {
return ( return (
<tr key={item.guid}> <tr key={item.guid}>
<td className={(item.status === 'Pass - Verified' || item.status === 'Successfully Updated') <td
? 'text-success' : (item.status === 'Pending in queue' className={
? 'text-warning' : (item.status === 'Already Verified' item.status === 'Pass - Verified' ||
? 'text-info': 'text-secondary'))}> item.status === 'Successfully Updated'
{item.status} ? 'text-success'
{item.status === 'Successfully Updated' && <CustomTooltip : item.status === 'Pending in queue'
placement={'bottom'} ? 'text-warning'
tooltipClasses="text-wrap" : item.status === 'Already Verified'
tooltipId="etherscan-receipt-proxy-status" ? 'text-info'
tooltipText={item.message} : 'text-secondary'
>
<i style={{ fontSize: 'small' }} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i>
</CustomTooltip>
} }
>
{item.status}
{item.status === 'Successfully Updated' && (
<CustomTooltip
placement={'bottom'}
tooltipClasses="text-wrap"
tooltipId="etherscan-receipt-proxy-status"
tooltipText={item.message}
>
<i
style={{fontSize: 'small'}}
className={
'ml-1 fal fa-info-circle align-self-center'
}
aria-hidden="true"
></i>
</CustomTooltip>
)}
</td> </td>
<td>{item.guid}</td> <td>{item.guid}</td>
</tr> </tr>

@ -1,16 +1,14 @@
import React, { useEffect, useRef, useState } from "react" import React, {useEffect, useRef, useState} from 'react'
import Web3 from 'web3' import Web3 from 'web3'
import { import {PluginClient} from '@remixproject/plugin'
PluginClient, import {CustomTooltip} from '@remix-ui/helper'
} from "@remixproject/plugin" import {Formik, ErrorMessage, Field} from 'formik'
import { CustomTooltip } from '@remix-ui/helper'
import { Formik, ErrorMessage, Field } from "formik"
import { SubmitButton } from "../components" import {SubmitButton} from '../components'
import { Receipt } from "../types" import {Receipt} from '../types'
import { verify } from "../utils/verify" import {verify} from '../utils/verify'
import { etherscanScripts } from "@remix-project/remix-ws-templates" import {etherscanScripts} from '@remix-project/remix-ws-templates'
interface Props { interface Props {
client: PluginClient client: PluginClient
@ -29,11 +27,11 @@ export const VerifyView: React.FC<Props> = ({
apiKey, apiKey,
client, client,
contracts, contracts,
onVerifiedContract, onVerifiedContract
}) => { }) => {
const [results, setResults] = useState("") const [results, setResults] = useState('')
const [networkName, setNetworkName] = useState("Loading...") const [networkName, setNetworkName] = useState('Loading...')
const [selectedContract, setSelectedContract] = useState("") const [selectedContract, setSelectedContract] = useState('')
const [showConstructorArgs, setShowConstructorArgs] = useState(false) const [showConstructorArgs, setShowConstructorArgs] = useState(false)
const [isProxyContract, setIsProxyContract] = useState(false) const [isProxyContract, setIsProxyContract] = useState(false)
const [constructorInputs, setConstructorInputs] = useState([]) const [constructorInputs, setConstructorInputs] = useState([])
@ -41,13 +39,19 @@ export const VerifyView: React.FC<Props> = ({
useEffect(() => { useEffect(() => {
if (client && client.on) { if (client && client.on) {
client.on("blockchain" as any, 'networkStatus', (result) => { client.on('blockchain' as any, 'networkStatus', (result) => {
setNetworkName(`${result.network.name} ${result.network.id !== '-' ? `(Chain id: ${result.network.id})` : '(Not supported)'}`) setNetworkName(
`${result.network.name} ${
result.network.id !== '-'
? `(Chain id: ${result.network.id})`
: '(Not supported)'
}`
)
}) })
} }
return () => { return () => {
// To fix memory leak // To fix memory leak
if (client && client.off) client.off("blockchain" as any, 'networkStatus') if (client && client.off) client.off('blockchain' as any, 'networkStatus')
} }
}, [client]) }, [client])
@ -56,36 +60,53 @@ export const VerifyView: React.FC<Props> = ({
}, [contracts]) }, [contracts])
const updateConsFields = (contractName) => { const updateConsFields = (contractName) => {
client.call("compilerArtefacts" as any, "getArtefactsByContractName", contractName).then((result) => { client
const { artefact } = result .call(
if (artefact && artefact.abi && artefact.abi[0] && artefact.abi[0].type && artefact.abi[0].type === 'constructor' && artefact.abi[0].inputs.length > 0) { 'compilerArtefacts' as any,
setConstructorInputs(artefact.abi[0].inputs) 'getArtefactsByContractName',
setShowConstructorArgs(true) contractName
} else { )
setConstructorInputs([]) .then((result) => {
setShowConstructorArgs(false) const {artefact} = result
} if (
}) artefact &&
artefact.abi &&
artefact.abi[0] &&
artefact.abi[0].type &&
artefact.abi[0].type === 'constructor' &&
artefact.abi[0].inputs.length > 0
) {
setConstructorInputs(artefact.abi[0].inputs)
setShowConstructorArgs(true)
} else {
setConstructorInputs([])
setShowConstructorArgs(false)
}
})
} }
const onVerifyContract = async (values: FormValues) => { const onVerifyContract = async (values: FormValues) => {
const compilationResult = (await client.call( const compilationResult = (await client.call(
"solidity", 'solidity',
"getCompilationResult" 'getCompilationResult'
)) as any )) as any
if (!compilationResult) { if (!compilationResult) {
throw new Error("no compilation result available") throw new Error('no compilation result available')
} }
const constructorValues = [] const constructorValues = []
for (const key in values) { for (const key in values) {
if (key.startsWith('contractArgValue')) constructorValues.push(values[key]) if (key.startsWith('contractArgValue'))
constructorValues.push(values[key])
} }
const web3 = new Web3() const web3 = new Web3()
const constructorTypes = constructorInputs.map(e => e.type) const constructorTypes = constructorInputs.map((e) => e.type)
let contractArguments = web3.eth.abi.encodeParameters(constructorTypes, constructorValues) let contractArguments = web3.eth.abi.encodeParameters(
contractArguments = contractArguments.replace("0x", "") constructorTypes,
constructorValues
)
contractArguments = contractArguments.replace('0x', '')
verificationResult.current = await verify( verificationResult.current = await verify(
apiKey, apiKey,
@ -98,7 +119,7 @@ export const VerifyView: React.FC<Props> = ({
values.expectedImplAddress, values.expectedImplAddress,
client, client,
onVerifiedContract, onVerifiedContract,
setResults, setResults
) )
setResults(verificationResult.current['message']) setResults(verificationResult.current['message'])
} }
@ -107,76 +128,90 @@ export const VerifyView: React.FC<Props> = ({
<div> <div>
<Formik <Formik
initialValues={{ initialValues={{
contractName: "", contractName: '',
contractAddress: "" contractAddress: ''
}} }}
validate={(values) => { validate={(values) => {
const errors = {} as any const errors = {} as any
if (!values.contractName) { if (!values.contractName) {
errors.contractName = "Required" errors.contractName = 'Required'
} }
if (!values.contractAddress) { if (!values.contractAddress) {
errors.contractAddress = "Required" errors.contractAddress = 'Required'
} }
if (values.contractAddress.trim() === "" || !values.contractAddress.startsWith('0x') if (
|| values.contractAddress.length !== 42) { values.contractAddress.trim() === '' ||
errors.contractAddress = "Please enter a valid contract address" !values.contractAddress.startsWith('0x') ||
values.contractAddress.length !== 42
) {
errors.contractAddress = 'Please enter a valid contract address'
} }
return errors return errors
}} }}
onSubmit={(values) => onVerifyContract(values)} onSubmit={(values) => onVerifyContract(values)}
> >
{({ errors, touched, handleSubmit, handleChange, isSubmitting }) => { {({errors, touched, handleSubmit, handleChange, isSubmitting}) => {
return (<form onSubmit={handleSubmit}> return (
<div className="form-group"> <form onSubmit={handleSubmit}>
<label htmlFor="network">Selected Network</label> <div className="form-group">
<CustomTooltip <label htmlFor="network">Selected Network</label>
tooltipText="Network is fetched from 'Deploy and Run Transactions' plugin's ENVIRONMENT field" <CustomTooltip
tooltipId='etherscan-impl-address2' tooltipText="Network is fetched from 'Deploy and Run Transactions' plugin's ENVIRONMENT field"
placement='bottom' tooltipId="etherscan-impl-address2"
> placement="bottom"
>
<Field
className="form-control"
type="text"
name="network"
value={networkName}
disabled={true}
/>
</CustomTooltip>
</div>
<div className="form-group">
<label htmlFor="contractName">Contract Name</label>
<Field <Field
className="form-control" as="select"
type="text" className={
name="network" errors.contractName &&
value={networkName} touched.contractName &&
disabled={true} contracts.length
/> ? 'form-control is-invalid'
</CustomTooltip> : 'form-control'
</div> }
<div className="form-group"> name="contractName"
<label htmlFor="contractName">Contract Name</label> onChange={async (e) => {
<Field handleChange(e)
as="select" setSelectedContract(e.target.value)
updateConsFields(e.target.value)
}}
>
<option disabled={true} value="">
{contracts.length
? 'Select a contract'
: `--- No compiled contracts ---`}
</option>
{contracts.map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</Field>
<ErrorMessage
className="invalid-feedback"
name="contractName"
component="div"
/>
</div>
<div
className={ className={
errors.contractName && touched.contractName && contracts.length showConstructorArgs
? "form-control is-invalid" ? 'form-group d-block'
: "form-control" : 'form-group d-none'
} }
name="contractName"
onChange={async (e) => {
handleChange(e)
setSelectedContract(e.target.value)
updateConsFields(e.target.value)
}}
> >
<option disabled={true} value=""> <label>Constructor Arguments</label>
{ contracts.length ? 'Select a contract' : `--- No compiled contracts ---` }
</option>
{contracts.map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</Field>
<ErrorMessage
className="invalid-feedback"
name="contractName"
component="div"
/>
</div>
<div className={ showConstructorArgs ? 'form-group d-block': 'form-group d-none' } >
<label>Constructor Arguments</label>
{constructorInputs.map((item, index) => { {constructorInputs.map((item, index) => {
return ( return (
<div className="d-flex"> <div className="d-flex">
@ -191,7 +226,7 @@ export const VerifyView: React.FC<Props> = ({
<CustomTooltip <CustomTooltip
tooltipText={`value of ${item.name}`} tooltipText={`value of ${item.name}`}
tooltipId={`etherscan-constructor-value${index}`} tooltipId={`etherscan-constructor-value${index}`}
placement='top' placement="top"
> >
<Field <Field
className="form-control m-1" className="form-control m-1"
@ -202,94 +237,120 @@ export const VerifyView: React.FC<Props> = ({
/> />
</CustomTooltip> </CustomTooltip>
</div> </div>
)} )
)} })}
</div>
</div> <div className="form-group">
<div className="form-group"> <label htmlFor="contractAddress">Contract Address</label>
<label htmlFor="contractAddress">Contract Address</label>
<Field
className={
errors.contractAddress && touched.contractAddress
? "form-control is-invalid"
: "form-control"
}
type="text"
name="contractAddress"
placeholder="e.g. 0x11b79afc03baf25c631dd70169bb6a3160b2706e"
/>
<ErrorMessage
className="invalid-feedback"
name="contractAddress"
component="div"
/>
<div className="d-flex mb-2 custom-control custom-checkbox">
<Field <Field
className="custom-control-input" className={
type="checkbox" errors.contractAddress && touched.contractAddress
name="isProxy" ? 'form-control is-invalid'
id="isProxy" : 'form-control'
onChange={async (e) => { }
handleChange(e) type="text"
if (e.target.checked) setIsProxyContract(true) name="contractAddress"
else setIsProxyContract(false) placeholder="e.g. 0x11b79afc03baf25c631dd70169bb6a3160b2706e"
}} />
<ErrorMessage
className="invalid-feedback"
name="contractAddress"
component="div"
/> />
<label className="form-check-label custom-control-label" htmlFor="isProxy">It's a proxy contract address</label> <div className="d-flex mb-2 custom-control custom-checkbox">
<Field
className="custom-control-input"
type="checkbox"
name="isProxy"
id="isProxy"
onChange={async (e) => {
handleChange(e)
if (e.target.checked) setIsProxyContract(true)
else setIsProxyContract(false)
}}
/>
<label
className="form-check-label custom-control-label"
htmlFor="isProxy"
>
It's a proxy contract address
</label>
</div>
</div> </div>
</div> <div
<div className={ isProxyContract ? 'form-group d-block': 'form-group d-none' }> className={
<label htmlFor="expectedImplAddress">Expected Implementation Address</label> isProxyContract ? 'form-group d-block' : 'form-group d-none'
}
>
<label htmlFor="expectedImplAddress">
Expected Implementation Address
</label>
<CustomTooltip
tooltipText="Providing expected implementation address enforces a check to ensure the returned implementation contract address is same as address picked up by the verifier"
tooltipId="etherscan-impl-address"
placement="bottom"
>
<Field
className="form-control"
type="text"
name="expectedImplAddress"
placeholder="verified implementation contract address"
/>
</CustomTooltip>
<i
style={{fontSize: 'x-small'}}
className={'ml-1 fal fa-info-circle align-self-center'}
aria-hidden="true"
></i>
<label>
{' '}
&nbsp;Make sure contract is already verified on Etherscan
</label>
</div>
<SubmitButton
dataId="verify-contract"
text="Verify"
isSubmitting={isSubmitting}
disable={
!contracts.length ||
!touched.contractName ||
!touched.contractAddress ||
(touched.contractName && errors.contractName) ||
(touched.contractAddress && errors.contractAddress) ||
networkName === 'VM (Not supported)'
? true
: false
}
/>
<br />
<CustomTooltip <CustomTooltip
tooltipText='Providing expected implementation address enforces a check to ensure the returned implementation contract address is same as address picked up by the verifier' tooltipText="Generate the required TS scripts to verify a contract on Etherscan"
tooltipId='etherscan-impl-address' tooltipId="etherscan-generate-scripts"
placement='bottom' placement="bottom"
> >
<Field <button
className="form-control" type="button"
type="text" className="mr-2 mb-2 py-1 px-2 btn btn-secondary btn-block"
name="expectedImplAddress" onClick={async () => {
placeholder="verified implementation contract address" etherscanScripts(client)
/> }}
</CustomTooltip>
<i style={{ fontSize: 'x-small' }} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i>
<label> &nbsp;Make sure contract is already verified on Etherscan</label>
</div>
<SubmitButton dataId="verify-contract" text="Verify"
isSubmitting={isSubmitting}
disable={ !contracts.length ||
!touched.contractName ||
!touched.contractAddress ||
(touched.contractName && errors.contractName) ||
(touched.contractAddress && errors.contractAddress) ||
(networkName === 'VM (Not supported)')
? true
: false}
/>
<br/>
<CustomTooltip
tooltipText='Generate the required TS scripts to verify a contract on Etherscan'
tooltipId='etherscan-generate-scripts'
placement='bottom'
>
<button
type="button"
className="mr-2 mb-2 py-1 px-2 btn btn-secondary btn-block"
onClick={async () => {
etherscanScripts(client)
}}
> >
Generate Verification Scripts Generate Verification Scripts
</button> </button>
</CustomTooltip> </CustomTooltip>
</form>) </form>
)
}} }}
</Formik> </Formik>
<div <div
data-id="verify-result" data-id="verify-result"
className={verificationResult.current['succeed'] ? "text-success mt-4 text-center" : "text-danger mt-4 text-center"} className={
style={{fontSize: "0.8em"}} verificationResult.current['succeed']
dangerouslySetInnerHTML={{ __html: results }} ? 'text-success mt-4 text-center'
: 'text-danger mt-4 text-center'
}
style={{fontSize: '0.8em'}}
dangerouslySetInnerHTML={{__html: results}}
/> />
{/* <div style={{ display: "block", textAlign: "center", marginTop: "1em" }}> {/* <div style={{ display: "block", textAlign: "center", marginTop: "1em" }}>
<Link to="/receipts">View Receipts</Link> <Link to="/receipts">View Receipts</Link>

@ -1,7 +1,11 @@
import { StrictMode } from 'react'; import {StrictMode} from 'react'
import * as ReactDOM from 'react-dom'; import * as ReactDOM from 'react-dom'
import App from './app/app'
import App from './app/app'; ReactDOM.render(
<StrictMode>
ReactDOM.render(<StrictMode><App /></StrictMode>, document.getElementById('root')); <App />
</StrictMode>,
document.getElementById('root')
)

@ -1,7 +1,7 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const versionData = { const versionData = {
timestamp: Date.now(), timestamp: Date.now(),
@ -15,29 +15,29 @@ module.exports = composePlugins(withNx(), (config) => {
// add fallback for node modules // add fallback for node modules
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"), crypto: require.resolve('crypto-browserify'),
"stream": require.resolve("stream-browserify"), stream: require.resolve('stream-browserify'),
"path": require.resolve("path-browserify"), path: require.resolve('path-browserify'),
"http": require.resolve("stream-http"), http: require.resolve('stream-http'),
"https": require.resolve("https-browserify"), https: require.resolve('https-browserify'),
"constants": require.resolve("constants-browserify"), constants: require.resolve('constants-browserify'),
"os": false, //require.resolve("os-browserify/browser"), os: false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"), timers: false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"), zlib: require.resolve('browserify-zlib'),
"fs": false, fs: false,
"module": false, module: false,
"tls": false, tls: false,
"net": false, net: false,
"readline": false, readline: false,
"child_process": false, child_process: false,
"buffer": require.resolve("buffer/"), buffer: require.resolve('buffer/'),
"vm": require.resolve('vm-browserify'), vm: require.resolve('vm-browserify')
} }
// add externals // add externals
config.externals = { config.externals = {
...config.externals, ...config.externals,
solc: 'solc', solc: 'solc'
} }
// add public path // add public path
@ -47,26 +47,24 @@ module.exports = composePlugins(withNx(), (config) => {
config.output.filename = `[name].plugin-etherscan.${versionData.timestamp}.js` config.output.filename = `[name].plugin-etherscan.${versionData.timestamp}.js`
config.output.chunkFilename = `[name].plugin-etherscan.${versionData.timestamp}.js` config.output.chunkFilename = `[name].plugin-etherscan.${versionData.timestamp}.js`
// add copy & provide plugin // add copy & provide plugin
config.plugins.push( config.plugins.push(
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}) })
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer // set minimizer
config.optimization.minimizer = [ config.optimization.minimizer = [
new TerserPlugin({ new TerserPlugin({
@ -76,17 +74,17 @@ module.exports = composePlugins(withNx(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
config.watchOptions = { config.watchOptions = {
ignored: /node_modules/ ignored: /node_modules/
} }
return config; return config
}); })

@ -7,7 +7,7 @@ module.exports = {
globals_path: '', globals_path: '',
test_settings: { test_settings: {
default: { 'default': {
selenium_port: 4444, selenium_port: 4444,
selenium_host: 'localhost', selenium_host: 'localhost',
globals: { globals: {
@ -20,31 +20,35 @@ module.exports = {
on_failure: true, on_failure: true,
on_error: true on_error: true
}, },
exclude: ['dist/apps/remix-ide-e2e/src/tests/runAndDeploy.test.js', 'dist/apps/remix-ide-e2e/src/tests/pluginManager.test.ts'] exclude: [
'dist/apps/remix-ide-e2e/src/tests/runAndDeploy.test.js',
'dist/apps/remix-ide-e2e/src/tests/pluginManager.test.ts'
]
}, },
chrome: { 'chrome': {
desiredCapabilities: { desiredCapabilities: {
browserName: 'chrome', 'browserName': 'chrome',
javascriptEnabled: true, 'javascriptEnabled': true,
acceptSslCerts: true, 'acceptSslCerts': true,
'goog:chromeOptions': { 'goog:chromeOptions': {
args: ['window-size=2560,1440', args: [
'start-fullscreen', 'window-size=2560,1440',
'--no-sandbox', 'start-fullscreen',
'--headless', '--no-sandbox',
'--verbose', '--headless',
"--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36", '--verbose',
'--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'
] ]
} }
} }
}, },
chromeDesktop: { 'chromeDesktop': {
desiredCapabilities: { desiredCapabilities: {
browserName: 'chrome', 'browserName': 'chrome',
javascriptEnabled: true, 'javascriptEnabled': true,
acceptSslCerts: true, 'acceptSslCerts': true,
'goog:chromeOptions': { 'goog:chromeOptions': {
args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox'] args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox']
} }
@ -53,40 +57,39 @@ module.exports = {
'chrome-runAndDeploy': { 'chrome-runAndDeploy': {
desiredCapabilities: { desiredCapabilities: {
browserName: 'chrome', 'browserName': 'chrome',
javascriptEnabled: true, 'javascriptEnabled': true,
acceptSslCerts: true, 'acceptSslCerts': true,
'goog:chromeOptions': { 'goog:chromeOptions': {
args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox', '--headless', '--verbose'] args: [
'window-size=2560,1440',
'start-fullscreen',
'--no-sandbox',
'--headless',
'--verbose'
]
} }
} }
}, },
firefoxDesktop: { 'firefoxDesktop': {
desiredCapabilities: { desiredCapabilities: {
browserName: 'firefox', 'browserName': 'firefox',
javascriptEnabled: true, 'javascriptEnabled': true,
acceptSslCerts: true, 'acceptSslCerts': true,
'moz:firefoxOptions': { 'moz:firefoxOptions': {
args: [ args: ['-width=2560', '-height=1440']
'-width=2560',
'-height=1440'
]
} }
} }
}, },
firefox: { 'firefox': {
desiredCapabilities: { desiredCapabilities: {
browserName: 'firefox', 'browserName': 'firefox',
javascriptEnabled: true, 'javascriptEnabled': true,
acceptSslCerts: true, 'acceptSslCerts': true,
'moz:firefoxOptions': { 'moz:firefoxOptions': {
args: [ args: ['-headless', '-width=2560', '-height=1440']
'-headless',
'-width=2560',
'-height=1440'
]
} }
} }
} }

@ -1,38 +1,55 @@
import React, {useEffect, useState} from 'react'
import React, { useEffect, useState } from 'react' import {RemixPlugin} from './Client'
import { RemixPlugin } from './Client' import {Logger} from './logger'
import { Logger } from './logger' import {filePanelProfile} from '@remixproject/plugin-api'
import { filePanelProfile } from '@remixproject/plugin-api' import {filSystemProfile} from '@remixproject/plugin-api'
import { filSystemProfile } from '@remixproject/plugin-api' import {dGitProfile} from '@remixproject/plugin-api'
import { dGitProfile } from '@remixproject/plugin-api' import {editorProfile} from '@remixproject/plugin-api'
import { editorProfile } from '@remixproject/plugin-api' import {settingsProfile} from '@remixproject/plugin-api'
import { settingsProfile } from '@remixproject/plugin-api' import {networkProfile} from '@remixproject/plugin-api'
import { networkProfile } from '@remixproject/plugin-api' import {udappProfile} from '@remixproject/plugin-api'
import { udappProfile } from '@remixproject/plugin-api' import {compilerProfile} from '@remixproject/plugin-api'
import { compilerProfile } from '@remixproject/plugin-api' import {contentImportProfile} from '@remixproject/plugin-api'
import { contentImportProfile } from '@remixproject/plugin-api' import {windowProfile} from '@remixproject/plugin-api'
import { windowProfile } from '@remixproject/plugin-api' import {pluginManagerProfile} from '@remixproject/plugin-api'
import { pluginManagerProfile } from '@remixproject/plugin-api' import {Profile} from '@remixproject/plugin-utils'
import { Profile } from '@remixproject/plugin-utils'
import './app.css' import './app.css'
const client = new RemixPlugin() const client = new RemixPlugin()
function App () { function App() {
const [payload, setPayload] = useState<string>('') const [payload, setPayload] = useState<string>('')
const [log, setLog] = useState<any>() const [log, setLog] = useState<any>()
const [started, setStarted] = useState<boolean>(false) const [started, setStarted] = useState<boolean>(false)
const [events, setEvents] = useState<any>() const [events, setEvents] = useState<any>()
const [profiles, setProfiles] = useState<Profile[]>([pluginManagerProfile, filePanelProfile, filSystemProfile, dGitProfile, networkProfile, settingsProfile, editorProfile, compilerProfile, udappProfile, contentImportProfile, windowProfile]) const [profiles, setProfiles] = useState<Profile[]>([
pluginManagerProfile,
filePanelProfile,
filSystemProfile,
dGitProfile,
networkProfile,
settingsProfile,
editorProfile,
compilerProfile,
udappProfile,
contentImportProfile,
windowProfile
])
const handleChange = ({ target }: any) => { const handleChange = ({target}: any) => {
setPayload(target.value) setPayload(target.value)
} }
useEffect(() => { useEffect(() => {
client.onload(async () => { client.onload(async () => {
const customProfiles = ['menuicons', 'tabs', 'solidityUnitTesting', 'hardhat-provider', 'notification'] const customProfiles = [
'menuicons',
'tabs',
'solidityUnitTesting',
'hardhat-provider',
'notification'
]
client.testCommand = async (data: any) => { client.testCommand = async (data: any) => {
console.log(data) console.log(data)
@ -44,7 +61,7 @@ function App () {
const p = await client.call('manager', 'getProfile', name) const p = await client.call('manager', 'getProfile', name)
addProfiles = [...addProfiles, p] addProfiles = [...addProfiles, p]
} }
setProfiles(profiles => [...profiles, ...addProfiles]) setProfiles((profiles) => [...profiles, ...addProfiles])
profiles.map((profile: Profile) => { profiles.map((profile: Profile) => {
if (profile.events) { if (profile.events) {
@ -77,8 +94,10 @@ function App () {
let ob: any = null let ob: any = null
try { try {
ob = JSON.parse(payload) ob = JSON.parse(payload)
if (ob && !Array.isArray(ob)) { ob = [ob] } if (ob && !Array.isArray(ob)) {
} catch (e) { } ob = [ob]
}
} catch (e) {}
const args = ob || [payload] const args = ob || [payload]
setStarted(true) setStarted(true)
setLog('') setLog('')
@ -97,13 +116,14 @@ function App () {
return ( return (
<div className="App container-fluid"> <div className="App container-fluid">
<h5>PLUGIN API TESTER</h5> <h5>PLUGIN API TESTER</h5>
<label id='callStatus'>{started ? <>start</> : <>stop</> }</label><br></br> <label id="callStatus">{started ? <>start</> : <>stop</>}</label>
<br></br>
<label>method results</label> <label>method results</label>
<Logger id='methods' log={log}></Logger> <Logger id="methods" log={log}></Logger>
<label>events</label> <label>events</label>
<Logger id='events' log={events}></Logger> <Logger id="events" log={events}></Logger>
<input <input
className='form-control w-100' className="form-control w-100"
type="text" type="text"
id="payload" id="payload"
placeholder="Enter payload here..." placeholder="Enter payload here..."
@ -113,14 +133,37 @@ function App () {
/> />
{profiles.map((profile: Profile) => { {profiles.map((profile: Profile) => {
const methods = profile.methods.map((method: string) => { const methods = profile.methods.map((method: string) => {
return <button data-id={`${profile.name}:${method}`} key={method} className='btn btn-primary btn-sm ml-1 mb-1' onClick={async () => await clientMethod(profile, method)}>{method}</button> return (
<button
data-id={`${profile.name}:${method}`}
key={method}
className="btn btn-primary btn-sm ml-1 mb-1"
onClick={async () => await clientMethod(profile, method)}
>
{method}
</button>
)
}) })
const events = profile.events ? profile.events.map((event: string) => { const events = profile.events
return <label key={event} className='m-1'>{event}</label> ? profile.events.map((event: string) => {
}) : null return (
return <div key={profile.name} className='small border-bottom'><label className='text-uppercase'>{profile.name}</label><br></br>{methods}<br></br>{events ? <label>EVENTS:</label> : null}{events}</div> <label key={event} className="m-1">
{event}
</label>
)
})
: null
return (
<div key={profile.name} className="small border-bottom">
<label className="text-uppercase">{profile.name}</label>
<br></br>
{methods}
<br></br>
{events ? <label>EVENTS:</label> : null}
{events}
</div>
)
})} })}
</div> </div>
) )
} }

@ -1,9 +1,13 @@
import React from 'react' import React from 'react'
interface loggerProps { interface loggerProps {
log: any, log: any
id: string id: string
} }
export const Logger: React.FC<loggerProps> = (props) => { export const Logger: React.FC<loggerProps> = (props) => {
return (<div id={props.id} className="jumbotron overflow-auto text-break mb-1 p-2">{props.log}</div>) return (
<div id={props.id} className="jumbotron overflow-auto text-break mb-1 p-2">
{props.log}
</div>
)
} }

@ -2,9 +2,12 @@
'use strict' 'use strict'
chrome.browserAction.onClicked.addListener(function (tab) { 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) { chrome.tabs.create(
// tab opened {url: chrome.extension.getURL('index.html')},
}) function (tab) {
// tab opened
}
)
}) })

@ -1,9 +1,9 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React from 'react' import React from 'react'
import { AbstractPanel } from './panel' import {AbstractPanel} from './panel'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { RemixPluginPanel } from '@remix-ui/panel' import {RemixPluginPanel} from '@remix-ui/panel'
import { PluginViewWrapper } from '@remix-ui/helper' import {PluginViewWrapper} from '@remix-ui/helper'
const profile = { const profile = {
name: 'hiddenPanel', name: 'hiddenPanel',
@ -16,35 +16,37 @@ const profile = {
export class HiddenPanel extends AbstractPanel { export class HiddenPanel extends AbstractPanel {
el: HTMLElement el: HTMLElement
dispatch: React.Dispatch<any> = () => {} dispatch: React.Dispatch<any> = () => {}
constructor () { constructor() {
super(profile) super(profile)
this.el = document.createElement('div') this.el = document.createElement('div')
this.el.setAttribute('class', 'pluginsContainer') this.el.setAttribute('class', 'pluginsContainer')
} }
addView (profile: any, view: any): void { addView(profile: any, view: any): void {
super.removeView(profile) super.removeView(profile)
super.addView(profile, view) super.addView(profile, view)
this.renderComponent() this.renderComponent()
} }
updateComponent (state: any) { updateComponent(state: any) {
return <RemixPluginPanel header={<></>} plugins={state.plugins}/> return <RemixPluginPanel header={<></>} plugins={state.plugins} />
} }
setDispatch (dispatch: React.Dispatch<any>) { setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch this.dispatch = dispatch
} }
render() { render() {
return ( return (
<div className='pluginsContainer'><PluginViewWrapper plugin={this} /></div> <div className="pluginsContainer">
); <PluginViewWrapper plugin={this} />
</div>
)
} }
renderComponent () { renderComponent() {
this.dispatch({ this.dispatch({
plugins: this.plugins, plugins: this.plugins
}) })
} }
} }

@ -1,8 +1,8 @@
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { AbstractPanel } from './panel' import {AbstractPanel} from './panel'
import { RemixPluginPanel } from '@remix-ui/panel' import {RemixPluginPanel} from '@remix-ui/panel'
import packageJson from '../../../../../package.json' import packageJson from '../../../../../package.json'
import { PluginViewWrapper } from '@remix-ui/helper' import {PluginViewWrapper} from '@remix-ui/helper'
const profile = { const profile = {
name: 'mainPanel', name: 'mainPanel',
@ -15,7 +15,7 @@ const profile = {
export class MainPanel extends AbstractPanel { export class MainPanel extends AbstractPanel {
element: HTMLDivElement element: HTMLDivElement
dispatch: React.Dispatch<any> = () => {} dispatch: React.Dispatch<any> = () => {}
constructor (config) { constructor(config) {
super(profile) super(profile)
this.element = document.createElement('div') this.element = document.createElement('div')
this.element.setAttribute('data-id', 'mainPanelPluginsContainer') this.element.setAttribute('data-id', 'mainPanelPluginsContainer')
@ -23,46 +23,53 @@ export class MainPanel extends AbstractPanel {
// this.config = config // this.config = config
} }
setDispatch (dispatch: React.Dispatch<any>) { setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch this.dispatch = dispatch
} }
onActivation () { onActivation() {
this.renderComponent() this.renderComponent()
} }
focus (name) { focus(name) {
this.emit('focusChanged', name) this.emit('focusChanged', name)
super.focus(name) super.focus(name)
this.renderComponent() this.renderComponent()
} }
addView (profile, view) { addView(profile, view) {
super.addView(profile, view) super.addView(profile, view)
this.renderComponent() this.renderComponent()
} }
removeView (profile) { removeView(profile) {
super.removeView(profile) super.removeView(profile)
this.renderComponent() this.renderComponent()
} }
async showContent (name) { async showContent(name) {
super.showContent(name) super.showContent(name)
this.renderComponent() this.renderComponent()
} }
renderComponent () { renderComponent() {
this.dispatch({ this.dispatch({
plugins: this.plugins plugins: this.plugins
}) })
} }
render() { render() {
return <div style={{height: '100%', width: '100%'}} data-id='mainPanelPluginsContainer'><PluginViewWrapper plugin={this} /></div> return (
<div
style={{height: '100%', width: '100%'}}
data-id="mainPanelPluginsContainer"
>
<PluginViewWrapper plugin={this} />
</div>
)
} }
updateComponent (state: any) { updateComponent(state: any) {
return <RemixPluginPanel header={<></>} plugins={state.plugins}/> return <RemixPluginPanel header={<></>} plugins={state.plugins} />
} }
} }

@ -1,16 +1,18 @@
import { RemixApp } from '@remix-ui/app' import {RemixApp} from '@remix-ui/app'
import React, { useEffect, useRef, useState } from 'react' import React, {useEffect, useRef, useState} from 'react'
import { render } from 'react-dom' import {render} from 'react-dom'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { fileSystem, fileSystems } from '../files/fileSystem' import {fileSystem, fileSystems} from '../files/fileSystem'
import { indexedDBFileSystem } from '../files/filesystems/indexedDB' import {indexedDBFileSystem} from '../files/filesystems/indexedDB'
import { localStorageFS } from '../files/filesystems/localStorage' import {localStorageFS} from '../files/filesystems/localStorage'
import { fileSystemUtility, migrationTestData } from '../files/filesystems/fileSystemUtility' import {
fileSystemUtility,
migrationTestData
} from '../files/filesystems/fileSystemUtility'
import './styles/preload.css' import './styles/preload.css'
const _paq = window._paq = window._paq || [] const _paq = (window._paq = window._paq || [])
export const Preload = () => { export const Preload = () => {
const [supported, setSupported] = useState<boolean>(true) const [supported, setSupported] = useState<boolean>(true)
const [error, setError] = useState<boolean>(false) const [error, setError] = useState<boolean>(false)
const [showDownloader, setShowDownloader] = useState<boolean>(false) const [showDownloader, setShowDownloader] = useState<boolean>(false)
@ -18,45 +20,74 @@ export const Preload = () => {
const remixIndexedDB = useRef<fileSystem>(new indexedDBFileSystem()) const remixIndexedDB = useRef<fileSystem>(new indexedDBFileSystem())
const localStorageFileSystem = useRef<fileSystem>(new localStorageFS()) const localStorageFileSystem = useRef<fileSystem>(new localStorageFS())
// url parameters to e2e test the fallbacks and error warnings // 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 testmigrationFallback = useRef<boolean>(
const testmigrationResult = useRef<boolean>(window.location.hash.includes('e2e_testmigration=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:') window.location.hash.includes('e2e_testmigration_fallback=true') &&
const testBlockStorage = useRef<boolean>(window.location.hash.includes('e2e_testblock_storage=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:') 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() { function loadAppComponent() {
import('../../app').then((AppComponent) => { import('../../app')
const appComponent = new AppComponent.default() .then((AppComponent) => {
appComponent.run().then(() => { const appComponent = new AppComponent.default()
render( appComponent.run().then(() => {
<> render(
<RemixApp app={appComponent} /> <>
</>, <RemixApp app={appComponent} />
document.getElementById('root') </>,
) document.getElementById('root')
)
})
})
.catch((err) => {
_paq.push(['trackEvent', 'Preload', 'error', err && err.message])
console.error('Error loading Remix:', err)
setError(true)
}) })
}).catch(err => {
_paq.push(['trackEvent', 'Preload', 'error', err && err.message])
console.error('Error loading Remix:', err)
setError(true)
})
} }
const downloadBackup = async () => { const downloadBackup = async () => {
setShowDownloader(false) setShowDownloader(false)
const fsUtility = new fileSystemUtility() const fsUtility = new fileSystemUtility()
await fsUtility.downloadBackup(remixFileSystems.current.fileSystems['localstorage']) await fsUtility.downloadBackup(
remixFileSystems.current.fileSystems['localstorage']
)
await migrateAndLoad() await migrateAndLoad()
} }
const migrateAndLoad = async () => { const migrateAndLoad = async () => {
setShowDownloader(false) setShowDownloader(false)
const fsUtility = new fileSystemUtility() const fsUtility = new fileSystemUtility()
const migrationResult = await fsUtility.migrate(localStorageFileSystem.current, remixIndexedDB.current) const migrationResult = await fsUtility.migrate(
_paq.push(['trackEvent', 'Migrate', 'result', migrationResult?'success' : 'fail']) localStorageFileSystem.current,
remixIndexedDB.current
)
_paq.push([
'trackEvent',
'Migrate',
'result',
migrationResult ? 'success' : 'fail'
])
await setFileSystems() await setFileSystems()
} }
const setFileSystems = async() => { const setFileSystems = async () => {
const fsLoaded = await remixFileSystems.current.setFileSystem([(testmigrationFallback.current || testBlockStorage.current)? null: remixIndexedDB.current, testBlockStorage.current? null:localStorageFileSystem.current]) const fsLoaded = await remixFileSystems.current.setFileSystem([
testmigrationFallback.current || testBlockStorage.current
? null
: remixIndexedDB.current,
testBlockStorage.current ? null : localStorageFileSystem.current
])
if (fsLoaded) { if (fsLoaded) {
console.log(fsLoaded.name + ' activated') console.log(fsLoaded.name + ' activated')
_paq.push(['trackEvent', 'Storage', 'activate', fsLoaded.name]) _paq.push(['trackEvent', 'Storage', 'activate', fsLoaded.name])
@ -67,78 +98,126 @@ export const Preload = () => {
} }
} }
const testmigration = async() => { const testmigration = async () => {
if (testmigrationResult.current) { if (testmigrationResult.current) {
const fsUtility = new fileSystemUtility() const fsUtility = new fileSystemUtility()
fsUtility.populateWorkspace(migrationTestData, remixFileSystems.current.fileSystems['localstorage'].fs) fsUtility.populateWorkspace(
migrationTestData,
remixFileSystems.current.fileSystems['localstorage'].fs
)
} }
} }
useEffect(() => { useEffect(() => {
async function loadStorage() { async function loadStorage() {
await remixFileSystems.current.addFileSystem(remixIndexedDB.current) || _paq.push(['trackEvent', 'Storage', 'error', 'indexedDB not supported']) ;(await remixFileSystems.current.addFileSystem(remixIndexedDB.current)) ||
await remixFileSystems.current.addFileSystem(localStorageFileSystem.current) || _paq.push(['trackEvent', 'Storage', 'error', 'localstorage not supported']) _paq.push(['trackEvent', 'Storage', 'error', 'indexedDB not supported'])
;(await remixFileSystems.current.addFileSystem(
localStorageFileSystem.current
)) ||
_paq.push([
'trackEvent',
'Storage',
'error',
'localstorage not supported'
])
await testmigration() await testmigration()
remixIndexedDB.current.loaded && await remixIndexedDB.current.checkWorkspaces() remixIndexedDB.current.loaded &&
localStorageFileSystem.current.loaded && await localStorageFileSystem.current.checkWorkspaces() (await remixIndexedDB.current.checkWorkspaces())
remixIndexedDB.current.loaded && ( (remixIndexedDB.current.hasWorkSpaces || !localStorageFileSystem.current.hasWorkSpaces)? await setFileSystems():setShowDownloader(true)) localStorageFileSystem.current.loaded &&
!remixIndexedDB.current.loaded && await setFileSystems() (await localStorageFileSystem.current.checkWorkspaces())
remixIndexedDB.current.loaded &&
(remixIndexedDB.current.hasWorkSpaces ||
!localStorageFileSystem.current.hasWorkSpaces
? await setFileSystems()
: setShowDownloader(true))
!remixIndexedDB.current.loaded && (await setFileSystems())
} }
loadStorage() loadStorage()
}, []) }, [])
return <> return (
<div className='preload-container'> <>
<div className='preload-logo pb-4'> <div className="preload-container">
{logo} <div className="preload-logo pb-4">
<div className="info-secondary splash"> {logo}
REMIX IDE <div className="info-secondary splash">
<br /> REMIX IDE
<span className='version'> v{packageJson.version}</span> <br />
<span className="version"> v{packageJson.version}</span>
</div>
</div> </div>
</div> {!supported ? (
{!supported ? <div className="preload-info-container alert alert-warning">
<div className='preload-info-container alert alert-warning'> Your browser does not support any of the filesystems required by
Your browser does not support any of the filesystems required by Remix. Remix. Either change the settings in your browser or use a supported
Either change the settings in your browser or use a supported browser. browser.
</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'>
Windows:<br></br>
- Chrome: CTRL + F5 or CTRL + Reload Button<br></br>
- Firefox: CTRL + SHIFT + R or CTRL + F5<br></br>
</div> </div>
<div className='pt-2'> ) : null}
MacOS:<br></br> {error ? (
- Chrome & FireFox: CMD + SHIFT + R or SHIFT + Reload Button<br></br> <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">
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">
MacOS:<br></br>- Chrome & FireFox: CMD + SHIFT + R or SHIFT +
Reload Button<br></br>
</div>
<div className="pt-2">
Linux:<br></br>- Chrome & FireFox: CTRL + SHIFT + R<br></br>
</div>
</div> </div>
<div className='pt-2'> ) : null}
Linux:<br></br> {showDownloader ? (
- Chrome & FireFox: CTRL + SHIFT + R<br></br> <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>
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> </div>
</div> : null} ) : null}
{showDownloader ? {supported && !error && !showDownloader ? (
<div className='preload-info-container alert alert-info'> <div>
This app will be updated now. Please download a backup of your files now to make sure you don't lose your work. <i className="fas fa-spinner fa-spin fa-2x"></i>
<br></br> </div>
You don't need to do anything else, your files will be available when the app loads. ) : null}
<div onClick={async () => { await downloadBackup() }} data-id='downloadbackup-btn' className='btn btn-primary mt-1'>download backup</div> </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 = (
const logo = <svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100"> <svg
<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" /> id="Ebene_2"
<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" /> data-name="Ebene 2"
<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" /> xmlns="http://www.w3.org/2000/svg"
</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" />
</svg>
)

@ -1,10 +1,10 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React from 'react' import React from 'react'
import { AbstractPanel } from './panel' import {AbstractPanel} from './panel'
import { RemixPluginPanel } from '@remix-ui/panel' import {RemixPluginPanel} from '@remix-ui/panel'
import packageJson from '../../../../../package.json' import packageJson from '../../../../../package.json'
import { RemixUIPanelHeader } from '@remix-ui/panel' import {RemixUIPanelHeader} from '@remix-ui/panel'
import { PluginViewWrapper } from '@remix-ui/helper' import {PluginViewWrapper} from '@remix-ui/helper'
// const csjs = require('csjs-inject') // const csjs = require('csjs-inject')
const sidePanel = { const sidePanel = {
@ -56,7 +56,8 @@ export class SidePanel extends AbstractPanel {
} }
removeView(profile) { removeView(profile) {
if (this.plugins[profile.name].active) this.call('menuicons', 'select', 'filePanel') if (this.plugins[profile.name].active)
this.call('menuicons', 'select', 'filePanel')
super.removeView(profile) super.removeView(profile)
this.emit('pluginDisabled', profile.name) this.emit('pluginDisabled', profile.name)
this.call('menuicons', 'unlinkContent', profile) this.call('menuicons', 'unlinkContent', profile)
@ -79,18 +80,28 @@ export class SidePanel extends AbstractPanel {
this.renderComponent() this.renderComponent()
} }
setDispatch (dispatch: React.Dispatch<any>) { setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch this.dispatch = dispatch
} }
render() { render() {
return ( return (
<section className='panel plugin-manager'> <PluginViewWrapper plugin={this} /></section> <section className="panel plugin-manager">
); {' '}
<PluginViewWrapper plugin={this} />
</section>
)
} }
updateComponent(state: any) { updateComponent(state: any) {
return <RemixPluginPanel header={<RemixUIPanelHeader plugins={state.plugins}></RemixUIPanelHeader>} plugins={state.plugins} /> return (
<RemixPluginPanel
header={
<RemixUIPanelHeader plugins={state.plugins}></RemixUIPanelHeader>
}
plugins={state.plugins}
/>
)
} }
renderComponent() { renderComponent() {

@ -1,11 +1,14 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React from 'react' import React from 'react'
import packageJson from '../../../../../package.json' import packageJson from '../../../../../package.json'
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { EventEmitter } from 'events' import {EventEmitter} from 'events'
import { IconRecord, RemixUiVerticalIconsPanel } from '@remix-ui/vertical-icons-panel' import {
import { Profile } from '@remixproject/plugin-utils' IconRecord,
import { PluginViewWrapper } from '@remix-ui/helper' RemixUiVerticalIconsPanel
} from '@remix-ui/vertical-icons-panel'
import {Profile} from '@remixproject/plugin-utils'
import {PluginViewWrapper} from '@remix-ui/helper'
const profile = { const profile = {
name: 'menuicons', name: 'menuicons',
@ -21,44 +24,63 @@ export class VerticalIcons extends Plugin {
htmlElement: HTMLDivElement htmlElement: HTMLDivElement
icons: Record<string, IconRecord> = {} icons: Record<string, IconRecord> = {}
dispatch: React.Dispatch<any> = () => {} dispatch: React.Dispatch<any> = () => {}
constructor () { constructor() {
super(profile) super(profile)
this.events = new EventEmitter() this.events = new EventEmitter()
this.htmlElement = document.createElement('div') this.htmlElement = document.createElement('div')
this.htmlElement.setAttribute('id', 'icon-panel') this.htmlElement.setAttribute('id', 'icon-panel')
} }
renderComponent () { renderComponent() {
const fixedOrder = ['filePanel', 'search', 'solidity','udapp', 'debugger', 'solidityStaticAnalysis', 'solidityUnitTesting', 'pluginManager'] const fixedOrder = [
'filePanel',
'search',
'solidity',
'udapp',
'debugger',
'solidityStaticAnalysis',
'solidityUnitTesting',
'pluginManager'
]
const divived = Object.values(this.icons).map((value) => { return { const divived = Object.values(this.icons)
...value, .map((value) => {
isRequired: fixedOrder.indexOf(value.profile.name) > -1 return {
}}).sort((a,b) => { ...value,
return a.timestamp - b.timestamp 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) => { const required = divived
return fixedOrder.indexOf(a.profile.name) - fixedOrder.indexOf(b.profile.name) .filter((value) => value.isRequired)
}) .sort((a, b) => {
return (
fixedOrder.indexOf(a.profile.name) -
fixedOrder.indexOf(b.profile.name)
)
})
const sorted: IconRecord[] = [ const sorted: IconRecord[] = [
...required, ...required,
...divived.filter((value) => { return !value.isRequired }) ...divived.filter((value) => {
return !value.isRequired
})
] ]
this.dispatch({ this.dispatch({
verticalIconsPlugin: this, verticalIconsPlugin: this,
icons: sorted icons: sorted
}) })
} }
setDispatch (dispatch: React.Dispatch<any>) { setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch this.dispatch = dispatch
} }
onActivation () { onActivation() {
this.renderComponent() this.renderComponent()
this.on('sidePanel', 'focusChanged', (name: string) => { this.on('sidePanel', 'focusChanged', (name: string) => {
Object.keys(this.icons).map((o) => { Object.keys(this.icons).map((o) => {
@ -69,19 +91,24 @@ export class VerticalIcons extends Plugin {
}) })
} }
async linkContent (profile: Profile) { async linkContent(profile: Profile) {
if (!profile.icon) return if (!profile.icon) return
if (!profile.kind) profile.kind = 'none' if (!profile.kind) profile.kind = 'none'
this.icons[profile.name] = { this.icons[profile.name] = {
profile: profile, profile: profile,
active: false, active: false,
canbeDeactivated: await this.call('manager', 'canDeactivate', this.profile, profile), canbeDeactivated: await this.call(
'manager',
'canDeactivate',
this.profile,
profile
),
timestamp: Date.now() timestamp: Date.now()
} }
this.renderComponent() this.renderComponent()
} }
unlinkContent (profile: Profile) { unlinkContent(profile: Profile) {
delete this.icons[profile.name] delete this.icons[profile.name]
this.renderComponent() this.renderComponent()
} }
@ -95,7 +122,7 @@ export class VerticalIcons extends Plugin {
* Set an icon as active * Set an icon as active
* @param {string} name Name of profile of the module to activate * @param {string} name Name of profile of the module to activate
*/ */
select (name: string) { select(name: string) {
// TODO: Only keep `this.emit` (issue#2210) // TODO: Only keep `this.emit` (issue#2210)
this.emit('showContent', name) this.emit('showContent', name)
this.events.emit('showContent', name) this.events.emit('showContent', name)
@ -105,22 +132,26 @@ export class VerticalIcons extends Plugin {
* Toggles the side panel for plugin * Toggles the side panel for plugin
* @param {string} name Name of profile of the module to activate * @param {string} name Name of profile of the module to activate
*/ */
toggle (name: string) { toggle(name: string) {
// TODO: Only keep `this.emit` (issue#2210) // TODO: Only keep `this.emit` (issue#2210)
this.emit('toggleContent', name) this.emit('toggleContent', name)
this.events.emit('toggleContent', name) this.events.emit('toggleContent', name)
} }
updateComponent(state: any){ updateComponent(state: any) {
return <RemixUiVerticalIconsPanel return (
verticalIconsPlugin={state.verticalIconsPlugin} <RemixUiVerticalIconsPanel
icons={state.icons} verticalIconsPlugin={state.verticalIconsPlugin}
/> icons={state.icons}
/>
)
} }
render() { render() {
return ( return (
<div id='icon-panel'><PluginViewWrapper plugin={this} /></div> <div id="icon-panel">
); <PluginViewWrapper plugin={this} />
</div>
)
} }
} }

@ -1,9 +1,13 @@
import React from 'react' import React from 'react'
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { customAction } from '@remixproject/plugin-api' import {customAction} from '@remixproject/plugin-api'
import { concatSourceFiles, getDependencyGraph, normalizeContractPath } from '@remix-ui/solidity-compiler' import {
concatSourceFiles,
getDependencyGraph,
normalizeContractPath
} from '@remix-ui/solidity-compiler'
const _paq = window._paq = window._paq || [] const _paq = (window._paq = window._paq || [])
const profile = { const profile = {
name: 'contractflattener', name: 'contractflattener',
@ -11,25 +15,28 @@ const profile = {
description: 'Flatten solidity contracts', description: 'Flatten solidity contracts',
methods: ['flattenAContract', 'flattenContract'], methods: ['flattenAContract', 'flattenContract'],
events: [], events: [],
maintainedBy: 'Remix', maintainedBy: 'Remix'
} }
export class ContractFlattener extends Plugin { export class ContractFlattener extends Plugin {
triggerFlattenContract: boolean = false triggerFlattenContract: boolean = false
constructor() { constructor() {
super(profile) super(profile)
} }
onActivation(): void { onActivation(): void {
this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data, input, version) => { this.on(
if(data.sources && Object.keys(data.sources).length > 1) { 'solidity',
if(this.triggerFlattenContract) { 'compilationFinished',
this.triggerFlattenContract = false async (file, source, languageVersion, data, input, version) => {
await this.flattenContract(source, file, data) 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']) _paq.push(['trackEvent', 'plugin', 'activated', 'contractFlattener'])
} }
@ -48,8 +55,11 @@ export class ContractFlattener extends Plugin {
* Takes the flattened result, writes it to a file and returns the result. * Takes the flattened result, writes it to a file and returns the result.
* @returns {Promise<string>} * @returns {Promise<string>}
*/ */
async flattenContract (source: { sources: any, target: string }, async flattenContract(
filePath: string, data: { contracts: any, sources: any }): Promise<string> { source: {sources: any; target: string},
filePath: string,
data: {contracts: any; sources: any}
): Promise<string> {
const appendage = '_flattened.sol' const appendage = '_flattened.sol'
const normalized = normalizeContractPath(filePath) const normalized = normalizeContractPath(filePath)
const path = `${normalized[normalized.length - 2]}${appendage}` const path = `${normalized[normalized.length - 2]}${appendage}`
@ -58,17 +68,17 @@ export class ContractFlattener extends Plugin {
let sorted let sorted
let result let result
let sources let sources
try{ try {
dependencyGraph = getDependencyGraph(ast, filePath) dependencyGraph = getDependencyGraph(ast, filePath)
sorted = dependencyGraph.isEmpty() sorted = dependencyGraph.isEmpty()
? [filePath] ? [filePath]
: dependencyGraph.sort().reverse() : dependencyGraph.sort().reverse()
sources = source.sources sources = source.sources
result = concatSourceFiles(sorted, sources) result = concatSourceFiles(sorted, sources)
}catch(err){ } catch (err) {
console.warn(err) console.warn(err)
} }
await this.call('fileManager', 'writeFile', path , result) await this.call('fileManager', 'writeFile', path, result)
_paq.push(['trackEvent', 'plugin', 'contractFlattener', 'flattenAContract']) _paq.push(['trackEvent', 'plugin', 'contractFlattener', 'flattenAContract'])
// clean up memory references & return result // clean up memory references & return result
sorted = null sorted = null
@ -76,4 +86,4 @@ export class ContractFlattener extends Plugin {
dependencyGraph = null dependencyGraph = null
return result return result
} }
} }

@ -1,11 +1,15 @@
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { LibraryProfile, MethodApi, StatusEvents } from '@remixproject/plugin-utils' import {
import { AppModal } from '@remix-ui/app' LibraryProfile,
import { AlertModal } from '@remix-ui/app' MethodApi,
import { dispatchModalInterface } from '@remix-ui/app' StatusEvents
} from '@remixproject/plugin-utils'
import {AppModal} from '@remix-ui/app'
import {AlertModal} from '@remix-ui/app'
import {dispatchModalInterface} from '@remix-ui/app'
interface INotificationApi { interface INotificationApi {
events: StatusEvents, events: StatusEvents
methods: { methods: {
modal: (args: AppModal) => void modal: (args: AppModal) => void
alert: (args: AlertModal) => void alert: (args: AlertModal) => void
@ -13,32 +17,35 @@ interface INotificationApi {
} }
} }
const profile:LibraryProfile<INotificationApi> = { const profile: LibraryProfile<INotificationApi> = {
name: 'notification', name: 'notification',
displayName: 'Notification', displayName: 'Notification',
description: 'Displays notifications', description: 'Displays notifications',
methods: ['modal', 'alert', 'toast'] methods: ['modal', 'alert', 'toast']
} }
export class NotificationPlugin extends Plugin implements MethodApi<INotificationApi> { export class NotificationPlugin
extends Plugin
implements MethodApi<INotificationApi>
{
dispatcher: dispatchModalInterface dispatcher: dispatchModalInterface
constructor () { constructor() {
super(profile) super(profile)
} }
setDispatcher (dispatcher: dispatchModalInterface) { setDispatcher(dispatcher: dispatchModalInterface) {
this.dispatcher = dispatcher this.dispatcher = dispatcher
} }
async modal (args: AppModal) { async modal(args: AppModal) {
return this.dispatcher.modal(args) return this.dispatcher.modal(args)
} }
async alert (args: AlertModal) { async alert(args: AlertModal) {
return this.dispatcher.alert(args) return this.dispatcher.alert(args)
} }
async toast (message: string | JSX.Element) { async toast(message: string | JSX.Element) {
this.dispatcher.toast(message) this.dispatcher.toast(message)
} }
} }

@ -1,63 +1,110 @@
'use strict' 'use strict'
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { sourceMappingDecoder } from '@remix-project/remix-debug' import {sourceMappingDecoder} from '@remix-project/remix-debug'
import { CompilerAbstract } from '@remix-project/remix-solidity' import {CompilerAbstract} from '@remix-project/remix-solidity'
import { CompilationResult } from '@remix-project/remix-solidity' import {CompilationResult} from '@remix-project/remix-solidity'
import CodeParserGasService from './services/code-parser-gas-service' import CodeParserGasService from './services/code-parser-gas-service'
import CodeParserCompiler from './services/code-parser-compiler' import CodeParserCompiler from './services/code-parser-compiler'
import CodeParserAntlrService from './services/code-parser-antlr-service' import CodeParserAntlrService from './services/code-parser-antlr-service'
import CodeParserImports, { CodeParserImportsData } from './services/code-parser-imports' import CodeParserImports, {
CodeParserImportsData
} from './services/code-parser-imports'
import React from 'react' import React from 'react'
import { Profile } from '@remixproject/plugin-utils' import {Profile} from '@remixproject/plugin-utils'
import { ContractDefinitionAstNode, EventDefinitionAstNode, FunctionCallAstNode, FunctionDefinitionAstNode, IdentifierAstNode, ImportDirectiveAstNode, ModifierDefinitionAstNode, SourceUnitAstNode, StructDefinitionAstNode, VariableDeclarationAstNode } from '@remix-project/remix-analyzer' import {
import { lastCompilationResult, RemixApi } from '@remixproject/plugin-api' ContractDefinitionAstNode,
import { antlr } from './types' EventDefinitionAstNode,
import { ParseResult } from './types/antlr-types' FunctionCallAstNode,
FunctionDefinitionAstNode,
IdentifierAstNode,
ImportDirectiveAstNode,
ModifierDefinitionAstNode,
SourceUnitAstNode,
StructDefinitionAstNode,
VariableDeclarationAstNode
} from '@remix-project/remix-analyzer'
import {lastCompilationResult, RemixApi} from '@remixproject/plugin-api'
import {antlr} from './types'
import {ParseResult} from './types/antlr-types'
const profile: Profile = { const profile: Profile = {
name: 'codeParser', name: 'codeParser',
methods: ['nodesAtPosition', 'getContractNodes', 'getCurrentFileNodes', 'getLineColumnOfNode', 'getLineColumnOfPosition', 'getFunctionParamaters', 'getDeclaration', 'getFunctionReturnParameters', 'getVariableDeclaration', 'getNodeDocumentation', 'getNodeLink', 'listAstNodes', 'getANTLRBlockAtPosition', 'getLastNodeInLine', 'resolveImports', 'parseSolidity', 'getNodesWithScope', 'getNodesWithName', 'getNodes', 'compile', 'getNodeById', 'getLastCompilationResult', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'getGasEstimates', 'getImports'], methods: [
'nodesAtPosition',
'getContractNodes',
'getCurrentFileNodes',
'getLineColumnOfNode',
'getLineColumnOfPosition',
'getFunctionParamaters',
'getDeclaration',
'getFunctionReturnParameters',
'getVariableDeclaration',
'getNodeDocumentation',
'getNodeLink',
'listAstNodes',
'getANTLRBlockAtPosition',
'getLastNodeInLine',
'resolveImports',
'parseSolidity',
'getNodesWithScope',
'getNodesWithName',
'getNodes',
'compile',
'getNodeById',
'getLastCompilationResult',
'positionOfDefinition',
'definitionAtPosition',
'jumpToDefinition',
'referrencesAtPosition',
'referencesOf',
'getActiveHighlights',
'gasEstimation',
'declarationOf',
'getGasEstimates',
'getImports'
],
events: [], events: [],
version: '0.0.1' version: '0.0.1'
} }
export function isNodeDefinition(node: genericASTNode) { export function isNodeDefinition(node: genericASTNode) {
return node.nodeType === 'ContractDefinition' || return (
node.nodeType === 'FunctionDefinition' || node.nodeType === 'ContractDefinition' ||
node.nodeType === 'ModifierDefinition' || node.nodeType === 'FunctionDefinition' ||
node.nodeType === 'VariableDeclaration' || node.nodeType === 'ModifierDefinition' ||
node.nodeType === 'StructDefinition' || node.nodeType === 'VariableDeclaration' ||
node.nodeType === 'EventDefinition' node.nodeType === 'StructDefinition' ||
node.nodeType === 'EventDefinition'
)
} }
export type genericASTNode = export type genericASTNode =
ContractDefinitionAstNode | ContractDefinitionAstNode
| FunctionDefinitionAstNode | FunctionDefinitionAstNode
| ModifierDefinitionAstNode | ModifierDefinitionAstNode
| VariableDeclarationAstNode | VariableDeclarationAstNode
| StructDefinitionAstNode | StructDefinitionAstNode
| EventDefinitionAstNode | EventDefinitionAstNode
| IdentifierAstNode | IdentifierAstNode
| FunctionCallAstNode | FunctionCallAstNode
| ImportDirectiveAstNode | ImportDirectiveAstNode
| SourceUnitAstNode | SourceUnitAstNode
interface flatReferenceIndexNode { interface flatReferenceIndexNode {
[id: number]: genericASTNode [id: number]: genericASTNode
} }
interface declarationIndexNode { interface declarationIndexNode {
[id: number]: genericASTNode[] [id: number]: genericASTNode[]
} }
interface codeParserIndex { interface codeParserIndex {
declarations: declarationIndexNode, declarations: declarationIndexNode
flatReferences: flatReferenceIndexNode, flatReferences: flatReferenceIndexNode
nodesPerFile: any nodesPerFile: any
} }
export class CodeParser extends Plugin { export class CodeParser extends Plugin {
compilerAbstract: CompilerAbstract compilerAbstract: CompilerAbstract
currentFile: string currentFile: string
nodeIndex: codeParserIndex nodeIndex: codeParserIndex
@ -80,7 +127,6 @@ export class CodeParser extends Plugin {
debuggerIsOn: boolean = false debuggerIsOn: boolean = false
constructor(astWalker: any) { constructor(astWalker: any) {
super(profile) super(profile)
this.astWalker = astWalker this.astWalker = astWalker
@ -92,31 +138,52 @@ export class CodeParser extends Plugin {
} }
async handleChangeEvents() { async handleChangeEvents() {
const completionSettings = await this.call('config', 'getAppParameter', 'auto-completion') const completionSettings = await this.call(
'config',
'getAppParameter',
'auto-completion'
)
if (completionSettings) { if (completionSettings) {
this.antlrService.enableWorker() this.antlrService.enableWorker()
} else { } else {
this.antlrService.disableWorker() this.antlrService.disableWorker()
} }
const showGasSettings = await this.call('config', 'getAppParameter', 'show-gas') const showGasSettings = await this.call(
const showErrorSettings = await this.call('config', 'getAppParameter', 'display-errors') 'config',
if (showGasSettings || showErrorSettings || completionSettings || this.debuggerIsOn) { 'getAppParameter',
'show-gas'
)
const showErrorSettings = await this.call(
'config',
'getAppParameter',
'display-errors'
)
if (
showGasSettings ||
showErrorSettings ||
completionSettings ||
this.debuggerIsOn
) {
await this.compilerService.compile() await this.compilerService.compile()
} }
} }
async onActivation() { async onActivation() {
this.gasService = new CodeParserGasService(this) this.gasService = new CodeParserGasService(this)
this.compilerService = new CodeParserCompiler(this) this.compilerService = new CodeParserCompiler(this)
this.antlrService = new CodeParserAntlrService(this) this.antlrService = new CodeParserAntlrService(this)
this.importService = new CodeParserImports(this) this.importService = new CodeParserImports(this)
this.parseSolidity = this.antlrService.parseSolidity.bind(this.antlrService) this.parseSolidity = this.antlrService.parseSolidity.bind(this.antlrService)
this.getLastNodeInLine = this.antlrService.getLastNodeInLine.bind(this.antlrService) this.getLastNodeInLine = this.antlrService.getLastNodeInLine.bind(
this.antlrService
)
this.listAstNodes = this.antlrService.listAstNodes.bind(this.antlrService) this.listAstNodes = this.antlrService.listAstNodes.bind(this.antlrService)
this.getANTLRBlockAtPosition = this.antlrService.getANTLRBlockAtPosition.bind(this.antlrService) this.getANTLRBlockAtPosition =
this.setCurrentFileAST = this.antlrService.setCurrentFileAST.bind(this.antlrService) this.antlrService.getANTLRBlockAtPosition.bind(this.antlrService)
this.setCurrentFileAST = this.antlrService.setCurrentFileAST.bind(
this.antlrService
)
this.getImports = this.importService.getImports.bind(this.importService) this.getImports = this.importService.getImports.bind(this.importService)
this.on('editor', 'didChangeFile', async (file) => { this.on('editor', 'didChangeFile', async (file) => {
@ -138,7 +205,11 @@ export class CodeParser extends Plugin {
this.on('fileManager', 'currentFileChanged', async () => { this.on('fileManager', 'currentFileChanged', async () => {
await this.call('editor', 'discardLineTexts') await this.call('editor', 'discardLineTexts')
const completionSettings = await this.call('config', 'getAppParameter', 'auto-completion') const completionSettings = await this.call(
'config',
'getAppParameter',
'auto-completion'
)
if (completionSettings) { if (completionSettings) {
this.antlrService.setCurrentFileAST() this.antlrService.setCurrentFileAST()
} }
@ -158,49 +229,53 @@ export class CodeParser extends Plugin {
}) })
await this.compilerService.init() await this.compilerService.init()
this.on('solidity', 'compilerLoaded', async () => { this.on('solidity', 'compilerLoaded', async () => {
await this.reload() await this.reload()
}) })
this.on('debugger', 'startDebugging', async () => { this.on('debugger', 'startDebugging', async () => {
this.debuggerIsOn = true this.debuggerIsOn = true
await this.reload() await this.reload()
}) })
this.on('debugger', 'stopDebugging', async () => { this.on('debugger', 'stopDebugging', async () => {
this.debuggerIsOn = false this.debuggerIsOn = false
await this.reload() await this.reload()
}) })
} }
async reload(){ async reload() {
await this.call('editor', 'discardLineTexts') await this.call('editor', 'discardLineTexts')
await this.call('fileDecorator', 'clearFileDecorators') await this.call('fileDecorator', 'clearFileDecorators')
await this.call('editor', 'clearErrorMarkers', [this.currentFile]) await this.call('editor', 'clearErrorMarkers', [this.currentFile])
await this.handleChangeEvents() await this.handleChangeEvents()
} }
/** /**
* *
* @returns * @returns
*/ */
async getLastCompilationResult() { async getLastCompilationResult() {
return this.compilerAbstract return this.compilerAbstract
} }
getSubNodes<T extends genericASTNode>(node: T): number[] { getSubNodes<T extends genericASTNode>(node: T): number[] {
return node.nodeType == "ContractDefinition" && node.contractDependencies; return node.nodeType == 'ContractDefinition' && node.contractDependencies
} }
/** /**
* Builds a flat index and declarations of all the nodes in the compilation result * Builds a flat index and declarations of all the nodes in the compilation result
* @param compilationResult * @param compilationResult
* @param source * @param source
*/ */
_buildIndex(compilationResult: CompilationResult, source) { _buildIndex(compilationResult: CompilationResult, source) {
if (compilationResult && compilationResult.sources) { if (compilationResult && compilationResult.sources) {
const callback = (node: genericASTNode) => { const callback = (node: genericASTNode) => {
if (node && ("referencedDeclaration" in node) && node.referencedDeclaration) { if (
node &&
'referencedDeclaration' in node &&
node.referencedDeclaration
) {
if (!this.nodeIndex.declarations[node.referencedDeclaration]) { if (!this.nodeIndex.declarations[node.referencedDeclaration]) {
this.nodeIndex.declarations[node.referencedDeclaration] = [] this.nodeIndex.declarations[node.referencedDeclaration] = []
} }
@ -211,9 +286,7 @@ export class CodeParser extends Plugin {
for (const s in compilationResult.sources) { for (const s in compilationResult.sources) {
this.astWalker.walkFull(compilationResult.sources[s].ast, callback) this.astWalker.walkFull(compilationResult.sources[s].ast, callback)
} }
} }
} }
// NODE HELPERS // NODE HELPERS
@ -232,16 +305,32 @@ export class CodeParser extends Plugin {
return '(' + params.toString() + ')' return '(' + params.toString() + ')'
} }
_flatNodeList(
_flatNodeList(contractNode: ContractDefinitionAstNode, fileName: string, inScope: boolean, compilatioResult: any) { contractNode: ContractDefinitionAstNode,
fileName: string,
inScope: boolean,
compilatioResult: any
) {
const index = {} const index = {}
const contractName: string = contractNode.name const contractName: string = contractNode.name
const callback = (node) => { const callback = (node) => {
if (inScope && node.scope !== contractNode.id if (
&& !(node.nodeType === 'EnumDefinition' || node.nodeType === 'EventDefinition' || node.nodeType === 'ModifierDefinition')) inScope &&
node.scope !== contractNode.id &&
!(
node.nodeType === 'EnumDefinition' ||
node.nodeType === 'EventDefinition' ||
node.nodeType === 'ModifierDefinition'
)
)
return return
if (inScope) node.isClassNode = true; if (inScope) node.isClassNode = true
node.gasEstimate = this._getContractGasEstimate(node, contractName, fileName, compilatioResult) node.gasEstimate = this._getContractGasEstimate(
node,
contractName,
fileName,
compilatioResult
)
node.functionName = node.name + this._getInputParams(node) node.functionName = node.name + this._getInputParams(node)
node.contractName = contractName node.contractName = contractName
node.contractId = contractNode.id node.contractId = contractNode.id
@ -251,17 +340,37 @@ export class CodeParser extends Plugin {
return index return index
} }
_extractFileNodes(fileName: string, compilationResult: lastCompilationResult) { _extractFileNodes(
if (compilationResult && compilationResult.data.sources && compilationResult.data.sources[fileName]) { fileName: string,
compilationResult: lastCompilationResult
) {
if (
compilationResult &&
compilationResult.data.sources &&
compilationResult.data.sources[fileName]
) {
const source = compilationResult.data.sources[fileName] const source = compilationResult.data.sources[fileName]
const nodesByContract: any = {} const nodesByContract: any = {}
nodesByContract.imports = {} nodesByContract.imports = {}
nodesByContract.contracts = {} nodesByContract.contracts = {}
this.astWalker.walkFull(source.ast, async (node) => { this.astWalker.walkFull(source.ast, async (node) => {
if (node.nodeType === 'ContractDefinition') { if (node.nodeType === 'ContractDefinition') {
const flatNodes = this._flatNodeList(node, fileName, false, compilationResult) const flatNodes = this._flatNodeList(
node.gasEstimate = this._getContractGasEstimate(node, node.name, fileName, compilationResult) node,
nodesByContract.contracts[node.name] = { contractDefinition: node, contractNodes: flatNodes } fileName,
false,
compilationResult
)
node.gasEstimate = this._getContractGasEstimate(
node,
node.name,
fileName,
compilationResult
)
nodesByContract.contracts[node.name] = {
contractDefinition: node,
contractNodes: flatNodes
}
const baseNodes = {} const baseNodes = {}
const baseNodesWithBaseContractScope = {} const baseNodesWithBaseContractScope = {}
if (node.linearizedBaseContracts) { if (node.linearizedBaseContracts) {
@ -271,11 +380,12 @@ export class CodeParser extends Plugin {
const callback = (node) => { const callback = (node) => {
node.contractName = (baseContract as any).name node.contractName = (baseContract as any).name
node.contractId = (baseContract as any).id node.contractId = (baseContract as any).id
node.isBaseNode = true; node.isBaseNode = true
baseNodes[node.id] = node baseNodes[node.id] = node
if ((node.scope && node.scope === baseContract.id) if (
|| node.nodeType === 'EnumDefinition' (node.scope && node.scope === baseContract.id) ||
|| node.nodeType === 'EventDefinition' node.nodeType === 'EnumDefinition' ||
node.nodeType === 'EventDefinition'
) { ) {
baseNodesWithBaseContractScope[node.id] = node baseNodesWithBaseContractScope[node.id] = node
} }
@ -283,7 +393,7 @@ export class CodeParser extends Plugin {
for (const member of node.members) { for (const member of node.members) {
member.contractName = (baseContract as any).name member.contractName = (baseContract as any).name
member.contractId = (baseContract as any).id member.contractId = (baseContract as any).id
member.isBaseNode = true; member.isBaseNode = true
} }
} }
} }
@ -292,14 +402,15 @@ export class CodeParser extends Plugin {
} }
} }
nodesByContract.contracts[node.name].baseNodes = baseNodes nodesByContract.contracts[node.name].baseNodes = baseNodes
nodesByContract.contracts[node.name].baseNodesWithBaseContractScope = baseNodesWithBaseContractScope nodesByContract.contracts[node.name].baseNodesWithBaseContractScope =
nodesByContract.contracts[node.name].contractScopeNodes = this._flatNodeList(node, fileName, true, compilationResult) baseNodesWithBaseContractScope
nodesByContract.contracts[node.name].contractScopeNodes =
this._flatNodeList(node, fileName, true, compilationResult)
} }
if (node.nodeType === 'ImportDirective') { if (node.nodeType === 'ImportDirective') {
const imported = await this.resolveImports(node, {}) const imported = await this.resolveImports(node, {})
for (const importedNode of (Object.values(imported) as any)) { for (const importedNode of Object.values(imported) as any) {
if (importedNode.nodes) if (importedNode.nodes)
for (const subNode of importedNode.nodes) { for (const subNode of importedNode.nodes) {
nodesByContract.imports[subNode.id] = subNode nodesByContract.imports[subNode.id] = subNode
@ -311,9 +422,15 @@ export class CodeParser extends Plugin {
} }
} }
_getContractGasEstimate(node: ContractDefinitionAstNode | FunctionDefinitionAstNode, contractName: string, fileName: string, compilationResult: lastCompilationResult) { _getContractGasEstimate(
node: ContractDefinitionAstNode | FunctionDefinitionAstNode,
const contracts = compilationResult.data.contracts && compilationResult.data.contracts[this.currentFile] contractName: string,
fileName: string,
compilationResult: lastCompilationResult
) {
const contracts =
compilationResult.data.contracts &&
compilationResult.data.contracts[this.currentFile]
for (const name in contracts) { for (const name in contracts) {
if (name === contractName) { if (name === contractName) {
const contract = contracts[name] const contract = contracts[name]
@ -327,15 +444,21 @@ export class CodeParser extends Plugin {
const fn = fnName + this._getInputParams(node) const fn = fnName + this._getInputParams(node)
if (visibility === 'public' || visibility === 'external') { if (visibility === 'public' || visibility === 'external') {
executionCost = estimationObj === null ? '-' : estimationObj.external[fn] executionCost =
estimationObj === null ? '-' : estimationObj.external[fn]
} else if (visibility === 'private' || visibility === 'internal') { } else if (visibility === 'private' || visibility === 'internal') {
executionCost = estimationObj === null ? '-' : estimationObj.internal[fn] executionCost =
estimationObj === null ? '-' : estimationObj.internal[fn]
} }
return { executionCost } return {executionCost}
} else { } else {
return { return {
creationCost: estimationObj === null ? '-' : estimationObj.creation.totalCost, creationCost:
codeDepositCost: estimationObj === null ? '-' : estimationObj.creation.codeDepositCost, estimationObj === null ? '-' : estimationObj.creation.totalCost,
codeDepositCost:
estimationObj === null
? '-'
: estimationObj.creation.codeDepositCost
} }
} }
} }
@ -344,31 +467,53 @@ export class CodeParser extends Plugin {
} }
/** /**
* Nodes at position where position is a number, offset * Nodes at position where position is a number, offset
* @param position * @param position
* @param type * @param type
* @returns * @returns
*/ */
async nodesAtPosition(position: number, type = ''): Promise<genericASTNode[]> { async nodesAtPosition(
position: number,
type = ''
): Promise<genericASTNode[]> {
let lastCompilationResult = this.compilerAbstract let lastCompilationResult = this.compilerAbstract
if(this.debuggerIsOn) { if (this.debuggerIsOn) {
lastCompilationResult = await this.call('compilerArtefacts', 'get', '__last') lastCompilationResult = await this.call(
'compilerArtefacts',
'get',
'__last'
)
this.currentFile = await this.call('fileManager', 'file') this.currentFile = await this.call('fileManager', 'file')
} }
if (!lastCompilationResult) return [] if (!lastCompilationResult) return []
const urlFromPath = await this.call('fileManager', 'getUrlFromPath', this.currentFile) const urlFromPath = await this.call(
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data && lastCompilationResult.data.sources && lastCompilationResult.data.sources[this.currentFile]) { 'fileManager',
const nodes: genericASTNode[] = sourceMappingDecoder.nodesAtPosition(type, position, lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file]) 'getUrlFromPath',
this.currentFile
)
if (
lastCompilationResult &&
lastCompilationResult.languageversion.indexOf('soljson') === 0 &&
lastCompilationResult.data &&
lastCompilationResult.data.sources &&
lastCompilationResult.data.sources[this.currentFile]
) {
const nodes: genericASTNode[] = sourceMappingDecoder.nodesAtPosition(
type,
position,
lastCompilationResult.data.sources[this.currentFile] ||
lastCompilationResult.data.sources[urlFromPath.file]
)
return nodes return nodes
} }
return [] return []
} }
/** /**
* *
* @param id * @param id
* @returns * @returns
*/ */
async getNodeById(id: number) { async getNodeById(id: number) {
for (const key in this.nodeIndex.flatReferences) { for (const key in this.nodeIndex.flatReferences) {
if (this.nodeIndex.flatReferences[key].id === id) { if (this.nodeIndex.flatReferences[key].id === id) {
@ -378,19 +523,20 @@ export class CodeParser extends Plugin {
} }
/** /**
* *
* @param id * @param id
* @returns * @returns
*/ */
async getDeclaration(id: number) { async getDeclaration(id: number) {
if (this.nodeIndex.declarations && this.nodeIndex.declarations[id]) return this.nodeIndex.declarations[id] if (this.nodeIndex.declarations && this.nodeIndex.declarations[id])
return this.nodeIndex.declarations[id]
} }
/** /**
* *
* @param scope * @param scope
* @returns * @returns
*/ */
async getNodesWithScope(scope: number) { async getNodesWithScope(scope: number) {
const nodes = [] const nodes = []
for (const node of Object.values(this.nodeIndex.flatReferences)) { for (const node of Object.values(this.nodeIndex.flatReferences)) {
@ -400,10 +546,10 @@ export class CodeParser extends Plugin {
} }
/** /**
* *
* @param name * @param name
* @returns * @returns
*/ */
async getNodesWithName(name: string) { async getNodesWithName(name: string) {
const nodes: genericASTNode[] = [] const nodes: genericASTNode[] = []
for (const node of Object.values(this.nodeIndex.flatReferences)) { for (const node of Object.values(this.nodeIndex.flatReferences)) {
@ -412,22 +558,22 @@ export class CodeParser extends Plugin {
return nodes return nodes
} }
/** /**
* *
* @param node * @param node
* @returns * @returns
*/ */
declarationOf<T extends genericASTNode>(node: T) { declarationOf<T extends genericASTNode>(node: T) {
if (node && ('referencedDeclaration' in node) && node.referencedDeclaration) { if (node && 'referencedDeclaration' in node && node.referencedDeclaration) {
return this.nodeIndex.flatReferences[node.referencedDeclaration] return this.nodeIndex.flatReferences[node.referencedDeclaration]
} }
return null return null
} }
/** /**
* *
* @param position * @param position
* @returns * @returns
*/ */
async definitionAtPosition(position: number) { async definitionAtPosition(position: number) {
const nodes = await this.nodesAtPosition(position) const nodes = await this.nodesAtPosition(position)
let nodeDefinition: any let nodeDefinition: any
@ -436,7 +582,7 @@ export class CodeParser extends Plugin {
node = nodes[nodes.length - 1] node = nodes[nodes.length - 1]
nodeDefinition = node nodeDefinition = node
if (!isNodeDefinition(node)) { if (!isNodeDefinition(node)) {
nodeDefinition = await this.declarationOf(node) || node nodeDefinition = (await this.declarationOf(node)) || node
} }
if (node.nodeType === 'ImportDirective') { if (node.nodeType === 'ImportDirective') {
for (const key in this.nodeIndex.flatReferences) { for (const key in this.nodeIndex.flatReferences) {
@ -457,39 +603,49 @@ export class CodeParser extends Plugin {
if (!nodeDefinition) nodeDefinition = node if (!nodeDefinition) nodeDefinition = node
} }
} }
if (nodeDefinition && nodeDefinition.type && nodeDefinition.type === 'Identifier') { if (
nodeDefinition &&
nodeDefinition.type &&
nodeDefinition.type === 'Identifier'
) {
const nodeForIdentifier = await this.findIdentifier(nodeDefinition) const nodeForIdentifier = await this.findIdentifier(nodeDefinition)
if (nodeForIdentifier) nodeDefinition = nodeForIdentifier if (nodeForIdentifier) nodeDefinition = nodeForIdentifier
} }
return nodeDefinition return nodeDefinition
} }
} }
} }
async getContractNodes(contractName: string) { async getContractNodes(contractName: string) {
if (this.nodeIndex.nodesPerFile if (
&& this.nodeIndex.nodesPerFile[this.currentFile] this.nodeIndex.nodesPerFile &&
&& this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName] this.nodeIndex.nodesPerFile[this.currentFile] &&
&& this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName].contractNodes) { this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName] &&
return this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName] this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName]
.contractNodes
) {
return this.nodeIndex.nodesPerFile[this.currentFile].contracts[
contractName
]
} }
return false return false
} }
async getCurrentFileNodes() { async getCurrentFileNodes() {
if (this.nodeIndex.nodesPerFile if (
&& this.nodeIndex.nodesPerFile[this.currentFile]) { this.nodeIndex.nodesPerFile &&
this.nodeIndex.nodesPerFile[this.currentFile]
) {
return this.nodeIndex.nodesPerFile[this.currentFile] return this.nodeIndex.nodesPerFile[this.currentFile]
} }
return false return false
} }
/** /**
* *
* @param identifierNode * @param identifierNode
* @returns * @returns
*/ */
async findIdentifier(identifierNode: any) { async findIdentifier(identifierNode: any) {
const astNodes = await this.antlrService.listAstNodes() const astNodes = await this.antlrService.listAstNodes()
for (const node of astNodes) { for (const node of astNodes) {
@ -500,10 +656,10 @@ export class CodeParser extends Plugin {
} }
/** /**
* *
* @param node * @param node
* @returns * @returns
*/ */
async positionOfDefinition(node: genericASTNode): Promise<any | null> { async positionOfDefinition(node: genericASTNode): Promise<any | null> {
if (node) { if (node) {
if (node.src) { if (node.src) {
@ -517,11 +673,11 @@ export class CodeParser extends Plugin {
} }
/** /**
* *
* @param node * @param node
* @param imported * @param imported
* @returns * @returns
*/ */
async resolveImports(node: any, imported = {}) { async resolveImports(node: any, imported = {}) {
if (node.nodeType === 'ImportDirective' && !imported[node.sourceUnit]) { if (node.nodeType === 'ImportDirective' && !imported[node.sourceUnit]) {
const importNode: any = await this.getNodeById(node.sourceUnit) const importNode: any = await this.getNodeById(node.sourceUnit)
@ -535,13 +691,11 @@ export class CodeParser extends Plugin {
return imported return imported
} }
/** /**
* *
* @param node * @param node
* @returns * @returns
*/ */
referencesOf(node: genericASTNode) { referencesOf(node: genericASTNode) {
const results: genericASTNode[] = [] const results: genericASTNode[] = []
const highlights = (id: number) => { const highlights = (id: number) => {
@ -553,7 +707,7 @@ export class CodeParser extends Plugin {
} }
} }
} }
if (node && ("referencedDeclaration" in node) && node.referencedDeclaration) { if (node && 'referencedDeclaration' in node && node.referencedDeclaration) {
highlights(node.referencedDeclaration) highlights(node.referencedDeclaration)
const current = this.nodeIndex.flatReferences[node.referencedDeclaration] const current = this.nodeIndex.flatReferences[node.referencedDeclaration]
results.push(current) results.push(current)
@ -564,10 +718,10 @@ export class CodeParser extends Plugin {
} }
/** /**
* *
* @param position * @param position
* @returns * @returns
*/ */
async referrencesAtPosition(position: any): Promise<genericASTNode[]> { async referrencesAtPosition(position: any): Promise<genericASTNode[]> {
const nodes = await this.nodesAtPosition(position) const nodes = await this.nodesAtPosition(position)
if (nodes && nodes.length) { if (nodes && nodes.length) {
@ -579,59 +733,68 @@ export class CodeParser extends Plugin {
} }
/** /**
* *
* @returns * @returns
*/ */
async getNodes(): Promise<flatReferenceIndexNode> { async getNodes(): Promise<flatReferenceIndexNode> {
return this.nodeIndex.flatReferences return this.nodeIndex.flatReferences
} }
/** /**
* *
* @param node * @param node
* @returns * @returns
*/ */
async getNodeLink(node: genericASTNode) { async getNodeLink(node: genericASTNode) {
const lineColumn = await this.getLineColumnOfNode(node) const lineColumn = await this.getLineColumnOfNode(node)
const position = await this.positionOfDefinition(node) const position = await this.positionOfDefinition(node)
if (this.compilerAbstract && this.compilerAbstract.source && position) { if (this.compilerAbstract && this.compilerAbstract.source && position) {
const fileName = this.compilerAbstract.getSourceName(position.file) const fileName = this.compilerAbstract.getSourceName(position.file)
return lineColumn ? `${fileName} ${lineColumn.start.line}:${lineColumn.start.column}` : null return lineColumn
? `${fileName} ${lineColumn.start.line}:${lineColumn.start.column}`
: null
} }
return '' return ''
} }
/* /*
* @param node * @param node
*/ */
async getLineColumnOfNode(node: any) { async getLineColumnOfNode(node: any) {
const position = await this.positionOfDefinition(node) const position = await this.positionOfDefinition(node)
return this.getLineColumnOfPosition(position) return this.getLineColumnOfPosition(position)
} }
/* /*
* @param position * @param position
*/ */
async getLineColumnOfPosition(position: any) { async getLineColumnOfPosition(position: any) {
if (position) { if (position) {
const fileName = this.compilerAbstract.getSourceName(position.file) const fileName = this.compilerAbstract.getSourceName(position.file)
const lineBreaks = sourceMappingDecoder.getLinebreakPositions(this.compilerAbstract.source.sources[fileName].content) const lineBreaks = sourceMappingDecoder.getLinebreakPositions(
const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn(position, lineBreaks) this.compilerAbstract.source.sources[fileName].content
)
const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn(
position,
lineBreaks
)
return lineColumn return lineColumn
} }
} }
/** /**
* *
* @param node * @param node
* @returns * @returns
*/ */
async getNodeDocumentation(node: genericASTNode) { async getNodeDocumentation(node: genericASTNode) {
if (("documentation" in node) && node.documentation && (node.documentation as any).text) { if (
let text = ''; 'documentation' in node &&
(node.documentation as any).text.split('\n').forEach(line => { node.documentation &&
(node.documentation as any).text
) {
let text = ''
;(node.documentation as any).text.split('\n').forEach((line) => {
text += `${line.trim()}\n` text += `${line.trim()}\n`
}) })
return text return text
@ -639,35 +802,35 @@ export class CodeParser extends Plugin {
} }
/** /**
* *
* @param node * @param node
* @returns * @returns
*/ */
async getVariableDeclaration(node: any) { async getVariableDeclaration(node: any) {
const nodeVisibility = node.visibility && node.visibility.length ? node.visibility + ' ' : '' const nodeVisibility =
node.visibility && node.visibility.length ? node.visibility + ' ' : ''
const nodeName = node.name && node.name.length ? node.name : '' const nodeName = node.name && node.name.length ? node.name : ''
if (node.typeDescriptions && node.typeDescriptions.typeString) { if (node.typeDescriptions && node.typeDescriptions.typeString) {
return `${node.typeDescriptions.typeString} ${nodeVisibility}${nodeName}` return `${node.typeDescriptions.typeString} ${nodeVisibility}${nodeName}`
} else { } else {
if (node.typeName && node.typeName.name) { if (node.typeName && node.typeName.name) {
return `${node.typeName.name} ${nodeVisibility}${nodeName}` return `${node.typeName.name} ${nodeVisibility}${nodeName}`
} } else if (node.typeName && node.typeName.namePath) {
else if (node.typeName && node.typeName.namePath) {
return `${node.typeName.namePath} ${nodeVisibility}${nodeName}` return `${node.typeName.namePath} ${nodeVisibility}${nodeName}`
} } else {
else {
return `${nodeName}${nodeName}` return `${nodeName}${nodeName}`
} }
} }
} }
/** /**
* *
* @param node * @param node
* @returns * @returns
*/ */
async getFunctionParamaters(node: any) { async getFunctionParamaters(node: any) {
const localParam = (node.parameters && node.parameters.parameters) || (node.parameters) const localParam =
(node.parameters && node.parameters.parameters) || node.parameters
if (localParam) { if (localParam) {
const params = [] const params = []
for (const param of localParam) { for (const param of localParam) {
@ -678,12 +841,12 @@ export class CodeParser extends Plugin {
} }
/** /**
* *
* @param node * @param node
* @returns * @returns
*/ */
async getFunctionReturnParameters(node: any) { async getFunctionReturnParameters(node: any) {
const localParam = (node.returnParameters && node.returnParameters.parameters) const localParam = node.returnParameters && node.returnParameters.parameters
if (localParam) { if (localParam) {
const params = [] const params = []
for (const param of localParam) { for (const param of localParam) {
@ -692,7 +855,4 @@ export class CodeParser extends Plugin {
return `(${params.join(', ')})` return `(${params.join(', ')})`
} }
} }
} }

@ -1,9 +1,12 @@
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { FormattedMessage } from 'react-intl' import {FormattedMessage} from 'react-intl'
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { AppModal } from '@remix-ui/app' import {AppModal} from '@remix-ui/app'
import { PermissionHandlerDialog, PermissionHandlerValue } from '@remix-ui/permission-handler' import {
import { Profile } from '@remixproject/plugin-utils' PermissionHandlerDialog,
PermissionHandlerValue
} from '@remix-ui/permission-handler'
import {Profile} from '@remixproject/plugin-utils'
const profile = { const profile = {
name: 'permissionhandler', name: 'permissionhandler',
@ -46,16 +49,22 @@ export class PermissionHandlerPlugin extends Plugin {
} }
} }
switchMode (from: Profile, to: Profile, method: string, set: boolean, sensitiveCall: boolean) { switchMode(
from: Profile,
to: Profile,
method: string,
set: boolean,
sensitiveCall: boolean
) {
if (sensitiveCall) { if (sensitiveCall) {
set set
? this.sessionPermissions[to.name][method][from.name] = {} ? (this.sessionPermissions[to.name][method][from.name] = {})
: delete this.sessionPermissions[to.name][method][from.name] : delete this.sessionPermissions[to.name][method][from.name]
} else { } else {
set set
? this.permissions[to.name][method][from.name] = {} ? (this.permissions[to.name][method][from.name] = {})
: delete this.permissions[to.name][method][from.name] : delete this.permissions[to.name][method][from.name]
} }
} }
clear() { clear() {
@ -65,7 +74,9 @@ export class PermissionHandlerPlugin extends Plugin {
} }
notAllowWarning(from: Profile, to: Profile, method: string) { notAllowWarning(from: Profile, to: Profile, method: string) {
return `${from.displayName || from.name} is not allowed to call ${method} method of ${to.displayName || to.name}.` return `${
from.displayName || from.name
} is not allowed to call ${method} method of ${to.displayName || to.name}.`
} }
async getTheme() { async getTheme() {
@ -80,20 +91,33 @@ export class PermissionHandlerPlugin extends Plugin {
* @param {string} message from the caller plugin to add more details if needed * @param {string} message from the caller plugin to add more details if needed
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
async askPermission(from: Profile, to: Profile, method: string, message: string, sensitiveCall: boolean) { async askPermission(
from: Profile,
to: Profile,
method: string,
message: string,
sensitiveCall: boolean
) {
try { try {
if (sensitiveCall) { if (sensitiveCall) {
if (!this.sessionPermissions[to.name]) this.sessionPermissions[to.name] = {} if (!this.sessionPermissions[to.name])
if (!this.sessionPermissions[to.name][method]) this.sessionPermissions[to.name][method] = {} this.sessionPermissions[to.name] = {}
if (!this.sessionPermissions[to.name][method][from.name]) return this.openPermission(from, to, method, message, sensitiveCall) 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 { } else {
this.permissions = this._getFromLocal() this.permissions = this._getFromLocal()
if (!this.permissions[to.name]) this.permissions[to.name] = {} 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])
if (!this.permissions[to.name][method][from.name]) return this.openPermission(from, to, method, message, sensitiveCall) 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] const {allow, hash} = sensitiveCall
? this.sessionPermissions[to.name][method][from.name]
: this.permissions[to.name][method][from.name]
if (!allow) { if (!allow) {
const warning = this.notAllowWarning(from, to, method) const warning = this.notAllowWarning(from, to, method)
this.call('notification', 'toast', warning) this.call('notification', 'toast', warning)
@ -107,7 +131,13 @@ export class PermissionHandlerPlugin extends Plugin {
} }
} }
async openPermission(from: Profile, to: Profile, method: string, message: string, sensitiveCall: boolean) { async openPermission(
from: Profile,
to: Profile,
method: string,
message: string,
sensitiveCall: boolean
) {
let remember let remember
if (sensitiveCall) { if (sensitiveCall) {
remember = this.sessionPermissions[to.name][method][from.name] remember = this.sessionPermissions[to.name][method][from.name]
@ -124,10 +154,21 @@ export class PermissionHandlerPlugin extends Plugin {
} }
const modal: AppModal = { const modal: AppModal = {
id: 'PermissionHandler', id: 'PermissionHandler',
title: <FormattedMessage id='permissionHandler.permissionNeededFor' values={{ to: to.displayName || to.name }} />, title: (
message: <PermissionHandlerDialog plugin={this} theme={await this.getTheme()} value={value}></PermissionHandlerDialog>, <FormattedMessage
okLabel: <FormattedMessage id='permissionHandler.accept' />, id="permissionHandler.permissionNeededFor"
cancelLabel: <FormattedMessage id='permissionHandler.decline' /> 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) const result = await this.call('notification', 'modal', modal)
@ -166,7 +207,7 @@ export class PermissionHandlerPlugin extends Plugin {
} }
this.persistPermissions() this.persistPermissions()
} }
} }
reject(this.notAllowWarning(from, to, method)) reject(this.notAllowWarning(from, to, method))
} }
}) })

@ -1,12 +1,12 @@
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
import React, { useRef, useState, useEffect } from 'react' // eslint-disable-line import React, {useRef, useState, useEffect} from 'react' // eslint-disable-line
import isElectron from 'is-electron' import isElectron from 'is-electron'
import { WebsocketPlugin } from '@remixproject/engine-web' import {WebsocketPlugin} from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries // eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import { version as remixdVersion } from '../../../../../libs/remixd/package.json' import {version as remixdVersion} from '../../../../../libs/remixd/package.json'
import { PluginManager } from '@remixproject/engine' import {PluginManager} from '@remixproject/engine'
import { AppModal, AlertModal } from '@remix-ui/app' import {AppModal, AlertModal} from '@remix-ui/app'
const LOCALHOST = ' - connect to localhost - ' const LOCALHOST = ' - connect to localhost - '
@ -14,15 +14,27 @@ const profile = {
name: 'remixd', name: 'remixd',
displayName: 'RemixD', displayName: 'RemixD',
url: 'ws://127.0.0.1:65520', url: 'ws://127.0.0.1:65520',
methods: ['folderIsReadOnly', 'resolveDirectory', 'get', 'exists', 'isFile', 'set', 'rename', 'remove', 'isDirectory', 'list', 'createDir'], methods: [
'folderIsReadOnly',
'resolveDirectory',
'get',
'exists',
'isFile',
'set',
'rename',
'remove',
'isDirectory',
'list',
'createDir'
],
events: [], events: [],
description: 'Using Remixd daemon, allow to access file system', description: 'Using Remixd daemon, allow to access file system',
kind: 'other', kind: 'other',
version: packageJson.version, version: packageJson.version,
repo: "https://github.com/ethereum/remix-project/tree/master/libs/remixd", repo: 'https://github.com/ethereum/remix-project/tree/master/libs/remixd',
maintainedBy: "Remix", maintainedBy: 'Remix',
documentation: "https://remix-ide.readthedocs.io/en/latest/remixd.html", documentation: 'https://remix-ide.readthedocs.io/en/latest/remixd.html',
authorContact: "" authorContact: ''
} }
export class RemixdHandle extends WebsocketPlugin { export class RemixdHandle extends WebsocketPlugin {
@ -62,7 +74,6 @@ export class RemixdHandle extends WebsocketPlugin {
} }
await this.appManager.deactivatePlugin('remixd') await this.appManager.deactivatePlugin('remixd')
} }
async callPluginMethod(key: string, payload?: any[]) { async callPluginMethod(key: string, payload?: any[]) {
@ -74,36 +85,44 @@ export class RemixdHandle extends WebsocketPlugin {
} }
/** /**
* connect to localhost if no connection and render the explorer * connect to localhost if no connection and render the explorer
* disconnect from localhost if connected and remove the explorer * disconnect from localhost if connected and remove the explorer
* *
* @param {String} txHash - hash of the transaction * @param {String} txHash - hash of the transaction
*/ */
async connectToLocalhost() { async connectToLocalhost() {
const connection = async (error?: any) => { const connection = async (error?: any) => {
if (error) { if (error) {
console.log(error) console.log(error)
const alert: AlertModal = { const alert: AlertModal = {
id: 'connectionAlert', id: 'connectionAlert',
message: 'Cannot connect to the remixd daemon. Please make sure you have the remixd running in the background.' message:
'Cannot connect to the remixd daemon. Please make sure you have the remixd running in the background.'
} }
this.call('notification', 'alert', alert) this.call('notification', 'alert', alert)
this.canceled() this.canceled()
} else { } else {
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed if (!this.socket || (this.socket && this.socket.readyState === 3)) {
// 3 means connection closed
clearInterval(intervalId) clearInterval(intervalId)
const alert: AlertModal = { const alert: AlertModal = {
id: 'connectionAlert', id: 'connectionAlert',
message: 'Connection to remixd terminated. Please make sure remixd is still running in the background.' message:
'Connection to remixd terminated. Please make sure remixd is still running in the background.'
} }
this.call('notification', 'alert', alert) this.call('notification', 'alert', alert)
this.canceled() this.canceled()
} }
}, 3000) }, 3000)
this.localhostProvider.init(() => { this.localhostProvider.init(() => {
this.call('filePanel', 'setWorkspace', { name: LOCALHOST, isLocalhost: true }, true) this.call(
}); 'filePanel',
'setWorkspace',
{name: LOCALHOST, isLocalhost: true},
true
)
})
for (const plugin of this.dependentPlugins) { for (const plugin of this.dependentPlugins) {
await this.appManager.activatePlugin(plugin) await this.appManager.activatePlugin(plugin)
} }
@ -118,7 +137,7 @@ export class RemixdHandle extends WebsocketPlugin {
title: 'Access file system using remixd', title: 'Access file system using remixd',
message: remixdDialog(), message: remixdDialog(),
okLabel: 'Connect', okLabel: 'Connect',
cancelLabel: 'Cancel', cancelLabel: 'Cancel'
} }
const result = await this.call('notification', 'modal', mod) const result = await this.call('notification', 'modal', mod)
if (result) { if (result) {
@ -126,7 +145,8 @@ export class RemixdHandle extends WebsocketPlugin {
this.localhostProvider.preInit() this.localhostProvider.preInit()
super.activate() super.activate()
setTimeout(() => { setTimeout(() => {
if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed if (!this.socket || (this.socket && this.socket.readyState === 3)) {
// 3 means connection closed
connection(new Error('Connection with daemon failed.')) connection(new Error('Connection with daemon failed.'))
} else { } else {
connection() connection()
@ -135,14 +155,15 @@ export class RemixdHandle extends WebsocketPlugin {
} catch (error) { } catch (error) {
connection(error) connection(error)
} }
} } else {
else {
await this.canceled() await this.canceled()
} }
} else { } else {
try { try {
super.activate() super.activate()
setTimeout(() => { connection() }, 2000) setTimeout(() => {
connection()
}, 2000)
} catch (error) { } catch (error) {
connection(error) connection(error)
} }
@ -152,38 +173,73 @@ export class RemixdHandle extends WebsocketPlugin {
function remixdDialog() { function remixdDialog() {
const commandText = 'remixd' const commandText = 'remixd'
const fullCommandText = 'remixd -s <path-to-the-shared-folder> -u <remix-ide-instance-URL>' const fullCommandText =
return (<> 'remixd -s <path-to-the-shared-folder> -u <remix-ide-instance-URL>'
<div className=''> return (
<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 className="">
</div> <div className="mb-2 text-break">
<div className='mb-2 text-break'> Access your local file system from Remix IDE using{' '}
Remixd <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">documentation</a>. <a
</div> target="_blank"
<div className='mb-2 text-break'> href="https://www.npmjs.com/package/@remix-project/remixd"
The remixd command is: >
<br /><b>{commandText}</b> Remixd NPM package
</div> </a>
<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> <div className="mb-2 text-break">
<div className='mb-2 text-break'> Remixd{' '}
Example command with flags: <br /> <a
<b>{fullCommandText}</b> target="_blank"
</div> href="https://remix-ide.readthedocs.io/en/latest/remixd.html"
<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> documentation
</div> </a>
<div className='mb-2 text-break'> .
This feature is still in Alpha. We recommend to keep a backup of the shared folder. </div>
</div> <div className="mb-2 text-break">
<div className='mb-2 text-break'> The remixd command is:
<h6 className="text-danger"> <br />
Before using, make sure remixd version is latest i.e. <b>v{remixdVersion}</b> <b>{commandText}</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> </div>
</h6> <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">
Example command with flags: <br />
<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">
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">
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> </div>
</div> </>
</>) )
} }

@ -1,9 +1,9 @@
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { format } from 'util' import {format} from 'util'
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { compile } from '@remix-project/remix-solidity' import {compile} from '@remix-project/remix-solidity'
import { TransactionConfig } from 'web3-core' import {TransactionConfig} from 'web3-core'
const _paq = window._paq = window._paq || [] //eslint-disable-line const _paq = (window._paq = window._paq || []) //eslint-disable-line
const profile = { const profile = {
name: 'solidity-script', name: 'solidity-script',
@ -13,17 +13,20 @@ const profile = {
} }
export class SolidityScript extends Plugin { export class SolidityScript extends Plugin {
constructor () { constructor() {
super(profile) super(profile)
} }
async execute (path: string, functionName: string = 'run') { async execute(path: string, functionName: string = 'run') {
_paq.push(['trackEvent', 'SolidityScript', 'execute', 'script']) _paq.push(['trackEvent', 'SolidityScript', 'execute', 'script'])
this.call('terminal', 'log', `Running free function '${functionName}' from ${path}...`) this.call(
'terminal',
'log',
`Running free function '${functionName}' from ${path}...`
)
let content = await this.call('fileManager', 'readFile', path) let content = await this.call('fileManager', 'readFile', path)
const params = await this.call('solidity', 'getCompilerParameters') const params = await this.call('solidity', 'getCompilerParameters')
content = ` content = `
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
@ -38,13 +41,15 @@ export class SolidityScript extends Plugin {
${functionName}(); ${functionName}();
} }
}` }`
const targets = { 'script.sol': { content } } const targets = {'script.sol': {content}}
// compile // compile
const compilation = await compile(targets, params, async (url, cb) => { 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)) await this.call('contentImport', 'resolveAndSave', url)
.then((result) => cb(null, result))
.catch((error) => cb(error.message))
}) })
if (compilation.data.error) { if (compilation.data.error) {
this.call('terminal', 'log', compilation.data.error.formattedMessage) this.call('terminal', 'log', compilation.data.error.formattedMessage)
} }
@ -53,7 +58,7 @@ export class SolidityScript extends Plugin {
this.call('terminal', 'log', error.formattedMessage) this.call('terminal', 'log', error.formattedMessage)
}) })
} }
// get the contract // get the contract
const contract = compilation.getContract('SolidityScript') const contract = compilation.getContract('SolidityScript')
if (!contract) { if (!contract) {
@ -83,24 +88,31 @@ export class SolidityScript extends Plugin {
const hhlogs = await web3.eth.getHHLogsForTx(receiptCall.transactionHash) const hhlogs = await web3.eth.getHHLogsForTx(receiptCall.transactionHash)
if (hhlogs && hhlogs.length) { if (hhlogs && hhlogs.length) {
const finalLogs = <div><div><b>console.log:</b></div> const finalLogs = (
{ <div>
hhlogs.map((log) => { <div>
<b>console.log:</b>
</div>
{hhlogs.map((log) => {
let formattedLog let formattedLog
// Hardhat implements the same formatting options that can be found in Node.js' console.log, // 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 // 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' // 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 // We check first arg to determine if 'util.format' is needed
if (typeof log[0] === 'string' && (log[0].includes('%s') || log[0].includes('%d'))) { if (
typeof log[0] === 'string' &&
(log[0].includes('%s') || log[0].includes('%d'))
) {
formattedLog = format(log[0], ...log.slice(1)) formattedLog = format(log[0], ...log.slice(1))
} else { } else {
formattedLog = log.join(' ') formattedLog = log.join(' ')
} }
return <div>{formattedLog}</div> return <div>{formattedLog}</div>
})} })}
</div> </div>
)
_paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log']) _paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log'])
this.call('terminal', 'logHtml', finalLogs) this.call('terminal', 'logHtml', finalLogs)
} }
} }
} }

@ -1,27 +1,32 @@
/* eslint-disable @nrwl/nx/enforce-module-boundaries */ /* eslint-disable @nrwl/nx/enforce-module-boundaries */
import { ViewPlugin } from '@remixproject/engine-web' import {ViewPlugin} from '@remixproject/engine-web'
import React from 'react' import React from 'react'
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries // eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import { RemixUiSolidityUmlGen } from '@remix-ui/solidity-uml-gen' import {RemixUiSolidityUmlGen} from '@remix-ui/solidity-uml-gen'
import { ISolidityUmlGen, ThemeQualityType, ThemeSummary } from 'libs/remix-ui/solidity-uml-gen/src/types' import {
import { RemixAppManager } from 'libs/remix-ui/plugin-manager/src/types' ISolidityUmlGen,
import { normalizeContractPath } from 'libs/remix-ui/solidity-compiler/src/lib/logic/flattenerUtilities' ThemeQualityType,
import { convertAST2UmlClasses } from 'sol2uml/lib/converterAST2Classes' ThemeSummary
} from 'libs/remix-ui/solidity-uml-gen/src/types'
import {RemixAppManager} from 'libs/remix-ui/plugin-manager/src/types'
import {normalizeContractPath} from 'libs/remix-ui/solidity-compiler/src/lib/logic/flattenerUtilities'
import {convertAST2UmlClasses} from 'sol2uml/lib/converterAST2Classes'
import vizRenderStringSync from '@aduh95/viz.js/sync' import vizRenderStringSync from '@aduh95/viz.js/sync'
import { PluginViewWrapper } from '@remix-ui/helper' import {PluginViewWrapper} from '@remix-ui/helper'
import { customAction } from '@remixproject/plugin-api' import {customAction} from '@remixproject/plugin-api'
import { ClassOptions } from 'sol2uml/lib/converterClass2Dot' import {ClassOptions} from 'sol2uml/lib/converterClass2Dot'
const parser = (window as any).SolidityParser const parser = (window as any).SolidityParser
const _paq = window._paq = window._paq || [] const _paq = (window._paq = window._paq || [])
const profile = { const profile = {
name: 'solidityumlgen', name: 'solidityumlgen',
displayName: 'Solidity UML Generator', displayName: 'Solidity UML Generator',
description: 'Generates UML diagram in svg format from last compiled contract', description:
'Generates UML diagram in svg format from last compiled contract',
location: 'mainPanel', location: 'mainPanel',
methods: ['showUmlDiagram', 'generateUml', 'generateCustomAction'], methods: ['showUmlDiagram', 'generateUml', 'generateCustomAction'],
events: [], events: []
} }
/** /**
@ -54,7 +59,6 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
this.currentlySelectedTheme = '' this.currentlySelectedTheme = ''
this.themeName = '' this.themeName = ''
this.activeTheme = {} as ThemeSummary this.activeTheme = {} as ThemeSummary
this.appManager = appManager this.appManager = appManager
this.element = document.createElement('div') this.element = document.createElement('div')
@ -63,37 +67,54 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
onActivation(): void { onActivation(): void {
this.handleThemeChange() this.handleThemeChange()
this.on('solidity', 'compilationFinished', async (file: string, source, languageVersion, data, input, version) => { this.on(
if(!this.triggerGenerateUml) return 'solidity',
this.triggerGenerateUml = false 'compilationFinished',
const currentTheme: ThemeQualityType = await this.call('theme', 'currentTheme') async (file: string, source, languageVersion, data, input, version) => {
this.currentlySelectedTheme = currentTheme.quality if (!this.triggerGenerateUml) return
this.themeName = currentTheme.name this.triggerGenerateUml = false
let result = '' const currentTheme: ThemeQualityType = await this.call(
const normalized = normalizeContractPath(file) 'theme',
this.currentFile = normalized[normalized.length - 1] 'currentTheme'
try { )
if (data.sources && Object.keys(data.sources).length > 1) { // we should flatten first as there are multiple asts this.currentlySelectedTheme = currentTheme.quality
result = await this.flattenContract(source, file, data) 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 = await this.call('theme', 'currentTheme')
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)
} }
const ast = result.length > 1 ? parser.parse(result) : parser.parse(source.sources[file].content)
this.umlClasses = convertAST2UmlClasses(ast, this.currentFile)
let umlDot = ''
this.activeTheme = await this.call('theme', 'currentTheme')
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) { getThemeCssVariables(cssVars: string) {
return window.getComputedStyle(document.documentElement) return window
.getComputedStyle(document.documentElement)
.getPropertyValue(cssVars) .getPropertyValue(cssVars)
} }
@ -102,7 +123,12 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
this.currentlySelectedTheme = theme.quality this.currentlySelectedTheme = theme.quality
this.activeTheme = theme this.activeTheme = theme
this.themeDark = theme.backgroundColor this.themeDark = theme.backgroundColor
const umlDot = convertUmlClasses2Dot(this.umlClasses, false, { backColor: this.activeTheme.backgroundColor, textColor: this.activeTheme.textColor, shapeColor: this.activeTheme.shapeColor, fillColor: this.activeTheme.fillColor }) 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.updatedSvg = vizRenderStringSync(umlDot)
this.renderComponent() this.renderComponent()
await this.call('tabs', 'focus', 'solidityumlgen') await this.call('tabs', 'focus', 'solidityumlgen')
@ -133,13 +159,17 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
* and assigns to a local property * and assigns to a local property
* @returns {Promise<string>} * @returns {Promise<string>}
*/ */
async flattenContract (source: any, filePath: string, data: any) { async flattenContract(source: any, filePath: string, data: any) {
const result = await this.call('contractflattener', 'flattenContract', source, filePath, data) const result = await this.call(
'contractflattener',
'flattenContract',
source,
filePath,
data
)
return result return result
} }
async showUmlDiagram(svgPayload: string) { async showUmlDiagram(svgPayload: string) {
this.updatedSvg = svgPayload this.updatedSvg = svgPayload
this.renderComponent() this.renderComponent()
@ -150,18 +180,20 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
this.renderComponent() this.renderComponent()
} }
setDispatch (dispatch: React.Dispatch<any>) { setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch this.dispatch = dispatch
this.renderComponent() this.renderComponent()
} }
render() { render() {
return <div id='sol-uml-gen'> return (
<PluginViewWrapper plugin={this} /> <div id="sol-uml-gen">
</div> <PluginViewWrapper plugin={this} />
</div>
)
} }
renderComponent () { renderComponent() {
this.dispatch({ this.dispatch({
...this, ...this,
updatedSvg: this.updatedSvg, updatedSvg: this.updatedSvg,
@ -171,24 +203,25 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
themeDark: this.themeDark, themeDark: this.themeDark,
fileName: this.currentFile, fileName: this.currentFile,
themeCollection: this.themeCollection, themeCollection: this.themeCollection,
activeTheme: this.activeTheme, activeTheme: this.activeTheme
}) })
} }
updateComponent(state: any) { updateComponent(state: any) {
return <RemixUiSolidityUmlGen return (
updatedSvg={state.updatedSvg} <RemixUiSolidityUmlGen
loading={state.loading} updatedSvg={state.updatedSvg}
themeSelected={state.currentlySelectedTheme} loading={state.loading}
themeName={state.themeName} themeSelected={state.currentlySelectedTheme}
fileName={state.fileName} themeName={state.themeName}
themeCollection={state.themeCollection} fileName={state.fileName}
themeDark={state.themeDark} themeCollection={state.themeCollection}
/> themeDark={state.themeDark}
/>
)
} }
} }
interface Sol2umlClassOptions extends ClassOptions { interface Sol2umlClassOptions extends ClassOptions {
backColor?: string backColor?: string
shapeColor?: string shapeColor?: string
@ -196,15 +229,15 @@ interface Sol2umlClassOptions extends ClassOptions {
textColor?: string textColor?: string
} }
import { dirname } from 'path' import {dirname} from 'path'
import { convertClass2Dot } from 'sol2uml/lib/converterClass2Dot' import {convertClass2Dot} from 'sol2uml/lib/converterClass2Dot'
import { import {
Association, Association,
ClassStereotype, ClassStereotype,
ReferenceType, ReferenceType,
UmlClass, UmlClass
} from 'sol2uml/lib/umlClass' } from 'sol2uml/lib/umlClass'
import { findAssociatedClass } from 'sol2uml/lib/associations' import {findAssociatedClass} from 'sol2uml/lib/associations'
// const debug = require('debug')('sol2uml') // const debug = require('debug')('sol2uml')
@ -347,20 +380,20 @@ function addAssociationToDot(
// Or associations to Structs, Enums or Constants if they are hidden // Or associations to Structs, Enums or Constants if they are hidden
if ( if (
(classOptions.hideLibraries && (classOptions.hideLibraries &&
(sourceUmlClass.stereotype === ClassStereotype.Library || (sourceUmlClass.stereotype === ClassStereotype.Library ||
targetUmlClass.stereotype === ClassStereotype.Library)) || targetUmlClass.stereotype === ClassStereotype.Library)) ||
(classOptions.hideInterfaces && (classOptions.hideInterfaces &&
(targetUmlClass.stereotype === ClassStereotype.Interface || (targetUmlClass.stereotype === ClassStereotype.Interface ||
sourceUmlClass.stereotype === ClassStereotype.Interface)) || sourceUmlClass.stereotype === ClassStereotype.Interface)) ||
(classOptions.hideAbstracts && (classOptions.hideAbstracts &&
(targetUmlClass.stereotype === ClassStereotype.Abstract || (targetUmlClass.stereotype === ClassStereotype.Abstract ||
sourceUmlClass.stereotype === ClassStereotype.Abstract)) || sourceUmlClass.stereotype === ClassStereotype.Abstract)) ||
(classOptions.hideStructs && (classOptions.hideStructs &&
targetUmlClass.stereotype === ClassStereotype.Struct) || targetUmlClass.stereotype === ClassStereotype.Struct) ||
(classOptions.hideEnums && (classOptions.hideEnums &&
targetUmlClass.stereotype === ClassStereotype.Enum) || targetUmlClass.stereotype === ClassStereotype.Enum) ||
(classOptions.hideConstants && (classOptions.hideConstants &&
targetUmlClass.stereotype === ClassStereotype.Constant) targetUmlClass.stereotype === ClassStereotype.Constant)
) { ) {
return '' return ''
} }
@ -369,8 +402,8 @@ function addAssociationToDot(
if ( if (
association.referenceType == ReferenceType.Memory || association.referenceType == ReferenceType.Memory ||
(association.realization && (association.realization &&
targetUmlClass.stereotype === ClassStereotype.Interface) targetUmlClass.stereotype === ClassStereotype.Interface)
) { ) {
dotString += 'style=dashed, ' dotString += 'style=dashed, '
} }

@ -1,30 +1,30 @@
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { AppModal, AlertModal, ModalTypes } from '@remix-ui/app' import {AppModal, AlertModal, ModalTypes} from '@remix-ui/app'
import { Blockchain } from '../../blockchain/blockchain' import {Blockchain} from '../../blockchain/blockchain'
import { ethers } from 'ethers' import {ethers} from 'ethers'
export type JsonDataRequest = { export type JsonDataRequest = {
id: number, id: number
jsonrpc: string // version jsonrpc: string // version
method: string, method: string
params: Array<any>, params: Array<any>
} }
export type JsonDataResult = { export type JsonDataResult = {
id: number, id: number
jsonrpc: string // version jsonrpc: string // version
result?: any, result?: any
error?: any, error?: any
} }
export type RejectRequest = (error: Error) => void export type RejectRequest = (error: Error) => void
export type SuccessRequest = (data: JsonDataResult) => void export type SuccessRequest = (data: JsonDataResult) => void
export interface IProvider { export interface IProvider {
options: { [id: string] : any } options: {[id: string]: any}
init(): Promise<{ [id: string] : any }> init(): Promise<{[id: string]: any}>
body(): JSX.Element body(): JSX.Element
sendAsync (data: JsonDataRequest): Promise<JsonDataResult> sendAsync(data: JsonDataRequest): Promise<JsonDataResult>
} }
export abstract class AbstractProvider extends Plugin implements IProvider { export abstract class AbstractProvider extends Plugin implements IProvider {
@ -33,9 +33,9 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
defaultUrl: string defaultUrl: string
connected: boolean connected: boolean
nodeUrl: string nodeUrl: string
options: { [id: string] : any } = {} options: {[id: string]: any} = {}
constructor (profile, blockchain, defaultUrl) { constructor(profile, blockchain, defaultUrl) {
super(profile) super(profile)
this.defaultUrl = defaultUrl this.defaultUrl = defaultUrl
this.provider = null this.provider = null
@ -46,11 +46,11 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
abstract body(): JSX.Element abstract body(): JSX.Element
onDeactivation () { onDeactivation() {
this.provider = null this.provider = null
} }
async init () { async init() {
this.nodeUrl = await ((): Promise<string> => { this.nodeUrl = await ((): Promise<string> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const modalContent: AppModal = { const modalContent: AppModal = {
@ -61,16 +61,17 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
okLabel: 'OK', okLabel: 'OK',
cancelLabel: 'Cancel', cancelLabel: 'Cancel',
validationFn: (value) => { validationFn: (value) => {
if (!value) return { valid: false, message: "value is empty" } if (!value) return {valid: false, message: 'value is empty'}
if (value.startsWith('https://') || value.startsWith('http://')) { if (value.startsWith('https://') || value.startsWith('http://')) {
return { return {
valid: true, valid: true,
message: '' message: ''
} }
} else { } else {
return { return {
valid: false, valid: false,
message: 'the provided value should contain the protocol ( e.g starts with http:// or https:// )' message:
'the provided value should contain the protocol ( e.g starts with http:// or https:// )'
} }
} }
}, },
@ -94,7 +95,7 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
} }
} }
sendAsync (data: JsonDataRequest): Promise<JsonDataResult> { sendAsync(data: JsonDataRequest): Promise<JsonDataResult> {
// eslint-disable-next-line no-async-promise-executor // eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
if (!this.provider) return reject(new Error('provider node set')) if (!this.provider) return reject(new Error('provider node set'))
@ -102,7 +103,7 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
}) })
} }
private async switchAway (showError) { private async switchAway(showError) {
if (!this.provider) return if (!this.provider) return
this.provider = null this.provider = null
this.connected = false this.connected = false
@ -110,28 +111,37 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
const modalContent: AlertModal = { const modalContent: AlertModal = {
id: this.profile.name, id: this.profile.name,
title: this.profile.displayName, title: this.profile.displayName,
message: `Error while connecting to the provider, provider not connected`, message: `Error while connecting to the provider, provider not connected`
} }
this.call('notification', 'alert', modalContent) this.call('notification', 'alert', modalContent)
} }
await this.call('udapp', 'setEnvironmentMode', { context: 'vm-merge'}) await this.call('udapp', 'setEnvironmentMode', {context: 'vm-merge'})
return return
} }
private async sendAsyncInternal (data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise<void> { private async sendAsyncInternal(
data: JsonDataRequest,
resolve: SuccessRequest,
reject: RejectRequest
): Promise<void> {
if (this.provider) { if (this.provider) {
try { try {
const result = await this.provider.send(data.method, data.params) const result = await this.provider.send(data.method, data.params)
resolve({ jsonrpc: '2.0', result, id: data.id }) resolve({jsonrpc: '2.0', result, id: data.id})
} catch (error) { } catch (error) {
if (error && error.message && error.message.includes('net_version') && error.message.includes('SERVER_ERROR')) { if (
error &&
error.message &&
error.message.includes('net_version') &&
error.message.includes('SERVER_ERROR')
) {
this.switchAway(true) this.switchAway(true)
} }
reject(error) reject(error)
} }
} else { } else {
const result = data.method === 'net_listening' ? 'canceled' : [] const result = data.method === 'net_listening' ? 'canceled' : []
resolve({ jsonrpc: '2.0', result: result, id: data.id }) resolve({jsonrpc: '2.0', result: result, id: data.id})
} }
} }
} }

@ -1,23 +1,26 @@
import React, { useRef } from 'react' // eslint-disable-line import React, {useRef} from 'react' // eslint-disable-line
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { AppModal, ModalTypes } from '@remix-ui/app' import {AppModal, ModalTypes} from '@remix-ui/app'
import { BasicVMProvider } from './vm-provider' import {BasicVMProvider} from './vm-provider'
import { Hardfork } from '@ethereumjs/common' import {Hardfork} from '@ethereumjs/common'
export class CustomForkVMProvider extends BasicVMProvider { export class CustomForkVMProvider extends BasicVMProvider {
nodeUrl: string nodeUrl: string
blockNumber: number | 'latest' blockNumber: number | 'latest'
inputs: any inputs: any
constructor (blockchain) { constructor(blockchain) {
super({ super(
name: 'vm-custom-fork', {
displayName: 'Custom fork - Remix VM', name: 'vm-custom-fork',
kind: 'provider', displayName: 'Custom fork - Remix VM',
description: 'Custom fork - Remix VM', kind: 'provider',
methods: ['sendAsync', 'init'], description: 'Custom fork - Remix VM',
version: packageJson.version methods: ['sendAsync', 'init'],
}, blockchain) version: packageJson.version
},
blockchain
)
this.blockchain = blockchain this.blockchain = blockchain
this.fork = '' this.fork = ''
this.nodeUrl = '' this.nodeUrl = ''
@ -25,28 +28,54 @@ export class CustomForkVMProvider extends BasicVMProvider {
this.inputs = {} this.inputs = {}
} }
async init () { async init() {
const body = () => { const body = () => {
return <div> return (
<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> <div>
<label className="mt-3 mb-1">Node URL</label> <span>
<input data-id="CustomForkNodeUrl" name="nodeUrl" type="text" className="border form-control border-right-0" /> 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> </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> => { const result = await ((): Promise<any> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const modalContent: AppModal = { const modalContent: AppModal = {
@ -54,7 +83,7 @@ export class CustomForkVMProvider extends BasicVMProvider {
title: this.profile.displayName, title: this.profile.displayName,
message: body(), message: body(),
validationFn: (data: any) => { validationFn: (data: any) => {
if(data.nodeUrl !== '' && !data.nodeUrl.startsWith("http")) { if (data.nodeUrl !== '' && !data.nodeUrl.startsWith('http')) {
return { return {
valid: false, valid: false,
message: 'node URL should be a valid URL' message: 'node URL should be a valid URL'
@ -96,11 +125,11 @@ export class CustomForkVMProvider extends BasicVMProvider {
this.nodeUrl = undefined this.nodeUrl = undefined
this.blockNumber = undefined this.blockNumber = undefined
} }
return { return {
'fork': this.fork, fork: this.fork,
'nodeUrl': this.nodeUrl, nodeUrl: this.nodeUrl,
'blockNumber': this.blockNumber blockNumber: this.blockNumber
} }
} }
} }

@ -1,6 +1,6 @@
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { AbstractProvider } from './abstract-provider' import {AbstractProvider} from './abstract-provider'
const profile = { const profile = {
name: 'basic-http-provider', name: 'basic-http-provider',
@ -12,25 +12,57 @@ const profile = {
} }
export class ExternalHttpProvider extends AbstractProvider { export class ExternalHttpProvider extends AbstractProvider {
constructor (blockchain) { constructor(blockchain) {
super(profile, blockchain, 'http://127.0.0.1:8545') super(profile, blockchain, 'http://127.0.0.1:8545')
} }
body (): JSX.Element { body(): JSX.Element {
const thePath = '<path/to/local/folder/for/test/chain>' const thePath = '<path/to/local/folder/for/test/chain>'
return ( return (
<> <>
<div className=""> <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>) Note: To use Geth & https://remix.ethereum.org, configure it to allow
<div className="border p-1">geth --http --http.corsdomain https://remix.ethereum.org</div> 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 /> <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>) To run Remix & a local Geth test node, use this command: (see{' '}
<div className="border p-1">geth --http --http.corsdomain="{window.origin}" --http.api web3,eth,debug,personal,net --vmdebug --datadir {thePath} --dev console</div> <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 />
<br /> <br />
<b>WARNING:</b> It is not safe to use the --http.corsdomain flag with a wildcard: <b>--http.corsdomain *</b> <b>WARNING:</b> It is not safe to use the --http.corsdomain flag with
a wildcard: <b>--http.corsdomain *</b>
<br /> <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 />
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 />
<br /> <br />
External HTTP Provider Endpoint External HTTP Provider Endpoint
@ -38,4 +70,4 @@ export class ExternalHttpProvider extends AbstractProvider {
</> </>
) )
} }
} }

@ -1,6 +1,6 @@
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { AbstractProvider } from './abstract-provider' import {AbstractProvider} from './abstract-provider'
const profile = { const profile = {
name: 'foundry-provider', name: 'foundry-provider',
@ -12,20 +12,29 @@ const profile = {
} }
export class FoundryProvider extends AbstractProvider { export class FoundryProvider extends AbstractProvider {
constructor (blockchain) { constructor(blockchain) {
super(profile, blockchain, 'http://127.0.0.1:8545') super(profile, blockchain, 'http://127.0.0.1:8545')
} }
body (): JSX.Element { body(): JSX.Element {
return ( return (
<div> Note: To run Anvil on your system, run: <div>
<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> 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"> <div className="pt-2 pb-4">
For more info, visit: <a href="https://github.com/foundry-rs/foundry" target="_blank">Foundry Documentation</a> For more info, visit:{' '}
<a href="https://github.com/foundry-rs/foundry" target="_blank">
Foundry Documentation
</a>
</div> </div>
<div>Anvil JSON-RPC Endpoint:</div> <div>Anvil JSON-RPC Endpoint:</div>
</div> </div>
) )
} }
} }

@ -1,6 +1,6 @@
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { AbstractProvider } from './abstract-provider' import {AbstractProvider} from './abstract-provider'
const profile = { const profile = {
name: 'ganache-provider', name: 'ganache-provider',
@ -12,20 +12,29 @@ const profile = {
} }
export class GanacheProvider extends AbstractProvider { export class GanacheProvider extends AbstractProvider {
constructor (blockchain) { constructor(blockchain) {
super(profile, blockchain, 'http://127.0.0.1:8545') super(profile, blockchain, 'http://127.0.0.1:8545')
} }
body (): JSX.Element { body(): JSX.Element {
return ( return (
<div> Note: To run Ganache on your system, run: <div>
<div className="p-1 pl-3"><b>yarn global add ganache</b></div> {' '}
<div className="p-1 pl-3"><b>ganache</b></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"> <div className="pt-2 pb-4">
For more info, visit: <a href="https://github.com/trufflesuite/ganache" target="_blank">Ganache Documentation</a> For more info, visit:{' '}
<a href="https://github.com/trufflesuite/ganache" target="_blank">
Ganache Documentation
</a>
</div> </div>
<div>Ganache JSON-RPC Endpoint:</div> <div>Ganache JSON-RPC Endpoint:</div>
</div> </div>
) )
} }
} }

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

@ -1,6 +1,6 @@
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { AbstractProvider } from './abstract-provider' import {AbstractProvider} from './abstract-provider'
const profile = { const profile = {
name: 'hardhat-provider', name: 'hardhat-provider',
@ -12,19 +12,30 @@ const profile = {
} }
export class HardhatProvider extends AbstractProvider { export class HardhatProvider extends AbstractProvider {
constructor (blockchain) { constructor(blockchain) {
super(profile, blockchain, 'http://127.0.0.1:8545') super(profile, blockchain, 'http://127.0.0.1:8545')
} }
body (): JSX.Element { body(): JSX.Element {
return ( return (
<div> Note: To run Hardhat network node on your system, go to hardhat project folder and run command: <div>
<div className="p-1 pl-3"><b>npx hardhat node</b></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"> <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> 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>
<div>Hardhat JSON-RPC Endpoint:</div> <div>Hardhat JSON-RPC Endpoint:</div>
</div> </div>
) )
} }
} }

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

@ -1,5 +1,5 @@
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { InjectedL2Provider } from './injected-L2-provider' import {InjectedL2Provider} from './injected-L2-provider'
const profile = { const profile = {
name: 'injected-arbitrum-one-provider', name: 'injected-arbitrum-one-provider',
@ -11,8 +11,7 @@ const profile = {
} }
export class InjectedArbitrumOneProvider extends InjectedL2Provider { export class InjectedArbitrumOneProvider extends InjectedL2Provider {
constructor() {
constructor () {
super(profile, 'Arbitrum One', '0xa4b1', ['https://arb1.arbitrum.io/rpc']) super(profile, 'Arbitrum One', '0xa4b1', ['https://arb1.arbitrum.io/rpc'])
} }
} }

@ -1,5 +1,5 @@
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { InjectedL2Provider } from './injected-L2-provider' import {InjectedL2Provider} from './injected-L2-provider'
const profile = { const profile = {
name: 'injected-optimism-provider', name: 'injected-optimism-provider',
@ -11,8 +11,7 @@ const profile = {
} }
export class Injected0ptimismProvider extends InjectedL2Provider { export class Injected0ptimismProvider extends InjectedL2Provider {
constructor() {
constructor () {
super(profile, 'Optimism', '0xa', ['https://mainnet.optimism.io']) super(profile, 'Optimism', '0xa', ['https://mainnet.optimism.io'])
} }
} }

@ -1,25 +1,34 @@
/* global ethereum */ /* global ethereum */
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { InjectedProvider } from './injected-provider' import {InjectedProvider} from './injected-provider'
export class InjectedProviderDefaultBase extends InjectedProvider { export class InjectedProviderDefaultBase extends InjectedProvider {
constructor (profile) { constructor(profile) {
super(profile) super(profile)
} }
async init () { async init() {
const injectedProvider = this.getInjectedProvider() const injectedProvider = this.getInjectedProvider()
if (injectedProvider && injectedProvider._metamask && injectedProvider._metamask.isUnlocked) { if (
if (!await injectedProvider._metamask.isUnlocked()) this.call('notification', 'toast', 'Please make sure the injected provider is unlocked (e.g Metamask).') 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() return super.init()
} }
getInjectedProvider () { getInjectedProvider() {
return (window as any).ethereum return (window as any).ethereum
} }
notFound () { 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).' 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).'
} }
} }
@ -34,7 +43,7 @@ const profile = {
} }
export class InjectedProviderDefault extends InjectedProviderDefaultBase { export class InjectedProviderDefault extends InjectedProviderDefaultBase {
constructor () { constructor() {
super(profile) super(profile)
} }
} }

@ -1,6 +1,6 @@
/* global ethereum */ /* global ethereum */
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { InjectedProvider } from './injected-provider' import {InjectedProvider} from './injected-provider'
const profile = { const profile = {
name: 'injected-trustwallet', name: 'injected-trustwallet',
@ -11,16 +11,16 @@ const profile = {
version: packageJson.version version: packageJson.version
} }
export class InjectedProviderTrustWallet extends InjectedProvider { export class InjectedProviderTrustWallet extends InjectedProvider {
constructor () { constructor() {
super(profile) super(profile)
} }
getInjectedProvider () { getInjectedProvider() {
return (window as any).trustwallet return (window as any).trustwallet
} }
notFound () { 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' 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'
} }
} }

@ -1,15 +1,19 @@
/* global ethereum */ /* global ethereum */
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { JsonDataRequest, RejectRequest, SuccessRequest } from '../providers/abstract-provider' import {
import { IProvider } from './abstract-provider' JsonDataRequest,
RejectRequest,
SuccessRequest
} from '../providers/abstract-provider'
import {IProvider} from './abstract-provider'
export abstract class InjectedProvider extends Plugin implements IProvider { export abstract class InjectedProvider extends Plugin implements IProvider {
options: { [id: string] : any } = {} options: {[id: string]: any} = {}
listenerAccountsChanged: (accounts: Array<string>) => void listenerAccountsChanged: (accounts: Array<string>) => void
listenerChainChanged: (chainId: number) => void listenerChainChanged: (chainId: number) => void
constructor (profile) { constructor(profile) {
super(profile) super(profile)
this.listenerAccountsChanged = (accounts: Array<string>) => { this.listenerAccountsChanged = (accounts: Array<string>) => {
this.emit('accountsChanged', accounts) this.emit('accountsChanged', accounts)
@ -25,39 +29,43 @@ export abstract class InjectedProvider extends Plugin implements IProvider {
onActivation(): void { onActivation(): void {
try { try {
const web3Provider = this.getInjectedProvider() const web3Provider = this.getInjectedProvider()
web3Provider.on('accountsChanged', this.listenerAccountsChanged); web3Provider.on('accountsChanged', this.listenerAccountsChanged)
web3Provider.on('chainChanged', this.listenerChainChanged); web3Provider.on('chainChanged', this.listenerChainChanged)
} catch (error) { } catch (error) {
console.log('unable to listen on context changed') console.log('unable to listen on context changed')
} }
} }
onDeactivation(): void { onDeactivation(): void {
try { try {
const web3Provider = this.getInjectedProvider() const web3Provider = this.getInjectedProvider()
web3Provider.removeListener('accountsChanged', this.listenerAccountsChanged) web3Provider.removeListener(
'accountsChanged',
this.listenerAccountsChanged
)
web3Provider.removeListener('chainChanged', this.listenerChainChanged) web3Provider.removeListener('chainChanged', this.listenerChainChanged)
} catch (error) { } catch (error) {
console.log('unable to remove listener on context changed') console.log('unable to remove listener on context changed')
} }
} }
askPermission (throwIfNoInjectedProvider) { askPermission(throwIfNoInjectedProvider) {
const web3Provider = this.getInjectedProvider() const web3Provider = this.getInjectedProvider()
if (typeof web3Provider !== "undefined" && typeof web3Provider.request === "function") { if (
web3Provider.request({ method: "eth_requestAccounts" }) typeof web3Provider !== 'undefined' &&
typeof web3Provider.request === 'function'
) {
web3Provider.request({method: 'eth_requestAccounts'})
} else if (throwIfNoInjectedProvider) { } else if (throwIfNoInjectedProvider) {
throw new Error(this.notFound()) throw new Error(this.notFound())
} }
} }
body (): JSX.Element { body(): JSX.Element {
return ( return <div></div>
<div></div>
)
} }
async init () { async init() {
const injectedProvider = this.getInjectedProvider() const injectedProvider = this.getInjectedProvider()
if (injectedProvider === undefined) { if (injectedProvider === undefined) {
this.call('notification', 'toast', this.notFound()) this.call('notification', 'toast', this.notFound())
@ -68,38 +76,60 @@ export abstract class InjectedProvider extends Plugin implements IProvider {
return {} return {}
} }
sendAsync (data: JsonDataRequest): Promise<any> { sendAsync(data: JsonDataRequest): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.sendAsyncInternal(data, resolve, reject) this.sendAsyncInternal(data, resolve, reject)
}) })
} }
private async sendAsyncInternal (data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise<void> { 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 // 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 !!' // This will be displayed on UI tooltip as 'cannot get account list: Environment Updated !!'
const web3Provider = this.getInjectedProvider() const web3Provider = this.getInjectedProvider()
if (!web3Provider) { if (!web3Provider) {
this.call('notification', 'toast', 'No injected provider (e.g Metamask) has been found.') this.call(
return resolve({ jsonrpc: '2.0', error: 'no injected provider found', id: data.id }) '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 { try {
let resultData let resultData
if (web3Provider.send) resultData = await web3Provider.send(data.method, data.params) if (web3Provider.send)
else if (web3Provider.request) resultData = await web3Provider.request({ method: data.method, params: data.params}) resultData = await web3Provider.send(data.method, data.params)
else if (web3Provider.request)
resultData = await web3Provider.request({
method: data.method,
params: data.params
})
else { else {
resolve({ jsonrpc: '2.0', error: 'provider not valid', id: data.id }) resolve({jsonrpc: '2.0', error: 'provider not valid', id: data.id})
return return
} }
if (resultData) { if (resultData) {
if (resultData.jsonrpc && resultData.jsonrpc === '2.0') { if (resultData.jsonrpc && resultData.jsonrpc === '2.0') {
resultData = resultData.result resultData = resultData.result
} }
resolve({ jsonrpc: '2.0', result: resultData, id: data.id }) resolve({jsonrpc: '2.0', result: resultData, id: data.id})
} else { } else {
resolve({ jsonrpc: '2.0', error: 'no return data provided', id: data.id }) resolve({jsonrpc: '2.0', error: 'no return data provided', id: data.id})
} }
} catch (error) { } catch (error) {
resolve({ jsonrpc: '2.0', error: error.data && error.data.message ? error.data.message : error.message, id: data.id }) resolve({
jsonrpc: '2.0',
error:
error.data && error.data.message ? error.data.message : error.message,
id: data.id
})
} }
} }
} }

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

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

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

@ -1,7 +1,7 @@
import { ViewPlugin } from '@remixproject/engine-web' import {ViewPlugin} from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { SearchTab } from '@remix-ui/search' import {SearchTab} from '@remix-ui/search'
const profile = { const profile = {
name: 'search', name: 'search',
displayName: 'Search in files', displayName: 'Search in files',
@ -17,17 +17,15 @@ const profile = {
} }
export class SearchPlugin extends ViewPlugin { export class SearchPlugin extends ViewPlugin {
constructor() {
constructor () {
super(profile) super(profile)
} }
render() { render() {
return ( return (
<div id='searchTab'> <div id="searchTab">
<SearchTab plugin={this}></SearchTab> <SearchTab plugin={this}></SearchTab>
</div> </div>
); )
} }
} }

@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { ViewPlugin } from '@remixproject/engine-web' import {ViewPlugin} from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { RemixUiSettings } from '@remix-ui/settings' //eslint-disable-line import {RemixUiSettings} from '@remix-ui/settings' //eslint-disable-line
import Registry from '../state/registry' import Registry from '../state/registry'
import { PluginViewWrapper } from '@remix-ui/helper' import {PluginViewWrapper} from '@remix-ui/helper'
const profile = { const profile = {
name: 'settings', name: 'settings',
@ -18,7 +18,7 @@ const profile = {
documentation: 'https://remix-ide.readthedocs.io/en/latest/settings.html', documentation: 'https://remix-ide.readthedocs.io/en/latest/settings.html',
version: packageJson.version, version: packageJson.version,
permission: true, permission: true,
maintainedBy: "Remix" maintainedBy: 'Remix'
} }
module.exports = class SettingsTab extends ViewPlugin { module.exports = class SettingsTab extends ViewPlugin {
@ -31,7 +31,7 @@ module.exports = class SettingsTab extends ViewPlugin {
element: HTMLDivElement element: HTMLDivElement
public useMatomoAnalytics: any public useMatomoAnalytics: any
dispatch: React.Dispatch<any> = () => {} dispatch: React.Dispatch<any> = () => {}
constructor (config, editor) { constructor(config, editor) {
super(profile) super(profile)
this.config = config this.config = config
this.config.events.on('configChanged', (changedConfig) => { this.config.events.on('configChanged', (changedConfig) => {
@ -47,37 +47,41 @@ module.exports = class SettingsTab extends ViewPlugin {
this.useMatomoAnalytics = null this.useMatomoAnalytics = null
} }
setDispatch (dispatch: React.Dispatch<any>) { setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch this.dispatch = dispatch
this.renderComponent() this.renderComponent()
} }
render() { render() {
return <div id='settingsTab'> return (
<PluginViewWrapper plugin={this} /> <div id="settingsTab">
</div> <PluginViewWrapper plugin={this} />
</div>
)
} }
updateComponent(state: any){ updateComponent(state: any) {
return <RemixUiSettings return (
config={state.config} <RemixUiSettings
editor={state.editor} config={state.config}
_deps={state._deps} editor={state.editor}
useMatomoAnalytics={state.useMatomoAnalytics} _deps={state._deps}
themeModule = {state._deps.themeModule} useMatomoAnalytics={state.useMatomoAnalytics}
localeModule={state._deps.localeModule} themeModule={state._deps.themeModule}
/> localeModule={state._deps.localeModule}
/>
)
} }
renderComponent () { renderComponent() {
this.dispatch(this) this.dispatch(this)
} }
get (key) { get(key) {
return this.config.get(key) return this.config.get(key)
} }
updateMatomoAnalyticsChoice (isChecked) { updateMatomoAnalyticsChoice(isChecked) {
this.config.set('settings/matomo-analytics', isChecked) this.config.set('settings/matomo-analytics', isChecked)
this.useMatomoAnalytics = isChecked this.useMatomoAnalytics = isChecked
this.dispatch({ this.dispatch({

File diff suppressed because it is too large Load Diff

@ -1,19 +1,18 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React from 'react' import React from 'react'
import { render } from 'react-dom' import {render} from 'react-dom'
import './index.css' import './index.css'
import { ThemeModule } from './app/tabs/theme-module' import {ThemeModule} from './app/tabs/theme-module'
import { Preload } from './app/components/preload' import {Preload} from './app/components/preload'
import Config from './config' import Config from './config'
import Registry from './app/state/registry' import Registry from './app/state/registry'
import { Storage } from '@remix-project/remix-lib' import {Storage} from '@remix-project/remix-lib'
;(async function () {
(async function () {
try { try {
const configStorage = new Storage('config-v0.8:') const configStorage = new Storage('config-v0.8:')
const config = new Config(configStorage); const config = new Config(configStorage)
Registry.getInstance().put({ api: config, name: 'config' }) Registry.getInstance().put({api: config, name: 'config'})
} catch (e) { } } catch (e) {}
const theme = new ThemeModule() const theme = new ThemeModule()
theme.initTheme() theme.initTheme()
@ -24,5 +23,3 @@ import { Storage } from '@remix-project/remix-lib'
document.getElementById('root') document.getElementById('root')
) )
})() })()

@ -1,11 +1,11 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react') const {withReact} = require('@nrwl/react')
const webpack = require('webpack') const webpack = require('webpack')
const CopyPlugin = require("copy-webpack-plugin") const CopyPlugin = require('copy-webpack-plugin')
const version = require('../../package.json').version const version = require('../../package.json').version
const fs = require('fs') const fs = require('fs')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const axios = require('axios') const axios = require('axios')
const versionData = { const versionData = {
@ -16,12 +16,18 @@ const versionData = {
const loadLocalSolJson = async () => { const loadLocalSolJson = async () => {
// execute apps/remix-ide/ci/downloadsoljson.sh // execute apps/remix-ide/ci/downloadsoljson.sh
const child = require('child_process').execSync('bash ./apps/remix-ide/ci/downloadsoljson.sh', { encoding: 'utf8', cwd: process.cwd(), shell: true }) const child = require('child_process').execSync(
'bash ./apps/remix-ide/ci/downloadsoljson.sh',
{encoding: 'utf8', cwd: process.cwd(), shell: true}
)
// show output // show output
console.log(child) console.log(child)
} }
fs.writeFileSync('./apps/remix-ide/src/assets/version.json', JSON.stringify(versionData)) fs.writeFileSync(
'./apps/remix-ide/src/assets/version.json',
JSON.stringify(versionData)
)
loadLocalSolJson() loadLocalSolJson()
@ -32,9 +38,8 @@ const implicitDependencies = JSON.parse(project).implicitDependencies
const copyPatterns = implicitDependencies.map((dep) => { const copyPatterns = implicitDependencies.map((dep) => {
try { try {
fs.statSync(__dirname + `/../../dist/apps/${dep}`).isDirectory() fs.statSync(__dirname + `/../../dist/apps/${dep}`).isDirectory()
return { from: `../../dist/apps/${dep}`, to: `plugins/${dep}` } return {from: `../../dist/apps/${dep}`, to: `plugins/${dep}`}
} } catch (e) {
catch (e) {
console.log('error', e) console.log('error', e)
return false return false
} }
@ -50,30 +55,29 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
// add fallback for node modules // add fallback for node modules
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"), crypto: require.resolve('crypto-browserify'),
"stream": require.resolve("stream-browserify"), stream: require.resolve('stream-browserify'),
"path": require.resolve("path-browserify"), path: require.resolve('path-browserify'),
"http": require.resolve("stream-http"), http: require.resolve('stream-http'),
"https": require.resolve("https-browserify"), https: require.resolve('https-browserify'),
"constants": require.resolve("constants-browserify"), constants: require.resolve('constants-browserify'),
"os": false, //require.resolve("os-browserify/browser"), os: false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"), timers: false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"), zlib: require.resolve('browserify-zlib'),
"fs": false, fs: false,
"module": false, module: false,
"tls": false, tls: false,
"net": false, net: false,
"readline": false, readline: false,
"child_process": false, child_process: false,
"buffer": require.resolve("buffer/"), buffer: require.resolve('buffer/'),
"vm": require.resolve('vm-browserify'), vm: require.resolve('vm-browserify')
} }
// add externals // add externals
config.externals = { config.externals = {
...config.externals, ...config.externals,
solc: 'solc', solc: 'solc'
} }
// add public path // add public path
@ -83,12 +87,14 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
config.output.filename = `[name].${versionData.version}.${versionData.timestamp}.js` config.output.filename = `[name].${versionData.version}.${versionData.timestamp}.js`
config.output.chunkFilename = `[name].${versionData.version}.${versionData.timestamp}.js` config.output.chunkFilename = `[name].${versionData.version}.${versionData.timestamp}.js`
// add copy & provide plugin // add copy & provide plugin
config.plugins.push( config.plugins.push(
new CopyPlugin({ new CopyPlugin({
patterns: [ patterns: [
{ from: '../../node_modules/monaco-editor/min/vs', to: 'assets/js/monaco-editor/min/vs' }, {
from: '../../node_modules/monaco-editor/min/vs',
to: 'assets/js/monaco-editor/min/vs'
},
...copyPatterns ...copyPatterns
].filter(Boolean) ].filter(Boolean)
}), }),
@ -96,15 +102,15 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}) })
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/, /require function/] // ignore source-map-loader warnings & AST warnings config.ignoreWarnings = [/Failed to parse source map/, /require function/] // ignore source-map-loader warnings & AST warnings
@ -118,23 +124,23 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
config.watchOptions = { config.watchOptions = {
ignored: /node_modules/ ignored: /node_modules/
} }
return config; return config
}); })
class CopyFileAfterBuild { class CopyFileAfterBuild {
apply(compiler) { apply(compiler) {
const onEnd = async () => { const onEnd = async () => {
try { try {
console.log('runnning CopyFileAfterBuild') console.log('runnning CopyFileAfterBuild')
@ -142,17 +148,20 @@ class CopyFileAfterBuild {
// This is needed because by default the etherscan resources are served from the /plugins/etherscan/ folder, // This is needed because by default the etherscan resources are served from the /plugins/etherscan/ folder,
// but the raw-loader try to access the resources from the root folder. // but the raw-loader try to access the resources from the root folder.
const files = fs.readdirSync('./dist/apps/etherscan') const files = fs.readdirSync('./dist/apps/etherscan')
files.forEach(file => { files.forEach((file) => {
if (file.includes('plugin-etherscan')) { if (file.includes('plugin-etherscan')) {
fs.copyFileSync('./dist/apps/etherscan/' + file, './dist/apps/remix-ide/' + file) fs.copyFileSync(
} './dist/apps/etherscan/' + file,
'./dist/apps/remix-ide/' + file
)
}
}) })
} catch (e) { } catch (e) {
console.error('running CopyFileAfterBuild failed with error: ' + e.message) console.error(
} 'running CopyFileAfterBuild failed with error: ' + e.message
)
}
} }
compiler.hooks.afterEmit.tapPromise('FileManagerPlugin', onEnd); compiler.hooks.afterEmit.tapPromise('FileManagerPlugin', onEnd)
} }
} }

@ -1,8 +1,8 @@
import React, { useEffect, useState } from "react"; import React, {useEffect, useState} from 'react'
import { SolHint } from "./SolhintPluginClient"; import {SolHint} from './SolhintPluginClient'
const client = new SolHint(); const client = new SolHint()
export default function App() { export default function App() {
return <></>; return <></>
} }

@ -1,18 +1,18 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react') const {withReact} = require('@nrwl/react')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack. // Nx plugins for webpack.
module.exports = composePlugins(withNx(), withReact(), (config) => { module.exports = composePlugins(withNx(), withReact(), (config) => {
// Update the webpack config as needed here. // Update the webpack config as needed here.
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"path": false, path: false,
"os": false, os: false,
"fs": false, fs: false,
"module": false, module: false
} }
// add public path // add public path
@ -23,18 +23,18 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}), }),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
"BROWSER": true, BROWSER: true
}), })
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
@ -48,13 +48,13 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
return config; return config
}) })

@ -1,9 +1,9 @@
/* eslint-disable no-use-before-define */ /* eslint-disable no-use-before-define */
import React from 'react' import React from 'react'
import { SolidityCompiler } from '@remix-ui/solidity-compiler' // eslint-disable-line import {SolidityCompiler} from '@remix-ui/solidity-compiler' // eslint-disable-line
import { CompilerClientApi } from './compiler' import {CompilerClientApi} from './compiler'
const remix = new CompilerClientApi() const remix = new CompilerClientApi()

@ -1,10 +1,10 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react') const {withReact} = require('@nrwl/react')
const webpack = require('webpack') const webpack = require('webpack')
const version = require('../../package.json').version const version = require('../../package.json').version
const fs = require('fs') const fs = require('fs')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const versionData = { const versionData = {
version: version, version: version,
@ -12,7 +12,10 @@ const versionData = {
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development' mode: process.env.NODE_ENV === 'production' ? 'production' : 'development'
} }
fs.writeFileSync('./apps/remix-ide/src/assets/version.json', JSON.stringify(versionData)) fs.writeFileSync(
'./apps/remix-ide/src/assets/version.json',
JSON.stringify(versionData)
)
// Nx plugins for webpack. // Nx plugins for webpack.
module.exports = composePlugins(withNx(), withReact(), (config) => { module.exports = composePlugins(withNx(), withReact(), (config) => {
@ -22,56 +25,52 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
// add fallback for node modules // add fallback for node modules
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"), crypto: require.resolve('crypto-browserify'),
"stream": require.resolve("stream-browserify"), stream: require.resolve('stream-browserify'),
"path": require.resolve("path-browserify"), path: require.resolve('path-browserify'),
"http": require.resolve("stream-http"), http: require.resolve('stream-http'),
"https": require.resolve("https-browserify"), https: require.resolve('https-browserify'),
"constants": require.resolve("constants-browserify"), constants: require.resolve('constants-browserify'),
"os": false, //require.resolve("os-browserify/browser"), os: false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"), timers: false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"), zlib: require.resolve('browserify-zlib'),
"fs": false, fs: false,
"module": false, module: false,
"tls": false, tls: false,
"net": false, net: false,
"readline": false, readline: false,
"child_process": false, child_process: false,
"buffer": require.resolve("buffer/"), buffer: require.resolve('buffer/'),
"vm": require.resolve('vm-browserify'), vm: require.resolve('vm-browserify')
} }
// add externals // add externals
config.externals = { config.externals = {
...config.externals, ...config.externals,
solc: 'solc', solc: 'solc'
} }
// add public path // add public path
config.output.publicPath = '/' config.output.publicPath = '/'
// add copy & provide plugin // add copy & provide plugin
config.plugins.push( config.plugins.push(
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}) })
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer // set minimizer
config.optimization.minimizer = [ config.optimization.minimizer = [
new TerserPlugin({ new TerserPlugin({
@ -81,17 +80,17 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
config.watchOptions = { config.watchOptions = {
ignored: /node_modules/ ignored: /node_modules/
} }
return config; return config
}); })

@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react' import React, {useState, useEffect} from 'react'
import { VyperCompilationOutput, remixClient } from './utils' import {VyperCompilationOutput, remixClient} from './utils'
import { CompilationResult } from '@remixproject/plugin-api' import {CompilationResult} from '@remixproject/plugin-api'
// Components // Components
import CompilerButton from './components/CompilerButton' import CompilerButton from './components/CompilerButton'
@ -38,7 +38,7 @@ const App: React.FC = () => {
async function start() { async function start() {
try { try {
await remixClient.loaded() await remixClient.loaded()
remixClient.onFileChange(name => setContract(name)) remixClient.onFileChange((name) => setContract(name))
remixClient.onNoFileSelected(() => setContract('')) remixClient.onNoFileSelected(() => setContract(''))
} catch (err) { } catch (err) {
console.log(err) console.log(err)
@ -53,11 +53,11 @@ const App: React.FC = () => {
/** Update the environment state value */ /** Update the environment state value */
function setEnvironment(environment: 'local' | 'remote') { function setEnvironment(environment: 'local' | 'remote') {
setState({ ...state, environment }) setState({...state, environment})
} }
function setLocalUrl(url: string) { function setLocalUrl(url: string) {
setState({ ...state, localUrl: url }) setState({...state, localUrl: url})
} }
function compilerUrl() { function compilerUrl() {
@ -78,12 +78,16 @@ const App: React.FC = () => {
href="https://github.com/ethereum/remix-project/tree/master/apps/vyper" href="https://github.com/ethereum/remix-project/tree/master/apps/vyper"
target="_blank" target="_blank"
> >
<i className="fab fa-github"></i> <i className="fab fa-github"></i>
</a> </a>
</header> </header>
<section> <section>
<div className="px-4 w-100"> <div className="px-4 w-100">
<Button data-id="add-repository" className="w-100 text-dark w-100 bg-light btn-outline-primary " onClick={() => remixClient.cloneVyperRepo()}> <Button
data-id="add-repository"
className="w-100 text-dark w-100 bg-light btn-outline-primary "
onClick={() => remixClient.cloneVyperRepo()}
>
Clone Vyper examples repository Clone Vyper examples repository
</Button> </Button>
</div> </div>
@ -93,10 +97,20 @@ const App: React.FC = () => {
type="radio" type="radio"
value={state.environment} value={state.environment}
> >
<ToggleButton data-id="remote-compiler" variant="secondary" name="remote" value="remote"> <ToggleButton
data-id="remote-compiler"
variant="secondary"
name="remote"
value="remote"
>
Remote Compiler v0.2.16 Remote Compiler v0.2.16
</ToggleButton> </ToggleButton>
<ToggleButton data-id="local-compiler" variant="secondary" name="local" value="local"> <ToggleButton
data-id="local-compiler"
variant="secondary"
name="local"
value="local"
>
Local Compiler Local Compiler
</ToggleButton> </ToggleButton>
</ToggleButtonGroup> </ToggleButtonGroup>
@ -110,9 +124,7 @@ const App: React.FC = () => {
<CompilerButton <CompilerButton
compilerUrl={compilerUrl()} compilerUrl={compilerUrl()}
contract={contract} contract={contract}
setOutput={(name, update) => setOutput={(name, update) => setOutput({...output, [name]: update})}
setOutput({ ...output, [name]: update })
}
/> />
</div> </div>
<article id="result" className="px-2"> <article id="result" className="px-2">

@ -11,12 +11,11 @@ import Button from 'react-bootstrap/Button'
interface Props { interface Props {
compilerUrl: string compilerUrl: string
contract?: string, contract?: string
setOutput: (name: string, output: VyperCompilationOutput) => void setOutput: (name: string, output: VyperCompilationOutput) => void
} }
function CompilerButton({ contract, setOutput, compilerUrl }: Props) { function CompilerButton({contract, setOutput, compilerUrl}: Props) {
if (!contract || !contract) { if (!contract || !contract) {
return <Button disabled>No contract selected</Button> return <Button disabled>No contract selected</Button>
} }
@ -33,9 +32,9 @@ function CompilerButton({ contract, setOutput, compilerUrl }: Props) {
try { try {
_contract = await remixClient.getContract() _contract = await remixClient.getContract()
} catch (e: any) { } catch (e: any) {
setOutput('', { status: 'failed', message: e.message}) setOutput('', {status: 'failed', message: e.message})
return return
} }
remixClient.changeStatus({ remixClient.changeStatus({
key: 'loading', key: 'loading',
type: 'info', type: 'info',
@ -45,17 +44,17 @@ function CompilerButton({ contract, setOutput, compilerUrl }: Props) {
try { try {
output = await compile(compilerUrl, _contract) output = await compile(compilerUrl, _contract)
} catch (e: any) { } catch (e: any) {
setOutput(_contract.name, { status: 'failed', message: e.message}) setOutput(_contract.name, {status: 'failed', message: e.message})
return return
} }
setOutput(_contract.name, output) setOutput(_contract.name, output)
// ERROR // ERROR
if (isCompilationError(output)) { if (isCompilationError(output)) {
const line = output.line const line = output.line
if (line) { if (line) {
const lineColumnPos = { const lineColumnPos = {
start: { line: line - 1, column: 10 }, start: {line: line - 1, column: 10},
end: { line: line - 1, column: 10 } end: {line: line - 1, column: 10}
} }
remixClient.highlight(lineColumnPos as any, _contract.name, '#e0b4b4') remixClient.highlight(lineColumnPos as any, _contract.name, '#e0b4b4')
} else { } else {
@ -69,15 +68,20 @@ function CompilerButton({ contract, setOutput, compilerUrl }: Props) {
errorIndex = errorIndex + 4 errorIndex = errorIndex + 4
if (message && message.split('\n\n').length > 0) { if (message && message.split('\n\n').length > 0) {
try { try {
message = message.split('\n\n')[message.split('\n\n').length - 1] message =
} catch (e) {} message.split('\n\n')[message.split('\n\n').length - 1]
} catch (e) {}
} }
if (location.length > 0) { if (location.length > 0) {
const lineColumnPos = { const lineColumnPos = {
start: { line: parseInt(location[0]) - 1, column: 10 }, start: {line: parseInt(location[0]) - 1, column: 10},
end: { line: parseInt(location[0]) - 1, column: 10 } end: {line: parseInt(location[0]) - 1, column: 10}
} }
remixClient.highlight(lineColumnPos as any, _contract.name, message) remixClient.highlight(
lineColumnPos as any,
_contract.name,
message
)
} }
}) })
} }
@ -103,9 +107,17 @@ function CompilerButton({ contract, setOutput, compilerUrl }: Props) {
} }
return ( return (
<Button data-id="compile" onClick={compileContract} variant="primary" title={contract} className="d-flex flex-column"> <Button
data-id="compile"
onClick={compileContract}
variant="primary"
title={contract}
className="d-flex flex-column"
>
<span>Compile</span> <span>Compile</span>
<span className="overflow-hidden text-truncate text-nowrap" >{contract}</span> <span className="overflow-hidden text-truncate text-nowrap">
{contract}
</span>
</Button> </Button>
) )
} }

@ -3,12 +3,11 @@ import Form from 'react-bootstrap/Form'
interface Props { interface Props {
url: string url: string
setUrl: (url: string) => void, setUrl: (url: string) => void
environment: 'remote' | 'local' environment: 'remote' | 'local'
} }
function LocalUrlInput({ url, setUrl, environment }: Props) { function LocalUrlInput({url, setUrl, environment}: Props) {
if (environment === 'remote') { if (environment === 'remote') {
return <></> return <></>
} }
@ -20,17 +19,20 @@ function LocalUrlInput({ url, setUrl, environment }: Props) {
return ( return (
<Form id="local-url"> <Form id="local-url">
<Form.Group controlId="localUrl"> <Form.Group controlId="localUrl">
<Form.Text className="text-warning pb-2">Currently we support vyper version > 0.2.16</Form.Text> <Form.Text className="text-warning pb-2">
{'Currently we support vyper version > 0.2.16'}
</Form.Text>
<Form.Label>Local Compiler Url</Form.Label> <Form.Label>Local Compiler Url</Form.Label>
<Form.Control onBlur={updateUrl} <Form.Control
onBlur={updateUrl}
defaultValue={url} defaultValue={url}
type="email" type="email"
placeholder="eg http://localhost:8000/compile" /> placeholder="eg http://localhost:8000/compile"
<Form.Text className="text-muted"> />
</Form.Text> <Form.Text className="text-muted"></Form.Text>
</Form.Group> </Form.Group>
</Form> </Form>
) )
} }
export default LocalUrlInput; export default LocalUrlInput

@ -1,75 +1,93 @@
import React, { useState } from 'react'; import React, {useState} from 'react'
import { import {
VyperCompilationResult, VyperCompilationResult,
VyperCompilationOutput, VyperCompilationOutput,
isCompilationError, isCompilationError
} from '../utils'; } from '../utils'
import Tabs from 'react-bootstrap/Tabs' import Tabs from 'react-bootstrap/Tabs'
import Tab from 'react-bootstrap/Tab' import Tab from 'react-bootstrap/Tab'
import Button from 'react-bootstrap/Button'; import Button from 'react-bootstrap/Button'
import JSONTree from 'react-json-view' import JSONTree from 'react-json-view'
import { CopyToClipboard } from '@remix-ui/clipboard' import {CopyToClipboard} from '@remix-ui/clipboard'
interface VyperResultProps { interface VyperResultProps {
output?: VyperCompilationOutput; output?: VyperCompilationOutput
} }
export type ExampleContract = { export type ExampleContract = {
name: string, name: string
address: string address: string
} }
function VyperResult({ output }: VyperResultProps) { function VyperResult({output}: VyperResultProps) {
const [ active, setActive ] = useState<keyof VyperCompilationResult>('abi') const [active, setActive] = useState<keyof VyperCompilationResult>('abi')
if (!output) return ( if (!output)
return (
<div id="result"> <div id="result">
<p className="my-3">No contract compiled yet.</p> <p className="my-3">No contract compiled yet.</p>
</div> </div>
) )
if (isCompilationError(output)) { if (isCompilationError(output)) {
return ( return (
<div id="result" className="error" title={output.message}> <div id="result" className="error" title={output.message}>
<i className="fas fa-exclamation-circle text-danger"></i> <i className="fas fa-exclamation-circle text-danger"></i>
<pre data-id="error-message" className="px-2 w-100 alert alert-danger" style={{ <pre
fontSize: "0.5rem", data-id="error-message"
overflowX: "hidden", className="px-2 w-100 alert alert-danger"
textOverflow: "ellipsis" style={{
}}>{output.message}</pre> fontSize: '0.5rem',
overflowX: 'hidden',
textOverflow: 'ellipsis'
}}
>
{output.message}
</pre>
</div> </div>
) )
} }
return ( return (
<Tabs id="result" activeKey={active} onSelect={(key: any) => setActive(key)}> <Tabs
id="result"
activeKey={active}
onSelect={(key: any) => setActive(key)}
>
<Tab eventKey="abi" title="ABI"> <Tab eventKey="abi" title="ABI">
<CopyToClipboard getContent={() => JSON.stringify(output.abi)}> <CopyToClipboard getContent={() => JSON.stringify(output.abi)}>
<Button variant="info" className="copy" data-id="copy-abi">Copy ABI</Button> <Button variant="info" className="copy" data-id="copy-abi">
Copy ABI
</Button>
</CopyToClipboard> </CopyToClipboard>
<JSONTree src={output.abi} /> <JSONTree src={output.abi} />
</Tab> </Tab>
<Tab eventKey="bytecode" title="Bytecode"> <Tab eventKey="bytecode" title="Bytecode">
<CopyToClipboard getContent={() => output.bytecode}> <CopyToClipboard getContent={() => output.bytecode}>
<Button variant="info" className="copy">Copy Bytecode</Button> <Button variant="info" className="copy">
Copy Bytecode
</Button>
</CopyToClipboard> </CopyToClipboard>
<textarea defaultValue={output.bytecode}></textarea> <textarea defaultValue={output.bytecode}></textarea>
</Tab> </Tab>
<Tab eventKey="bytecode_runtime" title="Runtime Bytecode"> <Tab eventKey="bytecode_runtime" title="Runtime Bytecode">
<CopyToClipboard getContent={() => output.bytecode_runtime}> <CopyToClipboard getContent={() => output.bytecode_runtime}>
<Button variant="info" className="copy">Copy Runtime Bytecode</Button> <Button variant="info" className="copy">
Copy Runtime Bytecode
</Button>
</CopyToClipboard> </CopyToClipboard>
<textarea defaultValue={output.bytecode_runtime}></textarea> <textarea defaultValue={output.bytecode_runtime}></textarea>
</Tab> </Tab>
<Tab eventKey="ir" title="LLL"> <Tab eventKey="ir" title="LLL">
<CopyToClipboard getContent={() => output.ir}> <CopyToClipboard getContent={() => output.ir}>
<Button variant="info" className="copy">Copy LLL Code</Button> <Button variant="info" className="copy">
Copy LLL Code
</Button>
</CopyToClipboard> </CopyToClipboard>
<textarea defaultValue={output.ir}></textarea> <textarea defaultValue={output.ir}></textarea>
</Tab> </Tab>
</Tabs> </Tabs>
); )
} }
export default VyperResult; export default VyperResult

@ -4,15 +4,17 @@ interface Props {
environment: 'remote' | 'local' environment: 'remote' | 'local'
} }
function WarnRemoteLabel({ environment }: Props) { function WarnRemoteLabel({environment}: Props) {
if (environment === 'local') { if (environment === 'local') {
return <></> return <></>
} }
return ( return (
<small className="mx-4 text-warning pb-4">The remote compiler should only be used for testing NOT for production environments. For production, use a local compiler.</small> <small className="mx-4 text-warning pb-4">
The remote compiler should only be used for testing NOT for production
environments. For production, use a local compiler.
</small>
) )
} }
export default WarnRemoteLabel; export default WarnRemoteLabel

@ -1,16 +1,16 @@
import { CompilationResult, ABIDescription } from "@remixproject/plugin-api"; import {CompilationResult, ABIDescription} from '@remixproject/plugin-api'
export interface Contract { export interface Contract {
name: string; name: string
content: string; content: string
} }
export interface VyperCompilationResult { export interface VyperCompilationResult {
status: 'success', status: 'success'
bytecode: string, bytecode: string
bytecode_runtime: string, bytecode_runtime: string
abi: ABIDescription[], abi: ABIDescription[]
ir: string, ir: string
method_identifiers: { method_identifiers: {
[method: string]: string [method: string]: string
} }
@ -23,19 +23,26 @@ export interface VyperCompilationError {
message: string message: string
} }
export type VyperCompilationOutput = VyperCompilationResult | VyperCompilationError export type VyperCompilationOutput =
| VyperCompilationResult
| VyperCompilationError
/** Check if the output is an error */ /** Check if the output is an error */
export function isCompilationError(output: VyperCompilationOutput): output is VyperCompilationError { export function isCompilationError(
output: VyperCompilationOutput
): output is VyperCompilationError {
return output.status === 'failed' return output.status === 'failed'
} }
/** /**
* Compile the a contract * Compile the a contract
* @param url The url of the compiler * @param url The url of the compiler
* @param contract The name and content of the contract * @param contract The name and content of the contract
*/ */
export async function compile(url: string, contract: Contract): Promise<VyperCompilationOutput> { export async function compile(
url: string,
contract: Contract
): Promise<VyperCompilationOutput> {
if (!contract.name) { if (!contract.name) {
throw new Error('Set your Vyper contract file.') throw new Error('Set your Vyper contract file.')
} }
@ -45,8 +52,8 @@ export async function compile(url: string, contract: Contract): Promise<VyperCom
} }
const response = await fetch(url, { const response = await fetch(url, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ code: contract.content }) body: JSON.stringify({code: contract.content})
}) })
if (response.status === 404) { if (response.status === 404) {
@ -63,9 +70,14 @@ export async function compile(url: string, contract: Contract): Promise<VyperCom
* @param name Name of the contract file * @param name Name of the contract file
* @param compilationResult Result returned by the compiler * @param compilationResult Result returned by the compiler
*/ */
export function toStandardOutput(fileName: string, compilationResult: VyperCompilationResult): CompilationResult { export function toStandardOutput(
const contractName = fileName.split('/').slice(-1)[0].split('.')[0]; fileName: string,
const methodIdentifiers = JSON.parse(JSON.stringify(compilationResult['method_identifiers']).replace(/0x/g,'')); compilationResult: VyperCompilationResult
): CompilationResult {
const contractName = fileName.split('/').slice(-1)[0].split('.')[0]
const methodIdentifiers = JSON.parse(
JSON.stringify(compilationResult['method_identifiers']).replace(/0x/g, '')
)
return { return {
sources: { sources: {
[fileName]: { [fileName]: {
@ -84,23 +96,22 @@ export function toStandardOutput(fileName: string, compilationResult: VyperCompi
evm: { evm: {
bytecode: { bytecode: {
linkReferences: {}, linkReferences: {},
object: compilationResult['bytecode'].replace('0x',''), object: compilationResult['bytecode'].replace('0x', ''),
opcodes: "" opcodes: ''
}, },
deployedBytecode: { deployedBytecode: {
linkReferences: {}, linkReferences: {},
object: compilationResult['bytecode_runtime'].replace('0x',''), object: compilationResult['bytecode_runtime'].replace('0x', ''),
opcodes: "" opcodes: ''
}, },
methodIdentifiers: methodIdentifiers methodIdentifiers: methodIdentifiers
} }
} }
} as any } as any
} }
}; }
} }
/* /*
export function createCompilationResultMessage(name: string, result: any) { export function createCompilationResultMessage(name: string, result: any) {
if(result.status == 'success') { if(result.status == 'success') {
@ -136,4 +147,4 @@ export function createCompilationResultMessage(name: string, result: any) {
ir: "" ir: ""
} }
} }
*/ */

@ -1,12 +1,16 @@
import { HighlightPosition, CompilationResult, RemixApi } from '@remixproject/plugin-api'; import {
import { Api, Status } from '@remixproject/plugin-utils'; HighlightPosition,
import { createClient } from '@remixproject/plugin-webview' CompilationResult,
import { PluginClient } from '@remixproject/plugin'; RemixApi
import { Contract } from './compiler'; } from '@remixproject/plugin-api'
import { ExampleContract } from '../components/VyperResult'; import {Api, Status} from '@remixproject/plugin-utils'
import {createClient} from '@remixproject/plugin-webview'
import {PluginClient} from '@remixproject/plugin'
import {Contract} from './compiler'
import {ExampleContract} from '../components/VyperResult'
export class RemixClient extends PluginClient { export class RemixClient extends PluginClient {
private client = createClient<Api, Readonly<RemixApi>>(this); private client = createClient<Api, Readonly<RemixApi>>(this)
loaded() { loaded() {
return this.client.onload() return this.client.onload()
@ -14,9 +18,13 @@ export class RemixClient extends PluginClient {
/** Emit an event when file changed */ /** Emit an event when file changed */
async onFileChange(cb: (contract: string) => any) { async onFileChange(cb: (contract: string) => any) {
this.client.on('fileManager', 'currentFileChanged', async (name: string) => { this.client.on(
cb(name) 'fileManager',
}) 'currentFileChanged',
async (name: string) => {
cb(name)
}
)
} }
/** Emit an event when file changed */ /** Emit an event when file changed */
@ -29,8 +37,17 @@ export class RemixClient extends PluginClient {
/** Load Ballot contract example into the file manager */ /** Load Ballot contract example into the file manager */
async loadContract({name, address}: ExampleContract) { async loadContract({name, address}: ExampleContract) {
try { try {
const content = await this.client.call('contentImport', 'resolve', address) const content = await this.client.call(
await this.client.call('fileManager', 'setFile', content.cleanUrl, content.content) 'contentImport',
'resolve',
address
)
await this.client.call(
'fileManager',
'setFile',
content.cleanUrl,
content.content
)
await this.client.call('fileManager', 'switchFile', content.cleanUrl) await this.client.call('fileManager', 'switchFile', content.cleanUrl)
} catch (err) { } catch (err) {
console.log(err) console.log(err)
@ -43,9 +60,18 @@ export class RemixClient extends PluginClient {
this.call('notification', 'toast', 'cloning Vyper repository...') this.call('notification', 'toast', 'cloning Vyper repository...')
await this.call('manager', 'activatePlugin', 'dGitProvider') await this.call('manager', 'activatePlugin', 'dGitProvider')
// @ts-ignore // @ts-ignore
await this.call('dGitProvider', 'clone', { url: 'https://github.com/vyperlang/vyper', token: null }, 'vyper-lang') await this.call(
'dGitProvider',
'clone',
{url: 'https://github.com/vyperlang/vyper', token: null},
'vyper-lang'
)
// @ts-ignore // @ts-ignore
this.call('notification', 'toast', 'Vyper repository cloned, the workspace Vyper has been created.') this.call(
'notification',
'toast',
'Vyper repository cloned, the workspace Vyper has been created.'
)
} catch (e) { } catch (e) {
// @ts-ignore // @ts-ignore
this.call('notification', 'toast', e.message) this.call('notification', 'toast', e.message)
@ -54,11 +80,15 @@ export class RemixClient extends PluginClient {
/** Update the status of the plugin in remix */ /** Update the status of the plugin in remix */
changeStatus(status: Status) { changeStatus(status: Status) {
this.client.emit('statusChanged', status); this.client.emit('statusChanged', status)
} }
/** Highlight a part of the editor */ /** Highlight a part of the editor */
async highlight(lineColumnPos: HighlightPosition, name: string, message: string) { async highlight(
lineColumnPos: HighlightPosition,
name: string,
message: string
) {
await this.client.call('editor', 'highlight', lineColumnPos, name) await this.client.call('editor', 'highlight', lineColumnPos, name)
/* /*
column: -1 column: -1
@ -94,15 +124,15 @@ export class RemixClient extends PluginClient {
const content = await this.client.call('fileManager', 'getFile', name) const content = await this.client.call('fileManager', 'getFile', name)
return { return {
name, name,
content, content
} }
} }
/** Emit an event to Remix with compilation result */ /** Emit an event to Remix with compilation result */
compilationFinish(title: string, content: string, data: CompilationResult) { compilationFinish(title: string, content: string, data: CompilationResult) {
this.client.emit('compilationFinished', title, content, 'vyper', data); this.client.emit('compilationFinished', title, content, 'vyper', data)
} }
} }
export const remixClient = new RemixClient() export const remixClient = new RemixClient()
// export const RemixClientContext = React.createContext(new RemixClient()) // export const RemixClientContext = React.createContext(new RemixClient())

@ -1,7 +1,11 @@
import { StrictMode } from 'react'; import {StrictMode} from 'react'
import * as ReactDOM from 'react-dom'; import * as ReactDOM from 'react-dom'
import App from './app/app'
import App from './app/app'; ReactDOM.render(
<StrictMode>
ReactDOM.render(<StrictMode><App /></StrictMode>, document.getElementById('root')); <App />
</StrictMode>,
document.getElementById('root')
)

@ -1,8 +1,7 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack. // Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => { module.exports = composePlugins(withNx(), (config) => {
@ -12,56 +11,52 @@ module.exports = composePlugins(withNx(), (config) => {
// add fallback for node modules // add fallback for node modules
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"), crypto: require.resolve('crypto-browserify'),
"stream": require.resolve("stream-browserify"), stream: require.resolve('stream-browserify'),
"path": require.resolve("path-browserify"), path: require.resolve('path-browserify'),
"http": require.resolve("stream-http"), http: require.resolve('stream-http'),
"https": require.resolve("https-browserify"), https: require.resolve('https-browserify'),
"constants": require.resolve("constants-browserify"), constants: require.resolve('constants-browserify'),
"os": false, //require.resolve("os-browserify/browser"), os: false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"), timers: false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"), zlib: require.resolve('browserify-zlib'),
"fs": false, fs: false,
"module": false, module: false,
"tls": false, tls: false,
"net": false, net: false,
"readline": false, readline: false,
"child_process": false, child_process: false,
"buffer": require.resolve("buffer/"), buffer: require.resolve('buffer/'),
"vm": require.resolve('vm-browserify'), vm: require.resolve('vm-browserify')
} }
// add externals // add externals
config.externals = { config.externals = {
...config.externals, ...config.externals,
solc: 'solc', solc: 'solc'
} }
// add public path // add public path
config.output.publicPath = '/' config.output.publicPath = '/'
// add copy & provide plugin // add copy & provide plugin
config.plugins.push( config.plugins.push(
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}) })
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer // set minimizer
config.optimization.minimizer = [ config.optimization.minimizer = [
new TerserPlugin({ new TerserPlugin({
@ -71,17 +66,17 @@ module.exports = composePlugins(withNx(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
config.watchOptions = { config.watchOptions = {
ignored: /node_modules/ ignored: /node_modules/
} }
return config; return config
}); })

@ -1,9 +1,9 @@
import React, { useEffect, useState } from 'react' import React, {useEffect, useState} from 'react'
import '../css/app.css' import '../css/app.css'
import '@fortawesome/fontawesome-free/css/all.css' import '@fortawesome/fontawesome-free/css/all.css'
import type { EthereumClient } from '@web3modal/ethereum' import type {EthereumClient} from '@web3modal/ethereum'
import { WalletConnectRemixClient } from '../services/WalletConnectRemixClient' import {WalletConnectRemixClient} from '../services/WalletConnectRemixClient'
import { WalletConnectUI } from './walletConnectUI' import {WalletConnectUI} from './walletConnectUI'
const remix = new WalletConnectRemixClient() const remix = new WalletConnectRemixClient()
@ -13,12 +13,12 @@ function App() {
const [theme, setTheme] = useState<string>('dark') const [theme, setTheme] = useState<string>('dark')
useEffect(() => { useEffect(() => {
(async () => { ;(async () => {
await remix.initClient() await remix.initClient()
remix.internalEvents.on('themeChanged', (theme: string) => { remix.internalEvents.on('themeChanged', (theme: string) => {
setTheme(theme) setTheme(theme)
}) })
setWagmiConfig(remix.wagmiConfig) setWagmiConfig(remix.wagmiConfig)
setEthereumClient(remix.ethereumClient) setEthereumClient(remix.ethereumClient)
})() })()
@ -26,10 +26,16 @@ function App() {
return ( return (
<div className="App"> <div className="App">
<h4 className='mt-1'>WalletConnect</h4> <h4 className="mt-1">WalletConnect</h4>
{ ethereumClient && wagmiConfig && <WalletConnectUI wagmiConfig={wagmiConfig} ethereumClient={ethereumClient} theme={theme} /> } {ethereumClient && wagmiConfig && (
<WalletConnectUI
wagmiConfig={wagmiConfig}
ethereumClient={ethereumClient}
theme={theme}
/>
)}
</div> </div>
) )
} }
export default App export default App

@ -1,17 +1,20 @@
import { Web3Button, Web3Modal } from "@web3modal/react" import {Web3Button, Web3Modal} from '@web3modal/react'
import { WagmiConfig } from "wagmi" import {WagmiConfig} from 'wagmi'
import { PROJECT_ID } from "../services/constant" import {PROJECT_ID} from '../services/constant'
export function WalletConnectUI ({ ethereumClient, wagmiConfig, theme }) { export function WalletConnectUI({ethereumClient, wagmiConfig, theme}) {
return (
return ( <div>
<div> <div style={{display: 'inline-block'}}>
<div style={{ display: 'inline-block' }}> <WagmiConfig config={wagmiConfig}>
<WagmiConfig config={wagmiConfig}> <Web3Button label="Connect to a wallet" />
<Web3Button label='Connect to a wallet' /> </WagmiConfig>
</WagmiConfig> </div>
</div> <Web3Modal
<Web3Modal projectId={PROJECT_ID} ethereumClient={ethereumClient} themeMode={theme} /> projectId={PROJECT_ID}
</div> ethereumClient={ethereumClient}
) themeMode={theme}
/>
</div>
)
} }

@ -2,7 +2,4 @@ import React from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import App from './app/app' import App from './app/app'
ReactDOM.render( ReactDOM.render(<App />, document.getElementById('root'))
<App />,
document.getElementById('root')
)

@ -1,7 +1,7 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack. // Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => { module.exports = composePlugins(withNx(), (config) => {
@ -10,30 +10,29 @@ module.exports = composePlugins(withNx(), (config) => {
// add fallback for node modules // add fallback for node modules
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"), crypto: require.resolve('crypto-browserify'),
"stream": require.resolve("stream-browserify"), stream: require.resolve('stream-browserify'),
"path": require.resolve("path-browserify"), path: require.resolve('path-browserify'),
"http": require.resolve("stream-http"), http: require.resolve('stream-http'),
"https": require.resolve("https-browserify"), https: require.resolve('https-browserify'),
"constants": require.resolve("constants-browserify"), constants: require.resolve('constants-browserify'),
"os": false, //require.resolve("os-browserify/browser"), os: false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"), timers: false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"), zlib: require.resolve('browserify-zlib'),
"fs": false, fs: false,
"module": false, module: false,
"tls": false, tls: false,
"net": false, net: false,
"readline": false, readline: false,
"child_process": false, child_process: false,
"buffer": require.resolve("buffer/"), buffer: require.resolve('buffer/'),
"vm": require.resolve('vm-browserify'), vm: require.resolve('vm-browserify')
} }
// add externals // add externals
config.externals = { config.externals = {
...config.externals, ...config.externals,
solc: 'solc', solc: 'solc'
} }
// add public path // add public path
@ -44,27 +43,28 @@ module.exports = composePlugins(withNx(), (config) => {
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}) })
) )
// set the define plugin to load the WALLET_CONNECT_PROJECT_ID // set the define plugin to load the WALLET_CONNECT_PROJECT_ID
config.plugins.push( config.plugins.push(
new webpack.DefinePlugin({ new webpack.DefinePlugin({
WALLET_CONNECT_PROJECT_ID: JSON.stringify(process.env.WALLET_CONNECT_PROJECT_ID), WALLET_CONNECT_PROJECT_ID: JSON.stringify(
process.env.WALLET_CONNECT_PROJECT_ID
)
}) })
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer // set minimizer
config.optimization.minimizer = [ config.optimization.minimizer = [
new TerserPlugin({ new TerserPlugin({
@ -74,17 +74,17 @@ module.exports = composePlugins(withNx(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
config.watchOptions = { config.watchOptions = {
ignored: /node_modules/ ignored: /node_modules/
} }
return config; return config
}); })

@ -10,8 +10,7 @@ const shortFilename = 'simple_storage.sol'
const inputJson = { const inputJson = {
language: 'Solidity', language: 'Solidity',
sources: { sources: {},
},
settings: { settings: {
optimizer: { optimizer: {
enabled: true, enabled: true,
@ -19,24 +18,38 @@ const inputJson = {
}, },
outputSelection: { outputSelection: {
'*': { '*': {
'': [ 'ast' ], '': ['ast'],
'*': [ 'abi', 'metadata', 'devdoc', 'userdoc', 'evm.legacyAssembly', 'evm.bytecode', 'evm.deployedBytecode', 'evm.methodIdentifiers', 'evm.gasEstimates' ] '*': [
'abi',
'metadata',
'devdoc',
'userdoc',
'evm.legacyAssembly',
'evm.bytecode',
'evm.deployedBytecode',
'evm.methodIdentifiers',
'evm.gasEstimates'
]
} }
} }
} }
} }
inputJson.sources[shortFilename] = {content: fs.readFileSync(filename).toString()} inputJson.sources[shortFilename] = {
content: fs.readFileSync(filename).toString()
}
console.dir(inputJson) console.dir(inputJson)
console.log('compiling...') console.log('compiling...')
const compilationData = JSON.parse(solc.compileStandardWrapper(JSON.stringify(inputJson))) const compilationData = JSON.parse(
solc.compileStandardWrapper(JSON.stringify(inputJson))
)
console.dir(Object.keys(compilationData)) console.dir(Object.keys(compilationData))
const compilation = {} const compilation = {}
compilation['data'] = compilationData compilation['data'] = compilationData
compilation['source'] = { sources: inputJson.sources } compilation['source'] = {sources: inputJson.sources}
console.dir(compilation) console.dir(compilation)
console.dir(compilation['data'].errors) console.dir(compilation['data'].errors)
@ -106,4 +119,3 @@ repl.start({
}) })
module.exports = cmdLine module.exports = cmdLine

@ -1,10 +1,10 @@
import React, { useEffect, useState, useRef } from 'react' import React, {useEffect, useState, useRef} from 'react'
import Draggable from 'react-draggable' import Draggable from 'react-draggable'
import './dragbar.css' import './dragbar.css'
interface IRemixDragBarUi { interface IRemixDragBarUi {
refObject: React.MutableRefObject<any>; refObject: React.MutableRefObject<any>
setHideStatus: (hide: boolean) => void; setHideStatus: (hide: boolean) => void
hidden: boolean hidden: boolean
minWidth: number minWidth: number
maximiseTrigger: number maximiseTrigger: number
@ -19,7 +19,9 @@ const DragBar = (props: IRemixDragBarUi) => {
const nodeRef = React.useRef(null) // fix for strictmode const nodeRef = React.useRef(null) // fix for strictmode
useEffect(() => { useEffect(() => {
setDragBarPosX(offset + (props.hidden ? 0 : props.refObject.current.offsetWidth)) setDragBarPosX(
offset + (props.hidden ? 0 : props.refObject.current.offsetWidth)
)
}, [props.hidden, offset]) }, [props.hidden, offset])
useEffect(() => { useEffect(() => {
@ -47,14 +49,15 @@ const DragBar = (props: IRemixDragBarUi) => {
const handleResize = () => { const handleResize = () => {
if (!props.refObject.current) return if (!props.refObject.current) return
setOffSet(props.refObject.current.offsetLeft) setOffSet(props.refObject.current.offsetLeft)
setDragBarPosX(props.refObject.current.offsetLeft + props.refObject.current.offsetWidth) setDragBarPosX(
props.refObject.current.offsetLeft + props.refObject.current.offsetWidth
)
} }
useEffect(() => { useEffect(() => {
window.addEventListener('resize', handleResize) window.addEventListener('resize', handleResize)
// TODO: not a good way to wait on the ref doms element to be rendered of course // TODO: not a good way to wait on the ref doms element to be rendered of course
setTimeout(() => setTimeout(() => handleResize(), 2000)
handleResize(), 2000)
return () => window.removeEventListener('resize', handleResize) return () => window.removeEventListener('resize', handleResize)
}, []) }, [])
@ -64,7 +67,7 @@ const DragBar = (props: IRemixDragBarUi) => {
setDragBarPosX(offset) setDragBarPosX(offset)
props.setHideStatus(true) props.setHideStatus(true)
} else { } else {
props.refObject.current.style.width = (data.x - offset) + 'px' props.refObject.current.style.width = data.x - offset + 'px'
setTimeout(() => { setTimeout(() => {
props.setHideStatus(false) props.setHideStatus(false)
setDragBarPosX(offset + props.refObject.current.offsetWidth) setDragBarPosX(offset + props.refObject.current.offsetWidth)
@ -75,12 +78,23 @@ const DragBar = (props: IRemixDragBarUi) => {
function startDrag() { function startDrag() {
setDragState(true) setDragState(true)
} }
return <> return (
<div className={`overlay ${dragState ? '' : 'd-none'}`} ></div> <>
<Draggable nodeRef={nodeRef} position={{ x: dragBarPosX, y: 0 }} onStart={startDrag} onStop={stopDrag} axis="x"> <div className={`overlay ${dragState ? '' : 'd-none'}`}></div>
<div ref={nodeRef} className={`dragbar ${dragState ? 'ondrag' : ''}`}></div> <Draggable
</Draggable> nodeRef={nodeRef}
</> position={{x: dragBarPosX, y: 0}}
onStart={startDrag}
onStop={stopDrag}
axis="x"
>
<div
ref={nodeRef}
className={`dragbar ${dragState ? 'ondrag' : ''}`}
></div>
</Draggable>
</>
)
} }
export default DragBar export default DragBar

@ -1,13 +1,13 @@
import React, { useContext, useEffect } from 'react' import React, {useContext, useEffect} from 'react'
import { AppContext } from '../../context/context' import {AppContext} from '../../context/context'
import { useDialogDispatchers } from '../../context/provider' import {useDialogDispatchers} from '../../context/provider'
const DialogViewPlugin = () => { const DialogViewPlugin = () => {
const { modal, alert, toast } = useDialogDispatchers() const {modal, alert, toast} = useDialogDispatchers()
const app = useContext(AppContext) const app = useContext(AppContext)
useEffect(() => { useEffect(() => {
app.modal.setDispatcher({ modal, alert, toast }) app.modal.setDispatcher({modal, alert, toast})
}, []) }, [])
return <></> return <></>
} }

@ -1,16 +1,21 @@
import React from 'react' import React from 'react'
import { useDialogDispatchers, useDialogs } from '../../context/provider' import {useDialogDispatchers, useDialogs} from '../../context/provider'
import { Toaster } from '@remix-ui/toaster' import {Toaster} from '@remix-ui/toaster'
import ModalWrapper from './modal-wrapper' import ModalWrapper from './modal-wrapper'
const AppDialogs = () => { const AppDialogs = () => {
const { handleHideModal, handleToaster } = useDialogDispatchers() const {handleHideModal, handleToaster} = useDialogDispatchers()
const { focusModal, focusToaster } = useDialogs() const {focusModal, focusToaster} = useDialogs()
return ( return (
<> <>
<ModalWrapper {...focusModal} handleHide={handleHideModal}></ModalWrapper> <ModalWrapper {...focusModal} handleHide={handleHideModal}></ModalWrapper>
<Toaster message={focusToaster.message} timestamp={focusToaster.timestamp} handleHide={handleToaster} /> <Toaster
</>) message={focusToaster.message}
timestamp={focusToaster.timestamp}
handleHide={handleToaster}
/>
</>
)
} }
export default AppDialogs export default AppDialogs

@ -1,30 +1,76 @@
import React, { useContext, useEffect, useState } from 'react' import React, {useContext, useEffect, useState} from 'react'
import { AppContext } from '../../context/context' import {AppContext} from '../../context/context'
import { useDialogDispatchers } from '../../context/provider' import {useDialogDispatchers} from '../../context/provider'
declare global { declare global {
interface Window { interface Window {
_paq: any _paq: any
} }
} }
const _paq = window._paq = window._paq || [] const _paq = (window._paq = window._paq || [])
const MatomoDialog = (props) => { const MatomoDialog = (props) => {
const { settings, showMatamo, appManager } = useContext(AppContext) const {settings, showMatamo, appManager} = useContext(AppContext)
const { modal } = useDialogDispatchers() const {modal} = useDialogDispatchers()
const [visible, setVisible] = useState<boolean>(props.hide) const [visible, setVisible] = useState<boolean>(props.hide)
const message = () => { const message = () => {
return (<><p>An Opt-in version of <a href="https://matomo.org" target="_blank" rel="noreferrer">Matomo</a>, an open source data analytics platform is being used to improve Remix IDE.</p> return (
<p>We realize that our users have sensitive information in their code and that their privacy - your privacy - must be protected.</p> <>
<p>All data collected through Matomo is stored on our own server - no data is ever given to third parties. Our analytics reports are public: <a href="https://matomo.ethereum.org/index.php?module=MultiSites&action=index&idSite=23&period=day&date=yesterday" target="_blank" rel="noreferrer">take a look</a>.</p> <p>
<p>We do not collect nor store any personally identifiable information (PII).</p> An Opt-in version of{' '}
<p>For more info, see: <a href="https://medium.com/p/66ef69e14931/" target="_blank" rel="noreferrer">Matomo Analyitcs on Remix iDE</a>.</p> <a href="https://matomo.org" target="_blank" rel="noreferrer">
<p>You can change your choice in the Settings panel anytime.</p></>) Matomo
</a>
, an open source data analytics platform is being used to improve
Remix IDE.
</p>
<p>
We realize that our users have sensitive information in their code and
that their privacy - your privacy - must be protected.
</p>
<p>
All data collected through Matomo is stored on our own server - no
data is ever given to third parties. Our analytics reports are public:{' '}
<a
href="https://matomo.ethereum.org/index.php?module=MultiSites&action=index&idSite=23&period=day&date=yesterday"
target="_blank"
rel="noreferrer"
>
take a look
</a>
.
</p>
<p>
We do not collect nor store any personally identifiable information
(PII).
</p>
<p>
For more info, see:{' '}
<a
href="https://medium.com/p/66ef69e14931/"
target="_blank"
rel="noreferrer"
>
Matomo Analyitcs on Remix iDE
</a>
.
</p>
<p>You can change your choice in the Settings panel anytime.</p>
</>
)
} }
useEffect(() => { useEffect(() => {
if (visible && showMatamo) { if (visible && showMatamo) {
modal({ id: 'matomoModal', title: 'Help us to improve Remix IDE', message: message(), okLabel: 'Accept', okFn: handleModalOkClick, cancelLabel: 'Decline', cancelFn: declineModal }) modal({
id: 'matomoModal',
title: 'Help us to improve Remix IDE',
message: message(),
okLabel: 'Accept',
okFn: handleModalOkClick,
cancelLabel: 'Decline',
cancelFn: declineModal
})
} }
}, [visible]) }, [visible])
@ -38,13 +84,14 @@ const MatomoDialog = (props) => {
const handleModalOkClick = async () => { const handleModalOkClick = async () => {
_paq.push(['forgetUserOptOut']) _paq.push(['forgetUserOptOut'])
// @TODO remove next line when https://github.com/matomo-org/matomo/commit/9e10a150585522ca30ecdd275007a882a70c6df5 is used // @TODO remove next line when https://github.com/matomo-org/matomo/commit/9e10a150585522ca30ecdd275007a882a70c6df5 is used
document.cookie = 'mtm_consent_removed=; expires=Thu, 01 Jan 1970 00:00:01 GMT;' document.cookie =
'mtm_consent_removed=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'
settings.updateMatomoAnalyticsChoice(true) settings.updateMatomoAnalyticsChoice(true)
appManager.call('walkthrough', 'start') appManager.call('walkthrough', 'start')
setVisible(false) setVisible(false)
} }
return (<></>) return <></>
} }
export default MatomoDialog export default MatomoDialog

@ -1,6 +1,10 @@
import React, { useEffect, useRef, useState } from 'react' import React, {useEffect, useRef, useState} from 'react'
import { ModalDialog, ModalDialogProps, ValidationResult } from '@remix-ui/modal-dialog' import {
import { ModalTypes } from '../../types' ModalDialog,
ModalDialogProps,
ValidationResult
} from '@remix-ui/modal-dialog'
import {ModalTypes} from '../../types'
interface ModalWrapperProps extends ModalDialogProps { interface ModalWrapperProps extends ModalDialogProps {
modalType?: ModalTypes modalType?: ModalTypes
@ -28,36 +32,54 @@ const ModalWrapper = (props: ModalWrapperProps) => {
if (ref.current === undefined && formRef.current === undefined) { if (ref.current === undefined && formRef.current === undefined) {
onOkFn() onOkFn()
} else if (formRef.current) { } else if (formRef.current) {
(props.okFn) ? props.okFn(getFormData()) : props.resolve(getFormData()) props.okFn ? props.okFn(getFormData()) : props.resolve(getFormData())
} else if(ref.current) { } else if (ref.current) {
// @ts-ignore: Object is possibly 'null'. // @ts-ignore: Object is possibly 'null'.
(props.okFn) ? props.okFn(ref.current.value) : props.resolve(ref.current.value) props.okFn
? props.okFn(ref.current.value)
: props.resolve(ref.current.value)
} }
} }
const onOkFn = async () => { const onOkFn = async () => {
(props.okFn) ? props.okFn(data.current) : props.resolve(data.current || true) props.okFn ? props.okFn(data.current) : props.resolve(data.current || true)
} }
const onCancelFn = async () => { const onCancelFn = async () => {
(props.cancelFn) ? props.cancelFn() : props.resolve(false) props.cancelFn ? props.cancelFn() : props.resolve(false)
} }
const onInputChanged = (event) => { const onInputChanged = (event) => {
if (props.validationFn) { if (props.validationFn) {
const validation = props.validationFn(event.target.value) const validation = props.validationFn(event.target.value)
setState(prevState => { setState((prevState) => {
return { ...prevState, message: createModalMessage(props.defaultValue, validation), validation } return {
...prevState,
message: createModalMessage(props.defaultValue, validation),
validation
}
}) })
} }
} }
const createModalMessage = (defaultValue: string, validation: ValidationResult) => { const createModalMessage = (
defaultValue: string,
validation: ValidationResult
) => {
return ( return (
<> <>
{props.message} {props.message}
<input onChange={onInputChanged} type={props.modalType === ModalTypes.password ? 'password' : 'text'} defaultValue={defaultValue} data-id="modalDialogCustomPromp" ref={ref} className="form-control" /> <input
{validation && !validation.valid && <span className='text-warning'>{validation.message}</span>} onChange={onInputChanged}
type={props.modalType === ModalTypes.password ? 'password' : 'text'}
defaultValue={defaultValue}
data-id="modalDialogCustomPromp"
ref={ref}
className="form-control"
/>
{validation && !validation.valid && (
<span className="text-warning">{validation.message}</span>
)}
</> </>
) )
} }
@ -65,8 +87,8 @@ const ModalWrapper = (props: ModalWrapperProps) => {
const onFormChanged = () => { const onFormChanged = () => {
if (props.validationFn) { if (props.validationFn) {
const validation = props.validationFn(getFormData()) const validation = props.validationFn(getFormData())
setState(prevState => { setState((prevState) => {
return { ...prevState, message: createForm(validation), validation } return {...prevState, message: createForm(validation), validation}
}) })
} }
} }
@ -77,7 +99,9 @@ const ModalWrapper = (props: ModalWrapperProps) => {
<form onChange={onFormChanged} ref={formRef}> <form onChange={onFormChanged} ref={formRef}>
{props.message} {props.message}
</form> </form>
{validation && !validation.valid && <span className='text-warning'>{validation.message}</span>} {validation && !validation.valid && (
<span className="text-warning">{validation.message}</span>
)}
</> </>
) )
} }
@ -92,7 +116,7 @@ const ModalWrapper = (props: ModalWrapperProps) => {
...props, ...props,
okFn: onFinishPrompt, okFn: onFinishPrompt,
cancelFn: onCancelFn, cancelFn: onCancelFn,
message: createModalMessage(props.defaultValue, { valid: true }) message: createModalMessage(props.defaultValue, {valid: true})
}) })
break break
case ModalTypes.form: case ModalTypes.form:
@ -100,7 +124,7 @@ const ModalWrapper = (props: ModalWrapperProps) => {
...props, ...props,
okFn: onFinishPrompt, okFn: onFinishPrompt,
cancelFn: onCancelFn, cancelFn: onCancelFn,
message: createForm({ valid: true }) message: createForm({valid: true})
}) })
break break
default: default:
@ -122,14 +146,12 @@ const ModalWrapper = (props: ModalWrapperProps) => {
// reset the message and input if any, so when the modal is shown again it doesn't show the previous value. // reset the message and input if any, so when the modal is shown again it doesn't show the previous value.
const handleHide = () => { const handleHide = () => {
setState(prevState => { setState((prevState) => {
return { ...prevState, message: '' } return {...prevState, message: ''}
}) })
props.handleHide() props.handleHide()
} }
return ( return <ModalDialog id={props.id} {...state} handleHide={handleHide} />
<ModalDialog id={props.id} {...state} handleHide={handleHide} />
)
} }
export default ModalWrapper export default ModalWrapper

@ -1,22 +1,31 @@
import React, { useEffect, useState } from 'react' import React, {useEffect, useState} from 'react'
import { ModalDialog } from '@remix-ui/modal-dialog' import {ModalDialog} from '@remix-ui/modal-dialog'
import { useDialogDispatchers } from '../../context/provider' import {useDialogDispatchers} from '../../context/provider'
const OriginWarning = () => { const OriginWarning = () => {
const { alert } = useDialogDispatchers() const {alert} = useDialogDispatchers()
const [content, setContent] = useState<string>(null) const [content, setContent] = useState<string>(null)
useEffect(() => { useEffect(() => {
// check the origin and warn message // check the origin and warn message
if (window.location.hostname === 'yann300.github.io') { if (window.location.hostname === 'yann300.github.io') {
setContent('This UNSTABLE ALPHA branch of Remix has been moved to http://ethereum.github.io/remix-live-alpha.') setContent(
} else if (window.location.hostname === 'remix-alpha.ethereum.org' || 'This UNSTABLE ALPHA branch of Remix has been moved to http://ethereum.github.io/remix-live-alpha.'
(window.location.hostname === 'ethereum.github.io' && window.location.pathname.indexOf('/remix-live-alpha') === 0)) { )
setContent('Welcome to the Remix alpha instance. Please use it to try out latest features. But use preferably https://remix.ethereum.org for any production work.') } else if (
} else if (window.location.protocol.indexOf('http') === 0 && window.location.hostname === 'remix-alpha.ethereum.org' ||
window.location.hostname !== 'remix.ethereum.org' && (window.location.hostname === 'ethereum.github.io' &&
window.location.hostname !== 'localhost' && window.location.pathname.indexOf('/remix-live-alpha') === 0)
window.location.hostname !== '127.0.0.1') { ) {
setContent(
'Welcome to the Remix alpha instance. Please use it to try out latest features. But use preferably https://remix.ethereum.org for any production work.'
)
} else if (
window.location.protocol.indexOf('http') === 0 &&
window.location.hostname !== 'remix.ethereum.org' &&
window.location.hostname !== 'localhost' &&
window.location.hostname !== '127.0.0.1'
) {
setContent(`The Remix IDE has moved to http://remix.ethereum.org.\n setContent(`The Remix IDE has moved to http://remix.ethereum.org.\n
This instance of Remix you are visiting WILL NOT BE UPDATED.\n This instance of Remix you are visiting WILL NOT BE UPDATED.\n
Please make a backup of your contracts and start using http://remix.ethereum.org`) Please make a backup of your contracts and start using http://remix.ethereum.org`)
@ -25,11 +34,11 @@ const OriginWarning = () => {
useEffect(() => { useEffect(() => {
if (content) { if (content) {
alert({ id: 'warningOriging', title: null, message: content }) alert({id: 'warningOriging', title: null, message: content})
} }
}, [content]) }, [content])
return (<></>) return <></>
} }
export default OriginWarning export default OriginWarning

@ -1,16 +1,27 @@
import React from 'react' import React from 'react'
const RemixSplashScreen = (props) => { const RemixSplashScreen = (props) => {
return (<> <div style={{ display: props.hide ? 'none' : 'block' }} className='centered'> return (
<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"/> <div
<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"/> style={{display: props.hide ? 'none' : 'block'}}
</svg> className="centered"
<div className="info-secondary splash"> >
REMIX IDE <svg
</div> id="Ebene_2"
</div></>) 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" />
</svg>
<div className="info-secondary splash">REMIX IDE</div>
</div>
</>
)
} }
export default RemixSplashScreen export default RemixSplashScreen

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import { AlertModal, AppModal } from '../interface' import {AlertModal, AppModal} from '../interface'
import { ModalInitialState } from '../state/modals' import {ModalInitialState} from '../state/modals'
export const AppContext = React.createContext<any>(null) export const AppContext = React.createContext<any>(null)
@ -8,16 +8,18 @@ export interface dispatchModalInterface {
modal: (data: AppModal) => void modal: (data: AppModal) => void
toast: (message: string | JSX.Element) => void toast: (message: string | JSX.Element) => void
alert: (data: AlertModal) => void alert: (data: AlertModal) => void
handleHideModal: () => void, handleHideModal: () => void
handleToaster: () => void handleToaster: () => void
} }
export const dispatchModalContext = React.createContext<dispatchModalInterface>({ export const dispatchModalContext = React.createContext<dispatchModalInterface>(
modal: (data: AppModal) => { }, {
toast: (message: string | JSX.Element) => {}, modal: (data: AppModal) => {},
alert: (data: AlertModal) => {}, toast: (message: string | JSX.Element) => {},
handleHideModal: () => {}, alert: (data: AlertModal) => {},
handleToaster: () => {} handleHideModal: () => {},
}) handleToaster: () => {}
}
)
export const modalContext = React.createContext(ModalInitialState) export const modalContext = React.createContext(ModalInitialState)

@ -1,13 +1,20 @@
import React, { useReducer } from 'react' import React, {useReducer} from 'react'
import { modalActionTypes } from '../actions/modals' import {modalActionTypes} from '../actions/modals'
import { AlertModal, AppModal } from '../interface' import {AlertModal, AppModal} from '../interface'
import { modalReducer } from '../reducer/modals' import {modalReducer} from '../reducer/modals'
import { ModalInitialState } from '../state/modals' import {ModalInitialState} from '../state/modals'
import { ModalTypes } from '../types' import {ModalTypes} from '../types'
import { AppContext, dispatchModalContext, modalContext } from './context' import {AppContext, dispatchModalContext, modalContext} from './context'
export const ModalProvider = ({ children = [], reducer = modalReducer, initialState = ModalInitialState } = {}) => { export const ModalProvider = ({
const [{ modals, toasters, focusModal, focusToaster }, dispatch] = useReducer(reducer, initialState) children = [],
reducer = modalReducer,
initialState = ModalInitialState
} = {}) => {
const [{modals, toasters, focusModal, focusToaster}, dispatch] = useReducer(
reducer,
initialState
)
const onNextFn = async () => { const onNextFn = async () => {
dispatch({ dispatch({
@ -16,17 +23,53 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
} }
const modal = (modalData: AppModal) => { const modal = (modalData: AppModal) => {
const { id, title, message, validationFn, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue, hideFn, data } = modalData const {
id,
title,
message,
validationFn,
okLabel,
okFn,
cancelLabel,
cancelFn,
modalType,
defaultValue,
hideFn,
data
} = modalData
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
dispatch({ dispatch({
type: modalActionTypes.setModal, type: modalActionTypes.setModal,
payload: { id, title, message, okLabel, validationFn, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue, hideFn, resolve, next: onNextFn, data } payload: {
id,
title,
message,
okLabel,
validationFn,
okFn,
cancelLabel,
cancelFn,
modalType: modalType || ModalTypes.default,
defaultValue: defaultValue,
hideFn,
resolve,
next: onNextFn,
data
}
}) })
}) })
} }
const alert = (modalData: AlertModal) => { const alert = (modalData: AlertModal) => {
return modal({ id: modalData.id, title: modalData.title || 'Alert', message: modalData.message || modalData.title, okLabel: 'OK', okFn: (value?:any) => {}, cancelLabel: '', cancelFn: () => {} }) return modal({
id: modalData.id,
title: modalData.title || 'Alert',
message: modalData.message || modalData.title,
okLabel: 'OK',
okFn: (value?: any) => {},
cancelLabel: '',
cancelFn: () => {}
})
} }
const handleHideModal = () => { const handleHideModal = () => {
@ -39,7 +82,7 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
const toast = (message: string | JSX.Element) => { const toast = (message: string | JSX.Element) => {
dispatch({ dispatch({
type: modalActionTypes.setToast, type: modalActionTypes.setToast,
payload: { message, timestamp: Date.now() } payload: {message, timestamp: Date.now()}
}) })
} }
@ -50,17 +93,25 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
}) })
} }
return (<dispatchModalContext.Provider value={{ modal, toast, alert, handleHideModal, handleToaster }}> return (
<modalContext.Provider value={{ modals, toasters, focusModal, focusToaster }}> <dispatchModalContext.Provider
{children} value={{modal, toast, alert, handleHideModal, handleToaster}}
</modalContext.Provider> >
</dispatchModalContext.Provider>) <modalContext.Provider
value={{modals, toasters, focusModal, focusToaster}}
>
{children}
</modalContext.Provider>
</dispatchModalContext.Provider>
)
} }
export const AppProvider = ({ children = [], value = {} } = {}) => { export const AppProvider = ({children = [], value = {}} = {}) => {
return <AppContext.Provider value={value}> return (
<ModalProvider>{children}</ModalProvider> <AppContext.Provider value={value}>
</AppContext.Provider> <ModalProvider>{children}</ModalProvider>
</AppContext.Provider>
)
} }
export const useDialogs = () => { export const useDialogs = () => {

@ -1,15 +1,15 @@
import React, { useEffect, useRef, useState } from 'react' import React, {useEffect, useRef, useState} from 'react'
import './style/remix-app.css' import './style/remix-app.css'
import { RemixUIMainPanel } from '@remix-ui/panel' import {RemixUIMainPanel} from '@remix-ui/panel'
import MatomoDialog from './components/modals/matomo' import MatomoDialog from './components/modals/matomo'
import OriginWarning from './components/modals/origin-warning' import OriginWarning from './components/modals/origin-warning'
import DragBar from './components/dragbar/dragbar' import DragBar from './components/dragbar/dragbar'
import { AppProvider } from './context/provider' import {AppProvider} from './context/provider'
import AppDialogs from './components/modals/dialogs' import AppDialogs from './components/modals/dialogs'
import DialogViewPlugin from './components/modals/dialogViewPlugin' import DialogViewPlugin from './components/modals/dialogViewPlugin'
import { AppContext } from './context/context' import {AppContext} from './context/context'
import { IntlProvider } from 'react-intl' import {IntlProvider} from 'react-intl'
import { CustomTooltip } from '@remix-ui/helper'; import {CustomTooltip} from '@remix-ui/helper'
interface IRemixAppUi { interface IRemixAppUi {
app: any app: any
@ -20,11 +20,14 @@ const RemixApp = (props: IRemixAppUi) => {
const [hideSidePanel, setHideSidePanel] = useState<boolean>(false) const [hideSidePanel, setHideSidePanel] = useState<boolean>(false)
const [maximiseTrigger, setMaximiseTrigger] = useState<number>(0) const [maximiseTrigger, setMaximiseTrigger] = useState<number>(0)
const [resetTrigger, setResetTrigger] = useState<number>(0) const [resetTrigger, setResetTrigger] = useState<number>(0)
const [locale, setLocale] = useState<{ code:string; messages:any }>({ code:'en', messages:{} }); const [locale, setLocale] = useState<{code: string; messages: any}>({
code: 'en',
messages: {}
})
const sidePanelRef = useRef(null) const sidePanelRef = useRef(null)
useEffect(() => { useEffect(() => {
async function activateApp () { async function activateApp() {
props.app.themeModule.initTheme(() => { props.app.themeModule.initTheme(() => {
setAppReady(true) setAppReady(true)
props.app.activate() props.app.activate()
@ -37,9 +40,9 @@ const RemixApp = (props: IRemixAppUi) => {
} }
}, []) }, [])
function setListeners () { function setListeners() {
props.app.sidePanel.events.on('toggle', () => { props.app.sidePanel.events.on('toggle', () => {
setHideSidePanel(prev => { setHideSidePanel((prev) => {
return !prev return !prev
}) })
}) })
@ -55,13 +58,13 @@ const RemixApp = (props: IRemixAppUi) => {
}) })
props.app.layout.event.on('maximisesidepanel', () => { props.app.layout.event.on('maximisesidepanel', () => {
setMaximiseTrigger(prev => { setMaximiseTrigger((prev) => {
return prev + 1 return prev + 1
}) })
}) })
props.app.layout.event.on('resetsidepanel', () => { props.app.layout.event.on('resetsidepanel', () => {
setResetTrigger(prev => { setResetTrigger((prev) => {
return prev + 1 return prev + 1
}) })
}) })
@ -84,18 +87,47 @@ const RemixApp = (props: IRemixAppUi) => {
<AppProvider value={value}> <AppProvider value={value}>
<OriginWarning></OriginWarning> <OriginWarning></OriginWarning>
<MatomoDialog hide={!appReady}></MatomoDialog> <MatomoDialog hide={!appReady}></MatomoDialog>
<div className={`remixIDE ${appReady ? '' : 'd-none'}`} data-id="remixIDE"> <div
<div id="icon-panel" data-id="remixIdeIconPanel" className="custom_icon_panel iconpanel bg-light">{props.app.menuicons.render()}</div> className={`remixIDE ${appReady ? '' : 'd-none'}`}
<div ref={sidePanelRef} id="side-panel" data-id="remixIdeSidePanel" className={`sidepanel border-right border-left ${hideSidePanel ? 'd-none' : ''}`}>{props.app.sidePanel.render()}</div> data-id="remixIDE"
<DragBar resetTrigger={resetTrigger} maximiseTrigger={maximiseTrigger} minWidth={285} refObject={sidePanelRef} hidden={hideSidePanel} setHideStatus={setHideSidePanel}></DragBar> >
<div id="main-panel" data-id="remixIdeMainPanel" className='mainpanel d-flex'> <div
id="icon-panel"
data-id="remixIdeIconPanel"
className="custom_icon_panel iconpanel bg-light"
>
{props.app.menuicons.render()}
</div>
<div
ref={sidePanelRef}
id="side-panel"
data-id="remixIdeSidePanel"
className={`sidepanel border-right border-left ${
hideSidePanel ? 'd-none' : ''
}`}
>
{props.app.sidePanel.render()}
</div>
<DragBar
resetTrigger={resetTrigger}
maximiseTrigger={maximiseTrigger}
minWidth={285}
refObject={sidePanelRef}
hidden={hideSidePanel}
setHideStatus={setHideSidePanel}
></DragBar>
<div
id="main-panel"
data-id="remixIdeMainPanel"
className="mainpanel d-flex"
>
<RemixUIMainPanel Context={AppContext}></RemixUIMainPanel> <RemixUIMainPanel Context={AppContext}></RemixUIMainPanel>
<CustomTooltip <CustomTooltip
placement="bottom" placement="bottom"
tooltipId="overlay-tooltip-all-tabs" tooltipId="overlay-tooltip-all-tabs"
tooltipText="Scroll to see all tabs" tooltipText="Scroll to see all tabs"
> >
<div className='remix-ui-tabs_end remix-bg-opacity position-absolute position-fixed'></div> <div className="remix-ui-tabs_end remix-bg-opacity position-absolute position-fixed"></div>
</CustomTooltip> </CustomTooltip>
</div> </div>
</div> </div>

@ -1,8 +1,7 @@
import { CustomTooltip } from '@remix-ui/helper'; import {CustomTooltip} from '@remix-ui/helper'
import React, { CSSProperties } from 'react' //eslint-disable-line import React, {CSSProperties} from 'react' //eslint-disable-line
import './remix-ui-checkbox.css' import './remix-ui-checkbox.css'
type Placement = import('react-overlays/usePopper').Placement; type Placement = import('react-overlays/usePopper').Placement
/* eslint-disable-next-line */ /* eslint-disable-next-line */
export interface RemixUiCheckboxProps { export interface RemixUiCheckboxProps {
@ -38,27 +37,41 @@ export const RemixUiCheckbox = ({
optionalClassName = '', optionalClassName = '',
display = 'flex', display = 'flex',
disabled, disabled,
tooltipPlacement = 'right', tooltipPlacement = 'right'
}: RemixUiCheckboxProps) => { }: RemixUiCheckboxProps) => {
const childJSXWithTooltip = ( const childJSXWithTooltip = (
<CustomTooltip <CustomTooltip
tooltipText={title} tooltipText={title}
tooltipId={`${name}Tooltip`} tooltipId={`${name}Tooltip`}
placement={tooltipPlacement} placement={tooltipPlacement}
> >
<div className={`listenOnNetwork_2A0YE0 custom-control custom-checkbox ${optionalClassName}`} style={{ display: display, alignItems: 'center', visibility: visibility } as CSSProperties } onClick={onClick}> <div
className={`listenOnNetwork_2A0YE0 custom-control custom-checkbox ${optionalClassName}`}
style={
{
display: display,
alignItems: 'center',
visibility: visibility
} as CSSProperties
}
onClick={onClick}
>
<input <input
id={id} id={id}
type={inputType} type={inputType}
onChange={onChange} onChange={onChange}
style={{ verticalAlign: 'bottom' }} style={{verticalAlign: 'bottom'}}
name={name} name={name}
className="custom-control-input" className="custom-control-input"
checked={checked} checked={checked}
disabled={disabled} disabled={disabled}
/> />
<label className="form-check-label custom-control-label" id={`heading${categoryId}`} style={{ paddingTop: '0.15rem' }} aria-disabled={disabled}> <label
className="form-check-label custom-control-label"
id={`heading${categoryId}`}
style={{paddingTop: '0.15rem'}}
aria-disabled={disabled}
>
{name ? <div className="font-weight-bold">{itemName}</div> : ''} {name ? <div className="font-weight-bold">{itemName}</div> : ''}
{label} {label}
</label> </label>
@ -66,25 +79,37 @@ export const RemixUiCheckbox = ({
</CustomTooltip> </CustomTooltip>
) )
const childJSX = ( const childJSX = (
<div className="listenOnNetwork_2A0YE0 custom-control custom-checkbox" style={{ display: display, alignItems: 'center', visibility: visibility } as CSSProperties } onClick={onClick}> <div
className="listenOnNetwork_2A0YE0 custom-control custom-checkbox"
style={
{
display: display,
alignItems: 'center',
visibility: visibility
} as CSSProperties
}
onClick={onClick}
>
<input <input
id={id} id={id}
type={inputType} type={inputType}
onChange={onChange} onChange={onChange}
style={{ verticalAlign: 'bottom' }} style={{verticalAlign: 'bottom'}}
name={name} name={name}
className="custom-control-input" className="custom-control-input"
checked={checked} checked={checked}
/> />
<label className="form-check-label custom-control-label" id={`heading${categoryId}`} style={{ paddingTop: '0.15rem' }}> <label
className="form-check-label custom-control-label"
id={`heading${categoryId}`}
style={{paddingTop: '0.15rem'}}
>
{name ? <div className="font-weight-bold">{itemName}</div> : ''} {name ? <div className="font-weight-bold">{itemName}</div> : ''}
{label} {label}
</label> </label>
</div> </div>
) )
return ( return title ? childJSXWithTooltip : childJSX
title ? (childJSXWithTooltip) : (childJSX)
)
} }
export default RemixUiCheckbox export default RemixUiCheckbox

@ -1,23 +1,30 @@
import React, { useState } from 'react' import React, {useState} from 'react'
import copy from 'copy-to-clipboard' import copy from 'copy-to-clipboard'
import { Placement } from 'react-bootstrap/esm/Overlay' import {Placement} from 'react-bootstrap/esm/Overlay'
import './copy-to-clipboard.css' import './copy-to-clipboard.css'
import { CustomTooltip } from '@remix-ui/helper' import {CustomTooltip} from '@remix-ui/helper'
interface ICopyToClipboard { interface ICopyToClipboard {
content?: any, content?: any
tip?: string, tip?: string
icon?: string, icon?: string
direction?: Placement, direction?: Placement
className?: string, className?: string
title?: string, title?: string
children?: JSX.Element, children?: JSX.Element
getContent?: () => any getContent?: () => any
} }
export const CopyToClipboard = (props: ICopyToClipboard) => { export const CopyToClipboard = (props: ICopyToClipboard) => {
const { tip = 'Copy', icon = 'fa-copy', direction = 'right', getContent, children, ...otherProps } = props const {
let { content } = props tip = 'Copy',
icon = 'fa-copy',
direction = 'right',
getContent,
children,
...otherProps
} = props
let {content} = props
const [message, setMessage] = useState(tip) const [message, setMessage] = useState(tip)
const copyData = () => { const copyData = () => {
@ -37,7 +44,8 @@ export const CopyToClipboard = (props: ICopyToClipboard) => {
} }
const handleClick = (e: any) => { const handleClick = (e: any) => {
if (content) { // module `copy` keeps last copied thing in the memory, so don't show tooltip if nothing is copied, because nothing was added to memory if (content) {
// module `copy` keeps last copied thing in the memory, so don't show tooltip if nothing is copied, because nothing was added to memory
copyData() copyData()
} else { } else {
content = getContent && getContent() content = getContent && getContent()
@ -50,14 +58,16 @@ export const CopyToClipboard = (props: ICopyToClipboard) => {
setTimeout(() => setMessage(tip), 500) setTimeout(() => setMessage(tip), 500)
} }
const childJSX = ( const childJSX = children || (
children || (<i className={`far ${icon} ml-1 p-2`} aria-hidden="true" <i
className={`far ${icon} ml-1 p-2`}
aria-hidden="true"
{...otherProps} {...otherProps}
></i>) ></i>
) )
return ( return (
<a href='#' onClick={handleClick} onMouseLeave={reset}> <a href="#" onClick={handleClick} onMouseLeave={reset}>
<CustomTooltip <CustomTooltip
tooltipText={message} tooltipText={message}
tooltipId="overlay-tooltip" tooltipId="overlay-tooltip"

@ -1,23 +1,28 @@
import React, { useState, useEffect } from 'react' // eslint-disable-line import React, {useState, useEffect} from 'react' // eslint-disable-line
import { ExtractData, ExtractFunc } from '../types' // eslint-disable-line import {ExtractData, ExtractFunc} from '../types' // eslint-disable-line
export const useExtractData = (json, extractFunc?: ExtractFunc): Array<{ key: string, data: ExtractData }> => { export const useExtractData = (
json,
extractFunc?: ExtractFunc
): Array<{key: string; data: ExtractData}> => {
const [data, setData] = useState([]) const [data, setData] = useState([])
useEffect(() => { useEffect(() => {
const data: Array<{ key: string, data: ExtractData }> = Object.keys(json).map((innerKey) => { const data: Array<{key: string; data: ExtractData}> = Object.keys(json).map(
if (extractFunc) { (innerKey) => {
return { if (extractFunc) {
key: innerKey, return {
data: extractFunc(json[innerKey], json) key: innerKey,
} data: extractFunc(json[innerKey], json)
} else { }
return { } else {
key: innerKey, return {
data: extractDataDefault(json[innerKey], json) key: innerKey,
data: extractDataDefault(json[innerKey], json)
}
} }
} }
}) )
setData(data) setData(data)
@ -31,14 +36,14 @@ export const useExtractData = (json, extractFunc?: ExtractFunc): Array<{ key: st
if (item instanceof Array) { if (item instanceof Array) {
ret.children = item.map((item, index) => { ret.children = item.map((item, index) => {
return { key: index, value: item } return {key: index, value: item}
}) })
ret.self = 'Array' ret.self = 'Array'
ret.isNode = true ret.isNode = true
ret.isLeaf = false ret.isLeaf = false
} else if (item instanceof Object) { } else if (item instanceof Object) {
ret.children = Object.keys(item).map((key) => { ret.children = Object.keys(item).map((key) => {
return { key: key, value: item[key] } return {key: key, value: item[key]}
}) })
ret.self = 'Object' ret.self = 'Object'
ret.isNode = true ret.isNode = true

@ -1,8 +1,20 @@
import { CustomTooltip } from '@remix-ui/helper' import {CustomTooltip} from '@remix-ui/helper'
import React, { useState, useEffect } from 'react' // eslint-disable-line import React, {useState, useEffect} from 'react' // eslint-disable-line
import './button-navigator.css' import './button-navigator.css'
export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward, stepOverForward, jumpOut, jumpPreviousBreakpoint, jumpNextBreakpoint, jumpToException, revertedReason, stepState, jumpOutDisabled }) => { export const ButtonNavigation = ({
stepOverBack,
stepIntoBack,
stepIntoForward,
stepOverForward,
jumpOut,
jumpPreviousBreakpoint,
jumpNextBreakpoint,
jumpToException,
revertedReason,
stepState,
jumpOutDisabled
}) => {
const [state, setState] = useState({ const [state, setState] = useState({
intoBackDisabled: true, intoBackDisabled: true,
overBackDisabled: true, overBackDisabled: true,
@ -46,29 +58,72 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
intoForwardDisabled: stepState === 'end', intoForwardDisabled: stepState === 'end',
overForwardDisabled: stepState === 'end', overForwardDisabled: stepState === 'end',
jumpNextBreakpointDisabled: stepState === 'end', jumpNextBreakpointDisabled: stepState === 'end',
jumpOutDisabled: (jumpOutDisabled !== null) && (jumpOutDisabled !== undefined) ? jumpOutDisabled : true jumpOutDisabled:
jumpOutDisabled !== null && jumpOutDisabled !== undefined
? jumpOutDisabled
: true
} }
}) })
} }
const stepBtnStyle = 'd-flex align-items-center justify-content-center btn btn-primary btn-sm stepButton h-75 m-0 p-1' const stepBtnStyle =
'd-flex align-items-center justify-content-center btn btn-primary btn-sm stepButton h-75 m-0 p-1'
const disableStepBtnStyle = 'stepButtonDisabled' const disableStepBtnStyle = 'stepButtonDisabled'
const disableJumpBtnStyle = 'jumpButtonDisabled' const disableJumpBtnStyle = 'jumpButtonDisabled'
const stepMarkupStructure = { const stepMarkupStructure = {
stepOverBackJSX : { stepOverBackJSX: {
markup: (<div className={state.overBackDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}`: `${stepBtnStyle}`} onClick={() => { stepOverBack && stepOverBack() }}> markup: (
<button id='overback' className='btn btn-link btn-sm stepButton m-0 p-0' onClick={() => { stepOverBack && stepOverBack() }} disabled={state.overBackDisabled} style={{ pointerEvents: 'none', color: 'white' }}> <div
<span className="fas fa-reply"></span> className={
</button> state.overBackDisabled
</div>), ? `${stepBtnStyle} ${disableStepBtnStyle}`
: `${stepBtnStyle}`
}
onClick={() => {
stepOverBack && stepOverBack()
}}
>
<button
id="overback"
className="btn btn-link btn-sm stepButton m-0 p-0"
onClick={() => {
stepOverBack && stepOverBack()
}}
disabled={state.overBackDisabled}
style={{pointerEvents: 'none', color: 'white'}}
>
<span className="fas fa-reply"></span>
</button>
</div>
),
placement: 'top-start', placement: 'top-start',
tagId: 'overbackTooltip', tagId: 'overbackTooltip',
tooltipMsg: 'Step over back' tooltipMsg: 'Step over back'
}, },
stepBackJSX : { stepBackJSX: {
markup: ( markup: (
<div className={state.intoBackDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}`: `${stepBtnStyle}`} onClick={() => { stepIntoBack && stepIntoBack() }} data-id="buttonNavigatorIntoBack" id="buttonNavigatorIntoBackContainer"> <div
<button id='intoback' data-id="buttonNavigatorIntoBack" className='btn btn-link btn-sm stepButton m-0 p-0' onClick={() => { stepIntoBack && stepIntoBack() }} disabled={state.intoBackDisabled} style={{ pointerEvents: 'none', color: 'white' }}> className={
state.intoBackDisabled
? `${stepBtnStyle} ${disableStepBtnStyle}`
: `${stepBtnStyle}`
}
onClick={() => {
stepIntoBack && stepIntoBack()
}}
data-id="buttonNavigatorIntoBack"
id="buttonNavigatorIntoBackContainer"
>
<button
id="intoback"
data-id="buttonNavigatorIntoBack"
className="btn btn-link btn-sm stepButton m-0 p-0"
onClick={() => {
stepIntoBack && stepIntoBack()
}}
disabled={state.intoBackDisabled}
style={{pointerEvents: 'none', color: 'white'}}
>
<span className="fas fa-level-up-alt"></span> <span className="fas fa-level-up-alt"></span>
</button> </button>
</div> </div>
@ -77,12 +132,30 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
tagId: 'intobackTooltip', tagId: 'intobackTooltip',
tooltipMsg: 'Step back' tooltipMsg: 'Step back'
}, },
stepIntoJSX : { stepIntoJSX: {
markup: ( markup: (
<div className={state.intoForwardDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}`: `${stepBtnStyle}`} onClick={() => { stepIntoForward && stepIntoForward() }} data-id="buttonNavigatorIntoForward" id="buttonNavigatorIntoFowardContainer"> <div
<button id='intoforward' data-id="buttonNavigatorIntoForward" className='btn btn-link btn-sm stepButton m-0 p-0' onClick={() => { stepIntoForward && stepIntoForward() }} disabled={state.intoForwardDisabled} className={
style={{ pointerEvents: 'none', color: 'white' }} state.intoForwardDisabled
? `${stepBtnStyle} ${disableStepBtnStyle}`
: `${stepBtnStyle}`
}
onClick={() => {
stepIntoForward && stepIntoForward()
}}
data-id="buttonNavigatorIntoForward"
id="buttonNavigatorIntoFowardContainer"
>
<button
id="intoforward"
data-id="buttonNavigatorIntoForward"
className="btn btn-link btn-sm stepButton m-0 p-0"
onClick={() => {
stepIntoForward && stepIntoForward()
}}
disabled={state.intoForwardDisabled}
style={{pointerEvents: 'none', color: 'white'}}
> >
<span className="fas fa-level-down-alt"></span> <span className="fas fa-level-down-alt"></span>
</button> </button>
@ -92,24 +165,67 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
tagId: 'intoforwardTooltip', tagId: 'intoforwardTooltip',
tooltipMsg: 'Step into' tooltipMsg: 'Step into'
}, },
stepOverForwardJSX : { stepOverForwardJSX: {
markup: ( markup: (
<div className={state.overForwardDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}`: `${stepBtnStyle}`} onClick={() => { stepOverForward && stepOverForward() }} data-id="buttonNavigatorOverForward" id="buttonNavigatorOverForwardContainer"> <div
<button id='overforward' className='btn btn-link btn-sm stepButton m-0 p-0' onClick={() => { stepOverForward && stepOverForward() }} disabled={state.overForwardDisabled} style={{ pointerEvents: 'none', color: 'white' }}> className={
state.overForwardDisabled
? `${stepBtnStyle} ${disableStepBtnStyle}`
: `${stepBtnStyle}`
}
onClick={() => {
stepOverForward && stepOverForward()
}}
data-id="buttonNavigatorOverForward"
id="buttonNavigatorOverForwardContainer"
>
<button
id="overforward"
className="btn btn-link btn-sm stepButton m-0 p-0"
onClick={() => {
stepOverForward && stepOverForward()
}}
disabled={state.overForwardDisabled}
style={{pointerEvents: 'none', color: 'white'}}
>
<span className="fas fa-share"></span> <span className="fas fa-share"></span>
</button> </button>
</div> </div>
), ),
placement: 'top-end', placement: 'top-end',
tagId: 'overbackTooltip', tagId: 'overbackTooltip',
tooltipMsg: 'Step over forward', tooltipMsg: 'Step over forward'
} }
} }
const jumpMarkupStructure = { const jumpMarkupStructure = {
jumpPreviousBreakpointJSX : { jumpPreviousBreakpointJSX: {
markup: ( markup: (
<div className={state.jumpPreviousBreakpointDisabled ? `${stepBtnStyle} ${disableJumpBtnStyle}`: `${stepBtnStyle}`} id="buttonNavigatorJumpPreviousBreakpointContainer" onClick={() => { jumpPreviousBreakpoint && jumpPreviousBreakpoint() }} data-id="buttonNavigatorJumpPreviousBreakpoint"> <div
<button className='btn btn-link btn-sm jumpButton m-0 p-0' id='jumppreviousbreakpoint' data-id="buttonNavigatorJumpPreviousBreakpoint" onClick={() => { jumpPreviousBreakpoint && jumpPreviousBreakpoint() }} disabled={state.jumpPreviousBreakpointDisabled} style={{ pointerEvents: 'none', backgroundColor: 'inherit', color: 'white' }}> className={
state.jumpPreviousBreakpointDisabled
? `${stepBtnStyle} ${disableJumpBtnStyle}`
: `${stepBtnStyle}`
}
id="buttonNavigatorJumpPreviousBreakpointContainer"
onClick={() => {
jumpPreviousBreakpoint && jumpPreviousBreakpoint()
}}
data-id="buttonNavigatorJumpPreviousBreakpoint"
>
<button
className="btn btn-link btn-sm jumpButton m-0 p-0"
id="jumppreviousbreakpoint"
data-id="buttonNavigatorJumpPreviousBreakpoint"
onClick={() => {
jumpPreviousBreakpoint && jumpPreviousBreakpoint()
}}
disabled={state.jumpPreviousBreakpointDisabled}
style={{
pointerEvents: 'none',
backgroundColor: 'inherit',
color: 'white'
}}
>
<span className="fas fa-step-backward"></span> <span className="fas fa-step-backward"></span>
</button> </button>
</div> </div>
@ -118,10 +234,34 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
tagId: 'jumppreviousbreakpointTooltip', tagId: 'jumppreviousbreakpointTooltip',
tooltipMsg: 'Jump to the previous breakpoint' tooltipMsg: 'Jump to the previous breakpoint'
}, },
jumpOutJSX : { jumpOutJSX: {
markup: ( markup: (
<div className={state.jumpOutDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}`: `${stepBtnStyle}`} onClick={() => { jumpOut && jumpOut() }} data-id="buttonNavigatorJumpOut" id="buttonNavigatorJumpOutContainer"> <div
<button className='btn btn-link btn-sm jumpButton m-0 p-0' id='jumpout' onClick={() => { jumpOut && jumpOut() }} disabled={state.jumpOutDisabled} style={{ pointerEvents: 'none', backgroundColor: 'inherit', color: 'white' }} data-id="buttonNavigatorJumpOut"> className={
state.jumpOutDisabled
? `${stepBtnStyle} ${disableStepBtnStyle}`
: `${stepBtnStyle}`
}
onClick={() => {
jumpOut && jumpOut()
}}
data-id="buttonNavigatorJumpOut"
id="buttonNavigatorJumpOutContainer"
>
<button
className="btn btn-link btn-sm jumpButton m-0 p-0"
id="jumpout"
onClick={() => {
jumpOut && jumpOut()
}}
disabled={state.jumpOutDisabled}
style={{
pointerEvents: 'none',
backgroundColor: 'inherit',
color: 'white'
}}
data-id="buttonNavigatorJumpOut"
>
<span className="fas fa-eject"></span> <span className="fas fa-eject"></span>
</button> </button>
</div> </div>
@ -130,10 +270,30 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
tagId: 'jumpoutTooltip', tagId: 'jumpoutTooltip',
tooltipMsg: 'Jump out' tooltipMsg: 'Jump out'
}, },
jumpNextBreakpointJSX : { jumpNextBreakpointJSX: {
markup: ( markup: (
<div className={state.jumpNextBreakpointDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}`: `${stepBtnStyle}`} onClick={() => { jumpNextBreakpoint && jumpNextBreakpoint() }} data-id="buttonNavigatorJumpNextBreakpoint" id="buttonNavigatorJumpNextBreakpointContainer"> <div
<button className='btn btn-link btn-sm jumpButton m-0 p-0' id='jumpnextbreakpoint' data-id="buttonNavigatorJumpNextBreakpoint" onClick={() => { jumpNextBreakpoint && jumpNextBreakpoint() }} disabled={state.jumpNextBreakpointDisabled} style={{ pointerEvents: 'none', color: 'white' }}> className={
state.jumpNextBreakpointDisabled
? `${stepBtnStyle} ${disableStepBtnStyle}`
: `${stepBtnStyle}`
}
onClick={() => {
jumpNextBreakpoint && jumpNextBreakpoint()
}}
data-id="buttonNavigatorJumpNextBreakpoint"
id="buttonNavigatorJumpNextBreakpointContainer"
>
<button
className="btn btn-link btn-sm jumpButton m-0 p-0"
id="jumpnextbreakpoint"
data-id="buttonNavigatorJumpNextBreakpoint"
onClick={() => {
jumpNextBreakpoint && jumpNextBreakpoint()
}}
disabled={state.jumpNextBreakpointDisabled}
style={{pointerEvents: 'none', color: 'white'}}
>
<span className="fas fa-step-forward"></span> <span className="fas fa-step-forward"></span>
</button> </button>
</div> </div>
@ -147,39 +307,68 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
return ( return (
<div className="buttons"> <div className="buttons">
<div className="stepButtons btn-group py-1"> <div className="stepButtons btn-group py-1">
{ {Object.keys(stepMarkupStructure).map((x) => (
Object.keys(stepMarkupStructure).map(x => ( <CustomTooltip
<CustomTooltip placement={stepMarkupStructure[x].placement}
placement={stepMarkupStructure[x].placement} tooltipId={stepMarkupStructure[x].tagId}
tooltipId={stepMarkupStructure[x].tagId} tooltipText={stepMarkupStructure[x].tooltipMsg}
tooltipText={stepMarkupStructure[x].tooltipMsg} key={`${stepMarkupStructure[x].placement}-${stepMarkupStructure[x].tooltipMsg}-${stepMarkupStructure[x].tagId}`}
key={`${stepMarkupStructure[x].placement}-${stepMarkupStructure[x].tooltipMsg}-${stepMarkupStructure[x].tagId}`} >
> {stepMarkupStructure[x].markup}
{stepMarkupStructure[x].markup} </CustomTooltip>
</CustomTooltip> ))}
))
}
</div> </div>
<div className="jumpButtons btn-group py-1"> <div className="jumpButtons btn-group py-1">
{ {Object.keys(jumpMarkupStructure).map((x) => (
Object.keys(jumpMarkupStructure).map(x => ( <CustomTooltip
<CustomTooltip placement={jumpMarkupStructure[x].placement}
placement={jumpMarkupStructure[x].placement} tooltipText={jumpMarkupStructure[x].tooltipMsg}
tooltipText={jumpMarkupStructure[x].tooltipMsg} tooltipId={jumpMarkupStructure[x].tooltipId}
tooltipId={jumpMarkupStructure[x].tooltipId} key={`${jumpMarkupStructure[x].placement}-${jumpMarkupStructure[x].tooltipMsg}-${jumpMarkupStructure[x].tagId}`}
key={`${jumpMarkupStructure[x].placement}-${jumpMarkupStructure[x].tooltipMsg}-${jumpMarkupStructure[x].tagId}`} >
> {jumpMarkupStructure[x].markup}
{jumpMarkupStructure[x].markup} </CustomTooltip>
</CustomTooltip> ))}
))
}
</div> </div>
<div id='reverted' style={{ display: revertedReason === '' ? 'none' : 'block' }}> <div
<span className='text-warning'>This call has reverted, state changes made during the call will be reverted.</span> id="reverted"
<span className='text-warning' id='outofgas' style={{ display: revertedReason === 'outofgas' ? 'inline' : 'none' }}>This call will run out of gas.</span> style={{display: revertedReason === '' ? 'none' : 'block'}}
<span className='text-warning' id='parenthasthrown' style={{ display: revertedReason === 'parenthasthrown' ? 'inline' : 'none' }}>The parent call will throw an exception</span> >
<div className='text-warning'>Click <u data-id="debugGoToRevert" className="cursorPointerRemixDebugger" role="button" onClick={() => { jumpToException && jumpToException() }}>here</u> to jump where the call reverted.</div> <span className="text-warning">
This call has reverted, state changes made during the call will be
reverted.
</span>
<span
className="text-warning"
id="outofgas"
style={{display: revertedReason === 'outofgas' ? 'inline' : 'none'}}
>
This call will run out of gas.
</span>
<span
className="text-warning"
id="parenthasthrown"
style={{
display: revertedReason === 'parenthasthrown' ? 'inline' : 'none'
}}
>
The parent call will throw an exception
</span>
<div className="text-warning">
Click{' '}
<u
data-id="debugGoToRevert"
className="cursorPointerRemixDebugger"
role="button"
onClick={() => {
jumpToException && jumpToException()
}}
>
here
</u>{' '}
to jump where the call reverted.
</div>
</div> </div>
</div> </div>
) )

@ -1,16 +1,16 @@
import React, { useState, useEffect, useRef } from 'react' // eslint-disable-line import React, {useState, useEffect, useRef} from 'react' // eslint-disable-line
import { FormattedMessage } from 'react-intl' import {FormattedMessage} from 'react-intl'
import TxBrowser from './tx-browser/tx-browser' // eslint-disable-line import TxBrowser from './tx-browser/tx-browser' // eslint-disable-line
import StepManager from './step-manager/step-manager' // eslint-disable-line import StepManager from './step-manager/step-manager' // eslint-disable-line
import VmDebugger from './vm-debugger/vm-debugger' // eslint-disable-line import VmDebugger from './vm-debugger/vm-debugger' // eslint-disable-line
import VmDebuggerHead from './vm-debugger/vm-debugger-head' // eslint-disable-line import VmDebuggerHead from './vm-debugger/vm-debugger-head' // eslint-disable-line
import { TransactionDebugger as Debugger } from '@remix-project/remix-debug' // eslint-disable-line import {TransactionDebugger as Debugger} from '@remix-project/remix-debug' // eslint-disable-line
import { DebuggerUIProps } from './idebugger-api' // eslint-disable-line import {DebuggerUIProps} from './idebugger-api' // eslint-disable-line
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line import {Toaster} from '@remix-ui/toaster' // eslint-disable-line
import { CustomTooltip, isValidHash } from '@remix-ui/helper' import {CustomTooltip, isValidHash} from '@remix-ui/helper'
/* eslint-disable-next-line */ /* eslint-disable-next-line */
import './debugger-ui.css' import './debugger-ui.css'
const _paq = (window as any)._paq = (window as any)._paq || [] const _paq = ((window as any)._paq = (window as any)._paq || [])
export const DebuggerUI = (props: DebuggerUIProps) => { export const DebuggerUI = (props: DebuggerUIProps) => {
const debuggerModule = props.debuggerAPI const debuggerModule = props.debuggerAPI
@ -54,7 +54,12 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
const handleResize = () => { const handleResize = () => {
if (panelsRef.current && debuggerTopRef.current) { if (panelsRef.current && debuggerTopRef.current) {
panelsRef.current.style.height = (window.innerHeight - debuggerTopRef.current.clientHeight) - debuggerTopRef.current.offsetTop - 7 +'px' panelsRef.current.style.height =
window.innerHeight -
debuggerTopRef.current.clientHeight -
debuggerTopRef.current.offsetTop -
7 +
'px'
} }
} }
@ -65,8 +70,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
useEffect(() => { useEffect(() => {
window.addEventListener('resize', handleResize) window.addEventListener('resize', handleResize)
// TODO: not a good way to wait on the ref doms element to be rendered of course // TODO: not a good way to wait on the ref doms element to be rendered of course
setTimeout(() => setTimeout(() => handleResize(), 2000)
handleResize(), 2000)
return () => window.removeEventListener('resize', handleResize) return () => window.removeEventListener('resize', handleResize)
}, [state.debugging, state.isActive]) }, [state.debugging, state.isActive])
@ -85,11 +89,16 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
useEffect(() => { useEffect(() => {
const setEditor = () => { const setEditor = () => {
debuggerModule.onBreakpointCleared((fileName, row) => { debuggerModule.onBreakpointCleared((fileName, row) => {
if (state.debugger) state.debugger.breakPointManager.remove({ fileName: fileName, row: row }) if (state.debugger)
state.debugger.breakPointManager.remove({
fileName: fileName,
row: row
})
}) })
debuggerModule.onBreakpointAdded((fileName, row) => { debuggerModule.onBreakpointAdded((fileName, row) => {
if (state.debugger) state.debugger.breakPointManager.add({ fileName: fileName, row: row }) if (state.debugger)
state.debugger.breakPointManager.add({fileName: fileName, row: row})
}) })
debuggerModule.onEditorContentChanged(() => { debuggerModule.onEditorContentChanged(() => {
@ -101,9 +110,10 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
const providerChanged = () => { const providerChanged = () => {
debuggerModule.onEnvChanged((provider) => { debuggerModule.onEnvChanged((provider) => {
setState(prevState => { setState((prevState) => {
const isLocalNodeUsed = !provider.startsWith('vm') && provider !== 'injected' const isLocalNodeUsed =
return { ...prevState, isLocalNodeUsed: isLocalNodeUsed } !provider.startsWith('vm') && provider !== 'injected'
return {...prevState, isLocalNodeUsed: isLocalNodeUsed}
}) })
}) })
} }
@ -116,64 +126,90 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
debuggerInstance.event.register('debuggerStatus', async (isActive) => { debuggerInstance.event.register('debuggerStatus', async (isActive) => {
await debuggerModule.discardHighlight() await debuggerModule.discardHighlight()
setState(prevState => { setState((prevState) => {
return { ...prevState, isActive } return {...prevState, isActive}
}) })
}) })
debuggerInstance.event.register('locatingBreakpoint', async (isActive) => { debuggerInstance.event.register('locatingBreakpoint', async (isActive) => {
setState(prevState => { setState((prevState) => {
return { ...prevState, sourceLocationStatus: 'Locating breakpoint, this might take a while...' } return {
...prevState,
sourceLocationStatus:
'Locating breakpoint, this might take a while...'
}
}) })
}) })
debuggerInstance.event.register('noBreakpointHit', async (isActive) => { debuggerInstance.event.register('noBreakpointHit', async (isActive) => {
setState(prevState => { setState((prevState) => {
return { ...prevState, sourceLocationStatus: '' } return {...prevState, sourceLocationStatus: ''}
}) })
}) })
debuggerInstance.event.register('newSourceLocation', async (lineColumnPos, rawLocation, generatedSources, address, stepDetail, lineGasCost) => { debuggerInstance.event.register(
if (!lineColumnPos) { 'newSourceLocation',
await debuggerModule.discardHighlight() async (
setState(prevState => { lineColumnPos,
return { ...prevState, sourceLocationStatus: 'Source location not available, neither in Sourcify nor in Etherscan. Please make sure the Etherscan api key is provided in the settings.' } rawLocation,
}) generatedSources,
return address,
} stepDetail,
const contracts = await debuggerModule.fetchContractAndCompile( lineGasCost
address || currentReceipt.contractAddress || currentReceipt.to, ) => {
currentReceipt) if (!lineColumnPos) {
if (contracts) { await debuggerModule.discardHighlight()
let path = contracts.getSourceName(rawLocation.file) setState((prevState) => {
if (!path) { return {
// check in generated sources ...prevState,
for (const source of generatedSources) { sourceLocationStatus:
if (source.id === rawLocation.file) { 'Source location not available, neither in Sourcify nor in Etherscan. Please make sure the Etherscan api key is provided in the settings.'
path = `browser/.debugger/generated-sources/${source.name}` }
let content })
try { return
content = await debuggerModule.getFile(path) }
} catch (e) { const contracts = await debuggerModule.fetchContractAndCompile(
const message = 'Unable to fetch generated sources, the file probably doesn\'t exist yet.' address || currentReceipt.contractAddress || currentReceipt.to,
console.log(message, ' ', e) currentReceipt
} )
if (content !== source.contents) { if (contracts) {
await debuggerModule.setFile(path, source.contents) let path = contracts.getSourceName(rawLocation.file)
if (!path) {
// check in generated sources
for (const source of generatedSources) {
if (source.id === rawLocation.file) {
path = `browser/.debugger/generated-sources/${source.name}`
let content
try {
content = await debuggerModule.getFile(path)
} catch (e) {
const message =
"Unable to fetch generated sources, the file probably doesn't exist yet."
console.log(message, ' ', e)
}
if (content !== source.contents) {
await debuggerModule.setFile(path, source.contents)
}
break
} }
break
} }
} }
} if (path) {
if (path) { setState((prevState) => {
setState(prevState => { return {...prevState, sourceLocationStatus: ''}
return { ...prevState, sourceLocationStatus: '' } })
}) await debuggerModule.discardHighlight()
await debuggerModule.discardHighlight() await debuggerModule.highlight(
await debuggerModule.highlight(lineColumnPos, path, rawLocation, stepDetail, lineGasCost) lineColumnPos,
path,
rawLocation,
stepDetail,
lineGasCost
)
}
} }
} }
}) )
debuggerInstance.event.register('debuggerUnloaded', () => unLoad()) debuggerInstance.event.register('debuggerUnloaded', () => unLoad())
} }
@ -183,7 +219,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
} }
const updateTxNumberFlag = (empty: boolean) => { const updateTxNumberFlag = (empty: boolean) => {
setState(prevState => { setState((prevState) => {
return { return {
...prevState, ...prevState,
txNumberIsEmpty: empty, txNumberIsEmpty: empty,
@ -194,7 +230,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
const unloadRequested = (blockNumber, txIndex, tx) => { const unloadRequested = (blockNumber, txIndex, tx) => {
unLoad() unLoad()
setState(prevState => { setState((prevState) => {
return { return {
...prevState, ...prevState,
sourceLocationStatus: '' sourceLocationStatus: ''
@ -205,7 +241,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
const unLoad = () => { const unLoad = () => {
debuggerModule.onStopDebugging() debuggerModule.onStopDebugging()
if (state.debugger) state.debugger.unload() if (state.debugger) state.debugger.unload()
setState(prevState => { setState((prevState) => {
return { return {
...prevState, ...prevState,
isActive: false, isActive: false,
@ -228,10 +264,10 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
const startDebugging = async (blockNumber, txNumber, tx, optWeb3?) => { const startDebugging = async (blockNumber, txNumber, tx, optWeb3?) => {
if (state.debugger) { if (state.debugger) {
unLoad() unLoad()
await (new Promise((resolve) => setTimeout(() => resolve({}), 1000))) await new Promise((resolve) => setTimeout(() => resolve({}), 1000))
} }
if (!txNumber) return if (!txNumber) return
setState(prevState => { setState((prevState) => {
return { return {
...prevState, ...prevState,
txNumber: txNumber, txNumber: txNumber,
@ -239,7 +275,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
} }
}) })
if (!isValidHash(txNumber)) { if (!isValidHash(txNumber)) {
setState(prevState => { setState((prevState) => {
return { return {
...prevState, ...prevState,
validationError: 'Invalid transaction hash.' validationError: 'Invalid transaction hash.'
@ -248,15 +284,20 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
return return
} }
const web3 = optWeb3 || (state.opt.debugWithLocalNode ? await debuggerModule.web3() : await debuggerModule.getDebugWeb3()) const web3 =
optWeb3 ||
(state.opt.debugWithLocalNode
? await debuggerModule.web3()
: await debuggerModule.getDebugWeb3())
try { try {
const networkId = await web3.eth.net.getId() const networkId = await web3.eth.net.getId()
_paq.push(['trackEvent', 'debugger', 'startDebugging', networkId]) _paq.push(['trackEvent', 'debugger', 'startDebugging', networkId])
if (networkId === 42) { if (networkId === 42) {
setState(prevState => { setState((prevState) => {
return { return {
...prevState, ...prevState,
validationError: 'Unfortunately, the Kovan network is not supported.' validationError:
'Unfortunately, the Kovan network is not supported.'
} }
}) })
return return
@ -272,7 +313,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
currentBlock = await web3.eth.getBlock(currentReceipt.blockHash) currentBlock = await web3.eth.getBlock(currentReceipt.blockHash)
currentTransaction = await web3.eth.getTransaction(txNumber) currentTransaction = await web3.eth.getTransaction(txNumber)
} catch (e) { } catch (e) {
setState(prevState => { setState((prevState) => {
return { return {
...prevState, ...prevState,
validationError: e.message validationError: e.message
@ -287,7 +328,11 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
offsetToLineColumnConverter: debuggerModule.offsetToLineColumnConverter, offsetToLineColumnConverter: debuggerModule.offsetToLineColumnConverter,
compilationResult: async (address) => { compilationResult: async (address) => {
try { try {
if (!localCache[address]) localCache[address] = await debuggerModule.fetchContractAndCompile(address, currentReceipt) if (!localCache[address])
localCache[address] = await debuggerModule.fetchContractAndCompile(
address,
currentReceipt
)
return localCache[address] return localCache[address]
} catch (e) { } catch (e) {
// debuggerModule.showMessage('Debugging error', 'Unable to fetch a transaction.') // debuggerModule.showMessage('Debugging error', 'Unable to fetch a transaction.')
@ -298,12 +343,12 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
debugWithGeneratedSources: state.opt.debugWithGeneratedSources debugWithGeneratedSources: state.opt.debugWithGeneratedSources
}) })
setTimeout(async() => { setTimeout(async () => {
debuggerModule.onStartDebugging(debuggerInstance) debuggerModule.onStartDebugging(debuggerInstance)
try { try {
await debuggerInstance.debug(blockNumber, txNumber, tx, () => { await debuggerInstance.debug(blockNumber, txNumber, tx, () => {
listenToEvents(debuggerInstance, currentReceipt) listenToEvents(debuggerInstance, currentReceipt)
setState(prevState => { setState((prevState) => {
return { return {
...prevState, ...prevState,
blockNumber, blockNumber,
@ -320,10 +365,12 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
}) })
} catch (error) { } catch (error) {
unLoad() unLoad()
setState(prevState => { setState((prevState) => {
let errorMsg = error.message || error let errorMsg = error.message || error
if (typeof errorMsg !== 'string') { if (typeof errorMsg !== 'string') {
errorMsg = JSON.stringify(errorMsg) + '. Possible error: the current endpoint does not support retrieving the trace of a transaction.' errorMsg =
JSON.stringify(errorMsg) +
'. Possible error: the current endpoint does not support retrieving the trace of a transaction.'
} }
return { return {
...prevState, ...prevState,
@ -338,7 +385,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
} }
const debug = (txHash, web3?) => { const debug = (txHash, web3?) => {
setState(prevState => { setState((prevState) => {
return { return {
...prevState, ...prevState,
validationError: '', validationError: '',
@ -350,36 +397,105 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
} }
const stepManager = { const stepManager = {
jumpTo: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.jumpTo.bind(state.debugger.step_manager) : null, jumpTo:
stepOverBack: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.stepOverBack.bind(state.debugger.step_manager) : null, state.debugger && state.debugger.step_manager
stepIntoBack: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.stepIntoBack.bind(state.debugger.step_manager) : null, ? state.debugger.step_manager.jumpTo.bind(state.debugger.step_manager)
stepIntoForward: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.stepIntoForward.bind(state.debugger.step_manager) : null, : null,
stepOverForward: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.stepOverForward.bind(state.debugger.step_manager) : null, stepOverBack:
jumpOut: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.jumpOut.bind(state.debugger.step_manager) : null, state.debugger && state.debugger.step_manager
jumpPreviousBreakpoint: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.jumpPreviousBreakpoint.bind(state.debugger.step_manager) : null, ? state.debugger.step_manager.stepOverBack.bind(
jumpNextBreakpoint: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.jumpNextBreakpoint.bind(state.debugger.step_manager) : null, state.debugger.step_manager
jumpToException: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.jumpToException.bind(state.debugger.step_manager) : null, )
traceLength: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.traceLength : null, : null,
registerEvent: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.event.register.bind(state.debugger.step_manager.event) : null stepIntoBack:
state.debugger && state.debugger.step_manager
? state.debugger.step_manager.stepIntoBack.bind(
state.debugger.step_manager
)
: null,
stepIntoForward:
state.debugger && state.debugger.step_manager
? state.debugger.step_manager.stepIntoForward.bind(
state.debugger.step_manager
)
: null,
stepOverForward:
state.debugger && state.debugger.step_manager
? state.debugger.step_manager.stepOverForward.bind(
state.debugger.step_manager
)
: null,
jumpOut:
state.debugger && state.debugger.step_manager
? state.debugger.step_manager.jumpOut.bind(state.debugger.step_manager)
: null,
jumpPreviousBreakpoint:
state.debugger && state.debugger.step_manager
? state.debugger.step_manager.jumpPreviousBreakpoint.bind(
state.debugger.step_manager
)
: null,
jumpNextBreakpoint:
state.debugger && state.debugger.step_manager
? state.debugger.step_manager.jumpNextBreakpoint.bind(
state.debugger.step_manager
)
: null,
jumpToException:
state.debugger && state.debugger.step_manager
? state.debugger.step_manager.jumpToException.bind(
state.debugger.step_manager
)
: null,
traceLength:
state.debugger && state.debugger.step_manager
? state.debugger.step_manager.traceLength
: null,
registerEvent:
state.debugger && state.debugger.step_manager
? state.debugger.step_manager.event.register.bind(
state.debugger.step_manager.event
)
: null
} }
const vmDebugger = { const vmDebugger = {
registerEvent: state.debugger && state.debugger.vmDebuggerLogic ? state.debugger.vmDebuggerLogic.event.register.bind(state.debugger.vmDebuggerLogic.event) : null, registerEvent:
triggerEvent: state.debugger && state.debugger.vmDebuggerLogic ? state.debugger.vmDebuggerLogic.event.trigger.bind(state.debugger.vmDebuggerLogic.event) : null state.debugger && state.debugger.vmDebuggerLogic
? state.debugger.vmDebuggerLogic.event.register.bind(
state.debugger.vmDebuggerLogic.event
)
: null,
triggerEvent:
state.debugger && state.debugger.vmDebuggerLogic
? state.debugger.vmDebuggerLogic.event.trigger.bind(
state.debugger.vmDebuggerLogic.event
)
: null
} }
const customJSX = ( const customJSX = (
<span className="p-0 m-0"> <span className="p-0 m-0">
<input className="custom-control-input" id="debugGeneratedSourcesInput" onChange={({ target: { checked } }) => { <input
setState(prevState => { className="custom-control-input"
return { ...prevState, opt: { ...prevState.opt, debugWithGeneratedSources: checked } } id="debugGeneratedSourcesInput"
}) onChange={({target: {checked}}) => {
}} type="checkbox" /> setState((prevState) => {
return {
...prevState,
opt: {...prevState.opt, debugWithGeneratedSources: checked}
}
})
}}
type="checkbox"
/>
<label <label
data-id="debugGeneratedSourcesLabel" data-id="debugGeneratedSourcesLabel"
className="form-check-label custom-control-label" className="form-check-label custom-control-label"
htmlFor="debugGeneratedSourcesInput"> htmlFor="debugGeneratedSourcesInput"
<FormattedMessage id='debugger.useGeneratedSources' />(Solidity {'>='} v0.7.2) >
<FormattedMessage id="debugger.useGeneratedSources" />
(Solidity {'>='} v0.7.2)
</label> </label>
</span> </span>
) )
@ -391,48 +507,93 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
<div className="mt-2 mb-2 debuggerConfig custom-control custom-checkbox"> <div className="mt-2 mb-2 debuggerConfig custom-control custom-checkbox">
<CustomTooltip <CustomTooltip
tooltipId="debuggerGenSourceCheckbox" tooltipId="debuggerGenSourceCheckbox"
tooltipText={<FormattedMessage id='debugger.debugWithGeneratedSources' />} tooltipText={
<FormattedMessage id="debugger.debugWithGeneratedSources" />
}
placement="bottom-start" placement="bottom-start"
> >
{customJSX} {customJSX}
</CustomTooltip> </CustomTooltip>
</div> </div>
{ state.isLocalNodeUsed && <div className="mb-2 debuggerConfig custom-control custom-checkbox"> {state.isLocalNodeUsed && (
<CustomTooltip <div className="mb-2 debuggerConfig custom-control custom-checkbox">
tooltipId="debuggerGenSourceInput" <CustomTooltip
tooltipText="Force the debugger to use the current local node" tooltipId="debuggerGenSourceInput"
placement='right' tooltipText="Force the debugger to use the current local node"
> placement="right"
<input >
className="custom-control-input" <input
id="debugWithLocalNodeInput" className="custom-control-input"
onChange={({ target: { checked } }) => { id="debugWithLocalNodeInput"
setState(prevState => { onChange={({target: {checked}}) => {
return { ...prevState, opt: { ...prevState.opt, debugWithLocalNode: checked } } setState((prevState) => {
}) return {
}} ...prevState,
type="checkbox" opt: {...prevState.opt, debugWithLocalNode: checked}
/> }
</CustomTooltip> })
<label data-id="debugLocaNodeLabel" className="form-check-label custom-control-label" htmlFor="debugWithLocalNodeInput"><FormattedMessage id='debugger.debugLocaNodeLabel' /></label> }}
</div> type="checkbox"
} />
{ state.validationError && <span className="w-100 py-1 text-danger validationError">{state.validationError}</span> } </CustomTooltip>
<label
data-id="debugLocaNodeLabel"
className="form-check-label custom-control-label"
htmlFor="debugWithLocalNodeInput"
>
<FormattedMessage id="debugger.debugLocaNodeLabel" />
</label>
</div>
)}
{state.validationError && (
<span className="w-100 py-1 text-danger validationError">
{state.validationError}
</span>
)}
</div> </div>
<TxBrowser requestDebug={ requestDebug } unloadRequested={ unloadRequested } updateTxNumberFlag={ updateTxNumberFlag } transactionNumber={ state.txNumber } debugging={ state.debugging } /> <TxBrowser
{ state.debugging && state.sourceLocationStatus && <div className="text-warning"><i className="fas fa-exclamation-triangle" aria-hidden="true"></i> {state.sourceLocationStatus}</div> } requestDebug={requestDebug}
{ !state.debugging && unloadRequested={unloadRequested}
<div> updateTxNumberFlag={updateTxNumberFlag}
<i className="fas fa-info-triangle" aria-hidden="true"></i> transactionNumber={state.txNumber}
<span> debugging={state.debugging}
<FormattedMessage id='debugger.introduction' />: <a href="https://docs.sourcify.dev/docs/chains/" target="__blank" >Sourcify docs</a> & <a href="https://etherscan.io/contractsVerified" target="__blank">https://etherscan.io/contractsVerified</a> />
</span> {state.debugging && state.sourceLocationStatus && (
</div> } <div className="text-warning">
{ state.debugging && <StepManager stepManager={ stepManager } /> } <i className="fas fa-exclamation-triangle" aria-hidden="true"></i>{' '}
{state.sourceLocationStatus}
</div>
)}
{!state.debugging && (
<div>
<i className="fas fa-info-triangle" aria-hidden="true"></i>
<span>
<FormattedMessage id="debugger.introduction" />:{' '}
<a href="https://docs.sourcify.dev/docs/chains/" target="__blank">
Sourcify docs
</a>{' '}
&{' '}
<a href="https://etherscan.io/contractsVerified" target="__blank">
https://etherscan.io/contractsVerified
</a>
</span>
</div>
)}
{state.debugging && <StepManager stepManager={stepManager} />}
</div> </div>
<div className="debuggerPanels" ref={panelsRef}> <div className="debuggerPanels" ref={panelsRef}>
{ state.debugging && <VmDebuggerHead debugging={state.debugging} vmDebugger={ vmDebugger } /> } {state.debugging && (
{ state.debugging && <VmDebugger debugging={state.debugging} vmDebugger={ vmDebugger } currentBlock={ state.currentBlock } currentReceipt={ state.currentReceipt } currentTransaction={ state.currentTransaction } /> } <VmDebuggerHead debugging={state.debugging} vmDebugger={vmDebugger} />
)}
{state.debugging && (
<VmDebugger
debugging={state.debugging}
vmDebugger={vmDebugger}
currentBlock={state.currentBlock}
currentReceipt={state.currentReceipt}
currentTransaction={state.currentTransaction}
/>
)}
</div> </div>
</div> </div>
) )

@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef } from 'react' // eslint-disable-line import React, {useState, useEffect, useRef} from 'react' // eslint-disable-line
export const Slider = ({ jumpTo, sliderValue, traceLength }) => { export const Slider = ({jumpTo, sliderValue, traceLength}) => {
const onChangeId = useRef(null) const onChangeId = useRef(null)
const slider = useRef(null) const slider = useRef(null)
@ -9,13 +9,13 @@ export const Slider = ({ jumpTo, sliderValue, traceLength }) => {
}, [sliderValue]) }, [sliderValue])
const setValue = (value) => { const setValue = (value) => {
if (value < 0) return if (value < 0) return
if (value === slider.current.value) return if (value === slider.current.value) return
slider.current.value = value slider.current.value = value
if (onChangeId.current) { if (onChangeId.current) {
clearTimeout(onChangeId.current) clearTimeout(onChangeId.current)
} }
((value) => { ;((value) => {
onChangeId.current = setTimeout(() => { onChangeId.current = setTimeout(() => {
jumpTo && jumpTo(value) jumpTo && jumpTo(value)
}, 100) }, 100)
@ -30,11 +30,12 @@ export const Slider = ({ jumpTo, sliderValue, traceLength }) => {
return ( return (
<div> <div>
<input id='slider' <input
id="slider"
data-id="slider" data-id="slider"
className='w-100 my-0' className="w-100 my-0"
ref={slider} ref={slider}
type='range' type="range"
min={0} min={0}
max={traceLength ? traceLength - 1 : 0} max={traceLength ? traceLength - 1 : 0}
onMouseUp={handleChange} onMouseUp={handleChange}

@ -1,8 +1,22 @@
import React, { useState, useEffect } from 'react' // eslint-disable-line import React, {useState, useEffect} from 'react' // eslint-disable-line
import Slider from '../slider/slider' // eslint-disable-line import Slider from '../slider/slider' // eslint-disable-line
import ButtonNavigator from '../button-navigator/button-navigator' // eslint-disable-line import ButtonNavigator from '../button-navigator/button-navigator' // eslint-disable-line
export const StepManager = ({ stepManager: { jumpTo, traceLength, stepIntoBack, stepIntoForward, stepOverBack, stepOverForward, jumpOut, jumpNextBreakpoint, jumpPreviousBreakpoint, jumpToException, registerEvent } }) => { export const StepManager = ({
stepManager: {
jumpTo,
traceLength,
stepIntoBack,
stepIntoForward,
stepOverBack,
stepOverForward,
jumpOut,
jumpNextBreakpoint,
jumpPreviousBreakpoint,
jumpToException,
registerEvent
}
}) => {
const [state, setState] = useState({ const [state, setState] = useState({
sliderValue: -1, sliderValue: -1,
revertWarning: '', revertWarning: '',
@ -16,21 +30,25 @@ export const StepManager = ({ stepManager: { jumpTo, traceLength, stepIntoBack,
}, [registerEvent]) }, [registerEvent])
const setRevertWarning = (warning) => { const setRevertWarning = (warning) => {
setState(prevState => { setState((prevState) => {
return { ...prevState, revertWarning: warning } return {...prevState, revertWarning: warning}
}) })
} }
const updateStep = (step, stepState, jumpOutDisabled) => { const updateStep = (step, stepState, jumpOutDisabled) => {
setState(prevState => { setState((prevState) => {
return { ...prevState, sliderValue: step, stepState, jumpOutDisabled } return {...prevState, sliderValue: step, stepState, jumpOutDisabled}
}) })
} }
const { sliderValue, revertWarning, stepState, jumpOutDisabled } = state const {sliderValue, revertWarning, stepState, jumpOutDisabled} = state
return ( return (
<div className="py-1"> <div className="py-1">
<Slider jumpTo={jumpTo} sliderValue={sliderValue} traceLength={traceLength} /> <Slider
jumpTo={jumpTo}
sliderValue={sliderValue}
traceLength={traceLength}
/>
<ButtonNavigator <ButtonNavigator
stepIntoBack={stepIntoBack} stepIntoBack={stepIntoBack}
stepIntoForward={stepIntoForward} stepIntoForward={stepIntoForward}

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

Loading…
Cancel
Save