Refactor CodeDiff

This commit is contained in:
Thomas Nordquist
2019-06-04 11:32:59 +02:00
parent 8cc3d416b8
commit e66f6d098a
5 changed files with 136 additions and 87 deletions

View File

@@ -0,0 +1,39 @@
import * as React from 'react'
import { Theme } from '@material-ui/core';
import { withStyles } from '@material-ui/styles'
interface Props {
changes: Array<Diff.Change>
classes: {[s: string]: any}
nameOfCompareMessage: string
}
function changeAmount(props: Props) {
const additions = props.changes.map(change => (change.added === true) ? (change.count || 0) : 0).reduce((a, b) => a + b)
const deletions = props.changes.map(change => (change.removed === true) ? (change.count || 0) : 0).reduce((a, b) => a + b)
if (additions === 0 && deletions === 0) {
return null
}
return (
<span style={{ display: 'block', marginBottom: '8px', float: 'right' }}>
<span>
Comparing with <b>{props.nameOfCompareMessage}</b> message:&nbsp;
<span className={props.classes.additions}>+ {additions} line{additions === 1 ? '' : 's'}</span>
, <span className={props.classes.deletions}>- {deletions} line{deletions === 1 ? '' : 's'}</span>
</span>
</span>
)
}
const style = (theme: Theme) => ({
additions: {
color: 'rgb(10, 255, 10)',
},
deletions: {
color: 'rgb(255, 10, 10)',
},
})
export default withStyles(style)(changeAmount)

View File

@@ -0,0 +1,38 @@
import * as diff from 'diff'
import * as React from 'react'
import ShowChart from '@material-ui/icons/ShowChart'
import { JsonPropertyLocation, literalsMappedByLines } from '../../../../../backend/src/JsonAstParser'
import { lineChangeStyle, trimNewlineRight } from './util'
import { Theme } from '@material-ui/core'
import { withStyles } from '@material-ui/styles'
interface Props {
changes: Array<diff.Change>,
literalPositions: Array<JsonPropertyLocation>
classes: any
}
const style = (theme: Theme) => {
return {
gutterLine: {
textAlign: 'right' as 'right',
paddingRight: theme.spacing(0.5),
},
}
}
function Gutters(props: Props) {
const gutters = props.changes.map((change, key) => {
return trimNewlineRight(change.value)
.split('\n')
.map((_, idx) => (
<div key={`${key}-${idx}`} style={lineChangeStyle(change)} className={props.classes.gutterLine}>
{change.added ? '+' : null}{change.removed ? '-' : null}{!change.added && !change.removed ? ' ' : null}
</div>
))
}).reduce((a, b) => a.concat(b), [])
return <div>{gutters}</div>
}
export default withStyles(style)(Gutters)

View File

@@ -1,10 +1,14 @@
import * as diff from 'diff' import * as diff from 'diff'
import * as Prism from 'prismjs' import * as Prism from 'prismjs'
import * as React from 'react' import * as React from 'react'
import { CodeBlockColors, CodeBlockColorsBraceMonokai } from './CodeBlockColors' import DiffCount from './DiffCount'
import { selectTextWithCtrlA } from '../../utils/handleTextSelectWithCtrlA' import { CodeBlockColors, CodeBlockColorsBraceMonokai } from '../CodeBlockColors'
import { literalsMappedByLines, parseJson } from '../../../../../backend/src/JsonAstParser'
import { selectTextWithCtrlA } from '../../../utils/handleTextSelectWithCtrlA'
import { Theme, withStyles } from '@material-ui/core' import { Theme, withStyles } from '@material-ui/core'
import 'prismjs/components/prism-json' import 'prismjs/components/prism-json'
import { trimNewlineRight, lineChangeStyle } from './util';
import Gutters from './Gutters'
interface Props { interface Props {
previous: string previous: string
@@ -21,48 +25,10 @@ class CodeDiff extends React.Component<Props, {}> {
super(props) super(props)
} }
private renderChangeAmount(changes: Array<Diff.Change>) {
const additions = changes.map(change => (change.added === true) ? (change.count || 0) : 0).reduce((a, b) => a + b)
const deletions = changes.map(change => (change.removed === true) ? (change.count || 0) : 0).reduce((a, b) => a + b)
if (additions === 0 && deletions === 0) {
return null
}
return (
<span style={{ display: 'block', marginBottom: '8px', float: 'right' }}>
<span>
Comparing with <b>{this.props.nameOfCompareMessage}</b> message:&nbsp;
<span className={this.props.classes.additions}>+ {additions} line{additions === 1 ? '' : 's'}</span>
, <span className={this.props.classes.deletions}>- {deletions} line{deletions === 1 ? '' : 's'}</span>
</span>
</span>
)
}
private trimNewlineRight(str: string) {
if (str.slice(-1) === '\n') {
return str.slice(0, -1)
}
return str
}
private cssClassForChange(change: diff.Change) {
if (change.added === true) {
return this.props.classes.addition
}
if (change.removed === true) {
return this.props.classes.deletion
}
return this.props.classes.noChange
}
public render() { public render() {
const changes = diff.diffLines(this.props.previous, this.props.current) const changes = diff.diffLines(this.props.previous, this.props.current)
const styledLines = Prism.highlight(this.props.current, Prism.languages.json, 'json').split('\n') const styledLines = Prism.highlight(this.props.current, Prism.languages.json, 'json').split('\n')
const literalPositions = literalsMappedByLines(this.props.current)
let lineNumber = 0 let lineNumber = 0
const code = changes.map((change, key) => { const code = changes.map((change, key) => {
@@ -71,37 +37,27 @@ class CodeDiff extends React.Component<Props, {}> {
if (hasStyledCode && this.props.language === 'json') { if (hasStyledCode && this.props.language === 'json') {
const currentLines = styledLines.slice(lineNumber, lineNumber + changedLines) const currentLines = styledLines.slice(lineNumber, lineNumber + changedLines)
const lines = currentLines.map((html: string, idx: number) => { const lines = currentLines.map((html: string, idx: number) => {
return <div key={`${key}-${idx}`} className={`${this.props.classes.line} ${this.cssClassForChange(change)}`}><span dangerouslySetInnerHTML={{ __html: html }} /></div> return <div key={`${key}-${idx}`} style={lineChangeStyle(change)} className={`${this.props.classes.line}`}><span dangerouslySetInnerHTML={{ __html: html }} /></div>
}) })
lineNumber += changedLines lineNumber += changedLines
return <div key={key}>{lines}</div> return <div key={key}>{lines}</div>
} }
return this.trimNewlineRight(change.value) return trimNewlineRight(change.value)
.split('\n') .split('\n')
.map((line, idx) => { .map((line, idx) => {
return <div key={`${key}-${idx}`} className={`${this.props.classes.line} ${this.cssClassForChange(change)}`}><span>{line}</span></div> return <div key={`${key}-${idx}`} style={lineChangeStyle(change)} className={this.props.classes.line}><span>{line}</span></div>
}) })
}) })
const gutters = changes.map((change, key) => {
return this.trimNewlineRight(change.value)
.split('\n')
.map((_, idx) => (
<div key={`${key}-${idx}`} className={`${this.cssClassForChange(change)} ${this.props.classes.gutterLine}`}>
{change.added ? '+' : null}{change.removed ? '-' : null}{!change.added && !change.removed ? ' ' : null}
</div>
))
})
return ( return (
<div> <div>
<div tabIndex={0} onKeyDown={this.handleCtrlA} className={this.props.classes.codeWrapper}> <div tabIndex={0} onKeyDown={this.handleCtrlA} className={this.props.classes.codeWrapper}>
<pre className={this.props.classes.gutters}>{gutters}</pre> <pre className={this.props.classes.gutters}><Gutters changes={changes} literalPositions={literalPositions} /></pre>
<pre className={this.props.classes.codeBlock}>{code}</pre> <pre className={this.props.classes.codeBlock}>{code}</pre>
</div> </div>
{this.renderChangeAmount(changes)} <DiffCount changes={changes} nameOfCompareMessage={this.props.nameOfCompareMessage} />
</div> </div>
) )
} }
@@ -109,9 +65,6 @@ class CodeDiff extends React.Component<Props, {}> {
const style = (theme: Theme) => { const style = (theme: Theme) => {
const codeBlockColors = theme.palette.type === 'light' ? CodeBlockColors : CodeBlockColorsBraceMonokai const codeBlockColors = theme.palette.type === 'light' ? CodeBlockColors : CodeBlockColorsBraceMonokai
const gutterBaseStyle = {
width: '100%',
}
const codeBaseStyle = { const codeBaseStyle = {
font: "12px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace", font: "12px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace",
@@ -121,19 +74,10 @@ const style = (theme: Theme) => {
} }
return { return {
additions: {
color: 'rgb(10, 255, 10)',
},
deletions: {
color: 'rgb(255, 10, 10)',
},
line: { line: {
lineHeight: 'normal' as 'normal', lineHeight: 'normal' as 'normal',
paddingLeft: '4px', paddingLeft: '4px',
}, width: '100%',
gutterLine: {
textAlign: 'right' as 'right',
paddingRight: theme.spacing(0.5),
}, },
codeWrapper: { codeWrapper: {
maxHeight: '15em', maxHeight: '15em',
@@ -176,23 +120,6 @@ const style = (theme: Theme) => {
color: codeBlockColors.text, color: codeBlockColors.text,
}, },
}, },
noChange: {
...gutterBaseStyle,
},
deletion: {
...gutterBaseStyle,
backgroundColor: 'rgba(255, 10, 10, 0.3)',
// '&:hover': {
// backgroundColor: 'rgba(255, 10, 10, 0.3)',
// },
},
addition: {
...gutterBaseStyle,
backgroundColor: 'rgba(10, 255, 10, 0.3)',
// '&:hover': {
// backgroundColor: 'rgba(10, 255, 10, 0.5)',
// },
},
} }
} }

View File

@@ -0,0 +1,33 @@
export function trimNewlineRight(str: string) {
if (str.slice(-1) === '\n') {
return str.slice(0, -1)
}
return str
}
const gutterBaseStyle = {
width: '100%',
}
const additionStyle = {
...gutterBaseStyle,
backgroundColor: 'rgba(10, 255, 10, 0.3)',
}
const deletionStyle = {
...gutterBaseStyle,
backgroundColor: 'rgba(255, 10, 10, 0.3)',
}
export function lineChangeStyle(change: Diff.Change) {
if (change.added === true) {
return additionStyle
}
if (change.removed === true) {
return deletionStyle
}
return gutterBaseStyle
}

View File

@@ -1,8 +1,9 @@
const parse = require('json-to-ast') const parse = require('json-to-ast')
interface JsonPropertyLocation { export interface JsonPropertyLocation {
path: string path: string
line: number line: number
value: any
column: number column: number
} }
@@ -59,6 +60,7 @@ function jsonToPropertyPaths(ast: JsonAst, previousPath: Array<string> = []): Ar
let children: Array<Array<JsonPropertyLocation>> = [] let children: Array<Array<JsonPropertyLocation>> = []
if (ast.type === 'Literal') { if (ast.type === 'Literal') {
return [{ return [{
value: ast.value,
path: previousPath.join('.'), path: previousPath.join('.'),
line: ast.loc.start.line, line: ast.loc.start.line,
column: ast.loc.start.column, column: ast.loc.start.column,
@@ -75,3 +77,13 @@ function jsonToPropertyPaths(ast: JsonAst, previousPath: Array<string> = []): Ar
export function parseJson(formattedJson: string): Array<JsonPropertyLocation> { export function parseJson(formattedJson: string): Array<JsonPropertyLocation> {
return jsonToPropertyPaths((parse(formattedJson) as JsonAst), []) return jsonToPropertyPaths((parse(formattedJson) as JsonAst), [])
} }
export function literalsMappedByLines(formattedJson: string): Array<JsonPropertyLocation> {
const literals = jsonToPropertyPaths((parse(formattedJson) as JsonAst), [])
const lines = []
for (const literal of literals) {
lines[literal.line - 1] = literal
}
return lines
}