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 = { |
||||
"presets": ["@babel/preset-react", "@babel/preset-typescript"], |
||||
"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