Screw up looks, greatly improve performance

This commit is contained in:
Thomas Nordquist
2019-01-07 03:32:28 +01:00
parent 260f31fea0
commit e2192b11c7
9 changed files with 92 additions and 53 deletions

10
app/package-lock.json generated
View File

@@ -1167,6 +1167,11 @@
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
}, },
"copy-text-to-clipboard": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-1.0.4.tgz",
"integrity": "sha512-4hDE+0bgqm4G/nXnt91CP3rc0vOptaePPU5WfVZuhv2AYNJogdLHR4pF1XPgXDAGY4QCzj9pD7zKATa+50sQPg=="
},
"core-js": { "core-js": {
"version": "1.2.7", "version": "1.2.7",
"resolved": "http://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", "resolved": "http://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
@@ -3522,6 +3527,11 @@
"run-queue": "^1.0.3" "run-queue": "^1.0.3"
} }
}, },
"moving-average": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/moving-average/-/moving-average-1.0.0.tgz",
"integrity": "sha512-97cgMz0U2zciiDp4xRl/n+MYgrm9l7UiYbtsBLPr0rhw6KH3m4LyK2w4d96V6+UwKo+ph7KtQSoL2qgnqZVgvA=="
},
"ms": { "ms": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",

View File

@@ -22,8 +22,10 @@
"@types/socket.io-client": "^1.4.32", "@types/socket.io-client": "^1.4.32",
"@types/vis": "^4.21.9", "@types/vis": "^4.21.9",
"awesome-typescript-loader": "^5.2.1", "awesome-typescript-loader": "^5.2.1",
"copy-text-to-clipboard": "^1.0.4",
"jquery": "^3.3.1", "jquery": "^3.3.1",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"moving-average": "^1.0.0",
"react": "^16.3.2", "react": "^16.3.2",
"react-dom": "^16.3.3", "react-dom": "^16.3.3",
"react-json-view": "^1.19.1", "react-json-view": "^1.19.1",

View File

@@ -25,15 +25,12 @@ class NodeStats extends React.Component<Props, State> {
} }
public render() { public render() {
const leafes = this.props.node.leafes() const { node } = this.props
const leafMessages = leafes
.map(leaf => leaf.messages)
.reduce((a, b) => a + b)
return <div> return <div>
<Typography>Messages: #{this.props.node.messages}</Typography> <Typography>Messages: #{node.messages}</Typography>
<Typography>Subtopics: {leafes.length}</Typography> <Typography>Subtopics: {node.leafCount()}</Typography>
<Typography>Messages Subtopics: #{leafMessages}</Typography> <Typography>Messages Subtopics: #{node.leafMessageCount()}</Typography>
</div> </div>
} }
} }

View File

@@ -2,6 +2,7 @@ import * as React from 'react'
import * as q from '../../../../backend/src/Model' import * as q from '../../../../backend/src/Model'
import { withStyles, Theme, StyleRulesCallback } from '@material-ui/core/styles' import { withStyles, Theme, StyleRulesCallback } from '@material-ui/core/styles'
import Button from '@material-ui/core/Button' import Button from '@material-ui/core/Button'
const copy = require('copy-text-to-clipboard')
interface Props { interface Props {
classes: any classes: any
@@ -50,7 +51,10 @@ class Topic extends React.Component<Props, {}> {
prev.concat([<span key={key += 1}>/</span>]).concat(current), prev.concat([<span key={key += 1}>/</span>]).concat(current),
) )
return <span style={{ lineHeight: '2.2em' }}>{joinedBreadCrumps}</span> return <span style={{ lineHeight: '2.2em' }}>
<a onClick={() => copy(this.props.node && this.props.node.path())}>📋</a>
{joinedBreadCrumps}
</span>
} }
} }

View File

@@ -1,11 +1,16 @@
import * as React from 'react' import * as React from 'react'
import * as q from '../../../../backend/src/Model' import * as q from '../../../../backend/src/Model'
import TreeNode from './TreeNode' import TreeNode from './TreeNode'
import List from '@material-ui/core/List' import { Typography } from '@material-ui/core'
import { makeConnectionMessageEvent, rendererEvents } from '../../../../events' import { makeConnectionMessageEvent, rendererEvents } from '../../../../events'
import { } from '../../../../events/Events' import { } from '../../../../events/Events'
const MovingAvaerage = require('moving-average')
declare const performance: any declare const performance: any
const timeInterval = 10 * 1000
const average = MovingAvaerage(timeInterval)
interface Props{ interface Props{
didSelectNode?: (node: q.TreeNode) => void didSelectNode?: (node: q.TreeNode) => void
connectionId?: string connectionId?: string
@@ -20,7 +25,7 @@ export class Tree extends React.Component<Props, TreeState> {
private renderDuration: number = 300 private renderDuration: number = 300
private updateTimer?: any private updateTimer?: any
private lastUpdate: number = 0 private lastUpdate: number = 0
private perf:number = 0 private perf: number = 0
constructor(props: any) { constructor(props: any) {
super(props) super(props)
@@ -40,7 +45,8 @@ export class Tree extends React.Component<Props, TreeState> {
return return
} }
const updateInterval = Math.max(this.renderDuration * 5, 300) const expectedRenderTime = average.forecast()
const updateInterval = Math.max(expectedRenderTime * 5, 300)
const timeUntilNextUpdate = updateInterval - (performance.now() - this.lastUpdate) const timeUntilNextUpdate = updateInterval - (performance.now() - this.lastUpdate)
this.updateTimer = setTimeout(() => { this.updateTimer = setTimeout(() => {
@@ -85,21 +91,20 @@ export class Tree extends React.Component<Props, TreeState> {
} }
public render() { public render() {
return <div> return <Typography>
<List>
<TreeNode <TreeNode
animateChages={true} animateChages={true}
autoExpandLimit={0} autoExpandLimit={3000}
isRoot={true} isRoot={true}
didSelectNode={this.props.didSelectNode} didSelectNode={this.props.didSelectNode}
treeNode={this.state.tree} treeNode={this.state.tree}
name="/" collapsed={false} name="/" collapsed={false}
key="rootNode" key="rootNode"
performanceCallback={(ms: number) => { performanceCallback={(ms: number) => {
average.push(Date.now(), ms)
this.renderDuration = ms this.renderDuration = ms
}} }}
/> />
</List> </Typography>
</div>
} }
} }

View File

@@ -138,9 +138,10 @@ class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
this.dirtyState = this.dirtyEdges = this.dirtyMessage = this.dirtySubnodes = false this.dirtyState = this.dirtyEdges = this.dirtyMessage = this.dirtySubnodes = false
return <div key={this.props.treeNode.hash()} style={ displayBlock }> return <div key={this.props.treeNode.hash()} style={ { display: 'block', marginLeft: '10px' } }>
<div style={animationStyle} ref={this.titleRef} onClick={() => this.toggle()}> <span ref={this.titleRef} style={animationStyle}>
<TreeNodeTitle <TreeNodeTitle
onClick={() => this.toggle()}
edgeCount={this.state.edgeCount} edgeCount={this.state.edgeCount}
collapsed={this.collapsed()} collapsed={this.collapsed()}
treeNode={this.props.treeNode} treeNode={this.props.treeNode}
@@ -148,17 +149,9 @@ class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
didSelectNode={this.props.didSelectNode} didSelectNode={this.props.didSelectNode}
toggleCollapsed={() => this.toggle()} toggleCollapsed={() => this.toggle()}
/> />
</span>
{ this.renderNodes() }
</div> </div>
{ this.clear() }
<div style = { displayBlock }>
{this.renderNodes()}
</div>
</div>
}
private clear() {
return <div style={{ clear: 'both' }} />
} }
private renderNodes() { private renderNodes() {
@@ -172,7 +165,7 @@ class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
/> />
} }
private indicatingChangeAnimationStyle() { private indicatingChangeAnimationStyle(): React.CSSProperties {
if (this.props.isRoot) { if (this.props.isRoot) {
return {} return {}
} }
@@ -188,6 +181,8 @@ class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
} }
return { animation: 'example 0.5s' } return { animation: 'example 0.5s' }
} }
return {}
} }
} }

View File

@@ -29,10 +29,9 @@ class TreeNodeSubnodes extends React.Component<Props, {}> {
const listItems = edges const listItems = edges
.map(edge => edge.target) .map(edge => edge.target)
.map(node => ( .map(node => (
<ListItem <div
key={node.hash()} key={node.hash()}
style={listItemStyle} style={listItemStyle}
button
> >
<TreeNode <TreeNode
animateChages={this.props.animateChanges} animateChages={this.props.animateChanges}
@@ -40,12 +39,14 @@ class TreeNodeSubnodes extends React.Component<Props, {}> {
didSelectNode={this.props.didSelectNode} didSelectNode={this.props.didSelectNode}
autoExpandLimit={this.props.autoExpandLimit} autoExpandLimit={this.props.autoExpandLimit}
/> />
</ListItem> </div>
)) ))
return <Collapse in={!this.props.collapsed} timeout="auto" unmountOnExit> return <span
<List style={listStyle}>{listItems}</List> style={{ display: 'block', clear: 'both' }}
</Collapse> >
{this.props.collapsed ? null : listItems}
</span>
} }
return null return null

View File

@@ -3,8 +3,9 @@ import * as q from '../../../../backend/src/Model'
import { Typography } from '@material-ui/core' import { Typography } from '@material-ui/core'
import { withTheme, Theme } from '@material-ui/core/styles' import { withTheme, Theme } from '@material-ui/core/styles'
export interface TreeNodeProps { export interface TreeNodeProps extends React.HTMLAttributes<HTMLElement> {
treeNode: q.TreeNode treeNode: q.TreeNode
// ref: React.Ref<HTMLElement>
name?: string | undefined name?: string | undefined
collapsed?: boolean | undefined collapsed?: boolean | undefined
toggleCollapsed: () => void toggleCollapsed: () => void
@@ -31,9 +32,14 @@ class TreeNodeTitle extends React.Component<TreeNodeProps, {}> {
lineHeight: '1em', lineHeight: '1em',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
} }
return <Typography style={style}> return <span
style={style}
onClick={() => {
this.toggle()
this.props.didSelectNode && this.props.didSelectNode(this.props.treeNode)
}}>
{this.renderExpander()} {this.renderSourceEdge()} {this.renderCollapsedSubnodes()} {this.renderValue()} {this.renderExpander()} {this.renderSourceEdge()} {this.renderCollapsedSubnodes()} {this.renderValue()}
</Typography> </span>
} }
private renderSourceEdge() { private renderSourceEdge() {
@@ -44,10 +50,7 @@ class TreeNodeTitle extends React.Component<TreeNodeProps, {}> {
} }
const name = this.props.name || (this.props.treeNode.sourceEdge && this.props.treeNode.sourceEdge.name) const name = this.props.name || (this.props.treeNode.sourceEdge && this.props.treeNode.sourceEdge.name)
return <span style={style} onClick={() => { return <span style={style}>{name}</span>
this.toggle()
this.props.didSelectNode && this.props.didSelectNode(this.props.treeNode)
}}>{name}</span>
} }
private renderValue() { private renderValue() {
@@ -77,9 +80,7 @@ class TreeNodeTitle extends React.Component<TreeNodeProps, {}> {
return null return null
} }
return this.props.collapsed return this.props.collapsed ? '▶' : '▼'
? <span onClick={() => this.toggle()}></span>
: <span onClick={() => this.toggle()}></span>
} }
private renderCollapsedSubnodes() { private renderCollapsedSubnodes() {
@@ -87,8 +88,8 @@ class TreeNodeTitle extends React.Component<TreeNodeProps, {}> {
return null return null
} }
const messages = this.props.treeNode.leafes().map(leaf => leaf.messages).reduce((a, b) => a + b) const messages = this.props.treeNode.leafMessageCount()
return <span style={this.getStyles().collapsedSubnodes}>({this.props.treeNode.leafes().length} nodes, {messages} messages)</span> return <span style={this.getStyles().collapsedSubnodes}>({this.props.treeNode.leafCount()} nodes, {messages} messages)</span>
} }
} }

View File

@@ -11,6 +11,8 @@ export class TreeNode {
public onMerge = new EventDispatcher<void, TreeNode>(this) public onMerge = new EventDispatcher<void, TreeNode>(this)
public onEdgesChange = new EventDispatcher<void, TreeNode>(this) public onEdgesChange = new EventDispatcher<void, TreeNode>(this)
public onMessage = new EventDispatcher<Message, TreeNode>(this) public onMessage = new EventDispatcher<Message, TreeNode>(this)
private cachedLeafes?: TreeNode[]
private cachedLeafMessageCount?: number
constructor(sourceEdge?: Edge, message?: Message) { constructor(sourceEdge?: Edge, message?: Message) {
if (sourceEdge) { if (sourceEdge) {
@@ -19,6 +21,10 @@ export class TreeNode {
} }
this.setMessage(message) this.setMessage(message)
this.onMerge.subscribe(() => {
this.cachedLeafes = undefined
this.cachedLeafMessageCount = undefined
})
} }
public setMessage(value: any) { public setMessage(value: any) {
@@ -73,16 +79,34 @@ export class TreeNode {
this.onMerge.dispatch() this.onMerge.dispatch()
} }
public leafMessageCount() {
if (this.cachedLeafMessageCount === undefined) {
this.cachedLeafMessageCount = this.leafes()
.map(leaf => leaf.messages)
.reduce((a, b) => a + b)
}
return this.cachedLeafMessageCount
}
public leafCount(): number {
return this.leafes().length
}
public leafes(): TreeNode[] { public leafes(): TreeNode[] {
if (this.cachedLeafes === undefined) {
if (Object.values(this.edges).length === 0) { if (Object.values(this.edges).length === 0) {
return [this] return [this]
} }
return Object.values(this.edges) this.cachedLeafes = Object.values(this.edges)
.map(e => e.target.leafes()) .map(e => e.target.leafes())
.reduce((a, b) => a.concat(b), []) .reduce((a, b) => a.concat(b), [])
} }
return this.cachedLeafes
}
private mergeEdges(node: TreeNode) { private mergeEdges(node: TreeNode) {
const edgeKeys = Object.keys(node.edges) const edgeKeys = Object.keys(node.edges)
let edgesDidUpdate = false let edgesDidUpdate = false