Prevent unneccessary re-renders
This commit is contained in:
@@ -5,7 +5,7 @@ import Remove from '@material-ui/icons/Remove'
|
|||||||
import ShowChart from '@material-ui/icons/ShowChart'
|
import ShowChart from '@material-ui/icons/ShowChart'
|
||||||
import { JsonPropertyLocation } from '../../../../../backend/src/JsonAstParser'
|
import { JsonPropertyLocation } from '../../../../../backend/src/JsonAstParser'
|
||||||
import { lineChangeStyle, trimNewlineRight } from './util'
|
import { lineChangeStyle, trimNewlineRight } from './util'
|
||||||
import { Theme } from '@material-ui/core'
|
import { Theme, Tooltip } from '@material-ui/core'
|
||||||
import { withStyles } from '@material-ui/styles'
|
import { withStyles } from '@material-ui/styles'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -13,42 +13,55 @@ interface Props {
|
|||||||
literalPositions: Array<JsonPropertyLocation>
|
literalPositions: Array<JsonPropertyLocation>
|
||||||
classes: any
|
classes: any
|
||||||
className: string
|
className: string
|
||||||
showDiagram: (dotPath: string, target: EventTarget) => void
|
showDiagram: (dotPath: string, target: React.Ref<HTMLElement>) => void
|
||||||
hideDiagram: () => void
|
hideDiagram: () => void
|
||||||
|
hasEnoughDataToDisplayDiagrams: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const style = (theme: Theme) => {
|
const style = (theme: Theme) => {
|
||||||
|
const icon = {
|
||||||
|
verticalAlign: 'top',
|
||||||
|
width: '12px',
|
||||||
|
height: '12px',
|
||||||
|
marginTop: '2px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
icon,
|
||||||
|
iconButton: {
|
||||||
|
...icon,
|
||||||
|
width: '16px',
|
||||||
|
height: '16px',
|
||||||
|
marginTop: '1px',
|
||||||
|
padding: '2px',
|
||||||
|
'&:hover': {
|
||||||
|
color: theme.palette.primary.contrastText,
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
},
|
||||||
gutterLine: {
|
gutterLine: {
|
||||||
textAlign: 'right' as 'right',
|
textAlign: 'right' as 'right',
|
||||||
paddingRight: theme.spacing(0.5),
|
paddingRight: theme.spacing(0.5),
|
||||||
height: '16px',
|
height: '16px',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
},
|
||||||
icon: {
|
|
||||||
width: '12px',
|
|
||||||
height: '12px',
|
|
||||||
marginTop: '2px',
|
|
||||||
borderRadius: '50%',
|
|
||||||
'&:hover': {
|
|
||||||
color: theme.palette.primary.contrastText,
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChartIcon(props: { classes: any, literal: JsonPropertyLocation, showDiagram: (dotPath: string, target: EventTarget) => void, hideDiagram: () => void }) {
|
function ChartIcon(props: { classes: any, literal: JsonPropertyLocation, showDiagram: (dotPath: string, target: React.Ref<HTMLElement>) => void, hideDiagram: () => void }) {
|
||||||
const mouseOver = (event: React.MouseEvent<Element>) => {
|
const chartIconRef = React.useRef(null)
|
||||||
props.showDiagram(props.literal.path, event.target)
|
|
||||||
}
|
|
||||||
|
|
||||||
const mouseOut = (event: React.MouseEvent) => {
|
const mouseOver = React.useCallback((event: React.MouseEvent<Element>) => {
|
||||||
|
props.showDiagram(props.literal.path, chartIconRef)
|
||||||
|
}, [props.literal.path])
|
||||||
|
|
||||||
|
const mouseOut = React.useCallback(() => {
|
||||||
props.hideDiagram()
|
props.hideDiagram()
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ShowChart className={props.classes.icon} onMouseEnter={mouseOver} onMouseLeave={mouseOut} />
|
<ShowChart ref={chartIconRef} className={props.classes.icon} onMouseEnter={mouseOver} onMouseLeave={mouseOut} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +69,7 @@ function tokensForLine(change: diff.Change, line: number, props: Props) {
|
|||||||
const { classes, literalPositions } = props
|
const { classes, literalPositions } = props
|
||||||
|
|
||||||
const literal = literalPositions[line]
|
const literal = literalPositions[line]
|
||||||
const diagram = literal ? <ChartIcon classes={{ icon: props.classes.icon }} literal={literal} showDiagram={props.showDiagram} hideDiagram={props.hideDiagram}/> : null
|
const diagram = literal ? <Tooltip title="Not enough data"><ChartIcon classes={{ icon: props.classes.iconButton }} literal={literal} showDiagram={props.showDiagram} hideDiagram={props.hideDiagram}/></Tooltip> : null
|
||||||
|
|
||||||
if (change.added) {
|
if (change.added) {
|
||||||
return [diagram, <Add key="add" className={classes.icon} />]
|
return [diagram, <Add key="add" className={classes.icon} />]
|
||||||
|
|||||||
@@ -5,11 +5,16 @@ 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 TopicPlot from '../TopicPlot'
|
||||||
import { CodeBlockColors, CodeBlockColorsBraceMonokai } from '../CodeBlockColors'
|
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 { Theme, withStyles, Popper, Paper, Fade, Zoom } from '@material-ui/core'
|
|
||||||
import { selectTextWithCtrlA } from '../../../utils/handleTextSelectWithCtrlA'
|
import { selectTextWithCtrlA } from '../../../utils/handleTextSelectWithCtrlA'
|
||||||
|
import { style } from './style'
|
||||||
import 'prismjs/components/prism-json'
|
import 'prismjs/components/prism-json'
|
||||||
const throttle = require('lodash.throttle')
|
const throttle = require('lodash.throttle')
|
||||||
|
|
||||||
@@ -27,8 +32,8 @@ interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface DiagramOptions {
|
interface DiagramOptions {
|
||||||
dotPath?: string
|
dotPath: string
|
||||||
anchorEl?: EventTarget
|
anchorEl: React.Ref<HTMLElement>
|
||||||
}
|
}
|
||||||
|
|
||||||
class CodeDiff extends React.Component<Props, State> {
|
class CodeDiff extends React.Component<Props, State> {
|
||||||
@@ -43,33 +48,35 @@ class CodeDiff extends React.Component<Props, State> {
|
|||||||
this.state = {}
|
this.state = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private showDiagram(dotPath: string, target: EventTarget) {
|
private showDiagram = (dotPath: string, target: React.Ref<Element>) => {
|
||||||
this.updateDiagram({
|
this.updateDiagram({
|
||||||
dotPath,
|
dotPath,
|
||||||
anchorEl: target,
|
anchorEl: target,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private hideDiagram() {
|
private hideDiagram = () => {
|
||||||
this.updateDiagram(undefined)
|
this.updateDiagram(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
private plottableLiteralsIndexedWithLineNumbers() {
|
||||||
const changes = diff.diffLines(this.props.previous, this.props.current)
|
const allLiterals = this.isValidJson(this.props.current) ? (literalsMappedByLines(this.props.current) || []) : []
|
||||||
const styledLines = Prism.highlight(this.props.current, Prism.languages.json, 'json').split('\n')
|
|
||||||
const literalPositions = (
|
|
||||||
(literalsMappedByLines(this.props.current) || [])
|
|
||||||
.map((l: JsonPropertyLocation) => isPlottable(l.value) ? l : undefined)
|
|
||||||
) as Array<JsonPropertyLocation>
|
|
||||||
|
|
||||||
|
return allLiterals
|
||||||
|
.map((l: JsonPropertyLocation) => isPlottable(l.value) ? l : undefined) as Array<JsonPropertyLocation>
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderLines(changes: Array<Diff.Change>) {
|
||||||
|
const styledLines = Prism.highlight(this.props.current, Prism.languages.json, 'json').split('\n')
|
||||||
let lineNumber = 0
|
let lineNumber = 0
|
||||||
const code = changes.map((change, key) => {
|
|
||||||
|
return changes.map((change, key) => {
|
||||||
const hasStyledCode = change.removed !== true
|
const hasStyledCode = change.removed !== true
|
||||||
const changedLines = change.count || 0
|
const changedLines = change.count || 0
|
||||||
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}`} style={lineChangeStyle(change)} className={`${this.props.classes.line}`}><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
|
||||||
|
|
||||||
@@ -82,23 +89,31 @@ class CodeDiff extends React.Component<Props, State> {
|
|||||||
return <div key={`${key}-${idx}`} style={lineChangeStyle(change)} className={this.props.classes.line}><span>{line}</span></div>
|
return <div key={`${key}-${idx}`} style={lineChangeStyle(change)} className={this.props.classes.line}><span>{line}</span></div>
|
||||||
})
|
})
|
||||||
}).reduce((a, b) => a.concat(b), [])
|
}).reduce((a, b) => a.concat(b), [])
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const changes = diff.diffLines(this.props.previous, this.props.current)
|
||||||
|
const literalPositions = this.plottableLiteralsIndexedWithLineNumbers()
|
||||||
|
|
||||||
|
const code = this.renderLines(changes)
|
||||||
|
|
||||||
const { diagram } = this.state
|
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={(dotPath, target) => this.showDiagram(dotPath, target)}
|
showDiagram={this.showDiagram}
|
||||||
hideDiagram={() => this.hideDiagram()}
|
hideDiagram={this.hideDiagram}
|
||||||
className={this.props.classes.gutters}
|
className={this.props.classes.gutters}
|
||||||
|
hasEnoughDataToDisplayDiagrams={hasEnoughDataToDisplayDiagrams}
|
||||||
changes={changes}
|
changes={changes}
|
||||||
literalPositions={literalPositions} />
|
literalPositions={literalPositions} />
|
||||||
<pre className={this.props.classes.codeBlock}>{code}</pre>
|
<pre className={this.props.classes.codeBlock}>{code}</pre>
|
||||||
</div>
|
</div>
|
||||||
<Popper
|
<Popper
|
||||||
open={Boolean(this.state.diagram)}
|
open={Boolean(this.state.diagram) && hasEnoughDataToDisplayDiagrams}
|
||||||
anchorEl={diagram && diagram.anchorEl as any}
|
anchorEl={diagram && (diagram.anchorEl as any).current}
|
||||||
placement="left-end"
|
placement="left-end"
|
||||||
>
|
>
|
||||||
<Fade in={Boolean(this.state.diagram)} timeout={300}>
|
<Fade in={Boolean(this.state.diagram)} timeout={300}>
|
||||||
@@ -111,67 +126,14 @@ class CodeDiff extends React.Component<Props, State> {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const style = (theme: Theme) => {
|
private isValidJson(str: string) {
|
||||||
const codeBlockColors = theme.palette.type === 'light' ? CodeBlockColors : CodeBlockColorsBraceMonokai
|
try {
|
||||||
|
JSON.parse(str)
|
||||||
const codeBaseStyle = {
|
return true
|
||||||
font: "12px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace",
|
} catch (error) {
|
||||||
display: 'inline-grid' as 'inline-grid',
|
return false
|
||||||
margin: '0',
|
}
|
||||||
padding: '1px 0 0 0',
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
line: {
|
|
||||||
lineHeight: 'normal' as 'normal',
|
|
||||||
paddingLeft: '4px',
|
|
||||||
width: '100%',
|
|
||||||
height: '16px',
|
|
||||||
},
|
|
||||||
codeWrapper: {
|
|
||||||
display: 'flex',
|
|
||||||
maxHeight: '15em',
|
|
||||||
overflow: 'auto',
|
|
||||||
backgroundColor: `${codeBlockColors.background}`,
|
|
||||||
margin: '8px 0 0 0',
|
|
||||||
},
|
|
||||||
gutters: {
|
|
||||||
...codeBaseStyle,
|
|
||||||
width: '33px',
|
|
||||||
backgroundColor: codeBlockColors.gutters,
|
|
||||||
userSelect: 'none' as 'none',
|
|
||||||
},
|
|
||||||
codeBlock: {
|
|
||||||
...codeBaseStyle,
|
|
||||||
width: 'calc(100% - 33px)',
|
|
||||||
backgroundColor: 'inherit !important',
|
|
||||||
'& span': {
|
|
||||||
color: codeBlockColors.text,
|
|
||||||
},
|
|
||||||
'& .token.number': {
|
|
||||||
color: codeBlockColors.numeric,
|
|
||||||
},
|
|
||||||
'& .token.boolean': {
|
|
||||||
color: codeBlockColors.numeric,
|
|
||||||
},
|
|
||||||
'& .token.property': {
|
|
||||||
color: codeBlockColors.variable,
|
|
||||||
},
|
|
||||||
'& .token.string': {
|
|
||||||
color: codeBlockColors.string,
|
|
||||||
},
|
|
||||||
'& .token': {
|
|
||||||
color: codeBlockColors.text,
|
|
||||||
},
|
|
||||||
'& .token.operator': {
|
|
||||||
color: codeBlockColors.text,
|
|
||||||
},
|
|
||||||
'& .token.punctuation': {
|
|
||||||
color: codeBlockColors.text,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,4 +53,4 @@ export function toPlottableValue(value: any): number | undefined {
|
|||||||
|
|
||||||
export function isPlottable(value: any) {
|
export function isPlottable(value: any) {
|
||||||
return !isNaN(toPlottableValue(value) as any)
|
return !isNaN(toPlottableValue(value) as any)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class HistoryDrawer extends React.Component<Props, State> {
|
|||||||
this.setState({ collapsed: !this.state.collapsed })
|
this.setState({ collapsed: !this.state.collapsed })
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleCtrlA = selectTextWithCtrlA({targetSelector: 'pre'})
|
private handleCtrlA = selectTextWithCtrlA({ targetSelector: 'pre' })
|
||||||
|
|
||||||
public renderHistory() {
|
public renderHistory() {
|
||||||
const style = (element: HistoryItem) => ({
|
const style = (element: HistoryItem) => ({
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ export class RingBuffer<T extends Lengthwise> {
|
|||||||
return this.items.slice(this.start, this.end)
|
return this.items.slice(this.start, this.end)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public count() {
|
||||||
|
return this.end - this.start
|
||||||
|
}
|
||||||
|
|
||||||
public add(item: T) {
|
public add(item: T) {
|
||||||
const size = item.length
|
const size = item.length
|
||||||
this.enforceCapacityConstraints(size)
|
this.enforceCapacityConstraints(size)
|
||||||
|
|||||||
Reference in New Issue
Block a user