Add diff view for received messages

This commit is contained in:
Thomas Nordquist
2019-02-23 22:49:42 +01:00
parent 107ca83882
commit 138f51eb39
5 changed files with 178 additions and 15 deletions

View File

@@ -12,6 +12,7 @@
"@material-ui/core": "^4.0.0-alpha.0", "@material-ui/core": "^4.0.0-alpha.0",
"@material-ui/icons": "^3.0.1", "@material-ui/icons": "^3.0.1",
"@material-ui/styles": "^3.0.0-alpha.8", "@material-ui/styles": "^3.0.0-alpha.8",
"@types/diff": "^4.0.1",
"@types/node": "^10.12.18", "@types/node": "^10.12.18",
"@types/react": "^16.7.18", "@types/react": "^16.7.18",
"@types/react-dom": "^16.0.11", "@types/react-dom": "^16.0.11",
@@ -26,18 +27,21 @@
"compare-versions": "^3.4.0", "compare-versions": "^3.4.0",
"copy-text-to-clipboard": "^1.0.4", "copy-text-to-clipboard": "^1.0.4",
"css-loader": "^2.1.0", "css-loader": "^2.1.0",
"diff": "^4.0.1",
"electron-nucleus": "^1.11.0", "electron-nucleus": "^1.11.0",
"electron-telemetry": "git+https://github.com/thomasnordquist/electron-telemetry.git#dist", "electron-telemetry": "git+https://github.com/thomasnordquist/electron-telemetry.git#dist",
"html-webpack-plugin": "^4.0.0-beta.5", "html-webpack-plugin": "^4.0.0-beta.5",
"jquery": "^3.3.1", "jquery": "^3.3.1",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"monaco-editor-webpack-plugin": "^1.7.0",
"moving-average": "^1.0.0", "moving-average": "^1.0.0",
"number-abbreviate": "^2.0.0", "number-abbreviate": "^2.0.0",
"react": "16.8", "react": "16.8",
"react-ace": "^6.3.2", "react-ace": "^6.3.2",
"react-dom": "^16.7.0", "react-dom": "^16.7.0",
"react-json-view": "^1.19.1", "react-json-view": "^1.19.1",
"react-monaco-editor": "^0.24.0",
"react-redux": "^6.0.0", "react-redux": "^6.0.0",
"react-resize-detector": "^3.4.0", "react-resize-detector": "^3.4.0",
"react-split-pane": "^0.1.85", "react-split-pane": "^0.1.85",

View File

@@ -166,7 +166,7 @@ class Sidebar extends React.Component<Props, State> {
<div ref={this.valueRef}> <div ref={this.valueRef}>
<React.Suspense fallback={<div>Loading...</div>}> <React.Suspense fallback={<div>Loading...</div>}>
<ReactResizeDetector handleWidth={true} onResize={this.valueRenderWidthChange} /> <ReactResizeDetector handleWidth={true} onResize={this.valueRenderWidthChange} />
<ValueRenderer message={this.props.node && this.props.node.message} /> <ValueRenderer node={this.props.node} />
</React.Suspense> </React.Suspense>
</div> </div>
<div><MessageHistory onSelect={this.handleMessageHistorySelect} node={this.props.node} /></div> <div><MessageHistory onSelect={this.handleMessageHistorySelect} node={this.props.node} /></div>

View File

@@ -1,26 +1,42 @@
import * as React from 'react'
import * as q from '../../../../backend/src/Model' 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 { Theme, withTheme } from '@material-ui/core/styles'
import * as diff from 'diff'
import { default as ReactJson } from 'react-json-view'
interface Props { interface Props {
message?: q.Message node?: q.TreeNode<any>,
theme: Theme theme: Theme
} }
class ValueRenderer extends React.Component<Props, {}> { interface State {
width: number
modifiedValue?: string
node?: q.TreeNode<any>
currentMessage?: q.Message
}
class ValueRenderer extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { width: 0, node: props.node }
}
public render() { public render() {
return <div style={{ padding: '8px 0px 8px 8px' }}>{this.renderValue()}</div> return <div style={{ padding: '8px 0px 8px 8px' }}>{this.renderValue()}</div>
} }
public renderValue() { public renderValue() {
const { message } = this.props const { node } = this.props
if (!message) { if (!node || !node.message) {
return null return null
} }
const message = node.message
const previousMessages = node.messageHistory.toArray()
const previousMessage = previousMessages[previousMessages.length - 2]
debugger
let json let json
try { try {
json = JSON.parse(message.value) json = JSON.parse(message.value)
@@ -36,17 +52,96 @@ class ValueRenderer extends React.Component<Props, {}> {
return this.renderRawValue(message.value) return this.renderRawValue(message.value)
} else { } else {
const theme = (this.props.theme.palette.type === 'dark') ? 'monokai' : 'bright:inverted' const theme = (this.props.theme.palette.type === 'dark') ? 'monokai' : 'bright:inverted'
const current = this.messageToPrettyJson(message)
const previous = this.messageToPrettyJson(previousMessage)
return ( return (
<ReactJson <div>
style={{ width: '100%', overflowY: 'scroll', wordBreak: 'break-all' }} <ReactResizeDetector handleWidth={true} onResize={this.updateWidth} />
src={json} {this.renderDiff(current, previous || current)}
name={null} </div>
theme={theme}
/>
) )
} }
} }
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 (
<MonacoDiffEditor
language="json"
height={Math.min(height, 200)}
options={{ ...this.editorOptions, renderSideBySide: false }}
onChange={value => 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) { private renderRawValue(value: string) {
const style: React.CSSProperties = { const style: React.CSSProperties = {
padding: '8px 12px 8px 12px', padding: '8px 12px 8px 12px',

View File

@@ -1,6 +1,7 @@
const LiveReloadPlugin = require('webpack-livereload-plugin'); const LiveReloadPlugin = require('webpack-livereload-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const HtmlWebpackPlugin = require('html-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin')
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin')
module.exports = { module.exports = {
entry: { entry: {
@@ -66,6 +67,10 @@ module.exports = {
plugins: [ plugins: [
new LiveReloadPlugin({}), new LiveReloadPlugin({}),
new HtmlWebpackPlugin({ template: './index.html', file: './build/index.html', inject: false }), 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(), // new BundleAnalyzerPlugin(),
], ],

View File

@@ -111,6 +111,16 @@
prop-types "^15.6.0" prop-types "^15.6.0"
react-is "^16.8.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": "@types/jss@^9.5.6":
version "9.5.7" version "9.5.7"
resolved "https://registry.yarnpkg.com/@types/jss/-/jss-9.5.7.tgz#fa57a6d0b38a3abef8a425e3eb6a53495cb9d5a0" 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" resolved "https://registry.yarnpkg.com/@types/socket.io-client/-/socket.io-client-1.4.32.tgz#988a65a0386c274b1c22a55377fab6a30789ac14"
integrity sha512-Vs55Kq8F+OWvy1RLA31rT+cAyemzgm0EWNeax6BWF8H7QiiOYMJIdcwSDdm5LVgfEkoepsWkS+40+WNb7BUMbg== 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": "@types/uuid@^3.4.4":
version "3.4.4" version "3.4.4"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.4.tgz#7af69360fa65ef0decb41fd150bf4ca5c0cefdf5" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.4.tgz#7af69360fa65ef0decb41fd150bf4ca5c0cefdf5"
@@ -199,6 +221,17 @@
dependencies: dependencies:
moment ">=2.13.0" 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": "@webassemblyjs/ast@1.7.11":
version "1.7.11" version "1.7.11"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.11.tgz#b988582cafbb2b095e8b556526f30c90d057cace" 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" resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.4.tgz#6ac4b55237463761c4daf0dc603eb869124744b1"
integrity sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg== 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: diffie-hellman@^5.0.0:
version "5.0.3" version "5.0.3"
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" 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" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== 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: move-concurrently@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" 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" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== 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: react-motion@^0.5.2:
version "0.5.2" version "0.5.2"
resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316" resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316"