Merge pull request #453 from ethereum/refactor-debugger
Debugger UI Refactor (React Library)pull/5370/head
commit
b89b545f6a
@ -1,3 +1,3 @@ |
|||||||
{ |
{ |
||||||
"presets": ["@babel/preset-env"] |
"presets": ["@babel/preset-env", "@babel/preset-react"] |
||||||
} |
} |
||||||
|
@ -1,3 +1,4 @@ |
|||||||
module.exports = { |
module.exports = { |
||||||
|
"presets": ["@babel/preset-react", "@babel/preset-typescript"], |
||||||
"plugins": ["@babel/plugin-transform-modules-commonjs"] |
"plugins": ["@babel/plugin-transform-modules-commonjs"] |
||||||
} |
} |
@ -0,0 +1,4 @@ |
|||||||
|
{ |
||||||
|
"presets": ["@nrwl/react/babel"], |
||||||
|
"plugins": [] |
||||||
|
} |
@ -0,0 +1,248 @@ |
|||||||
|
{ |
||||||
|
"rules": { |
||||||
|
"array-callback-return": "warn", |
||||||
|
"dot-location": ["warn", "property"], |
||||||
|
"eqeqeq": ["warn", "smart"], |
||||||
|
"new-parens": "warn", |
||||||
|
"no-caller": "warn", |
||||||
|
"no-cond-assign": ["warn", "except-parens"], |
||||||
|
"no-const-assign": "warn", |
||||||
|
"no-control-regex": "warn", |
||||||
|
"no-delete-var": "warn", |
||||||
|
"no-dupe-args": "warn", |
||||||
|
"no-dupe-keys": "warn", |
||||||
|
"no-duplicate-case": "warn", |
||||||
|
"no-empty-character-class": "warn", |
||||||
|
"no-empty-pattern": "warn", |
||||||
|
"no-eval": "warn", |
||||||
|
"no-ex-assign": "warn", |
||||||
|
"no-extend-native": "warn", |
||||||
|
"no-extra-bind": "warn", |
||||||
|
"no-extra-label": "warn", |
||||||
|
"no-fallthrough": "warn", |
||||||
|
"no-func-assign": "warn", |
||||||
|
"no-implied-eval": "warn", |
||||||
|
"no-invalid-regexp": "warn", |
||||||
|
"no-iterator": "warn", |
||||||
|
"no-label-var": "warn", |
||||||
|
"no-labels": ["warn", { "allowLoop": true, "allowSwitch": false }], |
||||||
|
"no-lone-blocks": "warn", |
||||||
|
"no-loop-func": "warn", |
||||||
|
"no-mixed-operators": [ |
||||||
|
"warn", |
||||||
|
{ |
||||||
|
"groups": [ |
||||||
|
["&", "|", "^", "~", "<<", ">>", ">>>"], |
||||||
|
["==", "!=", "===", "!==", ">", ">=", "<", "<="], |
||||||
|
["&&", "||"], |
||||||
|
["in", "instanceof"] |
||||||
|
], |
||||||
|
"allowSamePrecedence": false |
||||||
|
} |
||||||
|
], |
||||||
|
"no-multi-str": "warn", |
||||||
|
"no-native-reassign": "warn", |
||||||
|
"no-negated-in-lhs": "warn", |
||||||
|
"no-new-func": "warn", |
||||||
|
"no-new-object": "warn", |
||||||
|
"no-new-symbol": "warn", |
||||||
|
"no-new-wrappers": "warn", |
||||||
|
"no-obj-calls": "warn", |
||||||
|
"no-octal": "warn", |
||||||
|
"no-octal-escape": "warn", |
||||||
|
"no-redeclare": "warn", |
||||||
|
"no-regex-spaces": "warn", |
||||||
|
"no-restricted-syntax": ["warn", "WithStatement"], |
||||||
|
"no-script-url": "warn", |
||||||
|
"no-self-assign": "warn", |
||||||
|
"no-self-compare": "warn", |
||||||
|
"no-sequences": "warn", |
||||||
|
"no-shadow-restricted-names": "warn", |
||||||
|
"no-sparse-arrays": "warn", |
||||||
|
"no-template-curly-in-string": "warn", |
||||||
|
"no-this-before-super": "warn", |
||||||
|
"no-throw-literal": "warn", |
||||||
|
"no-restricted-globals": [ |
||||||
|
"error", |
||||||
|
"addEventListener", |
||||||
|
"blur", |
||||||
|
"close", |
||||||
|
"closed", |
||||||
|
"confirm", |
||||||
|
"defaultStatus", |
||||||
|
"defaultstatus", |
||||||
|
"event", |
||||||
|
"external", |
||||||
|
"find", |
||||||
|
"focus", |
||||||
|
"frameElement", |
||||||
|
"frames", |
||||||
|
"history", |
||||||
|
"innerHeight", |
||||||
|
"innerWidth", |
||||||
|
"length", |
||||||
|
"location", |
||||||
|
"locationbar", |
||||||
|
"menubar", |
||||||
|
"moveBy", |
||||||
|
"moveTo", |
||||||
|
"name", |
||||||
|
"onblur", |
||||||
|
"onerror", |
||||||
|
"onfocus", |
||||||
|
"onload", |
||||||
|
"onresize", |
||||||
|
"onunload", |
||||||
|
"open", |
||||||
|
"opener", |
||||||
|
"opera", |
||||||
|
"outerHeight", |
||||||
|
"outerWidth", |
||||||
|
"pageXOffset", |
||||||
|
"pageYOffset", |
||||||
|
"parent", |
||||||
|
"print", |
||||||
|
"removeEventListener", |
||||||
|
"resizeBy", |
||||||
|
"resizeTo", |
||||||
|
"screen", |
||||||
|
"screenLeft", |
||||||
|
"screenTop", |
||||||
|
"screenX", |
||||||
|
"screenY", |
||||||
|
"scroll", |
||||||
|
"scrollbars", |
||||||
|
"scrollBy", |
||||||
|
"scrollTo", |
||||||
|
"scrollX", |
||||||
|
"scrollY", |
||||||
|
"self", |
||||||
|
"status", |
||||||
|
"statusbar", |
||||||
|
"stop", |
||||||
|
"toolbar", |
||||||
|
"top" |
||||||
|
], |
||||||
|
"no-unexpected-multiline": "warn", |
||||||
|
"no-unreachable": "warn", |
||||||
|
"no-unused-expressions": [ |
||||||
|
"error", |
||||||
|
{ |
||||||
|
"allowShortCircuit": true, |
||||||
|
"allowTernary": true, |
||||||
|
"allowTaggedTemplates": true |
||||||
|
} |
||||||
|
], |
||||||
|
"no-unused-labels": "warn", |
||||||
|
"no-useless-computed-key": "warn", |
||||||
|
"no-useless-concat": "warn", |
||||||
|
"no-useless-escape": "warn", |
||||||
|
"no-useless-rename": [ |
||||||
|
"warn", |
||||||
|
{ |
||||||
|
"ignoreDestructuring": false, |
||||||
|
"ignoreImport": false, |
||||||
|
"ignoreExport": false |
||||||
|
} |
||||||
|
], |
||||||
|
"no-with": "warn", |
||||||
|
"no-whitespace-before-property": "warn", |
||||||
|
"react-hooks/exhaustive-deps": "warn", |
||||||
|
"require-yield": "warn", |
||||||
|
"rest-spread-spacing": ["warn", "never"], |
||||||
|
"strict": ["warn", "never"], |
||||||
|
"unicode-bom": ["warn", "never"], |
||||||
|
"use-isnan": "warn", |
||||||
|
"valid-typeof": "warn", |
||||||
|
"no-restricted-properties": [ |
||||||
|
"error", |
||||||
|
{ |
||||||
|
"object": "require", |
||||||
|
"property": "ensure", |
||||||
|
"message": "Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"object": "System", |
||||||
|
"property": "import", |
||||||
|
"message": "Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting" |
||||||
|
} |
||||||
|
], |
||||||
|
"getter-return": "warn", |
||||||
|
"import/first": "error", |
||||||
|
"import/no-amd": "error", |
||||||
|
"import/no-webpack-loader-syntax": "error", |
||||||
|
"react/forbid-foreign-prop-types": ["warn", { "allowInPropTypes": true }], |
||||||
|
"react/jsx-no-comment-textnodes": "warn", |
||||||
|
"react/jsx-no-duplicate-props": "warn", |
||||||
|
"react/jsx-no-target-blank": "warn", |
||||||
|
"react/jsx-no-undef": "error", |
||||||
|
"react/jsx-pascal-case": ["warn", { "allowAllCaps": true, "ignore": [] }], |
||||||
|
"react/jsx-uses-react": "warn", |
||||||
|
"react/jsx-uses-vars": "warn", |
||||||
|
"react/no-danger-with-children": "warn", |
||||||
|
"react/no-direct-mutation-state": "warn", |
||||||
|
"react/no-is-mounted": "warn", |
||||||
|
"react/no-typos": "error", |
||||||
|
"react/react-in-jsx-scope": "error", |
||||||
|
"react/require-render-return": "error", |
||||||
|
"react/style-prop-object": "warn", |
||||||
|
"react/jsx-no-useless-fragment": "warn", |
||||||
|
"jsx-a11y/accessible-emoji": "warn", |
||||||
|
"jsx-a11y/alt-text": "warn", |
||||||
|
"jsx-a11y/anchor-has-content": "warn", |
||||||
|
"jsx-a11y/anchor-is-valid": [ |
||||||
|
"warn", |
||||||
|
{ "aspects": ["noHref", "invalidHref"] } |
||||||
|
], |
||||||
|
"jsx-a11y/aria-activedescendant-has-tabindex": "warn", |
||||||
|
"jsx-a11y/aria-props": "warn", |
||||||
|
"jsx-a11y/aria-proptypes": "warn", |
||||||
|
"jsx-a11y/aria-role": "warn", |
||||||
|
"jsx-a11y/aria-unsupported-elements": "warn", |
||||||
|
"jsx-a11y/heading-has-content": "warn", |
||||||
|
"jsx-a11y/iframe-has-title": "warn", |
||||||
|
"jsx-a11y/img-redundant-alt": "warn", |
||||||
|
"jsx-a11y/no-access-key": "warn", |
||||||
|
"jsx-a11y/no-distracting-elements": "warn", |
||||||
|
"jsx-a11y/no-redundant-roles": "warn", |
||||||
|
"jsx-a11y/role-has-required-aria-props": "warn", |
||||||
|
"jsx-a11y/role-supports-aria-props": "warn", |
||||||
|
"jsx-a11y/scope": "warn", |
||||||
|
"react-hooks/rules-of-hooks": "error", |
||||||
|
"default-case": "off", |
||||||
|
"no-dupe-class-members": "off", |
||||||
|
"no-undef": "off", |
||||||
|
"@typescript-eslint/consistent-type-assertions": "warn", |
||||||
|
"no-array-constructor": "off", |
||||||
|
"@typescript-eslint/no-array-constructor": "warn", |
||||||
|
"@typescript-eslint/no-namespace": "error", |
||||||
|
"no-use-before-define": "off", |
||||||
|
"@typescript-eslint/no-use-before-define": [ |
||||||
|
"warn", |
||||||
|
{ |
||||||
|
"functions": false, |
||||||
|
"classes": false, |
||||||
|
"variables": false, |
||||||
|
"typedefs": false |
||||||
|
} |
||||||
|
], |
||||||
|
"no-unused-vars": "off", |
||||||
|
"@typescript-eslint/no-unused-vars": [ |
||||||
|
"warn", |
||||||
|
{ "args": "none", "ignoreRestSiblings": true } |
||||||
|
], |
||||||
|
"no-useless-constructor": "off", |
||||||
|
"@typescript-eslint/no-useless-constructor": "warn" |
||||||
|
}, |
||||||
|
"env": { |
||||||
|
"browser": true, |
||||||
|
"commonjs": true, |
||||||
|
"es6": true, |
||||||
|
"jest": true, |
||||||
|
"node": true |
||||||
|
}, |
||||||
|
"settings": { "react": { "version": "detect" } }, |
||||||
|
"plugins": ["import", "jsx-a11y", "react", "react-hooks"], |
||||||
|
"extends": ["../../../.eslintrc"], |
||||||
|
"ignorePatterns": ["!**/*"] |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
# remix-ui-clipboard |
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev). |
||||||
|
|
||||||
|
## Running unit tests |
||||||
|
|
||||||
|
Run `nx test remix-ui-clipboard` to execute the unit tests via [Jest](https://jestjs.io). |
@ -0,0 +1,14 @@ |
|||||||
|
{ |
||||||
|
"presets": [ |
||||||
|
[ |
||||||
|
"@babel/preset-env", |
||||||
|
{ |
||||||
|
"targets": { |
||||||
|
"node": "current" |
||||||
|
} |
||||||
|
} |
||||||
|
], |
||||||
|
"@babel/preset-typescript", |
||||||
|
"@babel/preset-react" |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
module.exports = { |
||||||
|
name: 'remix-ui-clipboard', |
||||||
|
preset: '../../../jest.config.js', |
||||||
|
transform: { |
||||||
|
'^.+\\.[tj]sx?$': [ |
||||||
|
'babel-jest', |
||||||
|
{ cwd: __dirname, configFile: './babel-jest.config.json' } |
||||||
|
] |
||||||
|
}, |
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], |
||||||
|
coverageDirectory: '../../../coverage/libs/remix-ui/clipboard' |
||||||
|
}; |
@ -0,0 +1 @@ |
|||||||
|
export * from './lib/copy-to-clipboard/copy-to-clipboard'; |
@ -0,0 +1,4 @@ |
|||||||
|
.copyIcon { |
||||||
|
margin-left: 5px; |
||||||
|
cursor: pointer; |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
import React, { useState } from 'react' |
||||||
|
import copy from 'copy-text-to-clipboard' |
||||||
|
import { OverlayTrigger, Tooltip } from 'react-bootstrap' |
||||||
|
|
||||||
|
import './copy-to-clipboard.css' |
||||||
|
|
||||||
|
export const CopyToClipboard = ({ content, tip='Copy', icon='fa-copy', ...otherProps }) => { |
||||||
|
const [message, setMessage] = useState(tip) |
||||||
|
const handleClick = () => { |
||||||
|
if (content && 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
|
||||||
|
try { |
||||||
|
if (typeof content !== 'string') { |
||||||
|
content = JSON.stringify(content, null, '\t') |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
console.error(e) |
||||||
|
} |
||||||
|
copy(content) |
||||||
|
setMessage('Copied') |
||||||
|
} else { |
||||||
|
setMessage('Cannot copy empty content!') |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const reset = () => { |
||||||
|
setTimeout(() => setMessage('Copy'), 500) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<a href="#" onClick={handleClick} onMouseLeave={reset}> |
||||||
|
<OverlayTrigger placement="right" overlay={ |
||||||
|
<Tooltip id="overlay-tooltip"> |
||||||
|
{ message } |
||||||
|
</Tooltip> |
||||||
|
}> |
||||||
|
<i className={`far ${icon} ml-1 p-2`} aria-hidden="true" |
||||||
|
{...otherProps} |
||||||
|
></i> |
||||||
|
</OverlayTrigger> |
||||||
|
</a> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default CopyToClipboard |
@ -0,0 +1,19 @@ |
|||||||
|
{ |
||||||
|
"extends": "../../../tsconfig.json", |
||||||
|
"compilerOptions": { |
||||||
|
"jsx": "react", |
||||||
|
"allowJs": true, |
||||||
|
"esModuleInterop": true, |
||||||
|
"allowSyntheticDefaultImports": true |
||||||
|
}, |
||||||
|
"files": [], |
||||||
|
"include": [], |
||||||
|
"references": [ |
||||||
|
{ |
||||||
|
"path": "./tsconfig.lib.json" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"path": "./tsconfig.spec.json" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
{ |
||||||
|
"extends": "./tsconfig.json", |
||||||
|
"compilerOptions": { |
||||||
|
"outDir": "../../../dist/out-tsc", |
||||||
|
"types": ["node"] |
||||||
|
}, |
||||||
|
"files": [ |
||||||
|
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts", |
||||||
|
"../../../node_modules/@nrwl/react/typings/image.d.ts" |
||||||
|
], |
||||||
|
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"], |
||||||
|
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
{ |
||||||
|
"extends": "./tsconfig.json", |
||||||
|
"compilerOptions": { |
||||||
|
"outDir": "../../../dist/out-tsc", |
||||||
|
"module": "commonjs", |
||||||
|
"types": ["jest", "node"] |
||||||
|
}, |
||||||
|
"include": [ |
||||||
|
"**/*.spec.ts", |
||||||
|
"**/*.spec.tsx", |
||||||
|
"**/*.spec.js", |
||||||
|
"**/*.spec.jsx", |
||||||
|
"**/*.d.ts" |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,4 @@ |
|||||||
|
{ |
||||||
|
"presets": ["@nrwl/react/babel"], |
||||||
|
"plugins": [] |
||||||
|
} |
@ -0,0 +1,250 @@ |
|||||||
|
{ |
||||||
|
"rules": { |
||||||
|
"@typescript-eslint/ban-types": "off", |
||||||
|
"no-case-declarations": "off", |
||||||
|
"array-callback-return": "warn", |
||||||
|
"dot-location": ["warn", "property"], |
||||||
|
"eqeqeq": ["warn", "smart"], |
||||||
|
"new-parens": "warn", |
||||||
|
"no-caller": "warn", |
||||||
|
"no-cond-assign": ["warn", "except-parens"], |
||||||
|
"no-const-assign": "warn", |
||||||
|
"no-control-regex": "warn", |
||||||
|
"no-delete-var": "warn", |
||||||
|
"no-dupe-args": "warn", |
||||||
|
"no-dupe-keys": "warn", |
||||||
|
"no-duplicate-case": "warn", |
||||||
|
"no-empty-character-class": "warn", |
||||||
|
"no-empty-pattern": "warn", |
||||||
|
"no-eval": "warn", |
||||||
|
"no-ex-assign": "warn", |
||||||
|
"no-extend-native": "warn", |
||||||
|
"no-extra-bind": "warn", |
||||||
|
"no-extra-label": "warn", |
||||||
|
"no-fallthrough": "warn", |
||||||
|
"no-func-assign": "warn", |
||||||
|
"no-implied-eval": "warn", |
||||||
|
"no-invalid-regexp": "warn", |
||||||
|
"no-iterator": "warn", |
||||||
|
"no-label-var": "warn", |
||||||
|
"no-labels": ["warn", { "allowLoop": true, "allowSwitch": false }], |
||||||
|
"no-lone-blocks": "warn", |
||||||
|
"no-loop-func": "warn", |
||||||
|
"no-mixed-operators": [ |
||||||
|
"warn", |
||||||
|
{ |
||||||
|
"groups": [ |
||||||
|
["&", "|", "^", "~", "<<", ">>", ">>>"], |
||||||
|
["==", "!=", "===", "!==", ">", ">=", "<", "<="], |
||||||
|
["&&", "||"], |
||||||
|
["in", "instanceof"] |
||||||
|
], |
||||||
|
"allowSamePrecedence": false |
||||||
|
} |
||||||
|
], |
||||||
|
"no-multi-str": "warn", |
||||||
|
"no-native-reassign": "warn", |
||||||
|
"no-negated-in-lhs": "warn", |
||||||
|
"no-new-func": "warn", |
||||||
|
"no-new-object": "warn", |
||||||
|
"no-new-symbol": "warn", |
||||||
|
"no-new-wrappers": "warn", |
||||||
|
"no-obj-calls": "warn", |
||||||
|
"no-octal": "warn", |
||||||
|
"no-octal-escape": "warn", |
||||||
|
"no-redeclare": "warn", |
||||||
|
"no-regex-spaces": "warn", |
||||||
|
"no-restricted-syntax": ["warn", "WithStatement"], |
||||||
|
"no-script-url": "warn", |
||||||
|
"no-self-assign": "warn", |
||||||
|
"no-self-compare": "warn", |
||||||
|
"no-sequences": "warn", |
||||||
|
"no-shadow-restricted-names": "warn", |
||||||
|
"no-sparse-arrays": "warn", |
||||||
|
"no-template-curly-in-string": "warn", |
||||||
|
"no-this-before-super": "warn", |
||||||
|
"no-throw-literal": "warn", |
||||||
|
"no-restricted-globals": [ |
||||||
|
"error", |
||||||
|
"addEventListener", |
||||||
|
"blur", |
||||||
|
"close", |
||||||
|
"closed", |
||||||
|
"confirm", |
||||||
|
"defaultStatus", |
||||||
|
"defaultstatus", |
||||||
|
"event", |
||||||
|
"external", |
||||||
|
"find", |
||||||
|
"focus", |
||||||
|
"frameElement", |
||||||
|
"frames", |
||||||
|
"history", |
||||||
|
"innerHeight", |
||||||
|
"innerWidth", |
||||||
|
"length", |
||||||
|
"location", |
||||||
|
"locationbar", |
||||||
|
"menubar", |
||||||
|
"moveBy", |
||||||
|
"moveTo", |
||||||
|
"name", |
||||||
|
"onblur", |
||||||
|
"onerror", |
||||||
|
"onfocus", |
||||||
|
"onload", |
||||||
|
"onresize", |
||||||
|
"onunload", |
||||||
|
"open", |
||||||
|
"opener", |
||||||
|
"opera", |
||||||
|
"outerHeight", |
||||||
|
"outerWidth", |
||||||
|
"pageXOffset", |
||||||
|
"pageYOffset", |
||||||
|
"parent", |
||||||
|
"print", |
||||||
|
"removeEventListener", |
||||||
|
"resizeBy", |
||||||
|
"resizeTo", |
||||||
|
"screen", |
||||||
|
"screenLeft", |
||||||
|
"screenTop", |
||||||
|
"screenX", |
||||||
|
"screenY", |
||||||
|
"scroll", |
||||||
|
"scrollbars", |
||||||
|
"scrollBy", |
||||||
|
"scrollTo", |
||||||
|
"scrollX", |
||||||
|
"scrollY", |
||||||
|
"self", |
||||||
|
"status", |
||||||
|
"statusbar", |
||||||
|
"stop", |
||||||
|
"toolbar", |
||||||
|
"top" |
||||||
|
], |
||||||
|
"no-unexpected-multiline": "warn", |
||||||
|
"no-unreachable": "warn", |
||||||
|
"no-unused-expressions": [ |
||||||
|
"error", |
||||||
|
{ |
||||||
|
"allowShortCircuit": true, |
||||||
|
"allowTernary": true, |
||||||
|
"allowTaggedTemplates": true |
||||||
|
} |
||||||
|
], |
||||||
|
"no-unused-labels": "warn", |
||||||
|
"no-useless-computed-key": "warn", |
||||||
|
"no-useless-concat": "warn", |
||||||
|
"no-useless-escape": "warn", |
||||||
|
"no-useless-rename": [ |
||||||
|
"warn", |
||||||
|
{ |
||||||
|
"ignoreDestructuring": false, |
||||||
|
"ignoreImport": false, |
||||||
|
"ignoreExport": false |
||||||
|
} |
||||||
|
], |
||||||
|
"no-with": "warn", |
||||||
|
"no-whitespace-before-property": "warn", |
||||||
|
"react-hooks/exhaustive-deps": "warn", |
||||||
|
"require-yield": "warn", |
||||||
|
"rest-spread-spacing": ["warn", "never"], |
||||||
|
"strict": ["warn", "never"], |
||||||
|
"unicode-bom": ["warn", "never"], |
||||||
|
"use-isnan": "warn", |
||||||
|
"valid-typeof": "warn", |
||||||
|
"no-restricted-properties": [ |
||||||
|
"error", |
||||||
|
{ |
||||||
|
"object": "require", |
||||||
|
"property": "ensure", |
||||||
|
"message": "Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"object": "System", |
||||||
|
"property": "import", |
||||||
|
"message": "Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting" |
||||||
|
} |
||||||
|
], |
||||||
|
"getter-return": "warn", |
||||||
|
"import/first": "error", |
||||||
|
"import/no-amd": "error", |
||||||
|
"import/no-webpack-loader-syntax": "error", |
||||||
|
"react/forbid-foreign-prop-types": ["warn", { "allowInPropTypes": true }], |
||||||
|
"react/jsx-no-comment-textnodes": "warn", |
||||||
|
"react/jsx-no-duplicate-props": "warn", |
||||||
|
"react/jsx-no-target-blank": "warn", |
||||||
|
"react/jsx-no-undef": "error", |
||||||
|
"react/jsx-pascal-case": ["warn", { "allowAllCaps": true, "ignore": [] }], |
||||||
|
"react/jsx-uses-react": "warn", |
||||||
|
"react/jsx-uses-vars": "warn", |
||||||
|
"react/no-danger-with-children": "warn", |
||||||
|
"react/no-direct-mutation-state": "warn", |
||||||
|
"react/no-is-mounted": "warn", |
||||||
|
"react/no-typos": "error", |
||||||
|
"react/react-in-jsx-scope": "error", |
||||||
|
"react/require-render-return": "error", |
||||||
|
"react/style-prop-object": "warn", |
||||||
|
"react/jsx-no-useless-fragment": "warn", |
||||||
|
"jsx-a11y/accessible-emoji": "warn", |
||||||
|
"jsx-a11y/alt-text": "warn", |
||||||
|
"jsx-a11y/anchor-has-content": "warn", |
||||||
|
"jsx-a11y/anchor-is-valid": [ |
||||||
|
"warn", |
||||||
|
{ "aspects": ["noHref", "invalidHref"] } |
||||||
|
], |
||||||
|
"jsx-a11y/aria-activedescendant-has-tabindex": "warn", |
||||||
|
"jsx-a11y/aria-props": "warn", |
||||||
|
"jsx-a11y/aria-proptypes": "warn", |
||||||
|
"jsx-a11y/aria-role": "warn", |
||||||
|
"jsx-a11y/aria-unsupported-elements": "warn", |
||||||
|
"jsx-a11y/heading-has-content": "warn", |
||||||
|
"jsx-a11y/iframe-has-title": "warn", |
||||||
|
"jsx-a11y/img-redundant-alt": "warn", |
||||||
|
"jsx-a11y/no-access-key": "warn", |
||||||
|
"jsx-a11y/no-distracting-elements": "warn", |
||||||
|
"jsx-a11y/no-redundant-roles": "warn", |
||||||
|
"jsx-a11y/role-has-required-aria-props": "warn", |
||||||
|
"jsx-a11y/role-supports-aria-props": "warn", |
||||||
|
"jsx-a11y/scope": "warn", |
||||||
|
"react-hooks/rules-of-hooks": "error", |
||||||
|
"default-case": "off", |
||||||
|
"no-dupe-class-members": "off", |
||||||
|
"no-undef": "off", |
||||||
|
"@typescript-eslint/consistent-type-assertions": "warn", |
||||||
|
"no-array-constructor": "off", |
||||||
|
"@typescript-eslint/no-array-constructor": "warn", |
||||||
|
"@typescript-eslint/no-namespace": "error", |
||||||
|
"no-use-before-define": "off", |
||||||
|
"@typescript-eslint/no-use-before-define": [ |
||||||
|
"warn", |
||||||
|
{ |
||||||
|
"functions": false, |
||||||
|
"classes": false, |
||||||
|
"variables": false, |
||||||
|
"typedefs": false |
||||||
|
} |
||||||
|
], |
||||||
|
"no-unused-vars": "off", |
||||||
|
"@typescript-eslint/no-unused-vars": [ |
||||||
|
"warn", |
||||||
|
{ "args": "none", "ignoreRestSiblings": true } |
||||||
|
], |
||||||
|
"no-useless-constructor": "off", |
||||||
|
"@typescript-eslint/no-useless-constructor": "warn" |
||||||
|
}, |
||||||
|
"env": { |
||||||
|
"browser": true, |
||||||
|
"commonjs": true, |
||||||
|
"es6": true, |
||||||
|
"jest": true, |
||||||
|
"node": true |
||||||
|
}, |
||||||
|
"settings": { "react": { "version": "detect" } }, |
||||||
|
"plugins": ["import", "jsx-a11y", "react", "react-hooks"], |
||||||
|
"extends": ["../../../.eslintrc"], |
||||||
|
"ignorePatterns": ["!**/*"] |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
# debugger-ui |
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev). |
||||||
|
|
||||||
|
## Running unit tests |
||||||
|
|
||||||
|
Run `nx test debugger-ui` to execute the unit tests via [Jest](https://jestjs.io). |
@ -0,0 +1,14 @@ |
|||||||
|
{ |
||||||
|
"presets": [ |
||||||
|
[ |
||||||
|
"@babel/preset-env", |
||||||
|
{ |
||||||
|
"targets": { |
||||||
|
"node": "current" |
||||||
|
} |
||||||
|
} |
||||||
|
], |
||||||
|
"@babel/preset-typescript", |
||||||
|
"@babel/preset-react" |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
module.exports = { |
||||||
|
name: 'debugger-ui', |
||||||
|
preset: '../../jest.config.js', |
||||||
|
transform: { |
||||||
|
'^.+\\.[tj]sx?$': [ |
||||||
|
'babel-jest', |
||||||
|
{ cwd: __dirname, configFile: './babel-jest.config.json' } |
||||||
|
] |
||||||
|
}, |
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], |
||||||
|
coverageDirectory: '../../coverage/libs/debugger-ui' |
||||||
|
}; |
@ -0,0 +1,58 @@ |
|||||||
|
import React, { useState, useEffect } from 'react' |
||||||
|
import { ExtractData, ExtractFunc } from '../types' |
||||||
|
|
||||||
|
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) |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
setData(data) |
||||||
|
|
||||||
|
return () => { |
||||||
|
setData(null) |
||||||
|
} |
||||||
|
}, [json, extractFunc]) |
||||||
|
|
||||||
|
const extractDataDefault: ExtractFunc = (item, parent?) => { |
||||||
|
const ret: ExtractData = {} |
||||||
|
|
||||||
|
if (item instanceof Array) { |
||||||
|
ret.children = item.map((item, index) => { |
||||||
|
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]} |
||||||
|
}) |
||||||
|
ret.self = 'Object' |
||||||
|
ret.isNode = true |
||||||
|
ret.isLeaf = false |
||||||
|
} else { |
||||||
|
ret.self = item |
||||||
|
ret.children = null |
||||||
|
ret.isNode = false |
||||||
|
ret.isLeaf = true |
||||||
|
} |
||||||
|
return ret |
||||||
|
} |
||||||
|
|
||||||
|
return data |
||||||
|
} |
||||||
|
|
||||||
|
export default useExtractData |
@ -0,0 +1 @@ |
|||||||
|
export * from './lib/debugger-ui' |
@ -0,0 +1,22 @@ |
|||||||
|
.buttons { |
||||||
|
display: flex; |
||||||
|
flex-wrap: wrap; |
||||||
|
} |
||||||
|
.stepButtons { |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
} |
||||||
|
.stepButton { |
||||||
|
} |
||||||
|
.jumpButtons { |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
} |
||||||
|
.jumpButton { |
||||||
|
} |
||||||
|
.navigator { |
||||||
|
} |
||||||
|
.navigator:hover { |
||||||
|
} |
@ -0,0 +1,78 @@ |
|||||||
|
import React, { useState, useEffect } from 'react' |
||||||
|
import './button-navigator.css' |
||||||
|
|
||||||
|
export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward, stepOverForward, jumpOut, jumpPreviousBreakpoint, jumpNextBreakpoint, jumpToException, revertedReason, stepState, jumpOutDisabled }) => { |
||||||
|
const [state, setState] = useState({ |
||||||
|
intoBackDisabled: true, |
||||||
|
overBackDisabled: true, |
||||||
|
intoForwardDisabled: true, |
||||||
|
overForwardDisabled: true, |
||||||
|
jumpOutDisabled: true, |
||||||
|
jumpNextBreakpointDisabled: true, |
||||||
|
jumpPreviousBreakpointDisabled: true |
||||||
|
}) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
stepChanged(stepState, jumpOutDisabled) |
||||||
|
}, [stepState, jumpOutDisabled]) |
||||||
|
|
||||||
|
const reset = () => { |
||||||
|
setState(() => { |
||||||
|
return { |
||||||
|
intoBackDisabled: true, |
||||||
|
overBackDisabled: true, |
||||||
|
intoForwardDisabled: true, |
||||||
|
overForwardDisabled: true, |
||||||
|
jumpOutDisabled: true, |
||||||
|
jumpNextBreakpointDisabled: true, |
||||||
|
jumpPreviousBreakpointDisabled: true |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const stepChanged = (stepState, jumpOutDisabled) => { |
||||||
|
if (stepState === 'invalid') { |
||||||
|
// TODO: probably not necessary, already implicit done in the next steps
|
||||||
|
reset() |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
setState(() => { |
||||||
|
return { |
||||||
|
intoBackDisabled: stepState === 'initial', |
||||||
|
overBackDisabled: stepState === 'initial', |
||||||
|
jumpPreviousBreakpointDisabled: stepState === 'initial', |
||||||
|
intoForwardDisabled: stepState === 'end', |
||||||
|
overForwardDisabled: stepState === 'end', |
||||||
|
jumpNextBreakpointDisabled: stepState === 'end', |
||||||
|
jumpOutDisabled: jumpOutDisabled ? jumpOutDisabled : true |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="buttons"> |
||||||
|
<div className="stepButtons btn-group py-1"> |
||||||
|
<button id='overback' className='btn btn-primary btn-sm navigator stepButton fas fa-reply' title='Step over back' onClick={() => { stepOverBack && stepOverBack() }} disabled={state.overBackDisabled} ></button> |
||||||
|
<button id='intoback' data-id="buttonNavigatorIntoBack" className='btn btn-primary btn-sm navigator stepButton fas fa-level-up-alt' title='Step back' onClick={() => { stepIntoBack && stepIntoBack() }} disabled={state.intoBackDisabled}></button> |
||||||
|
<button id='intoforward' data-id="buttonNavigatorIntoForward" className='btn btn-primary btn-sm navigator stepButton fas fa-level-down-alt' title='Step into' onClick={() => { stepIntoForward && stepIntoForward() }} disabled={state.intoForwardDisabled}></button> |
||||||
|
<button id='overforward' className='btn btn-primary btn-sm navigator stepButton fas fa-share' title='Step over forward' onClick={() => { stepOverForward && stepOverForward() }} disabled={state.overForwardDisabled}></button> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="jumpButtons btn-group py-1"> |
||||||
|
<button className='btn btn-primary btn-sm navigator jumpButton fas fa-step-backward' id='jumppreviousbreakpoint' data-id="buttonNavigatorJumpPreviousBreakpoint" title='Jump to the previous breakpoint' onClick={() => { jumpPreviousBreakpoint && jumpPreviousBreakpoint() }} disabled={state.jumpPreviousBreakpointDisabled}></button> |
||||||
|
<button className='btn btn-primary btn-sm navigator jumpButton fas fa-eject' id='jumpout' title='Jump out' onClick={() => { jumpOut && jumpOut() }} disabled={state.jumpOutDisabled}></button> |
||||||
|
<button className='btn btn-primary btn-sm navigator jumpButton fas fa-step-forward' id='jumpnextbreakpoint' data-id="buttonNavigatorJumpNextBreakpoint" title='Jump to the next breakpoint' onClick={() => { jumpNextBreakpoint && jumpNextBreakpoint() }} disabled={state.jumpNextBreakpointDisabled}></button> |
||||||
|
</div> |
||||||
|
<div id='reverted' style={{ display: revertedReason === '' ? 'none' : 'block' }}> |
||||||
|
<button id='jumptoexception' title='Jump to exception' className='btn btn-danger btn-sm navigator button fas fa-exclamation-triangle' onClick={() => { jumpToException && jumpToException() }} disabled={state.jumpOutDisabled}> |
||||||
|
</button> |
||||||
|
<span>State changes made during this call will be reverted.</span> |
||||||
|
<span id='outofgas' style={{ display: revertedReason === 'outofgas' ? 'inline' : 'none' }}>This call will run out of gas.</span> |
||||||
|
<span id='parenthasthrown' style={{ display: revertedReason === 'parenthasthrown' ? 'inline' : 'none' }}>The parent call will throw an exception</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default ButtonNavigation |
@ -0,0 +1,16 @@ |
|||||||
|
.statusMessage { |
||||||
|
margin-left: 15px; |
||||||
|
} |
||||||
|
.debuggerLabel { |
||||||
|
margin-bottom: 2px; |
||||||
|
font-size: 11px; |
||||||
|
line-height: 12px; |
||||||
|
text-transform: uppercase; |
||||||
|
} |
||||||
|
.debuggerConfig { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
.debuggerConfig label { |
||||||
|
margin: 0; |
||||||
|
} |
@ -0,0 +1,284 @@ |
|||||||
|
import React, { useState, useEffect } from 'react' |
||||||
|
import TxBrowser from './tx-browser/tx-browser' |
||||||
|
import StepManager from './step-manager/step-manager' |
||||||
|
import VmDebugger from './vm-debugger/vm-debugger' |
||||||
|
import VmDebuggerHead from './vm-debugger/vm-debugger-head' |
||||||
|
import remixDebug, { TransactionDebugger as Debugger } from '@remix-project/remix-debug' |
||||||
|
/* eslint-disable-next-line */ |
||||||
|
import globalRegistry from '../../../../../apps/remix-ide/src/global/registry' |
||||||
|
import './debugger-ui.css' |
||||||
|
|
||||||
|
export const DebuggerUI = ({ debuggerModule }) => { |
||||||
|
const init = remixDebug.init |
||||||
|
const [state, setState] = useState({ |
||||||
|
isActive: false, |
||||||
|
statusMessage: '', |
||||||
|
debugger: null, |
||||||
|
currentReceipt: { |
||||||
|
contractAddress: null, |
||||||
|
to: null |
||||||
|
}, |
||||||
|
blockNumber: null, |
||||||
|
txNumber: '', |
||||||
|
debugging: false, |
||||||
|
opt: { |
||||||
|
debugWithGeneratedSources: false |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
return unLoad() |
||||||
|
}, []) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
debug(debuggerModule.debugHash) |
||||||
|
}, [debuggerModule.debugHash]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
getTrace(debuggerModule.getTraceHash) |
||||||
|
}, [debuggerModule.getTraceHash]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (debuggerModule.removeHighlights) deleteHighlights() |
||||||
|
}, [debuggerModule.removeHighlights]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const setEditor = () => { |
||||||
|
const editor = globalRegistry.get('editor').api |
||||||
|
|
||||||
|
editor.event.register('breakpointCleared', (fileName, row) => { |
||||||
|
if (state.debugger) state.debugger.breakPointManager.remove({fileName: fileName, row: row}) |
||||||
|
}) |
||||||
|
|
||||||
|
editor.event.register('breakpointAdded', (fileName, row) => { |
||||||
|
if (state.debugger) { |
||||||
|
state.debugger.breakPointManager.add({fileName: fileName, row: row}) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
editor.event.register('contentChanged', () => { |
||||||
|
unLoad() |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
setEditor() |
||||||
|
}, [state.debugger]) |
||||||
|
|
||||||
|
const fetchContractAndCompile = (address, receipt) => { |
||||||
|
const target = (address && remixDebug.traceHelper.isContractCreation(address)) ? receipt.contractAddress : address |
||||||
|
|
||||||
|
return debuggerModule.call('fetchAndCompile', 'resolve', target || receipt.contractAddress || receipt.to, '.debug', debuggerModule.blockchain.web3()) |
||||||
|
} |
||||||
|
|
||||||
|
const listenToEvents = (debuggerInstance, currentReceipt) => { |
||||||
|
if (!debuggerInstance) return |
||||||
|
|
||||||
|
debuggerInstance.event.register('debuggerStatus', async (isActive) => { |
||||||
|
await debuggerModule.call('editor', 'discardHighlight') |
||||||
|
setState( prevState => { |
||||||
|
return { ...prevState, isActive } |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
debuggerInstance.event.register('newSourceLocation', async (lineColumnPos, rawLocation, generatedSources) => { |
||||||
|
const contracts = await fetchContractAndCompile( |
||||||
|
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.call('fileManager', 'getFile', path, source.contents) |
||||||
|
} catch (e) { |
||||||
|
console.log('unable to fetch generated sources, the file probably doesn\'t exist yet', e) |
||||||
|
} |
||||||
|
if (content !== source.contents) { |
||||||
|
await debuggerModule.call('fileManager', 'setFile', path, source.contents) |
||||||
|
} |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if (path) { |
||||||
|
await debuggerModule.call('editor', 'discardHighlight') |
||||||
|
await debuggerModule.call('editor', 'highlight', lineColumnPos, path) |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
debuggerInstance.event.register('debuggerUnloaded', () => unLoad()) |
||||||
|
} |
||||||
|
|
||||||
|
const requestDebug = (blockNumber, txNumber, tx) => { |
||||||
|
startDebugging(blockNumber, txNumber, tx) |
||||||
|
} |
||||||
|
|
||||||
|
const unloadRequested = (blockNumber, txIndex, tx) => { |
||||||
|
unLoad() |
||||||
|
} |
||||||
|
|
||||||
|
const isDebuggerActive = () => { |
||||||
|
return state.isActive |
||||||
|
} |
||||||
|
|
||||||
|
const getDebugWeb3 = (): Promise<any> => { |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
debuggerModule.blockchain.detectNetwork((error, network) => { |
||||||
|
let web3 |
||||||
|
if (error || !network) { |
||||||
|
web3 = init.web3DebugNode(debuggerModule.blockchain.web3()) |
||||||
|
} else { |
||||||
|
const webDebugNode = init.web3DebugNode(network.name) |
||||||
|
web3 = !webDebugNode ? debuggerModule.blockchain.web3() : webDebugNode |
||||||
|
} |
||||||
|
init.extendWeb3(web3) |
||||||
|
resolve(web3) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const unLoad = () => { |
||||||
|
if (state.debugger) state.debugger.unload() |
||||||
|
setState(prevState => { |
||||||
|
return { |
||||||
|
...prevState, |
||||||
|
isActive: false, |
||||||
|
statusMessage: '', |
||||||
|
debugger: null, |
||||||
|
currentReceipt: { |
||||||
|
contractAddress: null, |
||||||
|
to: null |
||||||
|
}, |
||||||
|
blockNumber: null, |
||||||
|
ready: { |
||||||
|
vmDebugger: false, |
||||||
|
vmDebuggerHead: false |
||||||
|
}, |
||||||
|
debugging: false |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const startDebugging = async (blockNumber, txNumber, tx) => { |
||||||
|
if (state.debugger) unLoad() |
||||||
|
if (!txNumber) return |
||||||
|
const web3 = await getDebugWeb3() |
||||||
|
const currentReceipt = await web3.eth.getTransactionReceipt(txNumber) |
||||||
|
const debuggerInstance = new Debugger({ |
||||||
|
web3, |
||||||
|
offsetToLineColumnConverter: globalRegistry.get('offsettolinecolumnconverter').api, |
||||||
|
compilationResult: async (address) => { |
||||||
|
try { |
||||||
|
return await fetchContractAndCompile(address, currentReceipt) |
||||||
|
} catch (e) { |
||||||
|
console.error(e) |
||||||
|
} |
||||||
|
return null |
||||||
|
}, |
||||||
|
debugWithGeneratedSources: state.opt.debugWithGeneratedSources |
||||||
|
}) |
||||||
|
debuggerInstance.debug(blockNumber, txNumber, tx, () => { |
||||||
|
listenToEvents(debuggerInstance, currentReceipt) |
||||||
|
setState(prevState => { |
||||||
|
return { |
||||||
|
...prevState, |
||||||
|
blockNumber, |
||||||
|
txNumber, |
||||||
|
debugging: true, |
||||||
|
currentReceipt, |
||||||
|
debugger: debuggerInstance |
||||||
|
} |
||||||
|
}) |
||||||
|
}).catch((error) => { |
||||||
|
// toaster(error, null, null)
|
||||||
|
unLoad() |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const debug = (txHash) => { |
||||||
|
startDebugging(null, txHash, null) |
||||||
|
} |
||||||
|
|
||||||
|
const getTrace = (hash) => { |
||||||
|
if (!hash) return |
||||||
|
return new Promise(async (resolve, reject) => { /* eslint-disable-line */ |
||||||
|
const web3 = await getDebugWeb3() |
||||||
|
const currentReceipt = await web3.eth.getTransactionReceipt(hash) |
||||||
|
const debug = new Debugger({ |
||||||
|
web3, |
||||||
|
offsetToLineColumnConverter: globalRegistry.get('offsettolinecolumnconverter').api, |
||||||
|
compilationResult: async (address) => { |
||||||
|
try { |
||||||
|
return await fetchContractAndCompile(address, currentReceipt) |
||||||
|
} catch (e) { |
||||||
|
console.error(e) |
||||||
|
} |
||||||
|
return null |
||||||
|
}, |
||||||
|
debugWithGeneratedSources: false |
||||||
|
}) |
||||||
|
|
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, currentReceipt } |
||||||
|
}) |
||||||
|
|
||||||
|
debug.debugger.traceManager.traceRetriever.getTrace(hash, (error, trace) => { |
||||||
|
if (error) return reject(error) |
||||||
|
resolve(trace) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const deleteHighlights = async () => { |
||||||
|
await debuggerModule.call('editor', 'discardHighlight') |
||||||
|
} |
||||||
|
|
||||||
|
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, |
||||||
|
} |
||||||
|
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 |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<div className="px-2"> |
||||||
|
<div className="mt-3"> |
||||||
|
<p className="mt-2 debuggerLabel">Debugger Configuration</p> |
||||||
|
<div className="mt-2 debuggerConfig custom-control custom-checkbox"> |
||||||
|
<input className="custom-control-input" id="debugGeneratedSourcesInput" onChange={({ target: { checked } }) => { |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, opt: { debugWithGeneratedSources: checked }} |
||||||
|
}) |
||||||
|
}} type="checkbox" title="Debug with generated sources" /> |
||||||
|
<label data-id="debugGeneratedSourcesLabel" className="form-check-label custom-control-label" htmlFor="debugGeneratedSourcesInput">Debug generated sources if available (from Solidity v0.7.2)</label> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<TxBrowser requestDebug={ requestDebug } unloadRequested={ unloadRequested } transactionNumber={ state.txNumber } debugging={ state.debugging } /> |
||||||
|
{ state.debugging && <StepManager stepManager={ stepManager } /> } |
||||||
|
{ state.debugging && <VmDebuggerHead vmDebugger={ vmDebugger } /> } |
||||||
|
</div> |
||||||
|
{ state.debugging && <div className="statusMessage">{ state.statusMessage }</div> } |
||||||
|
{ state.debugging && <VmDebugger vmDebugger={ vmDebugger } /> } |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default DebuggerUI |
@ -0,0 +1,42 @@ |
|||||||
|
import React, { useState, useEffect } from 'react' |
||||||
|
|
||||||
|
export const Slider = ({ jumpTo, sliderValue, traceLength }) => { |
||||||
|
const [state, setState] = useState({ |
||||||
|
currentValue: 0 |
||||||
|
}) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
setValue(sliderValue) |
||||||
|
}, [sliderValue]) |
||||||
|
|
||||||
|
const setValue = (value) => { |
||||||
|
if (value === state.currentValue) return |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, currentValue: value } |
||||||
|
}) |
||||||
|
jumpTo && jumpTo(value) |
||||||
|
} |
||||||
|
|
||||||
|
const handleChange = (e) => { |
||||||
|
const value = parseInt(e.target.value) |
||||||
|
|
||||||
|
setValue(value) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<input id='slider' |
||||||
|
data-id="slider" |
||||||
|
className='w-100 my-0' |
||||||
|
type='range' |
||||||
|
min={0} |
||||||
|
max={traceLength ? traceLength - 1 : 0} |
||||||
|
value={state.currentValue} |
||||||
|
onChange={handleChange} |
||||||
|
disabled={traceLength ? traceLength === 0 : true} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default Slider |
@ -0,0 +1,51 @@ |
|||||||
|
import React, { useState, useEffect } from 'react' |
||||||
|
import Slider from '../slider/slider' |
||||||
|
import ButtonNavigator from '../button-navigator/button-navigator' |
||||||
|
|
||||||
|
export const StepManager = ({ stepManager: { jumpTo, traceLength, stepIntoBack, stepIntoForward, stepOverBack, stepOverForward, jumpOut, jumpNextBreakpoint, jumpPreviousBreakpoint, jumpToException, registerEvent } }) => { |
||||||
|
const [state, setState] = useState({ |
||||||
|
sliderValue: 0, |
||||||
|
revertWarning: '', |
||||||
|
stepState: '', |
||||||
|
jumpOutDisabled: true |
||||||
|
}) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
registerEvent && registerEvent('revertWarning', setRevertWarning) |
||||||
|
registerEvent && registerEvent('stepChanged', updateStep) |
||||||
|
}, [registerEvent]) |
||||||
|
|
||||||
|
const setRevertWarning = (warning) => { |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, revertWarning: warning } |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const updateStep = (step, stepState, jumpOutDisabled) => { |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, sliderValue: step, stepState, jumpOutDisabled } |
||||||
|
}) |
||||||
|
} |
||||||
|
const { sliderValue, revertWarning, stepState, jumpOutDisabled } = state |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="py-1"> |
||||||
|
<Slider jumpTo={jumpTo} sliderValue={sliderValue} traceLength={traceLength} /> |
||||||
|
<ButtonNavigator
|
||||||
|
stepIntoBack={stepIntoBack} |
||||||
|
stepIntoForward={stepIntoForward} |
||||||
|
stepOverBack={stepOverBack} |
||||||
|
stepOverForward={stepOverForward} |
||||||
|
revertedReason={revertWarning} |
||||||
|
stepState={stepState} |
||||||
|
jumpOutDisabled={jumpOutDisabled} |
||||||
|
jumpOut={jumpOut} |
||||||
|
jumpNextBreakpoint={jumpNextBreakpoint} |
||||||
|
jumpPreviousBreakpoint={jumpPreviousBreakpoint} |
||||||
|
jumpToException={jumpToException} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default StepManager |
@ -0,0 +1,24 @@ |
|||||||
|
.container { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
} |
||||||
|
.txContainer { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
} |
||||||
|
.txinput { |
||||||
|
width: inherit; |
||||||
|
font-size: small; |
||||||
|
white-space: nowrap; |
||||||
|
overflow: hidden; |
||||||
|
text-overflow: ellipsis; |
||||||
|
} |
||||||
|
.txbutton { |
||||||
|
width: inherit; |
||||||
|
} |
||||||
|
.txbutton:hover { |
||||||
|
} |
||||||
|
.vmargin { |
||||||
|
margin-top: 10px; |
||||||
|
margin-bottom: 10px; |
||||||
|
} |
@ -0,0 +1,78 @@ |
|||||||
|
import React, { useState, useEffect } from 'react' |
||||||
|
import './tx-browser.css' |
||||||
|
|
||||||
|
export const TxBrowser = ({ requestDebug, unloadRequested, transactionNumber, debugging }) => { |
||||||
|
const [state, setState] = useState({ |
||||||
|
txNumber: '' |
||||||
|
}) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
setState(prevState => { |
||||||
|
return { |
||||||
|
...prevState, |
||||||
|
txNumber: transactionNumber |
||||||
|
} |
||||||
|
}) |
||||||
|
}, [transactionNumber]) |
||||||
|
|
||||||
|
const handleSubmit = () => { |
||||||
|
if (debugging) { |
||||||
|
unload() |
||||||
|
} else { |
||||||
|
requestDebug(undefined, state.txNumber) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const unload = () => { |
||||||
|
unloadRequested() |
||||||
|
} |
||||||
|
|
||||||
|
const txInputChanged = (value) => { |
||||||
|
// todo check validation of txnumber in the input element, use
|
||||||
|
// required
|
||||||
|
// oninvalid="setCustomValidity('Please provide a valid transaction number, must start with 0x and have length of 22')"
|
||||||
|
// pattern="^0[x,X]+[0-9a-fA-F]{22}"
|
||||||
|
// this.state.txNumberInput.setCustomValidity('')
|
||||||
|
|
||||||
|
setState(prevState => { |
||||||
|
return { |
||||||
|
...prevState, |
||||||
|
txNumber: value |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="container px-0"> |
||||||
|
<div className="txContainer"> |
||||||
|
<div className="py-1 d-flex justify-content-center w-100 input-group"> |
||||||
|
<input |
||||||
|
value={state.txNumber} |
||||||
|
className="form-control m-0 txinput" |
||||||
|
id='txinput' |
||||||
|
type='text' |
||||||
|
onChange={({ target: { value } }) => txInputChanged(value)} |
||||||
|
placeholder={'Transaction hash, should start with 0x'} |
||||||
|
data-id="debuggerTransactionInput" |
||||||
|
disabled={debugging} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className="d-flex justify-content-center w-100 btn-group py-1"> |
||||||
|
<button |
||||||
|
className="btn btn-primary btn-sm txbutton" |
||||||
|
id="load" |
||||||
|
title={debugging ? 'Stop debugging' : 'Start debugging'} |
||||||
|
onClick={handleSubmit} |
||||||
|
data-id="debuggerTransactionStartButton" |
||||||
|
disabled={!state.txNumber ? true : false } |
||||||
|
> |
||||||
|
{ debugging ? 'Stop' : 'Start' } debugging |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<span id='error'></span> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default TxBrowser |
@ -0,0 +1,61 @@ |
|||||||
|
import React, { useState, useRef, useEffect, useReducer } from 'react' |
||||||
|
import { initialState, reducer } from '../../reducers/assembly-items' |
||||||
|
import './styles/assembly-items.css' |
||||||
|
|
||||||
|
export const AssemblyItems = ({ registerEvent }) => { |
||||||
|
const [assemblyItems, dispatch] = useReducer(reducer, initialState) |
||||||
|
const [selectedItem, setSelectedItem] = useState(0) |
||||||
|
const refs = useRef({}) |
||||||
|
const asmItemsRef = useRef(null) |
||||||
|
|
||||||
|
useEffect(()=>{ |
||||||
|
registerEvent && registerEvent('codeManagerChanged', (code, address, index) => { |
||||||
|
dispatch({ type: 'FETCH_OPCODES_SUCCESS', payload: { code, address, index } }) |
||||||
|
}) |
||||||
|
}, []) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (selectedItem !== assemblyItems.index) { |
||||||
|
indexChanged(assemblyItems.index) |
||||||
|
} |
||||||
|
}, [assemblyItems.index]) |
||||||
|
|
||||||
|
const indexChanged = (index: number) => { |
||||||
|
if (index < 0) return |
||||||
|
let currentItem = refs.current[selectedItem] ? refs.current[selectedItem] : null |
||||||
|
|
||||||
|
if (currentItem) { |
||||||
|
currentItem.removeAttribute('selected') |
||||||
|
currentItem.removeAttribute('style') |
||||||
|
if (currentItem.firstChild) { |
||||||
|
currentItem.firstChild.removeAttribute('style') |
||||||
|
} |
||||||
|
const codeView = asmItemsRef.current |
||||||
|
|
||||||
|
currentItem = codeView.children[index] |
||||||
|
currentItem.style.setProperty('border-color', 'var(--primary)') |
||||||
|
currentItem.style.setProperty('border-style', 'solid') |
||||||
|
currentItem.setAttribute('selected', 'selected') |
||||||
|
codeView.scrollTop = currentItem.offsetTop - parseInt(codeView.offsetTop) |
||||||
|
setSelectedItem(index) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="border rounded px-1 mt-1 bg-light"> |
||||||
|
<div className='dropdownpanel'> |
||||||
|
<div className='dropdowncontent'> |
||||||
|
<div className="pl-2 my-1 small instructions" id='asmitems' ref={asmItemsRef}> |
||||||
|
{ |
||||||
|
assemblyItems.display.map((item, i) => { |
||||||
|
return <div className="px-1" key={i} ref={ref => refs.current[i] = ref}><span>{item}</span></div> |
||||||
|
}) |
||||||
|
} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default AssemblyItems |
@ -0,0 +1,12 @@ |
|||||||
|
import React from 'react' |
||||||
|
import DropdownPanel from './dropdown-panel' |
||||||
|
|
||||||
|
export const CalldataPanel = ({ calldata }) => { |
||||||
|
return ( |
||||||
|
<div id='calldatapanel'> |
||||||
|
<DropdownPanel dropdownName='Call Data' calldata={calldata || {}} /> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default CalldataPanel |
@ -0,0 +1,12 @@ |
|||||||
|
import React from 'react' |
||||||
|
import DropdownPanel from './dropdown-panel' |
||||||
|
|
||||||
|
export const CallstackPanel = ({ calldata }) => { |
||||||
|
return ( |
||||||
|
<div id='callstackpanel'> |
||||||
|
<DropdownPanel dropdownName='Call Stack' calldata={calldata || {}} /> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default CallstackPanel |
@ -0,0 +1,43 @@ |
|||||||
|
import React, { useState, useEffect } from 'react' |
||||||
|
import AssemblyItems from './assembly-items' |
||||||
|
|
||||||
|
export const CodeListView = ({ registerEvent }) => { |
||||||
|
const [state, setState] = useState({ |
||||||
|
code: [], |
||||||
|
address: '', |
||||||
|
itemSelected: null, |
||||||
|
index: null |
||||||
|
}) |
||||||
|
|
||||||
|
const indexChanged = (index) => { |
||||||
|
if(index < 0) return |
||||||
|
setState(prevState => { |
||||||
|
return { |
||||||
|
...prevState, |
||||||
|
index |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const changed = (code, address, index) => { |
||||||
|
if (state.address === address) { |
||||||
|
return indexChanged(index) |
||||||
|
} |
||||||
|
setState(prevState => { |
||||||
|
return { |
||||||
|
...prevState, |
||||||
|
code, |
||||||
|
address |
||||||
|
} |
||||||
|
}) |
||||||
|
indexChanged(index) |
||||||
|
}
|
||||||
|
|
||||||
|
return ( |
||||||
|
<div id='asmcodes'> |
||||||
|
<AssemblyItems registerEvent={registerEvent} /> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default CodeListView |
@ -0,0 +1,211 @@ |
|||||||
|
import React, { useState, useEffect, useReducer } from 'react' |
||||||
|
import { TreeView, TreeViewItem } from '@remix-ui/tree-view' |
||||||
|
import { DropdownPanelProps, ExtractData, ExtractFunc } from '../../types' |
||||||
|
import { CopyToClipboard } from '@remix-ui/clipboard' |
||||||
|
import { initialState, reducer } from '../../reducers/calldata' |
||||||
|
import './styles/dropdown-panel.css' |
||||||
|
|
||||||
|
export const DropdownPanel = (props: DropdownPanelProps) => { |
||||||
|
const [calldataObj, dispatch] = useReducer(reducer, initialState) |
||||||
|
const { dropdownName, dropdownMessage, calldata, header, loading, extractFunc, formatSelfFunc, registerEvent, triggerEvent, loadMoreEvent, loadMoreCompletedEvent } = props |
||||||
|
const extractDataDefault: ExtractFunc = (item, parent?) => { |
||||||
|
const ret: ExtractData = {} |
||||||
|
|
||||||
|
if (item instanceof Array) { |
||||||
|
ret.children = item.map((item, index) => { |
||||||
|
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]} |
||||||
|
}) |
||||||
|
ret.self = 'Object' |
||||||
|
ret.isNode = true |
||||||
|
ret.isLeaf = false |
||||||
|
} else { |
||||||
|
ret.self = item |
||||||
|
ret.children = null |
||||||
|
ret.isNode = false |
||||||
|
ret.isLeaf = true |
||||||
|
} |
||||||
|
return ret |
||||||
|
} |
||||||
|
const formatSelfDefault = (key: string | number, data: ExtractData) => { |
||||||
|
return ( |
||||||
|
<div className="d-flex mb-1 flex-row label_item"> |
||||||
|
<label className="small font-weight-bold pr-1 label_key">{key}:</label>
|
||||||
|
<label className="m-0 label_value">{data.self}</label> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
const [state, setState] = useState({ |
||||||
|
header: '', |
||||||
|
toggleDropdown: false, |
||||||
|
message: { |
||||||
|
innerText: 'No data available.', |
||||||
|
display: 'block' |
||||||
|
}, |
||||||
|
dropdownContent: { |
||||||
|
innerText: '', |
||||||
|
display: 'none' |
||||||
|
}, |
||||||
|
title: { |
||||||
|
innerText: '', |
||||||
|
display: 'none' |
||||||
|
}, |
||||||
|
copiableContent: '', |
||||||
|
updating: false, |
||||||
|
expandPath: [], |
||||||
|
data: null |
||||||
|
}) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
registerEvent && registerEvent(loadMoreCompletedEvent, (updatedCalldata) => { |
||||||
|
dispatch({ type: 'UPDATE_CALLDATA_SUCCESS', payload: updatedCalldata }) |
||||||
|
}) |
||||||
|
}, []) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
dispatch({ type: 'FETCH_CALLDATA_SUCCESS', payload: calldata }) |
||||||
|
}, [calldata]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
update(calldata) |
||||||
|
}, [calldataObj.calldata]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
message(dropdownMessage) |
||||||
|
}, [dropdownMessage]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (loading && !state.updating) setLoading() |
||||||
|
}, [loading]) |
||||||
|
|
||||||
|
const handleToggle = () => { |
||||||
|
setState(prevState => { |
||||||
|
return { |
||||||
|
...prevState, |
||||||
|
toggleDropdown: !prevState.toggleDropdown |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const handleExpand = (keyPath) => { |
||||||
|
if (!state.expandPath.includes(keyPath)) { |
||||||
|
state.expandPath.push(keyPath) |
||||||
|
} else { |
||||||
|
state.expandPath = state.expandPath.filter(path => !path.startsWith(keyPath)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const message = (message) => { |
||||||
|
if (message === state.message.innerText) return |
||||||
|
setState(prevState => { |
||||||
|
return { |
||||||
|
...prevState, |
||||||
|
message: { |
||||||
|
innerText: message, |
||||||
|
display: message ? 'block' : '' |
||||||
|
}, |
||||||
|
updating: false |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const setLoading = () => { |
||||||
|
setState(prevState => { |
||||||
|
return { |
||||||
|
...prevState, |
||||||
|
message: { |
||||||
|
innerText: '', |
||||||
|
display: 'none' |
||||||
|
}, |
||||||
|
dropdownContent: { |
||||||
|
...prevState.dropdownContent, |
||||||
|
display: 'none' |
||||||
|
}, |
||||||
|
copiableContent: '', |
||||||
|
updating: true |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const update = function (calldata) { |
||||||
|
let isEmpty = !calldata ? true : false |
||||||
|
|
||||||
|
if(calldata && Array.isArray(calldata) && calldata.length === 0) isEmpty = true |
||||||
|
else if(calldata && Object.keys(calldata).length === 0 && calldata.constructor === Object) isEmpty = true |
||||||
|
|
||||||
|
setState(prevState => { |
||||||
|
return { |
||||||
|
...prevState, |
||||||
|
dropdownContent: { |
||||||
|
...prevState.dropdownContent, |
||||||
|
display: 'block' |
||||||
|
}, |
||||||
|
copiableContent: JSON.stringify(calldata, null, '\t'), |
||||||
|
message: { |
||||||
|
innerText: isEmpty ? 'No data available' : '', |
||||||
|
display: isEmpty ? 'block' : 'none' |
||||||
|
}, |
||||||
|
updating: false, |
||||||
|
toggleDropdown: !isEmpty, |
||||||
|
data: calldata |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const renderData = (item: ExtractData, parent, key: string | number, keyPath: string) => { |
||||||
|
const data = extractFunc ? extractFunc(item, parent) : extractDataDefault(item, parent) |
||||||
|
const children = (data.children || []).map((child) => { |
||||||
|
return ( |
||||||
|
renderData(child.value, data, child.key, keyPath + '/' + child.key) |
||||||
|
) |
||||||
|
}) |
||||||
|
|
||||||
|
if (children && children.length > 0 ) { |
||||||
|
return ( |
||||||
|
<TreeViewItem id={`treeViewItem${key}`} key={keyPath} label={ formatSelfFunc ? formatSelfFunc(key, data) : formatSelfDefault(key, data) } onClick={() => handleExpand(keyPath)} expand={state.expandPath.includes(keyPath)}> |
||||||
|
<TreeView id={`treeView${key}`} key={keyPath}> |
||||||
|
{ children } |
||||||
|
{ data.hasNext && <TreeViewItem id={`treeViewLoadMore`} data-id={`treeViewLoadMore`} className="cursor_pointer" label="Load more" onClick={() => { triggerEvent(loadMoreEvent, [data.cursor]) }} /> } |
||||||
|
</TreeView> |
||||||
|
</TreeViewItem> |
||||||
|
) |
||||||
|
} else { |
||||||
|
return <TreeViewItem id={key.toString()} key={keyPath} label={ formatSelfFunc ? formatSelfFunc(key, data) : formatSelfDefault(key, data) } onClick={() => handleExpand(keyPath)} expand={state.expandPath.includes(keyPath)} /> |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const uniquePanelName = dropdownName.split(' ').join('') |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="border rounded px-1 mt-1 bg-light"> |
||||||
|
<div className="py-0 px-1 title"> |
||||||
|
<div className={state.toggleDropdown ? 'icon fas fa-caret-down' : 'icon fas fa-caret-right'} onClick={handleToggle}></div> |
||||||
|
<div className="name" data-id={`dropdownPanel${uniquePanelName}`} onClick={handleToggle}>{dropdownName}</div><span className="nameDetail" onClick={handleToggle}>{ header }</span> |
||||||
|
<CopyToClipboard content={state.copiableContent} data-id={`dropdownPanelCopyToClipboard${uniquePanelName}`} /> |
||||||
|
</div> |
||||||
|
<div className='dropdownpanel' style={{ display: state.toggleDropdown ? 'block' : 'none' }}> |
||||||
|
<i className="refresh fas fa-sync" style={{ display: state.updating ? 'inline-block' : 'none' }} aria-hidden="true"></i> |
||||||
|
<div className='dropdowncontent' style={{ display: state.dropdownContent.display }}> |
||||||
|
{ |
||||||
|
state.data && |
||||||
|
<TreeView id="treeView"> |
||||||
|
{ |
||||||
|
Object.keys(state.data).map((innerkey) => renderData(state.data[innerkey], state.data, innerkey, innerkey)) |
||||||
|
} |
||||||
|
</TreeView> |
||||||
|
} |
||||||
|
</div> |
||||||
|
<div className='dropdownrawcontent' hidden={true}>{ state.copiableContent }</div> |
||||||
|
<div className='message' style={{ display: state.message.display }}>{ state.message.innerText }</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default DropdownPanel |
@ -0,0 +1,12 @@ |
|||||||
|
import React from 'react' |
||||||
|
import { DropdownPanel } from './dropdown-panel' |
||||||
|
|
||||||
|
export const FullStoragesChanges = ({ calldata }) => { |
||||||
|
return ( |
||||||
|
<div id='fullstorageschangespanel'> |
||||||
|
<DropdownPanel dropdownName='Full Storages Changes' calldata={ calldata || {}} /> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default FullStoragesChanges |
@ -0,0 +1,19 @@ |
|||||||
|
import React, { useState, useEffect } from 'react' |
||||||
|
import DropdownPanel from './dropdown-panel' |
||||||
|
import { default as deepequal } from 'deep-equal' |
||||||
|
|
||||||
|
export const FunctionPanel = ({ data }) => { |
||||||
|
const [calldata, setCalldata] = useState(null) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (!deepequal(calldata, data)) setCalldata(data) |
||||||
|
}, [data]) |
||||||
|
|
||||||
|
return ( |
||||||
|
<div id="FunctionPanel"> |
||||||
|
<DropdownPanel dropdownName='Function Stack' calldata={calldata || {}} /> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default FunctionPanel |
@ -0,0 +1,10 @@ |
|||||||
|
import React from 'react' |
||||||
|
import DropdownPanel from './dropdown-panel' |
||||||
|
|
||||||
|
export const MemoryPanel = ({ calldata }) => { |
||||||
|
return ( |
||||||
|
<DropdownPanel dropdownName='Memory' calldata={calldata || {}} /> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default MemoryPanel |
@ -0,0 +1,62 @@ |
|||||||
|
import React, { useState, useEffect } from 'react' |
||||||
|
import DropdownPanel from './dropdown-panel' |
||||||
|
import { extractData } from '../../utils/solidityTypeFormatter' |
||||||
|
import { ExtractData } from '../../types' |
||||||
|
|
||||||
|
export const SolidityLocals = ({ data, message, registerEvent, triggerEvent }) => { |
||||||
|
const [calldata, setCalldata] = useState(null) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
data && setCalldata(data) |
||||||
|
}, [data]) |
||||||
|
|
||||||
|
const formatSelf = (key: string, data: ExtractData) => { |
||||||
|
let color = 'var(--primary)' |
||||||
|
if (data.isArray || data.isStruct || data.isMapping) { |
||||||
|
color = 'var(--info)' |
||||||
|
} else if ( |
||||||
|
data.type.indexOf('uint') === 0 || |
||||||
|
data.type.indexOf('int') === 0 || |
||||||
|
data.type.indexOf('bool') === 0 || |
||||||
|
data.type.indexOf('enum') === 0 |
||||||
|
) { |
||||||
|
color = 'var(--green)' |
||||||
|
} else if (data.type === 'string') { |
||||||
|
color = 'var(--teal)' |
||||||
|
} else if (data.self == 0x0) { // eslint-disable-line
|
||||||
|
color = 'var(--gray)' |
||||||
|
} |
||||||
|
if (data.type === 'string') { |
||||||
|
data.self = JSON.stringify(data.self) |
||||||
|
} |
||||||
|
return ( |
||||||
|
<label className='mb-0' style={{ color: data.isProperty ? 'var(--info)' : '', whiteSpace: 'pre-wrap' }}> |
||||||
|
{' ' + key}: |
||||||
|
<label className='mb-0' style={{ color }}> |
||||||
|
{' ' + data.self} |
||||||
|
</label> |
||||||
|
<label style={{ fontStyle: 'italic' }}> |
||||||
|
{data.isProperty || !data.type ? '' : ' ' + data.type} |
||||||
|
</label> |
||||||
|
</label> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div id='soliditylocals' data-id="solidityLocals"> |
||||||
|
<DropdownPanel
|
||||||
|
dropdownName='Solidity Locals' |
||||||
|
dropdownMessage={message} |
||||||
|
calldata={calldata || {}} |
||||||
|
extractFunc={extractData} |
||||||
|
formatSelfFunc={formatSelf} |
||||||
|
registerEvent={registerEvent} |
||||||
|
triggerEvent={triggerEvent} |
||||||
|
loadMoreEvent='solidityLocalsLoadMore' |
||||||
|
loadMoreCompletedEvent='solidityLocalsLoadMoreCompleted' |
||||||
|
/> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default SolidityLocals |
@ -0,0 +1,45 @@ |
|||||||
|
import React from 'react' |
||||||
|
import DropdownPanel from './dropdown-panel' |
||||||
|
import { extractData } from '../../utils/solidityTypeFormatter' |
||||||
|
import { ExtractData } from '../../types' |
||||||
|
|
||||||
|
export const SolidityState = ({ calldata, message }) => { |
||||||
|
const formatSelf = (key: string, data: ExtractData) => { |
||||||
|
let color = 'var(--primary)' |
||||||
|
if (data.isArray || data.isStruct || data.isMapping) { |
||||||
|
color = 'var(--info)' |
||||||
|
} else if ( |
||||||
|
data.type.indexOf('uint') === 0 || |
||||||
|
data.type.indexOf('int') === 0 || |
||||||
|
data.type.indexOf('bool') === 0 || |
||||||
|
data.type.indexOf('enum') === 0 |
||||||
|
) { |
||||||
|
color = 'var(--green)' |
||||||
|
} else if (data.type === 'string') { |
||||||
|
color = 'var(--teal)' |
||||||
|
} else if (data.self == 0x0) { // eslint-disable-line
|
||||||
|
color = 'var(--gray)' |
||||||
|
} |
||||||
|
return ( |
||||||
|
<label className='mb-0' style={{ color: data.isProperty ? 'var(--info)' : '', whiteSpace: 'pre-wrap' }}> |
||||||
|
{' ' + key}: |
||||||
|
<label className='mb-0' style={{ color }}> |
||||||
|
{' ' + data.self} |
||||||
|
</label> |
||||||
|
<label style={{ fontStyle: 'italic' }}> |
||||||
|
{data.isProperty || !data.type ? '' : ' ' + data.type} |
||||||
|
</label> |
||||||
|
</label> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div id='soliditystate' data-id='soliditystate'> |
||||||
|
{ |
||||||
|
<DropdownPanel dropdownName='Solidity State' calldata={calldata || {}} formatSelfFunc={formatSelf} extractFunc={extractData} /> |
||||||
|
} |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default SolidityState |
@ -0,0 +1,12 @@ |
|||||||
|
import React from 'react' |
||||||
|
import DropdownPanel from './dropdown-panel' |
||||||
|
|
||||||
|
export const StackPanel = ({ calldata }) => { |
||||||
|
return ( |
||||||
|
<div id="stackpanel"> |
||||||
|
<DropdownPanel dropdownName='Stack' calldata={calldata || {}} /> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default StackPanel |
@ -0,0 +1,12 @@ |
|||||||
|
import React from 'react' |
||||||
|
import DropdownPanel from './dropdown-panel' |
||||||
|
|
||||||
|
export const StepDetail = ({ stepDetail }) => { |
||||||
|
return ( |
||||||
|
<div id='stepdetail' data-id="stepdetail"> |
||||||
|
<DropdownPanel dropdownName='Step details' calldata={stepDetail || {}} /> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default StepDetail |
@ -0,0 +1,12 @@ |
|||||||
|
import React from 'react' |
||||||
|
import DropdownPanel from './dropdown-panel' |
||||||
|
|
||||||
|
export const StoragePanel = ({ calldata, header }) => { |
||||||
|
return ( |
||||||
|
<div id='storagepanel'> |
||||||
|
<DropdownPanel dropdownName='Storage' calldata={calldata || {}} header={header} /> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default StoragePanel |
@ -0,0 +1,4 @@ |
|||||||
|
.instructions { |
||||||
|
overflow-y: scroll; |
||||||
|
max-height: 130px; |
||||||
|
} |
@ -0,0 +1,47 @@ |
|||||||
|
.title { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
.name { |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.nameDetail { |
||||||
|
font-weight: bold; |
||||||
|
margin-left: 3px; |
||||||
|
} |
||||||
|
.icon { |
||||||
|
margin-right: 5%; |
||||||
|
} |
||||||
|
.eyeButton { |
||||||
|
margin: 3px; |
||||||
|
} |
||||||
|
.dropdownpanel { |
||||||
|
width: 100%; |
||||||
|
word-break: break-word; |
||||||
|
} |
||||||
|
.dropdownrawcontent { |
||||||
|
padding: 2px; |
||||||
|
word-break: break-word; |
||||||
|
} |
||||||
|
.message { |
||||||
|
padding: 2px; |
||||||
|
word-break: break-word; |
||||||
|
} |
||||||
|
.refresh { |
||||||
|
display: none; |
||||||
|
margin-left: 4px; |
||||||
|
margin-top: 4px; |
||||||
|
animation: spin 2s linear infinite; |
||||||
|
} |
||||||
|
.cursor_pointer { |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
@-moz-keyframes spin { |
||||||
|
to { -moz-transform: rotate(359deg); } |
||||||
|
} |
||||||
|
@-webkit-keyframes spin { |
||||||
|
to { -webkit-transform: rotate(359deg); } |
||||||
|
} |
||||||
|
@keyframes spin { |
||||||
|
to {transform:rotate(359deg);} |
||||||
|
} |
@ -0,0 +1,114 @@ |
|||||||
|
import React, { useState, useEffect } from 'react' |
||||||
|
import CodeListView from './code-list-view' |
||||||
|
import FunctionPanel from './function-panel' |
||||||
|
import StepDetail from './step-detail' |
||||||
|
import SolidityState from './solidity-state' |
||||||
|
import SolidityLocals from './solidity-locals' |
||||||
|
|
||||||
|
export const VmDebuggerHead = ({ vmDebugger: { registerEvent, triggerEvent } }) => { |
||||||
|
const [functionPanel, setFunctionPanel] = useState(null) |
||||||
|
const [stepDetail, setStepDetail] = useState({ |
||||||
|
'vm trace step': '-', |
||||||
|
'execution step': '-', |
||||||
|
'add memory': '', |
||||||
|
'gas': '', |
||||||
|
'remaining gas': '-', |
||||||
|
'loaded address': '-' |
||||||
|
}) |
||||||
|
const [solidityState, setSolidityState] = useState({ |
||||||
|
calldata: null, |
||||||
|
message: null, |
||||||
|
}) |
||||||
|
const [solidityLocals, setSolidityLocals] = useState({ |
||||||
|
calldata: null, |
||||||
|
message: null, |
||||||
|
}) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
registerEvent && registerEvent('functionsStackUpdate', (stack) => { |
||||||
|
if (stack === null || stack.length === 0) return |
||||||
|
const functions = [] |
||||||
|
|
||||||
|
for (const func of stack) { |
||||||
|
functions.push(func.functionDefinition.name + '(' + func.inputs.join(', ') + ')') |
||||||
|
} |
||||||
|
setFunctionPanel(() => functions) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('traceUnloaded', () => { |
||||||
|
setStepDetail(() => { |
||||||
|
return { 'vm trace step': '-', 'execution step': '-', 'add memory': '', 'gas': '', 'remaining gas': '-', 'loaded address': '-' } |
||||||
|
}) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('newTraceLoaded', () => { |
||||||
|
setStepDetail(() => { |
||||||
|
return { 'vm trace step': '-', 'execution step': '-', 'add memory': '', 'gas': '', 'remaining gas': '-', 'loaded address': '-' } |
||||||
|
}) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('traceCurrentStepUpdate', (error, step) => { |
||||||
|
setStepDetail(prevState => { |
||||||
|
return { ...prevState, 'execution step': (error ? '-' : step) } |
||||||
|
}) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('traceMemExpandUpdate', (error, addmem) => { |
||||||
|
setStepDetail(prevState => { |
||||||
|
return { ...prevState, 'add memory': (error ? '-' : addmem) } |
||||||
|
}) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('traceStepCostUpdate', (error, gas) => { |
||||||
|
setStepDetail(prevState => { |
||||||
|
return { ...prevState, 'gas': (error ? '-' : gas) } |
||||||
|
}) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('traceCurrentCalledAddressAtUpdate', (error, address) => { |
||||||
|
setStepDetail(prevState => { |
||||||
|
return { ...prevState, 'loaded address': (error ? '-' : address) } |
||||||
|
}) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('traceRemainingGasUpdate', (error, remainingGas) => { |
||||||
|
setStepDetail(prevState => { |
||||||
|
return { ...prevState, 'remaining gas': (error ? '-' : remainingGas) } |
||||||
|
}) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('indexUpdate', (index) => { |
||||||
|
setStepDetail(prevState => { |
||||||
|
return { ...prevState, 'vm trace step': index } |
||||||
|
}) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('solidityState', (calldata) => { |
||||||
|
setSolidityState(() => { |
||||||
|
return { ...solidityState, calldata } |
||||||
|
}) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('solidityStateMessage', (message) => { |
||||||
|
setSolidityState(() => { |
||||||
|
return { ...solidityState, message } |
||||||
|
}) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('solidityLocals', (calldata) => { |
||||||
|
setSolidityLocals(() => { |
||||||
|
return { ...solidityLocals, calldata } |
||||||
|
}) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('solidityLocalsMessage', (message) => { |
||||||
|
setSolidityLocals(() => { |
||||||
|
return { ...solidityLocals, message } |
||||||
|
}) |
||||||
|
}) |
||||||
|
}, [registerEvent]) |
||||||
|
|
||||||
|
return ( |
||||||
|
<div id="vmheadView" className="mt-1 px-0"> |
||||||
|
<div className="d-flex flex-column"> |
||||||
|
<div className="w-100"> |
||||||
|
<FunctionPanel data={functionPanel} /> |
||||||
|
<SolidityLocals data={solidityLocals.calldata} message={solidityLocals.message} registerEvent={registerEvent} triggerEvent={triggerEvent} /> |
||||||
|
<SolidityState calldata={solidityState.calldata} message={solidityState.message} /> |
||||||
|
</div> |
||||||
|
<div className="w-100"><CodeListView registerEvent={registerEvent} /></div> |
||||||
|
<div className="w-100"><StepDetail stepDetail={stepDetail} /></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default VmDebuggerHead |
@ -0,0 +1,68 @@ |
|||||||
|
import React, { useState, useEffect } from 'react' |
||||||
|
import CalldataPanel from './calldata-panel' |
||||||
|
import MemoryPanel from './memory-panel' |
||||||
|
import CallstackPanel from './callstack-panel' |
||||||
|
import StackPanel from './stack-panel' |
||||||
|
import StoragePanel from './storage-panel' |
||||||
|
import ReturnValuesPanel from './dropdown-panel' |
||||||
|
import FullStoragesChangesPanel from './full-storages-changes' |
||||||
|
|
||||||
|
export const VmDebugger = ({ vmDebugger: { registerEvent } }) => { |
||||||
|
const [calldataPanel, setCalldataPanel] = useState(null) |
||||||
|
const [memoryPanel, setMemoryPanel] = useState(null) |
||||||
|
const [callStackPanel, setCallStackPanel] = useState(null) |
||||||
|
const [stackPanel, setStackPanel] = useState(null) |
||||||
|
const [storagePanel, setStoragePanel] = useState({ |
||||||
|
calldata: null, |
||||||
|
header: null |
||||||
|
}) |
||||||
|
const [returnValuesPanel, setReturnValuesPanel] = useState(null) |
||||||
|
const [fullStoragesChangesPanel, setFullStoragesChangesPanel] = useState(null) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
registerEvent && registerEvent('traceManagerCallDataUpdate', (calldata) => { |
||||||
|
setCalldataPanel(() => calldata) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('traceManagerMemoryUpdate', (calldata) => { |
||||||
|
setMemoryPanel(() => calldata) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('traceManagerCallStackUpdate', (calldata) => { |
||||||
|
setCallStackPanel(() => calldata) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('traceManagerStackUpdate', (calldata) => { |
||||||
|
setStackPanel(() => calldata) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('traceManagerStorageUpdate', (calldata, header) => { |
||||||
|
setStoragePanel(() => { |
||||||
|
return { calldata, header } |
||||||
|
}) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('traceReturnValueUpdate', (calldata) => { |
||||||
|
setReturnValuesPanel(() => calldata) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('traceAddressesUpdate', (calldata) => { |
||||||
|
setFullStoragesChangesPanel(() => { |
||||||
|
return {} |
||||||
|
}) |
||||||
|
}) |
||||||
|
registerEvent && registerEvent('traceStorageUpdate', (calldata) => { |
||||||
|
setFullStoragesChangesPanel(() => calldata) |
||||||
|
}) |
||||||
|
}, [registerEvent]) |
||||||
|
|
||||||
|
return ( |
||||||
|
<div id="vmdebugger" className="px-2"> |
||||||
|
<div> |
||||||
|
<StackPanel calldata={stackPanel} /> |
||||||
|
<MemoryPanel calldata={memoryPanel} /> |
||||||
|
<StoragePanel calldata={storagePanel.calldata} header={storagePanel.header} /> |
||||||
|
<CallstackPanel calldata={callStackPanel} /> |
||||||
|
<CalldataPanel calldata={calldataPanel} /> |
||||||
|
<ReturnValuesPanel dropdownName="Return Value" calldata={returnValuesPanel || {}} /> |
||||||
|
<FullStoragesChangesPanel calldata={fullStoragesChangesPanel} /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default VmDebugger |
@ -0,0 +1,63 @@ |
|||||||
|
import { default as deepEqual } from 'deep-equal' |
||||||
|
|
||||||
|
interface Action { |
||||||
|
type: string; |
||||||
|
payload: { [key: string]: any }; |
||||||
|
} |
||||||
|
|
||||||
|
export const initialState = { |
||||||
|
opCodes: { |
||||||
|
code: [], |
||||||
|
index: 0, |
||||||
|
address: '' |
||||||
|
}, |
||||||
|
display: [], |
||||||
|
index: 0, |
||||||
|
top: 0, |
||||||
|
bottom: 0, |
||||||
|
isRequesting: false, |
||||||
|
isSuccessful: false, |
||||||
|
hasError: null |
||||||
|
} |
||||||
|
|
||||||
|
export const reducer = (state = initialState, action: Action) => { |
||||||
|
switch (action.type) { |
||||||
|
case 'FETCH_OPCODES_REQUEST': { |
||||||
|
return { |
||||||
|
...state, |
||||||
|
isRequesting: true, |
||||||
|
isSuccessful: false, |
||||||
|
hasError: null |
||||||
|
}; |
||||||
|
} |
||||||
|
case 'FETCH_OPCODES_SUCCESS': { |
||||||
|
const opCodes = action.payload.address === state.opCodes.address ? {
|
||||||
|
...state.opCodes, index: action.payload.index
|
||||||
|
} : deepEqual(action.payload.code, state.opCodes.code) ? state.opCodes : action.payload |
||||||
|
const top = opCodes.index - 3 > 0 ? opCodes.index - 3 : 0 |
||||||
|
const bottom = opCodes.index + 4 < opCodes.code.length ? opCodes.index + 4 : opCodes.code.length |
||||||
|
const display = opCodes.code.slice(top, bottom) |
||||||
|
|
||||||
|
return { |
||||||
|
opCodes, |
||||||
|
display, |
||||||
|
index: display.findIndex(code => code === opCodes.code[opCodes.index]), |
||||||
|
top, |
||||||
|
bottom, |
||||||
|
isRequesting: false, |
||||||
|
isSuccessful: true, |
||||||
|
hasError: null |
||||||
|
}; |
||||||
|
} |
||||||
|
case 'FETCH_OPCODES_ERROR': { |
||||||
|
return { |
||||||
|
...state, |
||||||
|
isRequesting: false, |
||||||
|
isSuccessful: false, |
||||||
|
hasError: action.payload |
||||||
|
}; |
||||||
|
} |
||||||
|
default: |
||||||
|
throw new Error(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,72 @@ |
|||||||
|
interface Action { |
||||||
|
type: string; |
||||||
|
payload: { [key: string]: any }; |
||||||
|
} |
||||||
|
|
||||||
|
export const initialState = { |
||||||
|
calldata: {}, |
||||||
|
isRequesting: false, |
||||||
|
isSuccessful: false, |
||||||
|
hasError: null |
||||||
|
} |
||||||
|
|
||||||
|
export const reducer = (state = initialState, action: Action) => { |
||||||
|
switch (action.type) { |
||||||
|
case 'FETCH_CALLDATA_REQUEST': |
||||||
|
return { |
||||||
|
...state, |
||||||
|
isRequesting: true, |
||||||
|
isSuccessful: false, |
||||||
|
hasError: null |
||||||
|
}; |
||||||
|
case 'FETCH_CALLDATA_SUCCESS': |
||||||
|
return { |
||||||
|
calldata: action.payload, |
||||||
|
isRequesting: false, |
||||||
|
isSuccessful: true, |
||||||
|
hasError: null |
||||||
|
}; |
||||||
|
case 'FETCH_CALLDATA_ERROR': |
||||||
|
return { |
||||||
|
...state, |
||||||
|
isRequesting: false, |
||||||
|
isSuccessful: false, |
||||||
|
hasError: action.payload |
||||||
|
}; |
||||||
|
case 'UPDATE_CALLDATA_REQUEST': |
||||||
|
return { |
||||||
|
...state, |
||||||
|
isRequesting: true, |
||||||
|
isSuccessful: false, |
||||||
|
hasError: null |
||||||
|
}; |
||||||
|
case 'UPDATE_CALLDATA_SUCCESS': |
||||||
|
return { |
||||||
|
calldata: mergeLocals(action.payload, state.calldata), |
||||||
|
isRequesting: false, |
||||||
|
isSuccessful: true, |
||||||
|
hasError: null |
||||||
|
}; |
||||||
|
case 'UPDATE_CALLDATA_ERROR': |
||||||
|
return { |
||||||
|
...state, |
||||||
|
isRequesting: false, |
||||||
|
isSuccessful: false, |
||||||
|
hasError: action.payload |
||||||
|
}; |
||||||
|
default: |
||||||
|
throw new Error(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function mergeLocals (locals1, locals2) { |
||||||
|
Object.keys(locals2).map(item => { |
||||||
|
if (locals2[item].cursor && (parseInt(locals2[item].cursor) < parseInt(locals1[item].cursor))) { |
||||||
|
locals2[item] = { |
||||||
|
...locals1[item], |
||||||
|
value: [...locals2[item].value, ...locals1[item].value] |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
return locals2 |
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
export interface ExtractData { |
||||||
|
children?: Array<{key: number | string, value: ExtractData}> |
||||||
|
self?: string | number, |
||||||
|
isNode?: boolean, |
||||||
|
isLeaf?: boolean, |
||||||
|
isArray?: boolean, |
||||||
|
isStruct?: boolean, |
||||||
|
isMapping?: boolean, |
||||||
|
type?: string, |
||||||
|
isProperty?: boolean, |
||||||
|
hasNext?: boolean, |
||||||
|
cursor?: number |
||||||
|
} |
||||||
|
|
||||||
|
export type ExtractFunc = (json: any, parent?: any) => ExtractData |
||||||
|
|
||||||
|
export interface DropdownPanelProps { |
||||||
|
dropdownName: string, |
||||||
|
dropdownMessage?: string, |
||||||
|
calldata?: { |
||||||
|
[key: string]: string |
||||||
|
}, |
||||||
|
header?: string, |
||||||
|
loading?: boolean, |
||||||
|
extractFunc?: ExtractFunc, |
||||||
|
formatSelfFunc?: FormatSelfFunc, |
||||||
|
registerEvent?: Function, |
||||||
|
triggerEvent?: Function, |
||||||
|
loadMoreEvent?: string, |
||||||
|
loadMoreCompletedEvent?: string |
||||||
|
} |
||||||
|
|
||||||
|
export type FormatSelfFunc = (key: string | number, data: ExtractData) => JSX.Element |
@ -0,0 +1,44 @@ |
|||||||
|
import { BN } from 'ethereumjs-util' |
||||||
|
import { ExtractData } from '../types' |
||||||
|
|
||||||
|
export function extractData (item, parent): ExtractData { |
||||||
|
const ret: ExtractData = {} |
||||||
|
|
||||||
|
if (item.isProperty) { |
||||||
|
return item |
||||||
|
} |
||||||
|
if (item.type.lastIndexOf(']') === item.type.length - 1) { |
||||||
|
ret.children = (item.value || []).map(function (item, index) { |
||||||
|
return {key: index, value: item} |
||||||
|
}) |
||||||
|
ret.children.unshift({ |
||||||
|
key: 'length', |
||||||
|
value: { |
||||||
|
self: (new BN(item.length.replace('0x', ''), 16)).toString(10), |
||||||
|
type: 'uint', |
||||||
|
isProperty: true |
||||||
|
} |
||||||
|
}) |
||||||
|
ret.isArray = true |
||||||
|
ret.self = parent.isArray ? '' : item.type |
||||||
|
ret.cursor = item.cursor |
||||||
|
ret.hasNext = item.hasNext |
||||||
|
} else if (item.type.indexOf('struct') === 0) { |
||||||
|
ret.children = Object.keys((item.value || {})).map(function (key) { |
||||||
|
return {key: key, value: item.value[key]} |
||||||
|
}) |
||||||
|
ret.self = item.type |
||||||
|
ret.isStruct = true |
||||||
|
} else if (item.type.indexOf('mapping') === 0) { |
||||||
|
ret.children = Object.keys((item.value || {})).map(function (key) { |
||||||
|
return {key: key, value: item.value[key]} |
||||||
|
}) |
||||||
|
ret.isMapping = true |
||||||
|
ret.self = item.type |
||||||
|
} else { |
||||||
|
ret.children = null |
||||||
|
ret.self = item.value |
||||||
|
ret.type = item.type |
||||||
|
} |
||||||
|
return ret |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
{ |
||||||
|
"extends": "../../../tsconfig.json", |
||||||
|
"compilerOptions": { |
||||||
|
"jsx": "react", |
||||||
|
"allowJs": true, |
||||||
|
"esModuleInterop": true, |
||||||
|
"allowSyntheticDefaultImports": true |
||||||
|
}, |
||||||
|
"files": [], |
||||||
|
"include": [], |
||||||
|
"references": [ |
||||||
|
{ |
||||||
|
"path": "./tsconfig.lib.json" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"path": "./tsconfig.spec.json" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
{ |
||||||
|
"extends": "./tsconfig.json", |
||||||
|
"compilerOptions": { |
||||||
|
"outDir": "../../dist/out-tsc", |
||||||
|
"types": ["node"] |
||||||
|
}, |
||||||
|
"files": [ |
||||||
|
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts", |
||||||
|
"../../node_modules/@nrwl/react/typings/image.d.ts" |
||||||
|
], |
||||||
|
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"], |
||||||
|
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
{ |
||||||
|
"extends": "./tsconfig.json", |
||||||
|
"compilerOptions": { |
||||||
|
"outDir": "../../dist/out-tsc", |
||||||
|
"module": "commonjs", |
||||||
|
"types": ["jest", "node"] |
||||||
|
}, |
||||||
|
"include": [ |
||||||
|
"**/*.spec.ts", |
||||||
|
"**/*.spec.tsx", |
||||||
|
"**/*.spec.js", |
||||||
|
"**/*.spec.jsx", |
||||||
|
"**/*.d.ts" |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,4 @@ |
|||||||
|
{ |
||||||
|
"presets": ["@nrwl/react/babel"], |
||||||
|
"plugins": [] |
||||||
|
} |
@ -0,0 +1,248 @@ |
|||||||
|
{ |
||||||
|
"rules": { |
||||||
|
"array-callback-return": "warn", |
||||||
|
"dot-location": ["warn", "property"], |
||||||
|
"eqeqeq": ["warn", "smart"], |
||||||
|
"new-parens": "warn", |
||||||
|
"no-caller": "warn", |
||||||
|
"no-cond-assign": ["warn", "except-parens"], |
||||||
|
"no-const-assign": "warn", |
||||||
|
"no-control-regex": "warn", |
||||||
|
"no-delete-var": "warn", |
||||||
|
"no-dupe-args": "warn", |
||||||
|
"no-dupe-keys": "warn", |
||||||
|
"no-duplicate-case": "warn", |
||||||
|
"no-empty-character-class": "warn", |
||||||
|
"no-empty-pattern": "warn", |
||||||
|
"no-eval": "warn", |
||||||
|
"no-ex-assign": "warn", |
||||||
|
"no-extend-native": "warn", |
||||||
|
"no-extra-bind": "warn", |
||||||
|
"no-extra-label": "warn", |
||||||
|
"no-fallthrough": "warn", |
||||||
|
"no-func-assign": "warn", |
||||||
|
"no-implied-eval": "warn", |
||||||
|
"no-invalid-regexp": "warn", |
||||||
|
"no-iterator": "warn", |
||||||
|
"no-label-var": "warn", |
||||||
|
"no-labels": ["warn", { "allowLoop": true, "allowSwitch": false }], |
||||||
|
"no-lone-blocks": "warn", |
||||||
|
"no-loop-func": "warn", |
||||||
|
"no-mixed-operators": [ |
||||||
|
"warn", |
||||||
|
{ |
||||||
|
"groups": [ |
||||||
|
["&", "|", "^", "~", "<<", ">>", ">>>"], |
||||||
|
["==", "!=", "===", "!==", ">", ">=", "<", "<="], |
||||||
|
["&&", "||"], |
||||||
|
["in", "instanceof"] |
||||||
|
], |
||||||
|
"allowSamePrecedence": false |
||||||
|
} |
||||||
|
], |
||||||
|
"no-multi-str": "warn", |
||||||
|
"no-native-reassign": "warn", |
||||||
|
"no-negated-in-lhs": "warn", |
||||||
|
"no-new-func": "warn", |
||||||
|
"no-new-object": "warn", |
||||||
|
"no-new-symbol": "warn", |
||||||
|
"no-new-wrappers": "warn", |
||||||
|
"no-obj-calls": "warn", |
||||||
|
"no-octal": "warn", |
||||||
|
"no-octal-escape": "warn", |
||||||
|
"no-redeclare": "warn", |
||||||
|
"no-regex-spaces": "warn", |
||||||
|
"no-restricted-syntax": ["warn", "WithStatement"], |
||||||
|
"no-script-url": "warn", |
||||||
|
"no-self-assign": "warn", |
||||||
|
"no-self-compare": "warn", |
||||||
|
"no-sequences": "warn", |
||||||
|
"no-shadow-restricted-names": "warn", |
||||||
|
"no-sparse-arrays": "warn", |
||||||
|
"no-template-curly-in-string": "warn", |
||||||
|
"no-this-before-super": "warn", |
||||||
|
"no-throw-literal": "warn", |
||||||
|
"no-restricted-globals": [ |
||||||
|
"error", |
||||||
|
"addEventListener", |
||||||
|
"blur", |
||||||
|
"close", |
||||||
|
"closed", |
||||||
|
"confirm", |
||||||
|
"defaultStatus", |
||||||
|
"defaultstatus", |
||||||
|
"event", |
||||||
|
"external", |
||||||
|
"find", |
||||||
|
"focus", |
||||||
|
"frameElement", |
||||||
|
"frames", |
||||||
|
"history", |
||||||
|
"innerHeight", |
||||||
|
"innerWidth", |
||||||
|
"length", |
||||||
|
"location", |
||||||
|
"locationbar", |
||||||
|
"menubar", |
||||||
|
"moveBy", |
||||||
|
"moveTo", |
||||||
|
"name", |
||||||
|
"onblur", |
||||||
|
"onerror", |
||||||
|
"onfocus", |
||||||
|
"onload", |
||||||
|
"onresize", |
||||||
|
"onunload", |
||||||
|
"open", |
||||||
|
"opener", |
||||||
|
"opera", |
||||||
|
"outerHeight", |
||||||
|
"outerWidth", |
||||||
|
"pageXOffset", |
||||||
|
"pageYOffset", |
||||||
|
"parent", |
||||||
|
"print", |
||||||
|
"removeEventListener", |
||||||
|
"resizeBy", |
||||||
|
"resizeTo", |
||||||
|
"screen", |
||||||
|
"screenLeft", |
||||||
|
"screenTop", |
||||||
|
"screenX", |
||||||
|
"screenY", |
||||||
|
"scroll", |
||||||
|
"scrollbars", |
||||||
|
"scrollBy", |
||||||
|
"scrollTo", |
||||||
|
"scrollX", |
||||||
|
"scrollY", |
||||||
|
"self", |
||||||
|
"status", |
||||||
|
"statusbar", |
||||||
|
"stop", |
||||||
|
"toolbar", |
||||||
|
"top" |
||||||
|
], |
||||||
|
"no-unexpected-multiline": "warn", |
||||||
|
"no-unreachable": "warn", |
||||||
|
"no-unused-expressions": [ |
||||||
|
"error", |
||||||
|
{ |
||||||
|
"allowShortCircuit": true, |
||||||
|
"allowTernary": true, |
||||||
|
"allowTaggedTemplates": true |
||||||
|
} |
||||||
|
], |
||||||
|
"no-unused-labels": "warn", |
||||||
|
"no-useless-computed-key": "warn", |
||||||
|
"no-useless-concat": "warn", |
||||||
|
"no-useless-escape": "warn", |
||||||
|
"no-useless-rename": [ |
||||||
|
"warn", |
||||||
|
{ |
||||||
|
"ignoreDestructuring": false, |
||||||
|
"ignoreImport": false, |
||||||
|
"ignoreExport": false |
||||||
|
} |
||||||
|
], |
||||||
|
"no-with": "warn", |
||||||
|
"no-whitespace-before-property": "warn", |
||||||
|
"react-hooks/exhaustive-deps": "warn", |
||||||
|
"require-yield": "warn", |
||||||
|
"rest-spread-spacing": ["warn", "never"], |
||||||
|
"strict": ["warn", "never"], |
||||||
|
"unicode-bom": ["warn", "never"], |
||||||
|
"use-isnan": "warn", |
||||||
|
"valid-typeof": "warn", |
||||||
|
"no-restricted-properties": [ |
||||||
|
"error", |
||||||
|
{ |
||||||
|
"object": "require", |
||||||
|
"property": "ensure", |
||||||
|
"message": "Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"object": "System", |
||||||
|
"property": "import", |
||||||
|
"message": "Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting" |
||||||
|
} |
||||||
|
], |
||||||
|
"getter-return": "warn", |
||||||
|
"import/first": "error", |
||||||
|
"import/no-amd": "error", |
||||||
|
"import/no-webpack-loader-syntax": "error", |
||||||
|
"react/forbid-foreign-prop-types": ["warn", { "allowInPropTypes": true }], |
||||||
|
"react/jsx-no-comment-textnodes": "warn", |
||||||
|
"react/jsx-no-duplicate-props": "warn", |
||||||
|
"react/jsx-no-target-blank": "warn", |
||||||
|
"react/jsx-no-undef": "error", |
||||||
|
"react/jsx-pascal-case": ["warn", { "allowAllCaps": true, "ignore": [] }], |
||||||
|
"react/jsx-uses-react": "warn", |
||||||
|
"react/jsx-uses-vars": "warn", |
||||||
|
"react/no-danger-with-children": "warn", |
||||||
|
"react/no-direct-mutation-state": "warn", |
||||||
|
"react/no-is-mounted": "warn", |
||||||
|
"react/no-typos": "error", |
||||||
|
"react/react-in-jsx-scope": "error", |
||||||
|
"react/require-render-return": "error", |
||||||
|
"react/style-prop-object": "warn", |
||||||
|
"react/jsx-no-useless-fragment": "warn", |
||||||
|
"jsx-a11y/accessible-emoji": "warn", |
||||||
|
"jsx-a11y/alt-text": "warn", |
||||||
|
"jsx-a11y/anchor-has-content": "warn", |
||||||
|
"jsx-a11y/anchor-is-valid": [ |
||||||
|
"warn", |
||||||
|
{ "aspects": ["noHref", "invalidHref"] } |
||||||
|
], |
||||||
|
"jsx-a11y/aria-activedescendant-has-tabindex": "warn", |
||||||
|
"jsx-a11y/aria-props": "warn", |
||||||
|
"jsx-a11y/aria-proptypes": "warn", |
||||||
|
"jsx-a11y/aria-role": "warn", |
||||||
|
"jsx-a11y/aria-unsupported-elements": "warn", |
||||||
|
"jsx-a11y/heading-has-content": "warn", |
||||||
|
"jsx-a11y/iframe-has-title": "warn", |
||||||
|
"jsx-a11y/img-redundant-alt": "warn", |
||||||
|
"jsx-a11y/no-access-key": "warn", |
||||||
|
"jsx-a11y/no-distracting-elements": "warn", |
||||||
|
"jsx-a11y/no-redundant-roles": "warn", |
||||||
|
"jsx-a11y/role-has-required-aria-props": "warn", |
||||||
|
"jsx-a11y/role-supports-aria-props": "warn", |
||||||
|
"jsx-a11y/scope": "warn", |
||||||
|
"react-hooks/rules-of-hooks": "error", |
||||||
|
"default-case": "off", |
||||||
|
"no-dupe-class-members": "off", |
||||||
|
"no-undef": "off", |
||||||
|
"@typescript-eslint/consistent-type-assertions": "warn", |
||||||
|
"no-array-constructor": "off", |
||||||
|
"@typescript-eslint/no-array-constructor": "warn", |
||||||
|
"@typescript-eslint/no-namespace": "error", |
||||||
|
"no-use-before-define": "off", |
||||||
|
"@typescript-eslint/no-use-before-define": [ |
||||||
|
"warn", |
||||||
|
{ |
||||||
|
"functions": false, |
||||||
|
"classes": false, |
||||||
|
"variables": false, |
||||||
|
"typedefs": false |
||||||
|
} |
||||||
|
], |
||||||
|
"no-unused-vars": "off", |
||||||
|
"@typescript-eslint/no-unused-vars": [ |
||||||
|
"warn", |
||||||
|
{ "args": "none", "ignoreRestSiblings": true } |
||||||
|
], |
||||||
|
"no-useless-constructor": "off", |
||||||
|
"@typescript-eslint/no-useless-constructor": "warn" |
||||||
|
}, |
||||||
|
"env": { |
||||||
|
"browser": true, |
||||||
|
"commonjs": true, |
||||||
|
"es6": true, |
||||||
|
"jest": true, |
||||||
|
"node": true |
||||||
|
}, |
||||||
|
"settings": { "react": { "version": "detect" } }, |
||||||
|
"plugins": ["import", "jsx-a11y", "react", "react-hooks"], |
||||||
|
"extends": ["../../../.eslintrc"], |
||||||
|
"ignorePatterns": ["!**/*"] |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
# remix-ui-tree-view |
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev). |
||||||
|
|
||||||
|
## Running unit tests |
||||||
|
|
||||||
|
Run `nx test remix-ui-tree-view` to execute the unit tests via [Jest](https://jestjs.io). |
@ -0,0 +1,14 @@ |
|||||||
|
{ |
||||||
|
"presets": [ |
||||||
|
[ |
||||||
|
"@babel/preset-env", |
||||||
|
{ |
||||||
|
"targets": { |
||||||
|
"node": "current" |
||||||
|
} |
||||||
|
} |
||||||
|
], |
||||||
|
"@babel/preset-typescript", |
||||||
|
"@babel/preset-react" |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
module.exports = { |
||||||
|
name: 'remix-ui-tree-view', |
||||||
|
preset: '../../../jest.config.js', |
||||||
|
transform: { |
||||||
|
'^.+\\.[tj]sx?$': [ |
||||||
|
'babel-jest', |
||||||
|
{ cwd: __dirname, configFile: './babel-jest.config.json' } |
||||||
|
] |
||||||
|
}, |
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], |
||||||
|
coverageDirectory: '../../../coverage/libs/remix-ui/tree-view' |
||||||
|
}; |
@ -0,0 +1,2 @@ |
|||||||
|
export * from './lib/tree-view-item/tree-view-item'; |
||||||
|
export * from './lib/remix-ui-tree-view'; |
@ -0,0 +1,32 @@ |
|||||||
|
.li_tv { |
||||||
|
list-style-type: none; |
||||||
|
-webkit-margin-before: 0px; |
||||||
|
-webkit-margin-after: 0px; |
||||||
|
-webkit-margin-start: 0px; |
||||||
|
-webkit-margin-end: 0px; |
||||||
|
-webkit-padding-start: 0px; |
||||||
|
} |
||||||
|
.ul_tv { |
||||||
|
list-style-type: none; |
||||||
|
-webkit-margin-before: 0px; |
||||||
|
-webkit-margin-after: 0px; |
||||||
|
-webkit-margin-start: 0px; |
||||||
|
-webkit-margin-end: 0px; |
||||||
|
-webkit-padding-start: 0px; |
||||||
|
} |
||||||
|
.caret_tv { |
||||||
|
width: 10px; |
||||||
|
flex-shrink: 0; |
||||||
|
padding-right: 5px; |
||||||
|
} |
||||||
|
.label_item { |
||||||
|
word-break: break-all; |
||||||
|
} |
||||||
|
.label_key { |
||||||
|
min-width: 15%; |
||||||
|
max-width: 80%; |
||||||
|
word-break: break-word; |
||||||
|
} |
||||||
|
.label_value { |
||||||
|
min-width: 10%; |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
import React from 'react' |
||||||
|
import { TreeViewProps } from '../types' |
||||||
|
|
||||||
|
import './remix-ui-tree-view.css' |
||||||
|
|
||||||
|
export const TreeView = (props: TreeViewProps) => { |
||||||
|
const { children, id, ...otherProps } = props |
||||||
|
|
||||||
|
return ( |
||||||
|
<ul data-id={`treeViewUl${id}`} className="ul_tv ml-0 px-2" { ...otherProps }> |
||||||
|
{ children } |
||||||
|
</ul> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default TreeView |
@ -0,0 +1,27 @@ |
|||||||
|
import React, { useState, useEffect } from 'react' |
||||||
|
import { TreeViewItemProps } from '../../types' |
||||||
|
|
||||||
|
import './tree-view-item.css' |
||||||
|
|
||||||
|
export const TreeViewItem = (props: TreeViewItemProps) => { |
||||||
|
const { id, children, label, expand, ...otherProps } = props |
||||||
|
const [isExpanded, setIsExpanded] = useState(false) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
setIsExpanded(expand) |
||||||
|
}, [expand]) |
||||||
|
|
||||||
|
return ( |
||||||
|
<li key={`treeViewLi${id}`} data-id={`treeViewLi${id}`} className='li_tv' {...otherProps}> |
||||||
|
<div key={`treeViewDiv${id}`} data-id={`treeViewDiv${id}`} className='d-flex flex-row align-items-center' onClick={() => setIsExpanded(!isExpanded)}> |
||||||
|
<div className={isExpanded ? 'px-1 fas fa-caret-down caret caret_tv' : 'px-1 fas fa-caret-right caret caret_tv'} style={{ visibility: children ? 'visible' : 'hidden' }}></div> |
||||||
|
<span className='w-100 pl-1'> |
||||||
|
{ label } |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
{ isExpanded ? children : null } |
||||||
|
</li> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default TreeViewItem |
@ -0,0 +1,13 @@ |
|||||||
|
export interface TreeViewProps { |
||||||
|
children?: React.ReactNode, |
||||||
|
id: string |
||||||
|
} |
||||||
|
|
||||||
|
export interface TreeViewItemProps { |
||||||
|
children?: React.ReactNode, |
||||||
|
id: string, |
||||||
|
label: string | number | React.ReactNode, |
||||||
|
expand?: boolean, |
||||||
|
onClick?: VoidFunction, |
||||||
|
className?: string |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
{ |
||||||
|
"extends": "../../../tsconfig.json", |
||||||
|
"compilerOptions": { |
||||||
|
"jsx": "react", |
||||||
|
"allowJs": true, |
||||||
|
"esModuleInterop": true, |
||||||
|
"allowSyntheticDefaultImports": true |
||||||
|
}, |
||||||
|
"files": [], |
||||||
|
"include": [], |
||||||
|
"references": [ |
||||||
|
{ |
||||||
|
"path": "./tsconfig.lib.json" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"path": "./tsconfig.spec.json" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
{ |
||||||
|
"extends": "./tsconfig.json", |
||||||
|
"compilerOptions": { |
||||||
|
"outDir": "../../../dist/out-tsc", |
||||||
|
"types": ["node"] |
||||||
|
}, |
||||||
|
"files": [ |
||||||
|
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts", |
||||||
|
"../../../node_modules/@nrwl/react/typings/image.d.ts" |
||||||
|
], |
||||||
|
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"], |
||||||
|
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
{ |
||||||
|
"extends": "./tsconfig.json", |
||||||
|
"compilerOptions": { |
||||||
|
"outDir": "../../../dist/out-tsc", |
||||||
|
"module": "commonjs", |
||||||
|
"types": ["jest", "node"] |
||||||
|
}, |
||||||
|
"include": [ |
||||||
|
"**/*.spec.ts", |
||||||
|
"**/*.spec.tsx", |
||||||
|
"**/*.spec.js", |
||||||
|
"**/*.spec.jsx", |
||||||
|
"**/*.d.ts" |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,4 @@ |
|||||||
|
{ |
||||||
|
"presets": ["@nrwl/react/babel"], |
||||||
|
"plugins": [] |
||||||
|
} |
@ -0,0 +1,248 @@ |
|||||||
|
{ |
||||||
|
"rules": { |
||||||
|
"array-callback-return": "warn", |
||||||
|
"dot-location": ["warn", "property"], |
||||||
|
"eqeqeq": ["warn", "smart"], |
||||||
|
"new-parens": "warn", |
||||||
|
"no-caller": "warn", |
||||||
|
"no-cond-assign": ["warn", "except-parens"], |
||||||
|
"no-const-assign": "warn", |
||||||
|
"no-control-regex": "warn", |
||||||
|
"no-delete-var": "warn", |
||||||
|
"no-dupe-args": "warn", |
||||||
|
"no-dupe-keys": "warn", |
||||||
|
"no-duplicate-case": "warn", |
||||||
|
"no-empty-character-class": "warn", |
||||||
|
"no-empty-pattern": "warn", |
||||||
|
"no-eval": "warn", |
||||||
|
"no-ex-assign": "warn", |
||||||
|
"no-extend-native": "warn", |
||||||
|
"no-extra-bind": "warn", |
||||||
|
"no-extra-label": "warn", |
||||||
|
"no-fallthrough": "warn", |
||||||
|
"no-func-assign": "warn", |
||||||
|
"no-implied-eval": "warn", |
||||||
|
"no-invalid-regexp": "warn", |
||||||
|
"no-iterator": "warn", |
||||||
|
"no-label-var": "warn", |
||||||
|
"no-labels": ["warn", { "allowLoop": true, "allowSwitch": false }], |
||||||
|
"no-lone-blocks": "warn", |
||||||
|
"no-loop-func": "warn", |
||||||
|
"no-mixed-operators": [ |
||||||
|
"warn", |
||||||
|
{ |
||||||
|
"groups": [ |
||||||
|
["&", "|", "^", "~", "<<", ">>", ">>>"], |
||||||
|
["==", "!=", "===", "!==", ">", ">=", "<", "<="], |
||||||
|
["&&", "||"], |
||||||
|
["in", "instanceof"] |
||||||
|
], |
||||||
|
"allowSamePrecedence": false |
||||||
|
} |
||||||
|
], |
||||||
|
"no-multi-str": "warn", |
||||||
|
"no-native-reassign": "warn", |
||||||
|
"no-negated-in-lhs": "warn", |
||||||
|
"no-new-func": "warn", |
||||||
|
"no-new-object": "warn", |
||||||
|
"no-new-symbol": "warn", |
||||||
|
"no-new-wrappers": "warn", |
||||||
|
"no-obj-calls": "warn", |
||||||
|
"no-octal": "warn", |
||||||
|
"no-octal-escape": "warn", |
||||||
|
"no-redeclare": "warn", |
||||||
|
"no-regex-spaces": "warn", |
||||||
|
"no-restricted-syntax": ["warn", "WithStatement"], |
||||||
|
"no-script-url": "warn", |
||||||
|
"no-self-assign": "warn", |
||||||
|
"no-self-compare": "warn", |
||||||
|
"no-sequences": "warn", |
||||||
|
"no-shadow-restricted-names": "warn", |
||||||
|
"no-sparse-arrays": "warn", |
||||||
|
"no-template-curly-in-string": "warn", |
||||||
|
"no-this-before-super": "warn", |
||||||
|
"no-throw-literal": "warn", |
||||||
|
"no-restricted-globals": [ |
||||||
|
"error", |
||||||
|
"addEventListener", |
||||||
|
"blur", |
||||||
|
"close", |
||||||
|
"closed", |
||||||
|
"confirm", |
||||||
|
"defaultStatus", |
||||||
|
"defaultstatus", |
||||||
|
"event", |
||||||
|
"external", |
||||||
|
"find", |
||||||
|
"focus", |
||||||
|
"frameElement", |
||||||
|
"frames", |
||||||
|
"history", |
||||||
|
"innerHeight", |
||||||
|
"innerWidth", |
||||||
|
"length", |
||||||
|
"location", |
||||||
|
"locationbar", |
||||||
|
"menubar", |
||||||
|
"moveBy", |
||||||
|
"moveTo", |
||||||
|
"name", |
||||||
|
"onblur", |
||||||
|
"onerror", |
||||||
|
"onfocus", |
||||||
|
"onload", |
||||||
|
"onresize", |
||||||
|
"onunload", |
||||||
|
"open", |
||||||
|
"opener", |
||||||
|
"opera", |
||||||
|
"outerHeight", |
||||||
|
"outerWidth", |
||||||
|
"pageXOffset", |
||||||
|
"pageYOffset", |
||||||
|
"parent", |
||||||
|
"print", |
||||||
|
"removeEventListener", |
||||||
|
"resizeBy", |
||||||
|
"resizeTo", |
||||||
|
"screen", |
||||||
|
"screenLeft", |
||||||
|
"screenTop", |
||||||
|
"screenX", |
||||||
|
"screenY", |
||||||
|
"scroll", |
||||||
|
"scrollbars", |
||||||
|
"scrollBy", |
||||||
|
"scrollTo", |
||||||
|
"scrollX", |
||||||
|
"scrollY", |
||||||
|
"self", |
||||||
|
"status", |
||||||
|
"statusbar", |
||||||
|
"stop", |
||||||
|
"toolbar", |
||||||
|
"top" |
||||||
|
], |
||||||
|
"no-unexpected-multiline": "warn", |
||||||
|
"no-unreachable": "warn", |
||||||
|
"no-unused-expressions": [ |
||||||
|
"error", |
||||||
|
{ |
||||||
|
"allowShortCircuit": true, |
||||||
|
"allowTernary": true, |
||||||
|
"allowTaggedTemplates": true |
||||||
|
} |
||||||
|
], |
||||||
|
"no-unused-labels": "warn", |
||||||
|
"no-useless-computed-key": "warn", |
||||||
|
"no-useless-concat": "warn", |
||||||
|
"no-useless-escape": "warn", |
||||||
|
"no-useless-rename": [ |
||||||
|
"warn", |
||||||
|
{ |
||||||
|
"ignoreDestructuring": false, |
||||||
|
"ignoreImport": false, |
||||||
|
"ignoreExport": false |
||||||
|
} |
||||||
|
], |
||||||
|
"no-with": "warn", |
||||||
|
"no-whitespace-before-property": "warn", |
||||||
|
"react-hooks/exhaustive-deps": "warn", |
||||||
|
"require-yield": "warn", |
||||||
|
"rest-spread-spacing": ["warn", "never"], |
||||||
|
"strict": ["warn", "never"], |
||||||
|
"unicode-bom": ["warn", "never"], |
||||||
|
"use-isnan": "warn", |
||||||
|
"valid-typeof": "warn", |
||||||
|
"no-restricted-properties": [ |
||||||
|
"error", |
||||||
|
{ |
||||||
|
"object": "require", |
||||||
|
"property": "ensure", |
||||||
|
"message": "Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"object": "System", |
||||||
|
"property": "import", |
||||||
|
"message": "Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting" |
||||||
|
} |
||||||
|
], |
||||||
|
"getter-return": "warn", |
||||||
|
"import/first": "error", |
||||||
|
"import/no-amd": "error", |
||||||
|
"import/no-webpack-loader-syntax": "error", |
||||||
|
"react/forbid-foreign-prop-types": ["warn", { "allowInPropTypes": true }], |
||||||
|
"react/jsx-no-comment-textnodes": "warn", |
||||||
|
"react/jsx-no-duplicate-props": "warn", |
||||||
|
"react/jsx-no-target-blank": "warn", |
||||||
|
"react/jsx-no-undef": "error", |
||||||
|
"react/jsx-pascal-case": ["warn", { "allowAllCaps": true, "ignore": [] }], |
||||||
|
"react/jsx-uses-react": "warn", |
||||||
|
"react/jsx-uses-vars": "warn", |
||||||
|
"react/no-danger-with-children": "warn", |
||||||
|
"react/no-direct-mutation-state": "warn", |
||||||
|
"react/no-is-mounted": "warn", |
||||||
|
"react/no-typos": "error", |
||||||
|
"react/react-in-jsx-scope": "error", |
||||||
|
"react/require-render-return": "error", |
||||||
|
"react/style-prop-object": "warn", |
||||||
|
"react/jsx-no-useless-fragment": "warn", |
||||||
|
"jsx-a11y/accessible-emoji": "warn", |
||||||
|
"jsx-a11y/alt-text": "warn", |
||||||
|
"jsx-a11y/anchor-has-content": "warn", |
||||||
|
"jsx-a11y/anchor-is-valid": [ |
||||||
|
"warn", |
||||||
|
{ "aspects": ["noHref", "invalidHref"] } |
||||||
|
], |
||||||
|
"jsx-a11y/aria-activedescendant-has-tabindex": "warn", |
||||||
|
"jsx-a11y/aria-props": "warn", |
||||||
|
"jsx-a11y/aria-proptypes": "warn", |
||||||
|
"jsx-a11y/aria-role": "warn", |
||||||
|
"jsx-a11y/aria-unsupported-elements": "warn", |
||||||
|
"jsx-a11y/heading-has-content": "warn", |
||||||
|
"jsx-a11y/iframe-has-title": "warn", |
||||||
|
"jsx-a11y/img-redundant-alt": "warn", |
||||||
|
"jsx-a11y/no-access-key": "warn", |
||||||
|
"jsx-a11y/no-distracting-elements": "warn", |
||||||
|
"jsx-a11y/no-redundant-roles": "warn", |
||||||
|
"jsx-a11y/role-has-required-aria-props": "warn", |
||||||
|
"jsx-a11y/role-supports-aria-props": "warn", |
||||||
|
"jsx-a11y/scope": "warn", |
||||||
|
"react-hooks/rules-of-hooks": "error", |
||||||
|
"default-case": "off", |
||||||
|
"no-dupe-class-members": "off", |
||||||
|
"no-undef": "off", |
||||||
|
"@typescript-eslint/consistent-type-assertions": "warn", |
||||||
|
"no-array-constructor": "off", |
||||||
|
"@typescript-eslint/no-array-constructor": "warn", |
||||||
|
"@typescript-eslint/no-namespace": "error", |
||||||
|
"no-use-before-define": "off", |
||||||
|
"@typescript-eslint/no-use-before-define": [ |
||||||
|
"warn", |
||||||
|
{ |
||||||
|
"functions": false, |
||||||
|
"classes": false, |
||||||
|
"variables": false, |
||||||
|
"typedefs": false |
||||||
|
} |
||||||
|
], |
||||||
|
"no-unused-vars": "off", |
||||||
|
"@typescript-eslint/no-unused-vars": [ |
||||||
|
"warn", |
||||||
|
{ "args": "none", "ignoreRestSiblings": true } |
||||||
|
], |
||||||
|
"no-useless-constructor": "off", |
||||||
|
"@typescript-eslint/no-useless-constructor": "warn" |
||||||
|
}, |
||||||
|
"env": { |
||||||
|
"browser": true, |
||||||
|
"commonjs": true, |
||||||
|
"es6": true, |
||||||
|
"jest": true, |
||||||
|
"node": true |
||||||
|
}, |
||||||
|
"settings": { "react": { "version": "detect" } }, |
||||||
|
"plugins": ["import", "jsx-a11y", "react", "react-hooks"], |
||||||
|
"extends": ["../../../.eslintrc"], |
||||||
|
"ignorePatterns": ["!**/*"] |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
# remix-ui-utils |
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev). |
||||||
|
|
||||||
|
## Running unit tests |
||||||
|
|
||||||
|
Run `nx test remix-ui-utils` to execute the unit tests via [Jest](https://jestjs.io). |
@ -0,0 +1,14 @@ |
|||||||
|
{ |
||||||
|
"presets": [ |
||||||
|
[ |
||||||
|
"@babel/preset-env", |
||||||
|
{ |
||||||
|
"targets": { |
||||||
|
"node": "current" |
||||||
|
} |
||||||
|
} |
||||||
|
], |
||||||
|
"@babel/preset-typescript", |
||||||
|
"@babel/preset-react" |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
module.exports = { |
||||||
|
name: 'remix-ui-utils', |
||||||
|
preset: '../../../jest.config.js', |
||||||
|
transform: { |
||||||
|
'^.+\\.[tj]sx?$': [ |
||||||
|
'babel-jest', |
||||||
|
{ cwd: __dirname, configFile: './babel-jest.config.json' } |
||||||
|
] |
||||||
|
}, |
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], |
||||||
|
coverageDirectory: '../../../coverage/libs/remix-ui/utils' |
||||||
|
}; |
@ -0,0 +1 @@ |
|||||||
|
export * from './lib/should-render' |
@ -0,0 +1,15 @@ |
|||||||
|
import React from 'react' |
||||||
|
|
||||||
|
/* eslint-disable-next-line */ |
||||||
|
export interface ShouldRenderProps { |
||||||
|
children?: React.ReactNode, |
||||||
|
if: boolean |
||||||
|
} |
||||||
|
|
||||||
|
export const ShouldRender = (props: ShouldRenderProps) => { |
||||||
|
return props.if ? ( |
||||||
|
props.children |
||||||
|
) : null |
||||||
|
} |
||||||
|
|
||||||
|
export default ShouldRender |
@ -0,0 +1,19 @@ |
|||||||
|
{ |
||||||
|
"extends": "../../../tsconfig.json", |
||||||
|
"compilerOptions": { |
||||||
|
"jsx": "react", |
||||||
|
"allowJs": true, |
||||||
|
"esModuleInterop": true, |
||||||
|
"allowSyntheticDefaultImports": true |
||||||
|
}, |
||||||
|
"files": [], |
||||||
|
"include": [], |
||||||
|
"references": [ |
||||||
|
{ |
||||||
|
"path": "./tsconfig.lib.json" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"path": "./tsconfig.spec.json" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
{ |
||||||
|
"extends": "./tsconfig.json", |
||||||
|
"compilerOptions": { |
||||||
|
"outDir": "../../../dist/out-tsc", |
||||||
|
"types": ["node"] |
||||||
|
}, |
||||||
|
"files": [ |
||||||
|
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts", |
||||||
|
"../../../node_modules/@nrwl/react/typings/image.d.ts" |
||||||
|
], |
||||||
|
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"], |
||||||
|
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
{ |
||||||
|
"extends": "./tsconfig.json", |
||||||
|
"compilerOptions": { |
||||||
|
"outDir": "../../../dist/out-tsc", |
||||||
|
"module": "commonjs", |
||||||
|
"types": ["jest", "node"] |
||||||
|
}, |
||||||
|
"include": [ |
||||||
|
"**/*.spec.ts", |
||||||
|
"**/*.spec.tsx", |
||||||
|
"**/*.spec.js", |
||||||
|
"**/*.spec.jsx", |
||||||
|
"**/*.d.ts" |
||||||
|
] |
||||||
|
} |
Loading…
Reference in new issue