diff --git a/app/package.json b/app/package.json index 3aeffcd..33b6bc7 100644 --- a/app/package.json +++ b/app/package.json @@ -12,6 +12,7 @@ "@material-ui/core": "^4.0.0-alpha.0", "@material-ui/icons": "^3.0.1", "@material-ui/styles": "^3.0.0-alpha.8", + "@types/diff": "^4.0.1", "@types/node": "^10.12.18", "@types/react": "^16.7.18", "@types/react-dom": "^16.0.11", @@ -26,18 +27,21 @@ "compare-versions": "^3.4.0", "copy-text-to-clipboard": "^1.0.4", "css-loader": "^2.1.0", + "diff": "^4.0.1", "electron-nucleus": "^1.11.0", "electron-telemetry": "git+https://github.com/thomasnordquist/electron-telemetry.git#dist", "html-webpack-plugin": "^4.0.0-beta.5", "jquery": "^3.3.1", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", + "monaco-editor-webpack-plugin": "^1.7.0", "moving-average": "^1.0.0", "number-abbreviate": "^2.0.0", "react": "16.8", "react-ace": "^6.3.2", "react-dom": "^16.7.0", "react-json-view": "^1.19.1", + "react-monaco-editor": "^0.24.0", "react-redux": "^6.0.0", "react-resize-detector": "^3.4.0", "react-split-pane": "^0.1.85", diff --git a/app/src/components/Sidebar/Sidebar.tsx b/app/src/components/Sidebar/Sidebar.tsx index 3797953..c9ca855 100644 --- a/app/src/components/Sidebar/Sidebar.tsx +++ b/app/src/components/Sidebar/Sidebar.tsx @@ -166,7 +166,7 @@ class Sidebar extends React.Component {
Loading...
}> - +
diff --git a/app/src/components/Sidebar/ValueRenderer.tsx b/app/src/components/Sidebar/ValueRenderer.tsx index 6a88fd7..ee5046d 100644 --- a/app/src/components/Sidebar/ValueRenderer.tsx +++ b/app/src/components/Sidebar/ValueRenderer.tsx @@ -1,26 +1,42 @@ -import * as React from 'react' import * as q from '../../../../backend/src/Model' - +import * as React from 'react' +import MonacoEditor, { MonacoDiffEditor, MonacoEditorBaseProps, MonacoEditorProps } from 'react-monaco-editor' +import ReactResizeDetector from 'react-resize-detector' import { Theme, withTheme } from '@material-ui/core/styles' - -import { default as ReactJson } from 'react-json-view' +import * as diff from 'diff' interface Props { - message?: q.Message + node?: q.TreeNode, theme: Theme } -class ValueRenderer extends React.Component { +interface State { + width: number + modifiedValue?: string + node?: q.TreeNode + currentMessage?: q.Message +} + +class ValueRenderer extends React.Component { + constructor(props: Props) { + super(props) + this.state = { width: 0, node: props.node } + } + public render() { return
{this.renderValue()}
} public renderValue() { - const { message } = this.props - if (!message) { + const { node } = this.props + if (!node || !node.message) { return null } + const message = node.message + const previousMessages = node.messageHistory.toArray() + const previousMessage = previousMessages[previousMessages.length - 2] + debugger let json try { json = JSON.parse(message.value) @@ -36,17 +52,96 @@ class ValueRenderer extends React.Component { return this.renderRawValue(message.value) } else { const theme = (this.props.theme.palette.type === 'dark') ? 'monokai' : 'bright:inverted' + const current = this.messageToPrettyJson(message) + const previous = this.messageToPrettyJson(previousMessage) + return ( - +
+ + {this.renderDiff(current, previous || current)} +
) } } + public static getDerivedStateFromProps(props: Props, state: State) { + const discardEdit = props.node !== state.node || (props.node && props.node.message !== state.currentMessage) + return { + ...state, + node: props.node, + currentMessage: props.node && props.node.message, + modifiedValue: discardEdit ? undefined : state.modifiedValue, + } + } + + private heightForLines(lines: number) { + return 0 + (lines * 18) + } + + private editorOptions = { + lineHeigh: 16, + lineNumbers: 'off' as 'off', + scrollBeyondLastLine: false, + minimap: { enabled: false }, + theme: 'vs-dark', + } + + private renderDiff(current: string = '', previous: string = '') { + const value = this.state.modifiedValue !== undefined ? this.state.modifiedValue : current + const lines = this.expectedLineCountFor(value, previous) + const height = this.heightForLines(lines) + return ( + this.setState({ modifiedValue: value })} + original={previous} + width={this.state.width} + value={value} + /> + ) + } + + private expectedLineCountFor(current?: string, original?: string): number { + if (current === undefined) { + return 0 + } + + const originalStr = original || '' + const changes = diff.diffLines(originalStr, current) + + const added = changes + .map((change) => { + const added = (change.added && change.count) || 0 + const removed = (change.removed && change.count) || 0 + + return Math.abs(added) + }) + .reduce((a, b) => a + b, 0) + + const originalLines = originalStr.split('\n').length + + return originalLines + added + } + + private messageToPrettyJson(message?: q.Message): string | undefined { + if (!message || !message.value) { + return undefined + } + + try { + const json = JSON.parse(message.value) + return JSON.stringify(json, undefined, ' ') + } catch { + return undefined + } + } + + private updateWidth = (width: number) => { + this.setState({ width }) + } + private renderRawValue(value: string) { const style: React.CSSProperties = { padding: '8px 12px 8px 12px', diff --git a/app/webpack.config.js b/app/webpack.config.js index afe1d6e..5b0ceb5 100644 --- a/app/webpack.config.js +++ b/app/webpack.config.js @@ -1,6 +1,7 @@ const LiveReloadPlugin = require('webpack-livereload-plugin'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const HtmlWebpackPlugin = require('html-webpack-plugin') +const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin') module.exports = { entry: { @@ -66,6 +67,10 @@ module.exports = { plugins: [ new LiveReloadPlugin({}), new HtmlWebpackPlugin({ template: './index.html', file: './build/index.html', inject: false }), + new MonacoWebpackPlugin({ + // available options are documented at https://github.com/Microsoft/monaco-editor-webpack-plugin#options + languages: ['json'] + }) // new BundleAnalyzerPlugin(), ], diff --git a/app/yarn.lock b/app/yarn.lock index ea24a58..4d3b248 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -111,6 +111,16 @@ prop-types "^15.6.0" react-is "^16.8.0" +"@types/anymatch@*": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" + integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== + +"@types/diff@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/diff/-/diff-4.0.1.tgz#471950ff97e2205ab02404bd7ac960fac074d886" + integrity sha512-BFob98KGKIqjcJjbmO/g+daJinDOtSUs27e0NTirODSUHYhUUAVXmq/nKIrmRFsOHrOWj5mncvByrzyw1X1PNQ== + "@types/jss@^9.5.6": version "9.5.7" resolved "https://registry.yarnpkg.com/@types/jss/-/jss-9.5.7.tgz#fa57a6d0b38a3abef8a425e3eb6a53495cb9d5a0" @@ -185,6 +195,18 @@ resolved "https://registry.yarnpkg.com/@types/socket.io-client/-/socket.io-client-1.4.32.tgz#988a65a0386c274b1c22a55377fab6a30789ac14" integrity sha512-Vs55Kq8F+OWvy1RLA31rT+cAyemzgm0EWNeax6BWF8H7QiiOYMJIdcwSDdm5LVgfEkoepsWkS+40+WNb7BUMbg== +"@types/tapable@*": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370" + integrity sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ== + +"@types/uglify-js@*": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.0.4.tgz#96beae23df6f561862a830b4288a49e86baac082" + integrity sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ== + dependencies: + source-map "^0.6.1" + "@types/uuid@^3.4.4": version "3.4.4" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.4.tgz#7af69360fa65ef0decb41fd150bf4ca5c0cefdf5" @@ -199,6 +221,17 @@ dependencies: moment ">=2.13.0" +"@types/webpack@^4.4.19": + version "4.4.24" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.4.24.tgz#75bc301176066f566ec54151b6101c2b45abb8b2" + integrity sha512-yg99CjvB7xZ/iuHrsZ7dkGKoq/FRDzqLzAxKh2EmTem6FWjzrty4FqCqBYuX5z+MFwSaaQGDAX4Q9HQkLjGLnQ== + dependencies: + "@types/anymatch" "*" + "@types/node" "*" + "@types/tapable" "*" + "@types/uglify-js" "*" + source-map "^0.6.0" + "@webassemblyjs/ast@1.7.11": version "1.7.11" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.11.tgz#b988582cafbb2b095e8b556526f30c90d057cace" @@ -1645,6 +1678,11 @@ diff-match-patch@^1.0.4: resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.4.tgz#6ac4b55237463761c4daf0dc603eb869124744b1" integrity sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg== +diff@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" + integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -3621,6 +3659,18 @@ moment@>=2.13.0: resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== +monaco-editor-webpack-plugin@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-1.7.0.tgz#920cbeecca25f15d70d568a7e11b0ba4daf1ae83" + integrity sha512-oItymcnlL14Sjd7EF7q+CMhucfwR/2BxsqrXIBrWL6LQplFfAfV+grLEQRmVHeGSBZ/Gk9ptzfueXnWcoEcFuA== + dependencies: + "@types/webpack" "^4.4.19" + +monaco-editor@^0.15.6: + version "0.15.6" + resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.15.6.tgz#d63b3b06f86f803464f003b252627c3eb4a09483" + integrity sha512-JoU9V9k6KqT9R9Tiw1RTU8ohZ+Xnf9DMg6Ktqqw5hILumwmq7xqa/KLXw513uTUsWbhtnHoSJYYR++u3pkyxJg== + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -4525,6 +4575,15 @@ react-lifecycles-compat@^3.0.2, react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +react-monaco-editor@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/react-monaco-editor/-/react-monaco-editor-0.24.0.tgz#30e2028d3e235edc16a0e488f241637677929d54" + integrity sha512-SOCxZQYPVKShzVWPOMG0i+TM6l+I862x/+IpV+QPjTEtfh12Q+Cl6NO14EPnlphd9e8lmyFAKr5Md0aRdQfcLg== + dependencies: + "@types/react" "*" + monaco-editor "^0.15.6" + prop-types "^15.6.2" + react-motion@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316"