Fix flaky diagram
This commit is contained in:
@@ -1,11 +1,19 @@
|
|||||||
import * as diff from 'diff'
|
import * as diff from 'diff'
|
||||||
|
import * as q from '../../../../../backend/src/Model'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import Add from '@material-ui/icons/Add'
|
import Add from '@material-ui/icons/Add'
|
||||||
import Remove from '@material-ui/icons/Remove'
|
import Remove from '@material-ui/icons/Remove'
|
||||||
import ShowChart from '@material-ui/icons/ShowChart'
|
import ShowChart from '@material-ui/icons/ShowChart'
|
||||||
|
import TopicPlot from '../TopicPlot'
|
||||||
|
import {
|
||||||
|
Fade,
|
||||||
|
Paper,
|
||||||
|
Popper,
|
||||||
|
Theme,
|
||||||
|
Tooltip
|
||||||
|
} from '@material-ui/core'
|
||||||
import { JsonPropertyLocation } from '../../../../../backend/src/JsonAstParser'
|
import { JsonPropertyLocation } from '../../../../../backend/src/JsonAstParser'
|
||||||
import { lineChangeStyle, trimNewlineRight } from './util'
|
import { lineChangeStyle, trimNewlineRight } from './util'
|
||||||
import { Theme, Tooltip } from '@material-ui/core'
|
|
||||||
import { withStyles } from '@material-ui/styles'
|
import { withStyles } from '@material-ui/styles'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -13,9 +21,7 @@ interface Props {
|
|||||||
literalPositions: Array<JsonPropertyLocation>
|
literalPositions: Array<JsonPropertyLocation>
|
||||||
classes: any
|
classes: any
|
||||||
className: string
|
className: string
|
||||||
showDiagram: (dotPath: string, target: React.Ref<HTMLElement>) => void
|
messageHistory: q.MessageHistory
|
||||||
hideDiagram: () => void
|
|
||||||
hasEnoughDataToDisplayDiagrams: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const style = (theme: Theme) => {
|
const style = (theme: Theme) => {
|
||||||
@@ -29,11 +35,14 @@ const style = (theme: Theme) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
icon,
|
icon,
|
||||||
|
iconDisabled: {
|
||||||
|
...icon,
|
||||||
|
color: theme.palette.text.disabled,
|
||||||
|
},
|
||||||
iconButton: {
|
iconButton: {
|
||||||
...icon,
|
...icon,
|
||||||
width: '16px',
|
width: '16px',
|
||||||
height: '16px',
|
height: '16px',
|
||||||
marginTop: '1px',
|
|
||||||
padding: '2px',
|
padding: '2px',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
color: theme.palette.primary.contrastText,
|
color: theme.palette.primary.contrastText,
|
||||||
@@ -49,34 +58,55 @@ const style = (theme: Theme) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChartIcon(props: { classes: any, literal: JsonPropertyLocation, showDiagram: (dotPath: string, target: React.Ref<HTMLElement>) => void, hideDiagram: () => void }) {
|
function ChartIcon(props: { messageHistory: q.MessageHistory, classes: any, literal: JsonPropertyLocation }) {
|
||||||
const chartIconRef = React.useRef(null)
|
const chartIconRef = React.useRef(null)
|
||||||
|
const [open, setOpen] = React.useState(false)
|
||||||
|
|
||||||
const mouseOver = React.useCallback((event: React.MouseEvent<Element>) => {
|
const mouseOver = React.useCallback((event: React.MouseEvent<Element>) => {
|
||||||
props.showDiagram(props.literal.path, chartIconRef)
|
setOpen(true)
|
||||||
}, [props.literal.path])
|
}, [props.literal.path])
|
||||||
|
|
||||||
const mouseOut = React.useCallback(() => {
|
const mouseOut = React.useCallback(() => {
|
||||||
props.hideDiagram()
|
setOpen(false)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (<span>
|
||||||
<ShowChart ref={chartIconRef} className={props.classes.icon} onMouseEnter={mouseOver} onMouseLeave={mouseOut} />
|
<ShowChart ref={chartIconRef} className={props.classes.icon} onMouseEnter={mouseOver} onMouseLeave={mouseOut} />
|
||||||
|
<Popper
|
||||||
|
open={open}
|
||||||
|
anchorEl={chartIconRef.current}
|
||||||
|
placement="left-end"
|
||||||
|
>
|
||||||
|
<Fade in={open} timeout={300}>
|
||||||
|
<Paper style={{ width: '300px' }}>
|
||||||
|
{open ? <TopicPlot history={props.messageHistory} dotPath={props.literal.path} /> : <span/>}
|
||||||
|
</Paper>
|
||||||
|
</Fade>
|
||||||
|
</Popper>
|
||||||
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function tokensForLine(change: diff.Change, line: number, props: Props) {
|
function tokensForLine(change: diff.Change, line: number, props: Props) {
|
||||||
const { classes, literalPositions } = props
|
const { classes, literalPositions } = props
|
||||||
|
const hasEnoughDataToDisplayDiagrams = props.messageHistory.count() > 1
|
||||||
const literal = literalPositions[line]
|
const literal = literalPositions[line]
|
||||||
const diagram = literal ? <Tooltip title="Not enough data"><ChartIcon classes={{ icon: props.classes.iconButton }} literal={literal} showDiagram={props.showDiagram} hideDiagram={props.hideDiagram}/></Tooltip> : null
|
|
||||||
|
let chartIcon = null
|
||||||
|
if (literal) {
|
||||||
|
if (hasEnoughDataToDisplayDiagrams) {
|
||||||
|
chartIcon = <ChartIcon messageHistory={props.messageHistory} classes={{ icon: props.classes.iconButton }} literal={literal} />
|
||||||
|
} else {
|
||||||
|
chartIcon = <Tooltip title="Not enough data"><ShowChart className={props.classes.iconDisabled} style={{ color: '#aaa' }} /></Tooltip>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (change.added) {
|
if (change.added) {
|
||||||
return [diagram, <Add key="add" className={classes.icon} />]
|
return [chartIcon, <Add key="add" className={classes.icon} />]
|
||||||
} else if (change.removed) {
|
} else if (change.removed) {
|
||||||
return [<Remove key="remove" className={classes.icon} />]
|
return [<Remove key="remove" className={classes.icon} />]
|
||||||
} else {
|
} else {
|
||||||
return [diagram, <div key="placeholder" style={{ width: '12px', display: 'inline-block' }} dangerouslySetInnerHTML={{ __html: ' '}} />]
|
return [chartIcon, <div key="placeholder" style={{ width: '12px', display: 'inline-block' }} dangerouslySetInnerHTML={{ __html: ' ' }} />]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,19 +4,12 @@ import * as q from '../../../../../backend/src/Model'
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import DiffCount from './DiffCount'
|
import DiffCount from './DiffCount'
|
||||||
import Gutters from './Gutters'
|
import Gutters from './Gutters'
|
||||||
import TopicPlot from '../TopicPlot'
|
|
||||||
import {
|
|
||||||
Fade,
|
|
||||||
Paper,
|
|
||||||
Popper,
|
|
||||||
withStyles
|
|
||||||
} from '@material-ui/core'
|
|
||||||
import { isPlottable, lineChangeStyle, trimNewlineRight } from './util'
|
import { isPlottable, lineChangeStyle, trimNewlineRight } from './util'
|
||||||
import { JsonPropertyLocation, literalsMappedByLines } from '../../../../../backend/src/JsonAstParser'
|
import { JsonPropertyLocation, literalsMappedByLines } from '../../../../../backend/src/JsonAstParser'
|
||||||
import { selectTextWithCtrlA } from '../../../utils/handleTextSelectWithCtrlA'
|
import { selectTextWithCtrlA } from '../../../utils/handleTextSelectWithCtrlA'
|
||||||
import { style } from './style'
|
import { style } from './style'
|
||||||
|
import { withStyles } from '@material-ui/core'
|
||||||
import 'prismjs/components/prism-json'
|
import 'prismjs/components/prism-json'
|
||||||
const throttle = require('lodash.throttle')
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
messageHistory: q.MessageHistory
|
messageHistory: q.MessageHistory
|
||||||
@@ -27,36 +20,23 @@ interface Props {
|
|||||||
classes: any
|
classes: any
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {}
|
||||||
diagram?: DiagramOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DiagramOptions {
|
|
||||||
dotPath: string
|
|
||||||
anchorEl: React.Ref<HTMLElement>
|
|
||||||
}
|
|
||||||
|
|
||||||
class CodeDiff extends React.Component<Props, State> {
|
class CodeDiff extends React.Component<Props, State> {
|
||||||
private handleCtrlA = selectTextWithCtrlA({ targetSelector: 'pre ~ pre' })
|
private handleCtrlA = selectTextWithCtrlA({ targetSelector: 'pre ~ pre' })
|
||||||
|
|
||||||
private updateDiagram = throttle((diagram?: DiagramOptions) => {
|
|
||||||
this.setState({ diagram })
|
|
||||||
}, 200)
|
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {}
|
this.state = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private showDiagram = (dotPath: string, target: React.Ref<Element>) => {
|
private isValidJson(str: string) {
|
||||||
this.updateDiagram({
|
try {
|
||||||
dotPath,
|
JSON.parse(str)
|
||||||
anchorEl: target,
|
return true
|
||||||
})
|
} catch (error) {
|
||||||
}
|
return false
|
||||||
|
}
|
||||||
private hideDiagram = () => {
|
|
||||||
this.updateDiagram(undefined)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private plottableLiteralsIndexedWithLineNumbers() {
|
private plottableLiteralsIndexedWithLineNumbers() {
|
||||||
@@ -94,47 +74,22 @@ class CodeDiff extends React.Component<Props, State> {
|
|||||||
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 literalPositions = this.plottableLiteralsIndexedWithLineNumbers()
|
const literalPositions = this.plottableLiteralsIndexedWithLineNumbers()
|
||||||
|
|
||||||
const code = this.renderStyledCodeLines(changes)
|
const code = this.renderStyledCodeLines(changes)
|
||||||
|
|
||||||
const { diagram } = this.state
|
|
||||||
const hasEnoughDataToDisplayDiagrams = this.props.messageHistory.count() > 1
|
|
||||||
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}>
|
||||||
<Gutters
|
<Gutters
|
||||||
showDiagram={this.showDiagram}
|
|
||||||
hideDiagram={this.hideDiagram}
|
|
||||||
className={this.props.classes.gutters}
|
className={this.props.classes.gutters}
|
||||||
hasEnoughDataToDisplayDiagrams={hasEnoughDataToDisplayDiagrams}
|
|
||||||
changes={changes}
|
changes={changes}
|
||||||
|
messageHistory={this.props.messageHistory}
|
||||||
literalPositions={literalPositions} />
|
literalPositions={literalPositions} />
|
||||||
<pre className={this.props.classes.codeBlock}>{code}</pre>
|
<pre className={this.props.classes.codeBlock}>{code}</pre>
|
||||||
</div>
|
</div>
|
||||||
<Popper
|
|
||||||
open={Boolean(this.state.diagram) && hasEnoughDataToDisplayDiagrams}
|
|
||||||
anchorEl={diagram && (diagram.anchorEl as any).current}
|
|
||||||
placement="left-end"
|
|
||||||
>
|
|
||||||
<Fade in={Boolean(this.state.diagram)} timeout={300}>
|
|
||||||
<Paper style={{ width: '300px' }}>
|
|
||||||
{diagram ? <TopicPlot history={this.props.messageHistory} dotPath={diagram.dotPath} /> : <span/>}
|
|
||||||
</Paper>
|
|
||||||
</Fade>
|
|
||||||
</Popper>
|
|
||||||
<DiffCount changes={changes} nameOfCompareMessage={this.props.nameOfCompareMessage} />
|
<DiffCount changes={changes} nameOfCompareMessage={this.props.nameOfCompareMessage} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private isValidJson(str: string) {
|
|
||||||
try {
|
|
||||||
JSON.parse(str)
|
|
||||||
return true
|
|
||||||
} catch (error) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(style)(CodeDiff)
|
export default withStyles(style)(CodeDiff)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"no-submodule-imports": false,
|
"no-submodule-imports": false,
|
||||||
"array-type": [true, "generic"],
|
"array-type": [true, "generic"],
|
||||||
"prefer-array-literal": false,
|
"prefer-array-literal": false,
|
||||||
|
"function-name": false,
|
||||||
"variable-name": [true, "ban-keywords", "check-format", "allow-pascal-case"],
|
"variable-name": [true, "ban-keywords", "check-format", "allow-pascal-case"],
|
||||||
"trailing-comma": [
|
"trailing-comma": [
|
||||||
true,
|
true,
|
||||||
|
|||||||
Reference in New Issue
Block a user