Refactor CodeDiff
This commit is contained in:
39
app/src/components/Sidebar/CodeDiff/DiffCount.tsx
Normal file
39
app/src/components/Sidebar/CodeDiff/DiffCount.tsx
Normal 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:
|
||||||
|
|
||||||
|
<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)
|
||||||
38
app/src/components/Sidebar/CodeDiff/Gutters.tsx
Normal file
38
app/src/components/Sidebar/CodeDiff/Gutters.tsx
Normal 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)
|
||||||
@@ -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:
|
|
||||||
|
|
||||||
<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)',
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
33
app/src/components/Sidebar/CodeDiff/util.tsx
Normal file
33
app/src/components/Sidebar/CodeDiff/util.tsx
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user