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",
"jsx-a11y/anchor-is-valid": "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]
}
},
@ -67,4 +72,4 @@
"globals": {
"JSX": true
}
}
}

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

@ -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()
export const App = () => {
export const App = () => {
return (
<div className="debugger">
<DebuggerUI debuggerAPI={remix} />
</div>
);
};
)
}
export default App;
export default App

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

@ -4,13 +4,13 @@ import './App.css'
import { DocGenClient } from './docgen-client'
import { Build } from './docgen/site'
export const client = new DocGenClient()
export const client = new DocGenClient()
const App = () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [themeType, setThemeType] = useState<string>('dark');
const [hasBuild, setHasBuild] = useState<boolean>(false);
const [fileName, setFileName] = useState<string>('');
const [themeType, setThemeType] = useState<string>('dark')
const [hasBuild, setHasBuild] = useState<boolean>(false)
const [fileName, setFileName] = useState<string>('')
useEffect(() => {
const watchThemeSwitch = async () => {
@ -21,19 +21,27 @@ const App = () => {
setHasBuild(true)
setFileName(fileName)
})
client.eventEmitter.on('docsGenerated', (docs: string[]) => {
})
client.eventEmitter.on('docsGenerated', (docs: string[]) => {})
}
watchThemeSwitch()
}, [])
return (
<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>
{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>}
<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>
{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>
)
}

@ -1,4 +1,4 @@
import { useState } from "react";
import { useState } from 'react'
export function useLocalStorage(key: string, initialValue: any) {
// State to store our value
@ -6,32 +6,31 @@ export function useLocalStorage(key: string, initialValue: any) {
const [storedValue, setStoredValue] = useState(() => {
try {
// 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
return item ? JSON.parse(item) : initialValue;
return item ? JSON.parse(item) : initialValue
} catch (error) {
// If error also return initialValue
console.log(error);
return initialValue;
console.log(error)
return initialValue
}
});
})
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
const setValue = (value: any) => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value;
const valueToStore = value instanceof Function ? value(storedValue) : value
// Save state
setStoredValue(valueToStore);
setStoredValue(valueToStore)
// Save to local storage
window.localStorage.setItem(key, JSON.stringify(valueToStore));
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
// 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 = () => {
return (
<div
style={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<img
style={{ paddingBottom: "2em" }}
style={{ paddingBottom: '2em' }}
width="250"
src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png"
alt="Error page"
/>
<h5>Sorry, something unexpected happened. </h5>
<h5>
Please raise an issue:{" "}
<a
style={{ color: "red" }}
href="https://github.com/Machinalabs/remix-ethdoc-plugin/issues"
>
Please raise an issue:{' '}
<a style={{ color: 'red' }} href="https://github.com/Machinalabs/remix-ethdoc-plugin/issues">
Here
</a>
</h5>
</div>
);
};
)
}

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

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

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

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

@ -1,8 +1,8 @@
const { composePlugins, withNx } = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react')
const {composePlugins, withNx} = require('@nrwl/webpack')
const {withReact} = require('@nrwl/react')
const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack.
module.exports = composePlugins(withNx(), withReact(), (config) => {
@ -12,7 +12,7 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
// add externals
config.externals = {
...config.externals,
solc: 'solc',
solc: 'solc'
}
// add public path
@ -23,18 +23,16 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'],
process: 'process/browser',
}),
new webpack.DefinePlugin({
process: 'process/browser'
}),
new webpack.DefinePlugin({})
)
// souce-map loader
config.module.rules.push({
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre"
use: ['source-map-loader'],
enforce: 'pre'
})
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
@ -48,13 +46,13 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
compress: false,
mangle: false,
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 { PluginClient } from "@remixproject/plugin"
import React from 'react'
import {PluginClient} from '@remixproject/plugin'
import { Receipt, ThemeType } from "./types"
import {Receipt, ThemeType} from './types'
export const AppContext = React.createContext({
apiKey: "",
apiKey: '',
setAPIKey: (value: string) => {
console.log("Set API Key from Context")
console.log('Set API Key from Context')
},
clientInstance: {} as PluginClient,
receipts: [] as Receipt[],
setReceipts: (receipts: Receipt[]) => {
console.log("Calling Set Receipts")
console.log('Calling Set Receipts')
},
contracts: [] as 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) => {
console.log("Calling Set Theme Type")
},
console.log('Calling Set Theme Type')
}
})

@ -1,40 +1,80 @@
import { PluginClient } from '@remixproject/plugin';
import { verify, EtherScanReturn } from './utils/verify';
import { getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus } from './utils';
import {PluginClient} from '@remixproject/plugin'
import {verify, EtherScanReturn} from './utils/verify'
import {
getReceiptStatus,
getEtherScanApi,
getNetworkName,
getProxyContractReceiptStatus
} from './utils'
export class RemixClient extends PluginClient {
loaded() {
return this.onload()
}
loaded() {
return this.onload()
}
async verify (apiKey: string, contractAddress: string, contractArguments: string, contractName: string, compilationResultParam: any, chainRef?: number | string, isProxyContract?: boolean, expectedImplAddress?: string) {
const result = await verify(apiKey, contractAddress, contractArguments, contractName, compilationResultParam, chainRef, isProxyContract, expectedImplAddress, this,
(value: EtherScanReturn) => {}, (value: string) => {})
return result
}
async verify(
apiKey: string,
contractAddress: string,
contractArguments: string,
contractName: string,
compilationResultParam: any,
chainRef?: number | string,
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) {
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
if (isProxyContract) receiptStatus = await getProxyContractReceiptStatus(receiptGuid, apiKey, etherscanApi)
else receiptStatus = await getReceiptStatus(receiptGuid, apiKey, etherscanApi)
return {
message: receiptStatus.result,
succeed: receiptStatus.status === '0' ? false : true
}
} catch (e: any){
return {
status: 'error',
message: e.message,
succeed: false
}
}
if (isProxyContract)
receiptStatus = await getProxyContractReceiptStatus(
receiptGuid,
apiKey,
etherscanApi
)
else
receiptStatus = await getReceiptStatus(
receiptGuid,
apiKey,
etherscanApi
)
return {
message: receiptStatus.result,
succeed: receiptStatus.status === '0' ? false : true
}
} catch (e: any) {
return {
status: 'error',
message: e.message,
succeed: false
}
}
}
}

@ -1,22 +1,27 @@
import React, { useState, useEffect, useRef } from "react"
import React, {useState, useEffect, useRef} from 'react'
import {
CompilationFileSources,
CompilationResult,
} from "@remixproject/plugin-api"
CompilationResult
} from '@remixproject/plugin-api'
import { RemixClient } from "./RemixPlugin";
import { createClient } from "@remixproject/plugin-webview";
import {RemixClient} from './RemixPlugin'
import {createClient} from '@remixproject/plugin-webview'
import { AppContext } from "./AppContext"
import { DisplayRoutes } from "./routes"
import {AppContext} from './AppContext'
import {DisplayRoutes} from './routes'
import { useLocalStorage } from "./hooks/useLocalStorage"
import {useLocalStorage} from './hooks/useLocalStorage'
import { getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus } from "./utils"
import { Receipt, ThemeType } from "./types"
import {
getReceiptStatus,
getEtherScanApi,
getNetworkName,
getProxyContractReceiptStatus
} from './utils'
import {Receipt, ThemeType} from './types'
import "./App.css"
import './App.css'
export const getNewContractNames = (compilationResult: CompilationResult) => {
const compiledContracts = compilationResult.contracts
@ -31,11 +36,11 @@ export const getNewContractNames = (compilationResult: CompilationResult) => {
}
const App = () => {
const [apiKey, setAPIKey] = useLocalStorage("apiKey", "")
const [apiKey, setAPIKey] = useLocalStorage('apiKey', '')
const [clientInstance, setClientInstance] = useState(undefined as any)
const [receipts, setReceipts] = useLocalStorage("receipts", [])
const [receipts, setReceipts] = useLocalStorage('receipts', [])
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 clientInstanceRef = useRef(clientInstance)
@ -49,8 +54,9 @@ const App = () => {
const loadClient = async () => {
await client.onload()
setClientInstance(client)
client.on("solidity",
"compilationFinished",
client.on(
'solidity',
'compilationFinished',
(
fileName: string,
source: CompilationFileSources,
@ -61,7 +67,7 @@ const App = () => {
const newContractsToSave: string[] = [
...contractsRef.current,
...newContractsNames,
...newContractsNames
]
const uniqueContracts: string[] = [...new Set(newContractsToSave)]
@ -82,7 +88,10 @@ const App = () => {
useEffect(() => {
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) {
@ -91,17 +100,19 @@ const App = () => {
timer.current = null
}
timer.current = setInterval(async () => {
const { network, networkId } = await getNetworkName(clientInstanceRef.current)
const {network, networkId} = await getNetworkName(
clientInstanceRef.current
)
if (!clientInstanceRef.current) {
return
}
if (network === "vm") {
if (network === 'vm') {
return
}
let newReceipts = receipts
for (const item of receiptsNotVerified) {
await new Promise(r => setTimeout(r, 500)) // avoid api rate limit exceed.
for (const item of receiptsNotVerified) {
await new Promise((r) => setTimeout(r, 500)) // avoid api rate limit exceed.
let status
if (item.isProxyContract) {
status = await getProxyContractReceiptStatus(
@ -113,29 +124,35 @@ const App = () => {
status.message = status.result
status.result = 'Successfully Updated'
}
} else
} else
status = await getReceiptStatus(
item.guid,
apiKey,
getEtherScanApi(networkId)
)
if (status.result === "Pass - Verified" || status.result === "Already Verified" ||
status.result === "Successfully Updated") {
if (
status.result === 'Pass - Verified' ||
status.result === 'Already Verified' ||
status.result === 'Successfully Updated'
) {
newReceipts = newReceipts.map((currentReceipt: Receipt) => {
if (currentReceipt.guid === item.guid) {
let res = {
const res = {
...currentReceipt,
status: status.result,
status: status.result
}
if (currentReceipt.isProxyContract) res.message = status.message
return res
}
return currentReceipt
})
}
})
}
}
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) {
clearInterval(timer.current)
@ -157,7 +174,7 @@ const App = () => {
contracts,
setContracts,
themeType,
setThemeType,
setThemeType
}}
>
<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 { CustomTooltip } from '@remix-ui/helper'
import { AppContext } from "../AppContext"
import {NavLink} from 'react-router-dom'
import {CustomTooltip} from '@remix-ui/helper'
import {AppContext} from '../AppContext'
interface Props {
title?: string
@ -13,21 +13,29 @@ interface IconProps {
from: string
}
const HomeIcon: React.FC<IconProps> = ({ from }: IconProps) => {
const HomeIcon: React.FC<IconProps> = ({from}: IconProps) => {
return (
<NavLink
data-id="home"
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"}
style={ ({ isActive }) => !isActive ? { width: "1.8rem", filter: "contrast(0.5)"} : {width: "1.8rem"}}
state={ from }
className={({isActive}) =>
isActive
? '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
tooltipText='Home'
tooltipId='etherscan-nav-home'
placement='bottom'
tooltipText="Home"
tooltipId="etherscan-nav-home"
placement="bottom"
>
<i className="fas fa-home"></i>
</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 (
<NavLink
<NavLink
data-id="receipts"
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"}
style={ ({ isActive }) => !isActive ? { width: "1.8rem", filter: "contrast(0.5)"} : {width: "1.8rem"}}
state={ from }
className={({isActive}) =>
isActive
? '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
tooltipText='Receipts'
tooltipId='etherscan-nav-receipts'
placement='bottom'
tooltipText="Receipts"
tooltipId="etherscan-nav-receipts"
placement="bottom"
>
<i className="fas fa-receipt"></i>
</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 (
<NavLink
data-id="settings"
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"}
style={ ({ isActive }) => !isActive ? { width: "1.8rem", filter: "contrast(0.5)"} : {width: "1.8rem"}}
state= {from}
className={({isActive}) =>
isActive
? '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
tooltipText='Settings'
tooltipId='etherscan-nav-settings'
placement='bottom'
tooltipText="Settings"
tooltipId="etherscan-nav-settings"
placement="bottom"
>
<i className="fas fa-cog"></i>
</CustomTooltip>
@ -79,10 +103,7 @@ const SettingsIcon: React.FC<IconProps> = ({ from }: IconProps) => {
)
}
export const HeaderWithSettings: React.FC<Props> = ({
title = "",
from,
}) => {
export const HeaderWithSettings: React.FC<Props> = ({title = '', from}) => {
return (
<AppContext.Consumer>
{() => (

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

@ -1,4 +1,4 @@
import { useState } from "react"
import {useState} from 'react'
export function useLocalStorage(key: string, initialValue: any) {
// 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 {
from: string

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

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

@ -1,4 +1,4 @@
import React from "react"
import React from 'react'
export const ErrorView: React.FC = () => {
return (
@ -11,7 +11,7 @@ export const ErrorView: React.FC = () => {
/>
<h5>Sorry, something unexpected happened.</h5>
<h5>
Please raise an issue:{" "}
Please raise an issue:{' '}
<a
className="text-danger"
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 { Receipt } from "../types"
import {AppContext} from '../AppContext'
import {Receipt} from '../types'
import { VerifyView } from "./VerifyView"
import {VerifyView} from './VerifyView'
export const HomeView: React.FC = () => {
return (
<AppContext.Consumer>
{({ apiKey, clientInstance, setReceipts, receipts, contracts }) => {
{({apiKey, clientInstance, setReceipts, receipts, contracts}) => {
return !apiKey ? (
<Navigate
to={{
pathname: "/settings"
pathname: '/settings'
}}
/>
) : (
@ -28,8 +28,7 @@ export const HomeView: React.FC = () => {
}}
/>
)
}
}
}}
</AppContext.Consumer>
)
}

@ -1,13 +1,18 @@
import React, { useState } from "react"
import React, {useState} from 'react'
import { Formik, ErrorMessage, Field } from "formik"
import { getEtherScanApi, getNetworkName, getReceiptStatus, getProxyContractReceiptStatus } from "../utils"
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'
import {Formik, ErrorMessage, Field} from 'formik'
import {
getEtherScanApi,
getNetworkName,
getReceiptStatus,
getProxyContractReceiptStatus
} from '../utils'
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 {
receiptGuid: string
@ -23,11 +28,11 @@ export const ReceiptsView: React.FC = () => {
apiKey: string
) => {
try {
const { network, networkId } = await getNetworkName(clientInstance)
if (network === "vm") {
const {network, networkId} = await getNetworkName(clientInstance)
if (network === 'vm') {
setResults({
succeed: false,
message: "Cannot verify in the selected network"
message: 'Cannot verify in the selected network'
})
return
}
@ -51,7 +56,9 @@ export const ReceiptsView: React.FC = () => {
)
setResults({
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) {
setResults({
@ -63,21 +70,21 @@ export const ReceiptsView: React.FC = () => {
return (
<AppContext.Consumer>
{({ apiKey, clientInstance, receipts, setReceipts }) => {
{({apiKey, clientInstance, receipts, setReceipts}) => {
return !apiKey ? (
<Navigate
to={{
pathname: "/settings"
pathname: '/settings'
}}
/>
) : (
<div>
<Formik
initialValues={{ receiptGuid: "" }}
initialValues={{receiptGuid: ''}}
validate={(values) => {
const errors = {} as any
if (!values.receiptGuid) {
errors.receiptGuid = "Required"
errors.receiptGuid = 'Required'
}
return errors
}}
@ -85,15 +92,15 @@ export const ReceiptsView: React.FC = () => {
onGetReceiptStatus(values, clientInstance, apiKey)
}
>
{({ errors, touched, handleSubmit, handleChange }) => (
{({errors, touched, handleSubmit, handleChange}) => (
<form onSubmit={handleSubmit}>
<div className="form-group mb-2">
<label htmlFor="receiptGuid">Receipt GUID</label>
<Field
className={
errors.receiptGuid && touched.receiptGuid
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
? 'form-control form-control-sm is-invalid'
: 'form-control form-control-sm'
}
type="text"
name="receiptGuid"
@ -115,37 +122,63 @@ export const ReceiptsView: React.FC = () => {
handleChange(e)
if (e.target.checked) setIsProxyContractReceipt(true)
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>
<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>
)}
</Formik>
<div
className={results['succeed'] ? "text-success mt-3 text-center" : "text-danger mt-3 text-center"}
dangerouslySetInnerHTML={{ __html: results.message ? results.message : '' }}
className={
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
tooltipText="Clear the list of receipts"
tooltipId='etherscan-clear-receipts'
placement='bottom'
tooltipId="etherscan-clear-receipts"
placement="bottom"
>
<Button className="btn-sm" onClick={() => { setReceipts([]) }} >Clear</Button>
<Button
className="btn-sm"
onClick={() => {
setReceipts([])
}}
>
Clear
</Button>
</CustomTooltip>
</div>
)
}
}
}}
</AppContext.Consumer>
)
}
const ReceiptsTable: React.FC<{ receipts: Receipt[] }> = ({ receipts }) => {
const ReceiptsTable: React.FC<{receipts: Receipt[]}> = ({receipts}) => {
return (
<div className="table-responsive">
<h6>Receipts</h6>
@ -162,20 +195,35 @@ const ReceiptsTable: React.FC<{ receipts: Receipt[] }> = ({ receipts }) => {
receipts.map((item: Receipt, index) => {
return (
<tr key={item.guid}>
<td className={(item.status === 'Pass - Verified' || item.status === 'Successfully Updated')
? 'text-success' : (item.status === 'Pending in queue'
? 'text-warning' : (item.status === 'Already Verified'
? 'text-info': 'text-secondary'))}>
{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
className={
item.status === 'Pass - Verified' ||
item.status === 'Successfully Updated'
? 'text-success'
: item.status === 'Pending in queue'
? 'text-warning'
: item.status === 'Already Verified'
? 'text-info'
: 'text-secondary'
}
>
{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>{item.guid}</td>
</tr>

@ -1,16 +1,14 @@
import React, { useEffect, useRef, useState } from "react"
import React, {useEffect, useRef, useState} from 'react'
import Web3 from 'web3'
import {
PluginClient,
} from "@remixproject/plugin"
import { CustomTooltip } from '@remix-ui/helper'
import { Formik, ErrorMessage, Field } from "formik"
import {PluginClient} from '@remixproject/plugin'
import {CustomTooltip} from '@remix-ui/helper'
import {Formik, ErrorMessage, Field} from 'formik'
import { SubmitButton } from "../components"
import { Receipt } from "../types"
import { verify } from "../utils/verify"
import { etherscanScripts } from "@remix-project/remix-ws-templates"
import {SubmitButton} from '../components'
import {Receipt} from '../types'
import {verify} from '../utils/verify'
import {etherscanScripts} from '@remix-project/remix-ws-templates'
interface Props {
client: PluginClient
@ -29,11 +27,11 @@ export const VerifyView: React.FC<Props> = ({
apiKey,
client,
contracts,
onVerifiedContract,
onVerifiedContract
}) => {
const [results, setResults] = useState("")
const [networkName, setNetworkName] = useState("Loading...")
const [selectedContract, setSelectedContract] = useState("")
const [results, setResults] = useState('')
const [networkName, setNetworkName] = useState('Loading...')
const [selectedContract, setSelectedContract] = useState('')
const [showConstructorArgs, setShowConstructorArgs] = useState(false)
const [isProxyContract, setIsProxyContract] = useState(false)
const [constructorInputs, setConstructorInputs] = useState([])
@ -41,13 +39,19 @@ export const VerifyView: React.FC<Props> = ({
useEffect(() => {
if (client && client.on) {
client.on("blockchain" as any, 'networkStatus', (result) => {
setNetworkName(`${result.network.name} ${result.network.id !== '-' ? `(Chain id: ${result.network.id})` : '(Not supported)'}`)
client.on('blockchain' as any, 'networkStatus', (result) => {
setNetworkName(
`${result.network.name} ${
result.network.id !== '-'
? `(Chain id: ${result.network.id})`
: '(Not supported)'
}`
)
})
}
return () => {
// 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])
@ -56,36 +60,53 @@ export const VerifyView: React.FC<Props> = ({
}, [contracts])
const updateConsFields = (contractName) => {
client.call("compilerArtefacts" as any, "getArtefactsByContractName", contractName).then((result) => {
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)
}
})
client
.call(
'compilerArtefacts' as any,
'getArtefactsByContractName',
contractName
)
.then((result) => {
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 compilationResult = (await client.call(
"solidity",
"getCompilationResult"
'solidity',
'getCompilationResult'
)) as any
if (!compilationResult) {
throw new Error("no compilation result available")
throw new Error('no compilation result available')
}
const constructorValues = []
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 constructorTypes = constructorInputs.map(e => e.type)
let contractArguments = web3.eth.abi.encodeParameters(constructorTypes, constructorValues)
contractArguments = contractArguments.replace("0x", "")
const constructorTypes = constructorInputs.map((e) => e.type)
let contractArguments = web3.eth.abi.encodeParameters(
constructorTypes,
constructorValues
)
contractArguments = contractArguments.replace('0x', '')
verificationResult.current = await verify(
apiKey,
@ -98,7 +119,7 @@ export const VerifyView: React.FC<Props> = ({
values.expectedImplAddress,
client,
onVerifiedContract,
setResults,
setResults
)
setResults(verificationResult.current['message'])
}
@ -107,76 +128,90 @@ export const VerifyView: React.FC<Props> = ({
<div>
<Formik
initialValues={{
contractName: "",
contractAddress: ""
contractName: '',
contractAddress: ''
}}
validate={(values) => {
const errors = {} as any
if (!values.contractName) {
errors.contractName = "Required"
errors.contractName = 'Required'
}
if (!values.contractAddress) {
errors.contractAddress = "Required"
errors.contractAddress = 'Required'
}
if (values.contractAddress.trim() === "" || !values.contractAddress.startsWith('0x')
|| values.contractAddress.length !== 42) {
errors.contractAddress = "Please enter a valid contract address"
if (
values.contractAddress.trim() === '' ||
!values.contractAddress.startsWith('0x') ||
values.contractAddress.length !== 42
) {
errors.contractAddress = 'Please enter a valid contract address'
}
return errors
}}
onSubmit={(values) => onVerifyContract(values)}
>
{({ errors, touched, handleSubmit, handleChange, isSubmitting }) => {
return (<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="network">Selected Network</label>
<CustomTooltip
tooltipText="Network is fetched from 'Deploy and Run Transactions' plugin's ENVIRONMENT field"
tooltipId='etherscan-impl-address2'
placement='bottom'
>
{({errors, touched, handleSubmit, handleChange, isSubmitting}) => {
return (
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="network">Selected Network</label>
<CustomTooltip
tooltipText="Network is fetched from 'Deploy and Run Transactions' plugin's ENVIRONMENT field"
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
className="form-control"
type="text"
name="network"
value={networkName}
disabled={true}
/>
</CustomTooltip>
</div>
<div className="form-group">
<label htmlFor="contractName">Contract Name</label>
<Field
as="select"
as="select"
className={
errors.contractName &&
touched.contractName &&
contracts.length
? 'form-control is-invalid'
: 'form-control'
}
name="contractName"
onChange={async (e) => {
handleChange(e)
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={
errors.contractName && touched.contractName && contracts.length
? "form-control is-invalid"
: "form-control"
showConstructorArgs
? 'form-group d-block'
: 'form-group d-none'
}
name="contractName"
onChange={async (e) => {
handleChange(e)
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={ showConstructorArgs ? 'form-group d-block': 'form-group d-none' } >
<label>Constructor Arguments</label>
<label>Constructor Arguments</label>
{constructorInputs.map((item, index) => {
return (
<div className="d-flex">
@ -191,7 +226,7 @@ export const VerifyView: React.FC<Props> = ({
<CustomTooltip
tooltipText={`value of ${item.name}`}
tooltipId={`etherscan-constructor-value${index}`}
placement='top'
placement="top"
>
<Field
className="form-control m-1"
@ -202,94 +237,120 @@ export const VerifyView: React.FC<Props> = ({
/>
</CustomTooltip>
</div>
)}
)}
</div>
<div className="form-group">
<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">
)
})}
</div>
<div className="form-group">
<label htmlFor="contractAddress">Contract Address</label>
<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)
}}
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"
/>
<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 className={ isProxyContract ? 'form-group d-block': 'form-group d-none' }>
<label htmlFor="expectedImplAddress">Expected Implementation Address</label>
<div
className={
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
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'
tooltipText="Generate the required TS scripts to verify a contract on Etherscan"
tooltipId="etherscan-generate-scripts"
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
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)
}}
<button
type="button"
className="mr-2 mb-2 py-1 px-2 btn btn-secondary btn-block"
onClick={async () => {
etherscanScripts(client)
}}
>
Generate Verification Scripts
</button>
</CustomTooltip>
</form>)
</form>
)
}}
</Formik>
<div
data-id="verify-result"
className={verificationResult.current['succeed'] ? "text-success mt-4 text-center" : "text-danger mt-4 text-center"}
style={{fontSize: "0.8em"}}
dangerouslySetInnerHTML={{ __html: results }}
className={
verificationResult.current['succeed']
? '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" }}>
<Link to="/receipts">View Receipts</Link>

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

@ -7,7 +7,7 @@ module.exports = {
globals_path: '',
test_settings: {
default: {
'default': {
selenium_port: 4444,
selenium_host: 'localhost',
globals: {
@ -20,31 +20,35 @@ module.exports = {
on_failure: 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: {
browserName: 'chrome',
javascriptEnabled: true,
acceptSslCerts: true,
'browserName': 'chrome',
'javascriptEnabled': true,
'acceptSslCerts': true,
'goog:chromeOptions': {
args: ['window-size=2560,1440',
'start-fullscreen',
'--no-sandbox',
'--headless',
'--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",
args: [
'window-size=2560,1440',
'start-fullscreen',
'--no-sandbox',
'--headless',
'--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: {
browserName: 'chrome',
javascriptEnabled: true,
acceptSslCerts: true,
'browserName': 'chrome',
'javascriptEnabled': true,
'acceptSslCerts': true,
'goog:chromeOptions': {
args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox']
}
@ -53,40 +57,39 @@ module.exports = {
'chrome-runAndDeploy': {
desiredCapabilities: {
browserName: 'chrome',
javascriptEnabled: true,
acceptSslCerts: true,
'browserName': 'chrome',
'javascriptEnabled': true,
'acceptSslCerts': true,
'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: {
browserName: 'firefox',
javascriptEnabled: true,
acceptSslCerts: true,
'browserName': 'firefox',
'javascriptEnabled': true,
'acceptSslCerts': true,
'moz:firefoxOptions': {
args: [
'-width=2560',
'-height=1440'
]
args: ['-width=2560', '-height=1440']
}
}
},
firefox: {
'firefox': {
desiredCapabilities: {
browserName: 'firefox',
javascriptEnabled: true,
acceptSslCerts: true,
'browserName': 'firefox',
'javascriptEnabled': true,
'acceptSslCerts': true,
'moz:firefoxOptions': {
args: [
'-headless',
'-width=2560',
'-height=1440'
]
args: ['-headless', '-width=2560', '-height=1440']
}
}
}

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

@ -1,9 +1,13 @@
import React from 'react'
interface loggerProps {
log: any,
log: any
id: string
}
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'
chrome.browserAction.onClicked.addListener(function (tab) {
chrome.storage.sync.set({ 'chrome-app-sync': true })
chrome.storage.sync.set({'chrome-app-sync': true})
chrome.tabs.create({ 'url': chrome.extension.getURL('index.html') }, function (tab) {
// tab opened
})
chrome.tabs.create(
{url: chrome.extension.getURL('index.html')},
function (tab) {
// tab opened
}
)
})

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

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

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

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

@ -1,11 +1,14 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import packageJson from '../../../../../package.json'
import { Plugin } from '@remixproject/engine'
import { EventEmitter } from 'events'
import { IconRecord, RemixUiVerticalIconsPanel } from '@remix-ui/vertical-icons-panel'
import { Profile } from '@remixproject/plugin-utils'
import { PluginViewWrapper } from '@remix-ui/helper'
import {Plugin} from '@remixproject/engine'
import {EventEmitter} from 'events'
import {
IconRecord,
RemixUiVerticalIconsPanel
} from '@remix-ui/vertical-icons-panel'
import {Profile} from '@remixproject/plugin-utils'
import {PluginViewWrapper} from '@remix-ui/helper'
const profile = {
name: 'menuicons',
@ -21,44 +24,63 @@ export class VerticalIcons extends Plugin {
htmlElement: HTMLDivElement
icons: Record<string, IconRecord> = {}
dispatch: React.Dispatch<any> = () => {}
constructor () {
constructor() {
super(profile)
this.events = new EventEmitter()
this.htmlElement = document.createElement('div')
this.htmlElement.setAttribute('id', 'icon-panel')
}
renderComponent () {
const fixedOrder = ['filePanel', 'search', 'solidity','udapp', 'debugger', 'solidityStaticAnalysis', 'solidityUnitTesting', 'pluginManager']
renderComponent() {
const fixedOrder = [
'filePanel',
'search',
'solidity',
'udapp',
'debugger',
'solidityStaticAnalysis',
'solidityUnitTesting',
'pluginManager'
]
const divived = Object.values(this.icons).map((value) => { return {
...value,
isRequired: fixedOrder.indexOf(value.profile.name) > -1
}}).sort((a,b) => {
return a.timestamp - b.timestamp
})
const divived = Object.values(this.icons)
.map((value) => {
return {
...value,
isRequired: fixedOrder.indexOf(value.profile.name) > -1
}
})
.sort((a, b) => {
return a.timestamp - b.timestamp
})
const required = divived.filter((value) => value.isRequired).sort((a,b) => {
return fixedOrder.indexOf(a.profile.name) - fixedOrder.indexOf(b.profile.name)
})
const required = divived
.filter((value) => value.isRequired)
.sort((a, b) => {
return (
fixedOrder.indexOf(a.profile.name) -
fixedOrder.indexOf(b.profile.name)
)
})
const sorted: IconRecord[] = [
...required,
...divived.filter((value) => { return !value.isRequired })
...divived.filter((value) => {
return !value.isRequired
})
]
this.dispatch({
verticalIconsPlugin: this,
icons: sorted
})
}
setDispatch (dispatch: React.Dispatch<any>) {
setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
}
onActivation () {
onActivation() {
this.renderComponent()
this.on('sidePanel', 'focusChanged', (name: string) => {
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.kind) profile.kind = 'none'
this.icons[profile.name] = {
profile: profile,
active: false,
canbeDeactivated: await this.call('manager', 'canDeactivate', this.profile, profile),
canbeDeactivated: await this.call(
'manager',
'canDeactivate',
this.profile,
profile
),
timestamp: Date.now()
}
this.renderComponent()
}
unlinkContent (profile: Profile) {
unlinkContent(profile: Profile) {
delete this.icons[profile.name]
this.renderComponent()
}
@ -95,7 +122,7 @@ export class VerticalIcons extends Plugin {
* Set an icon as active
* @param {string} name Name of profile of the module to activate
*/
select (name: string) {
select(name: string) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('showContent', name)
this.events.emit('showContent', name)
@ -105,22 +132,26 @@ export class VerticalIcons extends Plugin {
* Toggles the side panel for plugin
* @param {string} name Name of profile of the module to activate
*/
toggle (name: string) {
toggle(name: string) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('toggleContent', name)
this.events.emit('toggleContent', name)
}
updateComponent(state: any){
return <RemixUiVerticalIconsPanel
verticalIconsPlugin={state.verticalIconsPlugin}
icons={state.icons}
/>
updateComponent(state: any) {
return (
<RemixUiVerticalIconsPanel
verticalIconsPlugin={state.verticalIconsPlugin}
icons={state.icons}
/>
)
}
render() {
render() {
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 { Plugin } from '@remixproject/engine'
import { customAction } from '@remixproject/plugin-api'
import { concatSourceFiles, getDependencyGraph, normalizeContractPath } from '@remix-ui/solidity-compiler'
import {Plugin} from '@remixproject/engine'
import {customAction} from '@remixproject/plugin-api'
import {
concatSourceFiles,
getDependencyGraph,
normalizeContractPath
} from '@remix-ui/solidity-compiler'
const _paq = window._paq = window._paq || []
const _paq = (window._paq = window._paq || [])
const profile = {
name: 'contractflattener',
@ -11,25 +15,28 @@ const profile = {
description: 'Flatten solidity contracts',
methods: ['flattenAContract', 'flattenContract'],
events: [],
maintainedBy: 'Remix',
maintainedBy: 'Remix'
}
export class ContractFlattener extends Plugin {
triggerFlattenContract: boolean = false
constructor() {
super(profile)
}
onActivation(): void {
this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data, input, version) => {
if(data.sources && Object.keys(data.sources).length > 1) {
if(this.triggerFlattenContract) {
this.triggerFlattenContract = false
await this.flattenContract(source, file, data)
this.on(
'solidity',
'compilationFinished',
async (file, source, languageVersion, data, input, version) => {
if (data.sources && Object.keys(data.sources).length > 1) {
if (this.triggerFlattenContract) {
this.triggerFlattenContract = false
await this.flattenContract(source, file, data)
}
}
}
})
)
_paq.push(['trackEvent', 'plugin', 'activated', 'contractFlattener'])
}
@ -48,8 +55,11 @@ export class ContractFlattener extends Plugin {
* Takes the flattened result, writes it to a file and returns the result.
* @returns {Promise<string>}
*/
async flattenContract (source: { sources: any, target: string },
filePath: string, data: { contracts: any, sources: any }): Promise<string> {
async flattenContract(
source: {sources: any; target: string},
filePath: string,
data: {contracts: any; sources: any}
): Promise<string> {
const appendage = '_flattened.sol'
const normalized = normalizeContractPath(filePath)
const path = `${normalized[normalized.length - 2]}${appendage}`
@ -58,17 +68,17 @@ export class ContractFlattener extends Plugin {
let sorted
let result
let sources
try{
try {
dependencyGraph = getDependencyGraph(ast, filePath)
sorted = dependencyGraph.isEmpty()
? [filePath]
: dependencyGraph.sort().reverse()
sources = source.sources
result = concatSourceFiles(sorted, sources)
}catch(err){
} catch (err) {
console.warn(err)
}
await this.call('fileManager', 'writeFile', path , result)
await this.call('fileManager', 'writeFile', path, result)
_paq.push(['trackEvent', 'plugin', 'contractFlattener', 'flattenAContract'])
// clean up memory references & return result
sorted = null
@ -76,4 +86,4 @@ export class ContractFlattener extends Plugin {
dependencyGraph = null
return result
}
}
}

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

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

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

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

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

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

@ -1,30 +1,30 @@
import { Plugin } from '@remixproject/engine'
import { AppModal, AlertModal, ModalTypes } from '@remix-ui/app'
import { Blockchain } from '../../blockchain/blockchain'
import { ethers } from 'ethers'
import {Plugin} from '@remixproject/engine'
import {AppModal, AlertModal, ModalTypes} from '@remix-ui/app'
import {Blockchain} from '../../blockchain/blockchain'
import {ethers} from 'ethers'
export type JsonDataRequest = {
id: number,
id: number
jsonrpc: string // version
method: string,
params: Array<any>,
method: string
params: Array<any>
}
export type JsonDataResult = {
id: number,
id: number
jsonrpc: string // version
result?: any,
error?: any,
result?: any
error?: any
}
export type RejectRequest = (error: Error) => void
export type SuccessRequest = (data: JsonDataResult) => void
export interface IProvider {
options: { [id: string] : any }
init(): Promise<{ [id: string] : any }>
options: {[id: string]: any}
init(): Promise<{[id: string]: any}>
body(): JSX.Element
sendAsync (data: JsonDataRequest): Promise<JsonDataResult>
sendAsync(data: JsonDataRequest): Promise<JsonDataResult>
}
export abstract class AbstractProvider extends Plugin implements IProvider {
@ -33,9 +33,9 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
defaultUrl: string
connected: boolean
nodeUrl: string
options: { [id: string] : any } = {}
options: {[id: string]: any} = {}
constructor (profile, blockchain, defaultUrl) {
constructor(profile, blockchain, defaultUrl) {
super(profile)
this.defaultUrl = defaultUrl
this.provider = null
@ -46,11 +46,11 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
abstract body(): JSX.Element
onDeactivation () {
onDeactivation() {
this.provider = null
}
async init () {
async init() {
this.nodeUrl = await ((): Promise<string> => {
return new Promise((resolve, reject) => {
const modalContent: AppModal = {
@ -61,16 +61,17 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
okLabel: 'OK',
cancelLabel: 'Cancel',
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://')) {
return {
valid: true,
return {
valid: true,
message: ''
}
} else {
return {
valid: false,
message: 'the provided value should contain the protocol ( e.g starts with http:// or https:// )'
valid: false,
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
return new Promise(async (resolve, reject) => {
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
this.provider = null
this.connected = false
@ -110,28 +111,37 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
const modalContent: AlertModal = {
id: this.profile.name,
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)
}
await this.call('udapp', 'setEnvironmentMode', { context: 'vm-merge'})
await this.call('udapp', 'setEnvironmentMode', {context: 'vm-merge'})
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) {
try {
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) {
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)
}
reject(error)
}
} else {
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 { AppModal, ModalTypes } from '@remix-ui/app'
import { BasicVMProvider } from './vm-provider'
import { Hardfork } from '@ethereumjs/common'
import {AppModal, ModalTypes} from '@remix-ui/app'
import {BasicVMProvider} from './vm-provider'
import {Hardfork} from '@ethereumjs/common'
export class CustomForkVMProvider extends BasicVMProvider {
nodeUrl: string
blockNumber: number | 'latest'
inputs: any
constructor (blockchain) {
super({
name: 'vm-custom-fork',
displayName: 'Custom fork - Remix VM',
kind: 'provider',
description: 'Custom fork - Remix VM',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
constructor(blockchain) {
super(
{
name: 'vm-custom-fork',
displayName: 'Custom fork - Remix VM',
kind: 'provider',
description: 'Custom fork - Remix VM',
methods: ['sendAsync', 'init'],
version: packageJson.version
},
blockchain
)
this.blockchain = blockchain
this.fork = ''
this.nodeUrl = ''
@ -25,28 +28,54 @@ export class CustomForkVMProvider extends BasicVMProvider {
this.inputs = {}
}
async init () {
async init() {
const body = () => {
return <div>
<span>Please provide information about the custom fork. If the node URL is not provided, the VM will start with an empty state.</span>
return (
<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" />
<span>
Please provide information about the custom fork. If the node URL is
not provided, the VM will start with an empty state.
</span>
<div>
<label className="mt-3 mb-1">Node URL</label>
<input
data-id="CustomForkNodeUrl"
name="nodeUrl"
type="text"
className="border form-control border-right-0"
/>
</div>
<div>
<label className="mt-3 mb-1">Block number (or "latest")</label>
<input
data-id="CustomForkBlockNumber"
name="blockNumber"
type="text"
defaultValue="latest"
placeholder='block number or "latest"'
className="border form-control border-right-0"
/>
</div>
<div>
<label className="mt-3 mb-1">EVM</label>
<select
data-id="CustomForkEvmType"
name="evmType"
defaultValue="merge"
className="border form-control border-right-0"
>
{Object.keys(Hardfork).map((value, index) => {
return (
<option value={Hardfork[value]} key={index}>
{value}
</option>
)
})}
</select>
</div>
</div>
<div>
<label className="mt-3 mb-1">Block number (or "latest")</label>
<input data-id="CustomForkBlockNumber" name="blockNumber" type="text" defaultValue="latest" placeholder='block number or "latest"' className="border form-control border-right-0" />
</div>
<div>
<label className="mt-3 mb-1">EVM</label>
<select data-id="CustomForkEvmType" name="evmType" defaultValue="merge" className="border form-control border-right-0">
{Object.keys(Hardfork).map((value, index) => {
return <option value={Hardfork[value]} key={index}>{value}</option>
})}
</select>
</div>
</div>
}
)
}
const result = await ((): Promise<any> => {
return new Promise((resolve, reject) => {
const modalContent: AppModal = {
@ -54,7 +83,7 @@ export class CustomForkVMProvider extends BasicVMProvider {
title: this.profile.displayName,
message: body(),
validationFn: (data: any) => {
if(data.nodeUrl !== '' && !data.nodeUrl.startsWith("http")) {
if (data.nodeUrl !== '' && !data.nodeUrl.startsWith('http')) {
return {
valid: false,
message: 'node URL should be a valid URL'
@ -96,11 +125,11 @@ export class CustomForkVMProvider extends BasicVMProvider {
this.nodeUrl = undefined
this.blockNumber = undefined
}
return {
'fork': this.fork,
'nodeUrl': this.nodeUrl,
'blockNumber': this.blockNumber
fork: this.fork,
nodeUrl: this.nodeUrl,
blockNumber: this.blockNumber
}
}
}

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

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

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

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

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

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

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

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

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

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

@ -1,15 +1,19 @@
/* global ethereum */
import React from 'react' // eslint-disable-line
import { Plugin } from '@remixproject/engine'
import { JsonDataRequest, RejectRequest, SuccessRequest } from '../providers/abstract-provider'
import { IProvider } from './abstract-provider'
import {Plugin} from '@remixproject/engine'
import {
JsonDataRequest,
RejectRequest,
SuccessRequest
} from '../providers/abstract-provider'
import {IProvider} from './abstract-provider'
export abstract class InjectedProvider extends Plugin implements IProvider {
options: { [id: string] : any } = {}
options: {[id: string]: any} = {}
listenerAccountsChanged: (accounts: Array<string>) => void
listenerChainChanged: (chainId: number) => void
constructor (profile) {
constructor(profile) {
super(profile)
this.listenerAccountsChanged = (accounts: Array<string>) => {
this.emit('accountsChanged', accounts)
@ -25,39 +29,43 @@ export abstract class InjectedProvider extends Plugin implements IProvider {
onActivation(): void {
try {
const web3Provider = this.getInjectedProvider()
web3Provider.on('accountsChanged', this.listenerAccountsChanged);
web3Provider.on('chainChanged', this.listenerChainChanged);
web3Provider.on('accountsChanged', this.listenerAccountsChanged)
web3Provider.on('chainChanged', this.listenerChainChanged)
} catch (error) {
console.log('unable to listen on context changed')
}
}
}
onDeactivation(): void {
try {
const web3Provider = this.getInjectedProvider()
web3Provider.removeListener('accountsChanged', this.listenerAccountsChanged)
web3Provider.removeListener(
'accountsChanged',
this.listenerAccountsChanged
)
web3Provider.removeListener('chainChanged', this.listenerChainChanged)
} catch (error) {
console.log('unable to remove listener on context changed')
}
}
}
askPermission (throwIfNoInjectedProvider) {
askPermission(throwIfNoInjectedProvider) {
const web3Provider = this.getInjectedProvider()
if (typeof web3Provider !== "undefined" && typeof web3Provider.request === "function") {
web3Provider.request({ method: "eth_requestAccounts" })
if (
typeof web3Provider !== 'undefined' &&
typeof web3Provider.request === 'function'
) {
web3Provider.request({method: 'eth_requestAccounts'})
} else if (throwIfNoInjectedProvider) {
throw new Error(this.notFound())
}
}
body (): JSX.Element {
return (
<div></div>
)
body(): JSX.Element {
return <div></div>
}
async init () {
async init() {
const injectedProvider = this.getInjectedProvider()
if (injectedProvider === undefined) {
this.call('notification', 'toast', this.notFound())
@ -68,38 +76,60 @@ export abstract class InjectedProvider extends Plugin implements IProvider {
return {}
}
sendAsync (data: JsonDataRequest): Promise<any> {
sendAsync(data: JsonDataRequest): Promise<any> {
return new Promise((resolve, reject) => {
this.sendAsyncInternal(data, resolve, reject)
})
}
private async sendAsyncInternal (data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise<void> {
private async sendAsyncInternal(
data: JsonDataRequest,
resolve: SuccessRequest,
reject: RejectRequest
): Promise<void> {
// Check the case where current environment is VM on UI and it still sends RPC requests
// This will be displayed on UI tooltip as 'cannot get account list: Environment Updated !!'
const web3Provider = this.getInjectedProvider()
if (!web3Provider) {
this.call('notification', 'toast', 'No injected provider (e.g Metamask) has been found.')
return resolve({ jsonrpc: '2.0', error: 'no injected provider found', id: data.id })
this.call(
'notification',
'toast',
'No injected provider (e.g Metamask) has been found.'
)
return resolve({
jsonrpc: '2.0',
error: 'no injected provider found',
id: data.id
})
}
try {
let resultData
if (web3Provider.send) resultData = await web3Provider.send(data.method, data.params)
else if (web3Provider.request) resultData = await web3Provider.request({ method: data.method, params: data.params})
if (web3Provider.send)
resultData = await web3Provider.send(data.method, data.params)
else if (web3Provider.request)
resultData = await web3Provider.request({
method: data.method,
params: data.params
})
else {
resolve({ jsonrpc: '2.0', error: 'provider not valid', id: data.id })
resolve({jsonrpc: '2.0', error: 'provider not valid', id: data.id})
return
}
if (resultData) {
if (resultData.jsonrpc && resultData.jsonrpc === '2.0') {
resultData = resultData.result
}
resolve({ jsonrpc: '2.0', result: resultData, id: data.id })
resolve({jsonrpc: '2.0', result: resultData, id: data.id})
} 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) {
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 { BasicVMProvider } from './vm-provider'
import {BasicVMProvider} from './vm-provider'
export class MainnetForkVMProvider extends BasicVMProvider {
nodeUrl: string
blockNumber: number | 'latest'
constructor (blockchain) {
super({
name: 'vm-mainnet-fork',
displayName: 'Mainet fork -Remix VM (London)',
kind: 'provider',
description: 'Remix VM (London)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
constructor(blockchain) {
super(
{
name: 'vm-mainnet-fork',
displayName: 'Mainet fork -Remix VM (London)',
kind: 'provider',
description: 'Remix VM (London)',
methods: ['sendAsync', 'init'],
version: packageJson.version
},
blockchain
)
this.blockchain = blockchain
this.fork = 'shanghai'
this.nodeUrl = 'https://mainnet.infura.io/v3/08b2a484451e4635a28b3d8234f24332'
this.nodeUrl =
'https://mainnet.infura.io/v3/08b2a484451e4635a28b3d8234f24332'
this.blockNumber = 'latest'
}
async init () {
async init() {
return {
'fork': this.fork,
'nodeUrl': this.nodeUrl,
'blockNumber': this.blockNumber
fork: this.fork,
nodeUrl: this.nodeUrl,
blockNumber: this.blockNumber
}
}
}

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

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

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

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

File diff suppressed because it is too large Load Diff

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

@ -1,11 +1,11 @@
const { composePlugins, withNx } = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react')
const {composePlugins, withNx} = require('@nrwl/webpack')
const {withReact} = require('@nrwl/react')
const webpack = require('webpack')
const CopyPlugin = require("copy-webpack-plugin")
const CopyPlugin = require('copy-webpack-plugin')
const version = require('../../package.json').version
const fs = require('fs')
const TerserPlugin = require("terser-webpack-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const axios = require('axios')
const versionData = {
@ -16,12 +16,18 @@ const versionData = {
const loadLocalSolJson = async () => {
// 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
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()
@ -32,9 +38,8 @@ const implicitDependencies = JSON.parse(project).implicitDependencies
const copyPatterns = implicitDependencies.map((dep) => {
try {
fs.statSync(__dirname + `/../../dist/apps/${dep}`).isDirectory()
return { from: `../../dist/apps/${dep}`, to: `plugins/${dep}` }
}
catch (e) {
return {from: `../../dist/apps/${dep}`, to: `plugins/${dep}`}
} catch (e) {
console.log('error', e)
return false
}
@ -50,30 +55,29 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
// add fallback for node modules
config.resolve.fallback = {
...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"),
"stream": require.resolve("stream-browserify"),
"path": require.resolve("path-browserify"),
"http": require.resolve("stream-http"),
"https": require.resolve("https-browserify"),
"constants": require.resolve("constants-browserify"),
"os": false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"),
"fs": false,
"module": false,
"tls": false,
"net": false,
"readline": false,
"child_process": false,
"buffer": require.resolve("buffer/"),
"vm": require.resolve('vm-browserify'),
crypto: require.resolve('crypto-browserify'),
stream: require.resolve('stream-browserify'),
path: require.resolve('path-browserify'),
http: require.resolve('stream-http'),
https: require.resolve('https-browserify'),
constants: require.resolve('constants-browserify'),
os: false, //require.resolve("os-browserify/browser"),
timers: false, // require.resolve("timers-browserify"),
zlib: require.resolve('browserify-zlib'),
fs: false,
module: false,
tls: false,
net: false,
readline: false,
child_process: false,
buffer: require.resolve('buffer/'),
vm: require.resolve('vm-browserify')
}
// add externals
config.externals = {
...config.externals,
solc: 'solc',
solc: 'solc'
}
// add public path
@ -83,12 +87,14 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
config.output.filename = `[name].${versionData.version}.${versionData.timestamp}.js`
config.output.chunkFilename = `[name].${versionData.version}.${versionData.timestamp}.js`
// add copy & provide plugin
config.plugins.push(
new CopyPlugin({
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
].filter(Boolean)
}),
@ -96,15 +102,15 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'],
process: 'process/browser',
process: 'process/browser'
})
)
// souce-map loader
config.module.rules.push({
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre"
use: ['source-map-loader'],
enforce: 'pre'
})
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,
mangle: false,
format: {
comments: false,
},
comments: false
}
},
extractComments: false,
extractComments: false
}),
new CssMinimizerPlugin(),
];
new CssMinimizerPlugin()
]
config.watchOptions = {
ignored: /node_modules/
}
return config;
});
return config
})
class CopyFileAfterBuild {
apply(compiler) {
apply(compiler) {
const onEnd = async () => {
try {
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,
// but the raw-loader try to access the resources from the root folder.
const files = fs.readdirSync('./dist/apps/etherscan')
files.forEach(file => {
files.forEach((file) => {
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) {
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 { SolHint } from "./SolhintPluginClient";
import React, {useEffect, useState} from 'react'
import {SolHint} from './SolhintPluginClient'
const client = new SolHint();
const client = new SolHint()
export default function App() {
return <></>;
return <></>
}

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

@ -1,10 +1,10 @@
const { composePlugins, withNx } = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react')
const {composePlugins, withNx} = require('@nrwl/webpack')
const {withReact} = require('@nrwl/react')
const webpack = require('webpack')
const version = require('../../package.json').version
const fs = require('fs')
const TerserPlugin = require("terser-webpack-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const versionData = {
version: version,
@ -12,7 +12,10 @@ const versionData = {
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.
module.exports = composePlugins(withNx(), withReact(), (config) => {
@ -22,56 +25,52 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
// add fallback for node modules
config.resolve.fallback = {
...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"),
"stream": require.resolve("stream-browserify"),
"path": require.resolve("path-browserify"),
"http": require.resolve("stream-http"),
"https": require.resolve("https-browserify"),
"constants": require.resolve("constants-browserify"),
"os": false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"),
"fs": false,
"module": false,
"tls": false,
"net": false,
"readline": false,
"child_process": false,
"buffer": require.resolve("buffer/"),
"vm": require.resolve('vm-browserify'),
crypto: require.resolve('crypto-browserify'),
stream: require.resolve('stream-browserify'),
path: require.resolve('path-browserify'),
http: require.resolve('stream-http'),
https: require.resolve('https-browserify'),
constants: require.resolve('constants-browserify'),
os: false, //require.resolve("os-browserify/browser"),
timers: false, // require.resolve("timers-browserify"),
zlib: require.resolve('browserify-zlib'),
fs: false,
module: false,
tls: false,
net: false,
readline: false,
child_process: false,
buffer: require.resolve('buffer/'),
vm: require.resolve('vm-browserify')
}
// add externals
config.externals = {
...config.externals,
solc: 'solc',
solc: 'solc'
}
// add public path
config.output.publicPath = '/'
// add copy & provide plugin
config.plugins.push(
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'],
process: 'process/browser',
process: 'process/browser'
})
)
// souce-map loader
config.module.rules.push({
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre"
use: ['source-map-loader'],
enforce: 'pre'
})
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer
config.optimization.minimizer = [
new TerserPlugin({
@ -81,17 +80,17 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
compress: false,
mangle: false,
format: {
comments: false,
},
comments: false
}
},
extractComments: false,
extractComments: false
}),
new CssMinimizerPlugin(),
];
new CssMinimizerPlugin()
]
config.watchOptions = {
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 { CompilationResult } from '@remixproject/plugin-api'
import {VyperCompilationOutput, remixClient} from './utils'
import {CompilationResult} from '@remixproject/plugin-api'
// Components
import CompilerButton from './components/CompilerButton'
@ -38,7 +38,7 @@ const App: React.FC = () => {
async function start() {
try {
await remixClient.loaded()
remixClient.onFileChange(name => setContract(name))
remixClient.onFileChange((name) => setContract(name))
remixClient.onNoFileSelected(() => setContract(''))
} catch (err) {
console.log(err)
@ -53,11 +53,11 @@ const App: React.FC = () => {
/** Update the environment state value */
function setEnvironment(environment: 'local' | 'remote') {
setState({ ...state, environment })
setState({...state, environment})
}
function setLocalUrl(url: string) {
setState({ ...state, localUrl: url })
setState({...state, localUrl: url})
}
function compilerUrl() {
@ -78,12 +78,16 @@ const App: React.FC = () => {
href="https://github.com/ethereum/remix-project/tree/master/apps/vyper"
target="_blank"
>
<i className="fab fa-github"></i>
<i className="fab fa-github"></i>
</a>
</header>
<section>
<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
</Button>
</div>
@ -93,10 +97,20 @@ const App: React.FC = () => {
type="radio"
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
</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
</ToggleButton>
</ToggleButtonGroup>
@ -110,9 +124,7 @@ const App: React.FC = () => {
<CompilerButton
compilerUrl={compilerUrl()}
contract={contract}
setOutput={(name, update) =>
setOutput({ ...output, [name]: update })
}
setOutput={(name, update) => setOutput({...output, [name]: update})}
/>
</div>
<article id="result" className="px-2">

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

@ -3,12 +3,11 @@ import Form from 'react-bootstrap/Form'
interface Props {
url: string
setUrl: (url: string) => void,
setUrl: (url: string) => void
environment: 'remote' | 'local'
}
function LocalUrlInput({ url, setUrl, environment }: Props) {
function LocalUrlInput({url, setUrl, environment}: Props) {
if (environment === 'remote') {
return <></>
}
@ -20,17 +19,20 @@ function LocalUrlInput({ url, setUrl, environment }: Props) {
return (
<Form id="local-url">
<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.Control onBlur={updateUrl}
<Form.Control
onBlur={updateUrl}
defaultValue={url}
type="email"
placeholder="eg http://localhost:8000/compile" />
<Form.Text className="text-muted">
</Form.Text>
placeholder="eg http://localhost:8000/compile"
/>
<Form.Text className="text-muted"></Form.Text>
</Form.Group>
</Form>
)
}
export default LocalUrlInput;
export default LocalUrlInput

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

@ -4,15 +4,17 @@ interface Props {
environment: 'remote' | 'local'
}
function WarnRemoteLabel({ environment }: Props) {
function WarnRemoteLabel({environment}: Props) {
if (environment === 'local') {
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 {
name: string;
content: string;
name: string
content: string
}
export interface VyperCompilationResult {
status: 'success',
bytecode: string,
bytecode_runtime: string,
abi: ABIDescription[],
ir: string,
status: 'success'
bytecode: string
bytecode_runtime: string
abi: ABIDescription[]
ir: string
method_identifiers: {
[method: string]: string
}
@ -23,19 +23,26 @@ export interface VyperCompilationError {
message: string
}
export type VyperCompilationOutput = VyperCompilationResult | VyperCompilationError
export type VyperCompilationOutput =
| VyperCompilationResult
| VyperCompilationError
/** 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'
}
/**
* 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
*/
export async function compile(url: string, contract: Contract): Promise<VyperCompilationOutput> {
export async function compile(
url: string,
contract: Contract
): Promise<VyperCompilationOutput> {
if (!contract.name) {
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, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: contract.content })
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({code: contract.content})
})
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 compilationResult Result returned by the compiler
*/
export function toStandardOutput(fileName: string, compilationResult: VyperCompilationResult): CompilationResult {
const contractName = fileName.split('/').slice(-1)[0].split('.')[0];
const methodIdentifiers = JSON.parse(JSON.stringify(compilationResult['method_identifiers']).replace(/0x/g,''));
export function toStandardOutput(
fileName: string,
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 {
sources: {
[fileName]: {
@ -84,23 +96,22 @@ export function toStandardOutput(fileName: string, compilationResult: VyperCompi
evm: {
bytecode: {
linkReferences: {},
object: compilationResult['bytecode'].replace('0x',''),
opcodes: ""
object: compilationResult['bytecode'].replace('0x', ''),
opcodes: ''
},
deployedBytecode: {
linkReferences: {},
object: compilationResult['bytecode_runtime'].replace('0x',''),
opcodes: ""
object: compilationResult['bytecode_runtime'].replace('0x', ''),
opcodes: ''
},
methodIdentifiers: methodIdentifiers
}
}
} as any
}
};
}
}
/*
export function createCompilationResultMessage(name: string, result: any) {
if(result.status == 'success') {
@ -136,4 +147,4 @@ export function createCompilationResultMessage(name: string, result: any) {
ir: ""
}
}
*/
*/

@ -1,12 +1,16 @@
import { HighlightPosition, CompilationResult, RemixApi } from '@remixproject/plugin-api';
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';
import {
HighlightPosition,
CompilationResult,
RemixApi
} from '@remixproject/plugin-api'
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 {
private client = createClient<Api, Readonly<RemixApi>>(this);
private client = createClient<Api, Readonly<RemixApi>>(this)
loaded() {
return this.client.onload()
@ -14,9 +18,13 @@ export class RemixClient extends PluginClient {
/** Emit an event when file changed */
async onFileChange(cb: (contract: string) => any) {
this.client.on('fileManager', 'currentFileChanged', async (name: string) => {
cb(name)
})
this.client.on(
'fileManager',
'currentFileChanged',
async (name: string) => {
cb(name)
}
)
}
/** Emit an event when file changed */
@ -29,8 +37,17 @@ export class RemixClient extends PluginClient {
/** Load Ballot contract example into the file manager */
async loadContract({name, address}: ExampleContract) {
try {
const content = await this.client.call('contentImport', 'resolve', address)
await this.client.call('fileManager', 'setFile', content.cleanUrl, content.content)
const content = await this.client.call(
'contentImport',
'resolve',
address
)
await this.client.call(
'fileManager',
'setFile',
content.cleanUrl,
content.content
)
await this.client.call('fileManager', 'switchFile', content.cleanUrl)
} catch (err) {
console.log(err)
@ -43,9 +60,18 @@ export class RemixClient extends PluginClient {
this.call('notification', 'toast', 'cloning Vyper repository...')
await this.call('manager', 'activatePlugin', 'dGitProvider')
// @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
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) {
// @ts-ignore
this.call('notification', 'toast', e.message)
@ -54,11 +80,15 @@ export class RemixClient extends PluginClient {
/** Update the status of the plugin in remix */
changeStatus(status: Status) {
this.client.emit('statusChanged', status);
this.client.emit('statusChanged', status)
}
/** 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)
/*
column: -1
@ -94,15 +124,15 @@ export class RemixClient extends PluginClient {
const content = await this.client.call('fileManager', 'getFile', name)
return {
name,
content,
content
}
}
/** Emit an event to Remix with compilation result */
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 RemixClientContext = React.createContext(new RemixClient())
// export const RemixClientContext = React.createContext(new RemixClient())

@ -1,7 +1,11 @@
import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom';
import {StrictMode} from 'react'
import * as ReactDOM from 'react-dom'
import App from './app/app'
import App from './app/app';
ReactDOM.render(<StrictMode><App /></StrictMode>, document.getElementById('root'));
ReactDOM.render(
<StrictMode>
<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 TerserPlugin = require("terser-webpack-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => {
@ -12,56 +11,52 @@ module.exports = composePlugins(withNx(), (config) => {
// add fallback for node modules
config.resolve.fallback = {
...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"),
"stream": require.resolve("stream-browserify"),
"path": require.resolve("path-browserify"),
"http": require.resolve("stream-http"),
"https": require.resolve("https-browserify"),
"constants": require.resolve("constants-browserify"),
"os": false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"),
"fs": false,
"module": false,
"tls": false,
"net": false,
"readline": false,
"child_process": false,
"buffer": require.resolve("buffer/"),
"vm": require.resolve('vm-browserify'),
crypto: require.resolve('crypto-browserify'),
stream: require.resolve('stream-browserify'),
path: require.resolve('path-browserify'),
http: require.resolve('stream-http'),
https: require.resolve('https-browserify'),
constants: require.resolve('constants-browserify'),
os: false, //require.resolve("os-browserify/browser"),
timers: false, // require.resolve("timers-browserify"),
zlib: require.resolve('browserify-zlib'),
fs: false,
module: false,
tls: false,
net: false,
readline: false,
child_process: false,
buffer: require.resolve('buffer/'),
vm: require.resolve('vm-browserify')
}
// add externals
config.externals = {
...config.externals,
solc: 'solc',
solc: 'solc'
}
// add public path
config.output.publicPath = '/'
// add copy & provide plugin
config.plugins.push(
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'],
process: 'process/browser',
process: 'process/browser'
})
)
// souce-map loader
config.module.rules.push({
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre"
use: ['source-map-loader'],
enforce: 'pre'
})
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer
config.optimization.minimizer = [
new TerserPlugin({
@ -71,17 +66,17 @@ module.exports = composePlugins(withNx(), (config) => {
compress: false,
mangle: false,
format: {
comments: false,
},
comments: false
}
},
extractComments: false,
extractComments: false
}),
new CssMinimizerPlugin(),
];
new CssMinimizerPlugin()
]
config.watchOptions = {
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 '@fortawesome/fontawesome-free/css/all.css'
import type { EthereumClient } from '@web3modal/ethereum'
import { WalletConnectRemixClient } from '../services/WalletConnectRemixClient'
import { WalletConnectUI } from './walletConnectUI'
import type {EthereumClient} from '@web3modal/ethereum'
import {WalletConnectRemixClient} from '../services/WalletConnectRemixClient'
import {WalletConnectUI} from './walletConnectUI'
const remix = new WalletConnectRemixClient()
@ -13,12 +13,12 @@ function App() {
const [theme, setTheme] = useState<string>('dark')
useEffect(() => {
(async () => {
;(async () => {
await remix.initClient()
remix.internalEvents.on('themeChanged', (theme: string) => {
setTheme(theme)
})
setWagmiConfig(remix.wagmiConfig)
setEthereumClient(remix.ethereumClient)
})()
@ -26,10 +26,16 @@ function App() {
return (
<div className="App">
<h4 className='mt-1'>WalletConnect</h4>
{ ethereumClient && wagmiConfig && <WalletConnectUI wagmiConfig={wagmiConfig} ethereumClient={ethereumClient} theme={theme} /> }
<h4 className="mt-1">WalletConnect</h4>
{ethereumClient && wagmiConfig && (
<WalletConnectUI
wagmiConfig={wagmiConfig}
ethereumClient={ethereumClient}
theme={theme}
/>
)}
</div>
)
}
export default App
export default App

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

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

@ -10,8 +10,7 @@ const shortFilename = 'simple_storage.sol'
const inputJson = {
language: 'Solidity',
sources: {
},
sources: {},
settings: {
optimizer: {
enabled: true,
@ -19,24 +18,38 @@ const inputJson = {
},
outputSelection: {
'*': {
'': [ 'ast' ],
'*': [ 'abi', 'metadata', 'devdoc', 'userdoc', 'evm.legacyAssembly', 'evm.bytecode', 'evm.deployedBytecode', 'evm.methodIdentifiers', 'evm.gasEstimates' ]
'': ['ast'],
'*': [
'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.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))
const compilation = {}
compilation['data'] = compilationData
compilation['source'] = { sources: inputJson.sources }
compilation['source'] = {sources: inputJson.sources}
console.dir(compilation)
console.dir(compilation['data'].errors)
@ -106,4 +119,3 @@ repl.start({
})
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 './dragbar.css'
interface IRemixDragBarUi {
refObject: React.MutableRefObject<any>;
setHideStatus: (hide: boolean) => void;
refObject: React.MutableRefObject<any>
setHideStatus: (hide: boolean) => void
hidden: boolean
minWidth: number
maximiseTrigger: number
@ -19,7 +19,9 @@ const DragBar = (props: IRemixDragBarUi) => {
const nodeRef = React.useRef(null) // fix for strictmode
useEffect(() => {
setDragBarPosX(offset + (props.hidden ? 0 : props.refObject.current.offsetWidth))
setDragBarPosX(
offset + (props.hidden ? 0 : props.refObject.current.offsetWidth)
)
}, [props.hidden, offset])
useEffect(() => {
@ -47,14 +49,15 @@ const DragBar = (props: IRemixDragBarUi) => {
const handleResize = () => {
if (!props.refObject.current) return
setOffSet(props.refObject.current.offsetLeft)
setDragBarPosX(props.refObject.current.offsetLeft + props.refObject.current.offsetWidth)
setDragBarPosX(
props.refObject.current.offsetLeft + props.refObject.current.offsetWidth
)
}
useEffect(() => {
window.addEventListener('resize', handleResize)
// TODO: not a good way to wait on the ref doms element to be rendered of course
setTimeout(() =>
handleResize(), 2000)
setTimeout(() => handleResize(), 2000)
return () => window.removeEventListener('resize', handleResize)
}, [])
@ -64,7 +67,7 @@ const DragBar = (props: IRemixDragBarUi) => {
setDragBarPosX(offset)
props.setHideStatus(true)
} else {
props.refObject.current.style.width = (data.x - offset) + 'px'
props.refObject.current.style.width = data.x - offset + 'px'
setTimeout(() => {
props.setHideStatus(false)
setDragBarPosX(offset + props.refObject.current.offsetWidth)
@ -75,12 +78,23 @@ const DragBar = (props: IRemixDragBarUi) => {
function startDrag() {
setDragState(true)
}
return <>
<div className={`overlay ${dragState ? '' : 'd-none'}`} ></div>
<Draggable nodeRef={nodeRef} position={{ x: dragBarPosX, y: 0 }} onStart={startDrag} onStop={stopDrag} axis="x">
<div ref={nodeRef} className={`dragbar ${dragState ? 'ondrag' : ''}`}></div>
</Draggable>
</>
return (
<>
<div className={`overlay ${dragState ? '' : 'd-none'}`}></div>
<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

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

@ -1,16 +1,21 @@
import React from 'react'
import { useDialogDispatchers, useDialogs } from '../../context/provider'
import { Toaster } from '@remix-ui/toaster'
import {useDialogDispatchers, useDialogs} from '../../context/provider'
import {Toaster} from '@remix-ui/toaster'
import ModalWrapper from './modal-wrapper'
const AppDialogs = () => {
const { handleHideModal, handleToaster } = useDialogDispatchers()
const { focusModal, focusToaster } = useDialogs()
const {handleHideModal, handleToaster} = useDialogDispatchers()
const {focusModal, focusToaster} = useDialogs()
return (
<>
<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

@ -1,30 +1,76 @@
import React, { useContext, useEffect, useState } from 'react'
import { AppContext } from '../../context/context'
import { useDialogDispatchers } from '../../context/provider'
import React, {useContext, useEffect, useState} from 'react'
import {AppContext} from '../../context/context'
import {useDialogDispatchers} from '../../context/provider'
declare global {
interface Window {
_paq: any
}
}
const _paq = window._paq = window._paq || []
const _paq = (window._paq = window._paq || [])
const MatomoDialog = (props) => {
const { settings, showMatamo, appManager } = useContext(AppContext)
const { modal } = useDialogDispatchers()
const {settings, showMatamo, appManager} = useContext(AppContext)
const {modal} = useDialogDispatchers()
const [visible, setVisible] = useState<boolean>(props.hide)
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>
<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></>)
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>
<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(() => {
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])
@ -38,13 +84,14 @@ const MatomoDialog = (props) => {
const handleModalOkClick = async () => {
_paq.push(['forgetUserOptOut'])
// @TODO remove next line when https://github.com/matomo-org/matomo/commit/9e10a150585522ca30ecdd275007a882a70c6df5 is used
document.cookie = 'mtm_consent_removed=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'
document.cookie =
'mtm_consent_removed=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'
settings.updateMatomoAnalyticsChoice(true)
appManager.call('walkthrough', 'start')
setVisible(false)
}
return (<></>)
return <></>
}
export default MatomoDialog

@ -1,6 +1,10 @@
import React, { useEffect, useRef, useState } from 'react'
import { ModalDialog, ModalDialogProps, ValidationResult } from '@remix-ui/modal-dialog'
import { ModalTypes } from '../../types'
import React, {useEffect, useRef, useState} from 'react'
import {
ModalDialog,
ModalDialogProps,
ValidationResult
} from '@remix-ui/modal-dialog'
import {ModalTypes} from '../../types'
interface ModalWrapperProps extends ModalDialogProps {
modalType?: ModalTypes
@ -28,36 +32,54 @@ const ModalWrapper = (props: ModalWrapperProps) => {
if (ref.current === undefined && formRef.current === undefined) {
onOkFn()
} else if (formRef.current) {
(props.okFn) ? props.okFn(getFormData()) : props.resolve(getFormData())
} else if(ref.current) {
props.okFn ? props.okFn(getFormData()) : props.resolve(getFormData())
} else if (ref.current) {
// @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 () => {
(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 () => {
(props.cancelFn) ? props.cancelFn() : props.resolve(false)
props.cancelFn ? props.cancelFn() : props.resolve(false)
}
const onInputChanged = (event) => {
if (props.validationFn) {
const validation = props.validationFn(event.target.value)
setState(prevState => {
return { ...prevState, message: createModalMessage(props.defaultValue, validation), validation }
setState((prevState) => {
return {
...prevState,
message: createModalMessage(props.defaultValue, validation),
validation
}
})
}
}
const createModalMessage = (defaultValue: string, validation: ValidationResult) => {
const createModalMessage = (
defaultValue: string,
validation: ValidationResult
) => {
return (
<>
{props.message}
<input 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>}
<input
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 = () => {
if (props.validationFn) {
const validation = props.validationFn(getFormData())
setState(prevState => {
return { ...prevState, message: createForm(validation), validation }
setState((prevState) => {
return {...prevState, message: createForm(validation), validation}
})
}
}
@ -77,7 +99,9 @@ const ModalWrapper = (props: ModalWrapperProps) => {
<form onChange={onFormChanged} ref={formRef}>
{props.message}
</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,
okFn: onFinishPrompt,
cancelFn: onCancelFn,
message: createModalMessage(props.defaultValue, { valid: true })
message: createModalMessage(props.defaultValue, {valid: true})
})
break
case ModalTypes.form:
@ -100,7 +124,7 @@ const ModalWrapper = (props: ModalWrapperProps) => {
...props,
okFn: onFinishPrompt,
cancelFn: onCancelFn,
message: createForm({ valid: true })
message: createForm({valid: true})
})
break
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.
const handleHide = () => {
setState(prevState => {
return { ...prevState, message: '' }
setState((prevState) => {
return {...prevState, message: ''}
})
props.handleHide()
}
return (
<ModalDialog id={props.id} {...state} handleHide={handleHide} />
)
return <ModalDialog id={props.id} {...state} handleHide={handleHide} />
}
export default ModalWrapper

@ -1,22 +1,31 @@
import React, { useEffect, useState } from 'react'
import { ModalDialog } from '@remix-ui/modal-dialog'
import { useDialogDispatchers } from '../../context/provider'
import React, {useEffect, useState} from 'react'
import {ModalDialog} from '@remix-ui/modal-dialog'
import {useDialogDispatchers} from '../../context/provider'
const OriginWarning = () => {
const { alert } = useDialogDispatchers()
const {alert} = useDialogDispatchers()
const [content, setContent] = useState<string>(null)
useEffect(() => {
// check the origin and warn message
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.')
} else if (window.location.hostname === 'remix-alpha.ethereum.org' ||
(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 (window.location.protocol.indexOf('http') === 0 &&
window.location.hostname !== 'remix.ethereum.org' &&
window.location.hostname !== 'localhost' &&
window.location.hostname !== '127.0.0.1') {
setContent(
'This UNSTABLE ALPHA branch of Remix has been moved to http://ethereum.github.io/remix-live-alpha.'
)
} else if (
window.location.hostname === 'remix-alpha.ethereum.org' ||
(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 (
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
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`)
@ -25,11 +34,11 @@ const OriginWarning = () => {
useEffect(() => {
if (content) {
alert({ id: 'warningOriging', title: null, message: content })
alert({id: 'warningOriging', title: null, message: content})
}
}, [content])
return (<></>)
return <></>
}
export default OriginWarning

@ -1,16 +1,27 @@
import React from 'react'
const RemixSplashScreen = (props) => {
return (<> <div style={{ display: props.hide ? 'none' : 'block' }} className='centered'>
<svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100">
<path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z"/>
<path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z"/>
<path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z"/>
</svg>
<div className="info-secondary splash">
REMIX IDE
</div>
</div></>)
return (
<>
{' '}
<div
style={{display: props.hide ? 'none' : 'block'}}
className="centered"
>
<svg
id="Ebene_2"
data-name="Ebene 2"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 105 100"
>
<path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z" />
<path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z" />
<path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z" />
</svg>
<div className="info-secondary splash">REMIX IDE</div>
</div>
</>
)
}
export default RemixSplashScreen

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

@ -1,13 +1,20 @@
import React, { useReducer } from 'react'
import { modalActionTypes } from '../actions/modals'
import { AlertModal, AppModal } from '../interface'
import { modalReducer } from '../reducer/modals'
import { ModalInitialState } from '../state/modals'
import { ModalTypes } from '../types'
import { AppContext, dispatchModalContext, modalContext } from './context'
import React, {useReducer} from 'react'
import {modalActionTypes} from '../actions/modals'
import {AlertModal, AppModal} from '../interface'
import {modalReducer} from '../reducer/modals'
import {ModalInitialState} from '../state/modals'
import {ModalTypes} from '../types'
import {AppContext, dispatchModalContext, modalContext} from './context'
export const ModalProvider = ({ children = [], reducer = modalReducer, initialState = ModalInitialState } = {}) => {
const [{ modals, toasters, focusModal, focusToaster }, dispatch] = useReducer(reducer, initialState)
export const ModalProvider = ({
children = [],
reducer = modalReducer,
initialState = ModalInitialState
} = {}) => {
const [{modals, toasters, focusModal, focusToaster}, dispatch] = useReducer(
reducer,
initialState
)
const onNextFn = async () => {
dispatch({
@ -16,17 +23,53 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
}
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) => {
dispatch({
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) => {
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 = () => {
@ -39,7 +82,7 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
const toast = (message: string | JSX.Element) => {
dispatch({
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 }}>
<modalContext.Provider value={{ modals, toasters, focusModal, focusToaster }}>
{children}
</modalContext.Provider>
</dispatchModalContext.Provider>)
return (
<dispatchModalContext.Provider
value={{modal, toast, alert, handleHideModal, handleToaster}}
>
<modalContext.Provider
value={{modals, toasters, focusModal, focusToaster}}
>
{children}
</modalContext.Provider>
</dispatchModalContext.Provider>
)
}
export const AppProvider = ({ children = [], value = {} } = {}) => {
return <AppContext.Provider value={value}>
<ModalProvider>{children}</ModalProvider>
</AppContext.Provider>
export const AppProvider = ({children = [], value = {}} = {}) => {
return (
<AppContext.Provider value={value}>
<ModalProvider>{children}</ModalProvider>
</AppContext.Provider>
)
}
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 { RemixUIMainPanel } from '@remix-ui/panel'
import {RemixUIMainPanel} from '@remix-ui/panel'
import MatomoDialog from './components/modals/matomo'
import OriginWarning from './components/modals/origin-warning'
import DragBar from './components/dragbar/dragbar'
import { AppProvider } from './context/provider'
import {AppProvider} from './context/provider'
import AppDialogs from './components/modals/dialogs'
import DialogViewPlugin from './components/modals/dialogViewPlugin'
import { AppContext } from './context/context'
import { IntlProvider } from 'react-intl'
import { CustomTooltip } from '@remix-ui/helper';
import {AppContext} from './context/context'
import {IntlProvider} from 'react-intl'
import {CustomTooltip} from '@remix-ui/helper'
interface IRemixAppUi {
app: any
@ -20,11 +20,14 @@ const RemixApp = (props: IRemixAppUi) => {
const [hideSidePanel, setHideSidePanel] = useState<boolean>(false)
const [maximiseTrigger, setMaximiseTrigger] = 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)
useEffect(() => {
async function activateApp () {
async function activateApp() {
props.app.themeModule.initTheme(() => {
setAppReady(true)
props.app.activate()
@ -37,9 +40,9 @@ const RemixApp = (props: IRemixAppUi) => {
}
}, [])
function setListeners () {
function setListeners() {
props.app.sidePanel.events.on('toggle', () => {
setHideSidePanel(prev => {
setHideSidePanel((prev) => {
return !prev
})
})
@ -55,13 +58,13 @@ const RemixApp = (props: IRemixAppUi) => {
})
props.app.layout.event.on('maximisesidepanel', () => {
setMaximiseTrigger(prev => {
setMaximiseTrigger((prev) => {
return prev + 1
})
})
props.app.layout.event.on('resetsidepanel', () => {
setResetTrigger(prev => {
setResetTrigger((prev) => {
return prev + 1
})
})
@ -84,18 +87,47 @@ const RemixApp = (props: IRemixAppUi) => {
<AppProvider value={value}>
<OriginWarning></OriginWarning>
<MatomoDialog hide={!appReady}></MatomoDialog>
<div className={`remixIDE ${appReady ? '' : 'd-none'}`} data-id="remixIDE">
<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'>
<div
className={`remixIDE ${appReady ? '' : 'd-none'}`}
data-id="remixIDE"
>
<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>
<CustomTooltip
placement="bottom"
tooltipId="overlay-tooltip-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>
</div>
</div>

@ -1,8 +1,7 @@
import { CustomTooltip } from '@remix-ui/helper';
import React, { CSSProperties } from 'react' //eslint-disable-line
import {CustomTooltip} from '@remix-ui/helper'
import React, {CSSProperties} from 'react' //eslint-disable-line
import './remix-ui-checkbox.css'
type Placement = import('react-overlays/usePopper').Placement;
type Placement = import('react-overlays/usePopper').Placement
/* eslint-disable-next-line */
export interface RemixUiCheckboxProps {
@ -38,27 +37,41 @@ export const RemixUiCheckbox = ({
optionalClassName = '',
display = 'flex',
disabled,
tooltipPlacement = 'right',
tooltipPlacement = 'right'
}: RemixUiCheckboxProps) => {
const childJSXWithTooltip = (
<CustomTooltip
tooltipText={title}
tooltipId={`${name}Tooltip`}
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
id={id}
type={inputType}
onChange={onChange}
style={{ verticalAlign: 'bottom' }}
style={{verticalAlign: 'bottom'}}
name={name}
className="custom-control-input"
checked={checked}
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> : ''}
{label}
</label>
@ -66,25 +79,37 @@ export const RemixUiCheckbox = ({
</CustomTooltip>
)
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
id={id}
type={inputType}
onChange={onChange}
style={{ verticalAlign: 'bottom' }}
style={{verticalAlign: 'bottom'}}
name={name}
className="custom-control-input"
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> : ''}
{label}
</label>
</div>
)
return (
title ? (childJSXWithTooltip) : (childJSX)
)
return title ? childJSXWithTooltip : childJSX
}
export default RemixUiCheckbox

@ -1,23 +1,30 @@
import React, { useState } from 'react'
import React, {useState} from 'react'
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 { CustomTooltip } from '@remix-ui/helper'
import {CustomTooltip} from '@remix-ui/helper'
interface ICopyToClipboard {
content?: any,
tip?: string,
icon?: string,
direction?: Placement,
className?: string,
title?: string,
children?: JSX.Element,
content?: any
tip?: string
icon?: string
direction?: Placement
className?: string
title?: string
children?: JSX.Element
getContent?: () => any
}
export const CopyToClipboard = (props: ICopyToClipboard) => {
const { tip = 'Copy', icon = 'fa-copy', direction = 'right', getContent, children, ...otherProps } = props
let { content } = props
const {
tip = 'Copy',
icon = 'fa-copy',
direction = 'right',
getContent,
children,
...otherProps
} = props
let {content} = props
const [message, setMessage] = useState(tip)
const copyData = () => {
@ -37,7 +44,8 @@ export const CopyToClipboard = (props: ICopyToClipboard) => {
}
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()
} else {
content = getContent && getContent()
@ -50,14 +58,16 @@ export const CopyToClipboard = (props: ICopyToClipboard) => {
setTimeout(() => setMessage(tip), 500)
}
const childJSX = (
children || (<i className={`far ${icon} ml-1 p-2`} aria-hidden="true"
const childJSX = children || (
<i
className={`far ${icon} ml-1 p-2`}
aria-hidden="true"
{...otherProps}
></i>)
></i>
)
return (
<a href='#' onClick={handleClick} onMouseLeave={reset}>
<a href="#" onClick={handleClick} onMouseLeave={reset}>
<CustomTooltip
tooltipText={message}
tooltipId="overlay-tooltip"

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

@ -1,8 +1,20 @@
import { CustomTooltip } from '@remix-ui/helper'
import React, { useState, useEffect } from 'react' // eslint-disable-line
import {CustomTooltip} from '@remix-ui/helper'
import React, {useState, useEffect} from 'react' // eslint-disable-line
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({
intoBackDisabled: true,
overBackDisabled: true,
@ -46,29 +58,72 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
intoForwardDisabled: stepState === 'end',
overForwardDisabled: 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 disableJumpBtnStyle = 'jumpButtonDisabled'
const stepMarkupStructure = {
stepOverBackJSX : {
markup: (<div className={state.overBackDisabled ? `${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>),
stepOverBackJSX: {
markup: (
<div
className={
state.overBackDisabled
? `${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',
tagId: 'overbackTooltip',
tooltipMsg: 'Step over back'
},
stepBackJSX : {
stepBackJSX: {
markup: (
<div 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' }}>
<div
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>
</button>
</div>
@ -77,12 +132,30 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
tagId: 'intobackTooltip',
tooltipMsg: 'Step back'
},
stepIntoJSX : {
stepIntoJSX: {
markup: (
<div className={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' }}
<div
className={
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>
</button>
@ -92,24 +165,67 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
tagId: 'intoforwardTooltip',
tooltipMsg: 'Step into'
},
stepOverForwardJSX : {
stepOverForwardJSX: {
markup: (
<div 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' }}>
<div
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>
</button>
</div>
),
placement: 'top-end',
tagId: 'overbackTooltip',
tooltipMsg: 'Step over forward',
tooltipMsg: 'Step over forward'
}
}
const jumpMarkupStructure = {
jumpPreviousBreakpointJSX : {
jumpPreviousBreakpointJSX: {
markup: (
<div 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' }}>
<div
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>
</button>
</div>
@ -118,10 +234,34 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
tagId: 'jumppreviousbreakpointTooltip',
tooltipMsg: 'Jump to the previous breakpoint'
},
jumpOutJSX : {
jumpOutJSX: {
markup: (
<div 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">
<div
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>
</button>
</div>
@ -130,10 +270,30 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
tagId: 'jumpoutTooltip',
tooltipMsg: 'Jump out'
},
jumpNextBreakpointJSX : {
jumpNextBreakpointJSX: {
markup: (
<div 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' }}>
<div
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>
</button>
</div>
@ -147,39 +307,68 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
return (
<div className="buttons">
<div className="stepButtons btn-group py-1">
{
Object.keys(stepMarkupStructure).map(x => (
<CustomTooltip
placement={stepMarkupStructure[x].placement}
tooltipId={stepMarkupStructure[x].tagId}
tooltipText={stepMarkupStructure[x].tooltipMsg}
key={`${stepMarkupStructure[x].placement}-${stepMarkupStructure[x].tooltipMsg}-${stepMarkupStructure[x].tagId}`}
>
{stepMarkupStructure[x].markup}
</CustomTooltip>
))
}
{Object.keys(stepMarkupStructure).map((x) => (
<CustomTooltip
placement={stepMarkupStructure[x].placement}
tooltipId={stepMarkupStructure[x].tagId}
tooltipText={stepMarkupStructure[x].tooltipMsg}
key={`${stepMarkupStructure[x].placement}-${stepMarkupStructure[x].tooltipMsg}-${stepMarkupStructure[x].tagId}`}
>
{stepMarkupStructure[x].markup}
</CustomTooltip>
))}
</div>
<div className="jumpButtons btn-group py-1">
{
Object.keys(jumpMarkupStructure).map(x => (
<CustomTooltip
placement={jumpMarkupStructure[x].placement}
tooltipText={jumpMarkupStructure[x].tooltipMsg}
tooltipId={jumpMarkupStructure[x].tooltipId}
key={`${jumpMarkupStructure[x].placement}-${jumpMarkupStructure[x].tooltipMsg}-${jumpMarkupStructure[x].tagId}`}
>
{jumpMarkupStructure[x].markup}
</CustomTooltip>
))
}
{Object.keys(jumpMarkupStructure).map((x) => (
<CustomTooltip
placement={jumpMarkupStructure[x].placement}
tooltipText={jumpMarkupStructure[x].tooltipMsg}
tooltipId={jumpMarkupStructure[x].tooltipId}
key={`${jumpMarkupStructure[x].placement}-${jumpMarkupStructure[x].tooltipMsg}-${jumpMarkupStructure[x].tagId}`}
>
{jumpMarkupStructure[x].markup}
</CustomTooltip>
))}
</div>
<div id='reverted' style={{ display: revertedReason === '' ? 'none' : 'block' }}>
<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
id="reverted"
style={{display: revertedReason === '' ? 'none' : 'block'}}
>
<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>
)

@ -1,16 +1,16 @@
import React, { useState, useEffect, useRef } from 'react' // eslint-disable-line
import { FormattedMessage } from 'react-intl'
import React, {useState, useEffect, useRef} from 'react' // eslint-disable-line
import {FormattedMessage} from 'react-intl'
import TxBrowser from './tx-browser/tx-browser' // eslint-disable-line
import StepManager from './step-manager/step-manager' // 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 { TransactionDebugger as Debugger } from '@remix-project/remix-debug' // eslint-disable-line
import { DebuggerUIProps } from './idebugger-api' // eslint-disable-line
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
import { CustomTooltip, isValidHash } from '@remix-ui/helper'
import {TransactionDebugger as Debugger} from '@remix-project/remix-debug' // eslint-disable-line
import {DebuggerUIProps} from './idebugger-api' // eslint-disable-line
import {Toaster} from '@remix-ui/toaster' // eslint-disable-line
import {CustomTooltip, isValidHash} from '@remix-ui/helper'
/* eslint-disable-next-line */
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) => {
const debuggerModule = props.debuggerAPI
@ -54,7 +54,12 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
const handleResize = () => {
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(() => {
window.addEventListener('resize', handleResize)
// TODO: not a good way to wait on the ref doms element to be rendered of course
setTimeout(() =>
handleResize(), 2000)
setTimeout(() => handleResize(), 2000)
return () => window.removeEventListener('resize', handleResize)
}, [state.debugging, state.isActive])
@ -85,11 +89,16 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
useEffect(() => {
const setEditor = () => {
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) => {
if (state.debugger) state.debugger.breakPointManager.add({ fileName: fileName, row: row })
if (state.debugger)
state.debugger.breakPointManager.add({fileName: fileName, row: row})
})
debuggerModule.onEditorContentChanged(() => {
@ -101,9 +110,10 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
const providerChanged = () => {
debuggerModule.onEnvChanged((provider) => {
setState(prevState => {
const isLocalNodeUsed = !provider.startsWith('vm') && provider !== 'injected'
return { ...prevState, isLocalNodeUsed: isLocalNodeUsed }
setState((prevState) => {
const 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) => {
await debuggerModule.discardHighlight()
setState(prevState => {
return { ...prevState, isActive }
setState((prevState) => {
return {...prevState, isActive}
})
})
debuggerInstance.event.register('locatingBreakpoint', async (isActive) => {
setState(prevState => {
return { ...prevState, sourceLocationStatus: 'Locating breakpoint, this might take a while...' }
setState((prevState) => {
return {
...prevState,
sourceLocationStatus:
'Locating breakpoint, this might take a while...'
}
})
})
debuggerInstance.event.register('noBreakpointHit', async (isActive) => {
setState(prevState => {
return { ...prevState, sourceLocationStatus: '' }
setState((prevState) => {
return {...prevState, sourceLocationStatus: ''}
})
})
debuggerInstance.event.register('newSourceLocation', async (lineColumnPos, rawLocation, generatedSources, address, stepDetail, lineGasCost) => {
if (!lineColumnPos) {
await debuggerModule.discardHighlight()
setState(prevState => {
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.' }
})
return
}
const contracts = await debuggerModule.fetchContractAndCompile(
address || currentReceipt.contractAddress || currentReceipt.to,
currentReceipt)
if (contracts) {
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)
debuggerInstance.event.register(
'newSourceLocation',
async (
lineColumnPos,
rawLocation,
generatedSources,
address,
stepDetail,
lineGasCost
) => {
if (!lineColumnPos) {
await debuggerModule.discardHighlight()
setState((prevState) => {
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.'
}
})
return
}
const contracts = await debuggerModule.fetchContractAndCompile(
address || currentReceipt.contractAddress || currentReceipt.to,
currentReceipt
)
if (contracts) {
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) {
setState(prevState => {
return { ...prevState, sourceLocationStatus: '' }
})
await debuggerModule.discardHighlight()
await debuggerModule.highlight(lineColumnPos, path, rawLocation, stepDetail, lineGasCost)
if (path) {
setState((prevState) => {
return {...prevState, sourceLocationStatus: ''}
})
await debuggerModule.discardHighlight()
await debuggerModule.highlight(
lineColumnPos,
path,
rawLocation,
stepDetail,
lineGasCost
)
}
}
}
})
)
debuggerInstance.event.register('debuggerUnloaded', () => unLoad())
}
@ -183,7 +219,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
}
const updateTxNumberFlag = (empty: boolean) => {
setState(prevState => {
setState((prevState) => {
return {
...prevState,
txNumberIsEmpty: empty,
@ -194,7 +230,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
const unloadRequested = (blockNumber, txIndex, tx) => {
unLoad()
setState(prevState => {
setState((prevState) => {
return {
...prevState,
sourceLocationStatus: ''
@ -205,7 +241,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
const unLoad = () => {
debuggerModule.onStopDebugging()
if (state.debugger) state.debugger.unload()
setState(prevState => {
setState((prevState) => {
return {
...prevState,
isActive: false,
@ -228,10 +264,10 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
const startDebugging = async (blockNumber, txNumber, tx, optWeb3?) => {
if (state.debugger) {
unLoad()
await (new Promise((resolve) => setTimeout(() => resolve({}), 1000)))
await new Promise((resolve) => setTimeout(() => resolve({}), 1000))
}
if (!txNumber) return
setState(prevState => {
setState((prevState) => {
return {
...prevState,
txNumber: txNumber,
@ -239,7 +275,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
}
})
if (!isValidHash(txNumber)) {
setState(prevState => {
setState((prevState) => {
return {
...prevState,
validationError: 'Invalid transaction hash.'
@ -248,15 +284,20 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
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 {
const networkId = await web3.eth.net.getId()
_paq.push(['trackEvent', 'debugger', 'startDebugging', networkId])
if (networkId === 42) {
setState(prevState => {
setState((prevState) => {
return {
...prevState,
validationError: 'Unfortunately, the Kovan network is not supported.'
validationError:
'Unfortunately, the Kovan network is not supported.'
}
})
return
@ -272,7 +313,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
currentBlock = await web3.eth.getBlock(currentReceipt.blockHash)
currentTransaction = await web3.eth.getTransaction(txNumber)
} catch (e) {
setState(prevState => {
setState((prevState) => {
return {
...prevState,
validationError: e.message
@ -287,7 +328,11 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
offsetToLineColumnConverter: debuggerModule.offsetToLineColumnConverter,
compilationResult: async (address) => {
try {
if (!localCache[address]) localCache[address] = await debuggerModule.fetchContractAndCompile(address, currentReceipt)
if (!localCache[address])
localCache[address] = await debuggerModule.fetchContractAndCompile(
address,
currentReceipt
)
return localCache[address]
} catch (e) {
// debuggerModule.showMessage('Debugging error', 'Unable to fetch a transaction.')
@ -298,12 +343,12 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
debugWithGeneratedSources: state.opt.debugWithGeneratedSources
})
setTimeout(async() => {
setTimeout(async () => {
debuggerModule.onStartDebugging(debuggerInstance)
try {
await debuggerInstance.debug(blockNumber, txNumber, tx, () => {
listenToEvents(debuggerInstance, currentReceipt)
setState(prevState => {
setState((prevState) => {
return {
...prevState,
blockNumber,
@ -320,10 +365,12 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
})
} catch (error) {
unLoad()
setState(prevState => {
setState((prevState) => {
let errorMsg = error.message || error
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 {
...prevState,
@ -338,7 +385,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
}
const debug = (txHash, web3?) => {
setState(prevState => {
setState((prevState) => {
return {
...prevState,
validationError: '',
@ -350,36 +397,105 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
}
const stepManager = {
jumpTo: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.jumpTo.bind(state.debugger.step_manager) : null,
stepOverBack: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.stepOverBack.bind(state.debugger.step_manager) : 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
jumpTo:
state.debugger && state.debugger.step_manager
? state.debugger.step_manager.jumpTo.bind(state.debugger.step_manager)
: null,
stepOverBack:
state.debugger && state.debugger.step_manager
? state.debugger.step_manager.stepOverBack.bind(
state.debugger.step_manager
)
: 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 = {
registerEvent: 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
registerEvent:
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 = (
<span className="p-0 m-0">
<input className="custom-control-input" id="debugGeneratedSourcesInput" onChange={({ target: { checked } }) => {
setState(prevState => {
return { ...prevState, opt: { ...prevState.opt, debugWithGeneratedSources: checked } }
})
}} type="checkbox" />
<input
className="custom-control-input"
id="debugGeneratedSourcesInput"
onChange={({target: {checked}}) => {
setState((prevState) => {
return {
...prevState,
opt: {...prevState.opt, debugWithGeneratedSources: checked}
}
})
}}
type="checkbox"
/>
<label
data-id="debugGeneratedSourcesLabel"
className="form-check-label custom-control-label"
htmlFor="debugGeneratedSourcesInput">
<FormattedMessage id='debugger.useGeneratedSources' />(Solidity {'>='} v0.7.2)
htmlFor="debugGeneratedSourcesInput"
>
<FormattedMessage id="debugger.useGeneratedSources" />
(Solidity {'>='} v0.7.2)
</label>
</span>
)
@ -391,48 +507,93 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
<div className="mt-2 mb-2 debuggerConfig custom-control custom-checkbox">
<CustomTooltip
tooltipId="debuggerGenSourceCheckbox"
tooltipText={<FormattedMessage id='debugger.debugWithGeneratedSources' />}
tooltipText={
<FormattedMessage id="debugger.debugWithGeneratedSources" />
}
placement="bottom-start"
>
{customJSX}
</CustomTooltip>
</div>
{ state.isLocalNodeUsed && <div className="mb-2 debuggerConfig custom-control custom-checkbox">
<CustomTooltip
tooltipId="debuggerGenSourceInput"
tooltipText="Force the debugger to use the current local node"
placement='right'
>
<input
className="custom-control-input"
id="debugWithLocalNodeInput"
onChange={({ target: { checked } }) => {
setState(prevState => {
return { ...prevState, opt: { ...prevState.opt, debugWithLocalNode: checked } }
})
}}
type="checkbox"
/>
</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> }
{state.isLocalNodeUsed && (
<div className="mb-2 debuggerConfig custom-control custom-checkbox">
<CustomTooltip
tooltipId="debuggerGenSourceInput"
tooltipText="Force the debugger to use the current local node"
placement="right"
>
<input
className="custom-control-input"
id="debugWithLocalNodeInput"
onChange={({target: {checked}}) => {
setState((prevState) => {
return {
...prevState,
opt: {...prevState.opt, debugWithLocalNode: checked}
}
})
}}
type="checkbox"
/>
</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>
<TxBrowser requestDebug={ requestDebug } unloadRequested={ unloadRequested } updateTxNumberFlag={ updateTxNumberFlag } transactionNumber={ state.txNumber } debugging={ state.debugging } />
{ state.debugging && state.sourceLocationStatus && <div className="text-warning"><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 } /> }
<TxBrowser
requestDebug={requestDebug}
unloadRequested={unloadRequested}
updateTxNumberFlag={updateTxNumberFlag}
transactionNumber={state.txNumber}
debugging={state.debugging}
/>
{state.debugging && state.sourceLocationStatus && (
<div className="text-warning">
<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 className="debuggerPanels" ref={panelsRef}>
{ state.debugging && <VmDebuggerHead debugging={state.debugging} vmDebugger={ vmDebugger } /> }
{ state.debugging && <VmDebugger debugging={state.debugging} vmDebugger={ vmDebugger } currentBlock={ state.currentBlock } currentReceipt={ state.currentReceipt } currentTransaction={ state.currentTransaction } /> }
{state.debugging && (
<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>
)

@ -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 slider = useRef(null)
@ -9,13 +9,13 @@ export const Slider = ({ jumpTo, sliderValue, traceLength }) => {
}, [sliderValue])
const setValue = (value) => {
if (value < 0) return
if (value < 0) return
if (value === slider.current.value) return
slider.current.value = value
if (onChangeId.current) {
clearTimeout(onChangeId.current)
}
((value) => {
;((value) => {
onChangeId.current = setTimeout(() => {
jumpTo && jumpTo(value)
}, 100)
@ -30,11 +30,12 @@ export const Slider = ({ jumpTo, sliderValue, traceLength }) => {
return (
<div>
<input id='slider'
<input
id="slider"
data-id="slider"
className='w-100 my-0'
className="w-100 my-0"
ref={slider}
type='range'
type="range"
min={0}
max={traceLength ? traceLength - 1 : 0}
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 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({
sliderValue: -1,
revertWarning: '',
@ -16,21 +30,25 @@ export const StepManager = ({ stepManager: { jumpTo, traceLength, stepIntoBack,
}, [registerEvent])
const setRevertWarning = (warning) => {
setState(prevState => {
return { ...prevState, revertWarning: warning }
setState((prevState) => {
return {...prevState, revertWarning: warning}
})
}
const updateStep = (step, stepState, jumpOutDisabled) => {
setState(prevState => {
return { ...prevState, sliderValue: step, stepState, jumpOutDisabled }
setState((prevState) => {
return {...prevState, sliderValue: step, stepState, jumpOutDisabled}
})
}
const { sliderValue, revertWarning, stepState, jumpOutDisabled } = state
const {sliderValue, revertWarning, stepState, jumpOutDisabled} = state
return (
<div className="py-1">
<Slider jumpTo={jumpTo} sliderValue={sliderValue} traceLength={traceLength} />
<Slider
jumpTo={jumpTo}
sliderValue={sliderValue}
traceLength={traceLength}
/>
<ButtonNavigator
stepIntoBack={stepIntoBack}
stepIntoForward={stepIntoForward}

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

Loading…
Cancel
Save