Improve ui & performance
This commit is contained in:
@@ -1,66 +0,0 @@
|
||||
import * as React from 'react'
|
||||
import * as q from '../../../backend/src/Model'
|
||||
import Drawer from '@material-ui/core/Drawer'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
import { ValueRenderer } from './ValueRenderer'
|
||||
|
||||
interface Props {
|
||||
node?: q.TreeNode | undefined
|
||||
}
|
||||
|
||||
interface State {
|
||||
node?: q.TreeNode | undefined
|
||||
}
|
||||
|
||||
export class Sidebar extends React.Component<Props, State> {
|
||||
private updateNode: (node?: q.TreeNode | undefined) => void
|
||||
constructor(props: any) {
|
||||
super(props)
|
||||
this.state = {}
|
||||
this.updateNode = (node) => {
|
||||
if (!node) {
|
||||
this.setState(this.state)
|
||||
} else {
|
||||
this.setState({ node })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
this.props.node && this.props.node.removeListener('update', this.updateNode)
|
||||
nextProps.node && nextProps.node.on('update', this.updateNode)
|
||||
nextProps.node && this.updateNode(nextProps.node)
|
||||
}
|
||||
|
||||
private open() {
|
||||
return this.props.node !== undefined
|
||||
}
|
||||
|
||||
public render() {
|
||||
return <Drawer open={this.open()} variant="permanent" anchor="right">
|
||||
{this.renderNode()}
|
||||
</Drawer>
|
||||
}
|
||||
|
||||
private renderNode() {
|
||||
const style: React.CSSProperties = { display: 'block', width: '40vw' }
|
||||
const topicStyle: React.CSSProperties = { width: '100%' }
|
||||
|
||||
if (!this.state.node) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div style={style}>
|
||||
<TextField style={topicStyle}
|
||||
label="Topic"
|
||||
value={this.state.node.path()}
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
/>
|
||||
<Paper>
|
||||
<ValueRenderer node={this.state.node} />
|
||||
</Paper>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
41
app/src/components/Sidebar/NodeStats.tsx
Normal file
41
app/src/components/Sidebar/NodeStats.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import * as React from 'react'
|
||||
import * as q from '../../../../backend/src/Model'
|
||||
// import Drawer from '@material-ui/core/Drawer'
|
||||
import { Typography } from '@material-ui/core'
|
||||
import { withStyles, Theme, StyleRulesCallback } from '@material-ui/core/styles'
|
||||
|
||||
interface Props {
|
||||
node: q.TreeNode,
|
||||
classes: any,
|
||||
theme: Theme
|
||||
}
|
||||
|
||||
interface State {
|
||||
node?: q.TreeNode | undefined
|
||||
}
|
||||
|
||||
class NodeStats extends React.Component<Props, State> {
|
||||
constructor(props: any) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
public static styles: StyleRulesCallback<string> = (theme: Theme) => {
|
||||
return {
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const leafes = this.props.node.leafes()
|
||||
const leafMessages = leafes
|
||||
.map(leaf => leaf.messages)
|
||||
.reduce((a, b) => a + b)
|
||||
|
||||
return <Typography>
|
||||
<p>Messages: #{this.props.node.messages}</p>
|
||||
<p>Subtopics: {leafes.length}</p>
|
||||
<p>Messages Subtopics: #{leafMessages}</p>
|
||||
</Typography>
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(NodeStats.styles, { withTheme: true })(NodeStats)
|
||||
105
app/src/components/Sidebar/Sidebar.tsx
Normal file
105
app/src/components/Sidebar/Sidebar.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import * as React from 'react'
|
||||
import * as q from '../../../../backend/src/Model'
|
||||
// import Drawer from '@material-ui/core/Drawer'
|
||||
import ExpansionPanel from '@material-ui/core/ExpansionPanel'
|
||||
import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'
|
||||
import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'
|
||||
import ExpandMore from '@material-ui/icons/ExpandMore'
|
||||
import ValueRenderer from './ValueRenderer'
|
||||
import NodeStats from './NodeStats'
|
||||
import Topic from './Topic'
|
||||
import { Typography } from '@material-ui/core'
|
||||
import { withStyles, Theme, StyleRulesCallback } from '@material-ui/core/styles'
|
||||
|
||||
interface Props {
|
||||
node?: q.TreeNode | undefined,
|
||||
classes: any,
|
||||
theme: Theme
|
||||
}
|
||||
|
||||
interface State {
|
||||
node?: q.TreeNode | undefined
|
||||
}
|
||||
|
||||
class Sidebar extends React.Component<Props, State> {
|
||||
private updateNode: (node?: q.TreeNode | undefined) => void
|
||||
constructor(props: any) {
|
||||
super(props)
|
||||
this.state = {}
|
||||
this.updateNode = (node) => {
|
||||
if (!node) {
|
||||
this.setState(this.state)
|
||||
} else {
|
||||
this.setState({ node })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static styles: StyleRulesCallback<string> = (theme: Theme) => {
|
||||
return {
|
||||
drawer: {
|
||||
display: 'block',
|
||||
height: '100%',
|
||||
},
|
||||
valuePaper: {
|
||||
margin: `${theme.spacing.unit}px ${theme.spacing.unit}px ${theme.spacing.unit}px ${theme.spacing.unit}px`,
|
||||
},
|
||||
heading: {
|
||||
fontSize: theme.typography.pxToRem(15),
|
||||
fontWeight: theme.typography.fontWeightRegular,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
this.props.node && this.props.node.removeListener('update', this.updateNode)
|
||||
nextProps.node && nextProps.node.on('update', this.updateNode)
|
||||
nextProps.node && this.updateNode(nextProps.node)
|
||||
}
|
||||
|
||||
private open(): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
public render() {
|
||||
return <div className={this.props.classes.drawer}>
|
||||
{this.renderNode()}
|
||||
</div>
|
||||
}
|
||||
|
||||
private renderNode() {
|
||||
const { classes } = this.props
|
||||
if (!this.state.node) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div>
|
||||
<ExpansionPanel key="topic" defaultExpanded={true}>
|
||||
<ExpansionPanelSummary expandIcon={<ExpandMore />}>
|
||||
<Typography className={classes.heading}>Topic</Typography>
|
||||
</ExpansionPanelSummary>
|
||||
<ExpansionPanelDetails>
|
||||
<Topic node={this.state.node} />
|
||||
</ExpansionPanelDetails>
|
||||
</ExpansionPanel>
|
||||
<ExpansionPanel key="value" defaultExpanded={true}>
|
||||
<ExpansionPanelSummary expandIcon={<ExpandMore />}>
|
||||
<Typography className={classes.heading}>Value</Typography>
|
||||
</ExpansionPanelSummary>
|
||||
<ExpansionPanelDetails>
|
||||
<ValueRenderer node={this.state.node} />
|
||||
</ExpansionPanelDetails>
|
||||
</ExpansionPanel>
|
||||
<ExpansionPanel key="stats" defaultExpanded={true}>
|
||||
<ExpansionPanelSummary expandIcon={<ExpandMore />}>
|
||||
<Typography className={classes.heading}>Stats</Typography>
|
||||
</ExpansionPanelSummary>
|
||||
<ExpansionPanelDetails>
|
||||
<NodeStats node={this.state.node} />
|
||||
</ExpansionPanelDetails>
|
||||
</ExpansionPanel>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(Sidebar.styles, { withTheme: true })(Sidebar)
|
||||
52
app/src/components/Sidebar/Topic.tsx
Normal file
52
app/src/components/Sidebar/Topic.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import * as React from 'react'
|
||||
import * as q from '../../../../backend/src/Model'
|
||||
import { withStyles, Theme, StyleRulesCallback } from '@material-ui/core/styles'
|
||||
import Button from '@material-ui/core/Button'
|
||||
|
||||
interface Props {
|
||||
classes: any
|
||||
theme: Theme
|
||||
node: q.TreeNode
|
||||
selected?: q.TreeNode
|
||||
}
|
||||
|
||||
class Topic extends React.Component<Props, {}> {
|
||||
public static styles: StyleRulesCallback<string> = (theme: Theme) => ({
|
||||
button: {
|
||||
textTransform: 'none',
|
||||
padding: '3px 5px 3px 5px',
|
||||
minWidth: '30px',
|
||||
},
|
||||
})
|
||||
|
||||
public render() {
|
||||
const { node } = this.props
|
||||
let i = 0
|
||||
const breadCrumps = node.branch()
|
||||
.map(node => node.sourceEdge)
|
||||
.filter(edge => Boolean(edge))
|
||||
.map(edge =>
|
||||
[<Button
|
||||
onClick={() => this.setState({ node: edge!.target })}
|
||||
size="small"
|
||||
color="secondary"
|
||||
className={this.props.classes.button}
|
||||
key={edge!.hash()}
|
||||
>
|
||||
{edge!.name}
|
||||
</Button>],
|
||||
)
|
||||
|
||||
if (breadCrumps.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const joinedBreadCrumps = breadCrumps.reduce((prev, current) =>
|
||||
prev.concat([<span key={i += 1}>/</span>]).concat(current),
|
||||
)
|
||||
|
||||
return <span style={{ lineHeight: '2.2em' }}>{joinedBreadCrumps}</span>
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(Topic.styles, { withTheme: true })(Topic)
|
||||
@@ -1,16 +1,18 @@
|
||||
import * as React from 'react'
|
||||
import * as q from '../../../backend/src/Model'
|
||||
import * as q from '../../../../backend/src/Model'
|
||||
import { default as ReactJson } from 'react-json-view'
|
||||
import { withTheme, Theme } from '@material-ui/core/styles'
|
||||
|
||||
interface Props {
|
||||
node?: q.TreeNode | undefined
|
||||
theme: Theme
|
||||
}
|
||||
|
||||
interface State {
|
||||
node?: q.TreeNode | undefined
|
||||
}
|
||||
|
||||
export class ValueRenderer extends React.Component<Props, State> {
|
||||
class ValueRenderer extends React.Component<Props, State> {
|
||||
private updateNode: (node?: q.TreeNode | undefined) => void
|
||||
constructor(props: any) {
|
||||
super(props)
|
||||
@@ -30,6 +32,10 @@ export class ValueRenderer extends React.Component<Props, State> {
|
||||
nextProps.node && this.updateNode(nextProps.node)
|
||||
}
|
||||
|
||||
private style = (theme: Theme) => {
|
||||
|
||||
}
|
||||
|
||||
public render() {
|
||||
const node = this.props.node
|
||||
if (!node || !node.message) {
|
||||
@@ -48,7 +54,14 @@ export class ValueRenderer extends React.Component<Props, State> {
|
||||
} else if (typeof json === 'number') {
|
||||
return this.renderRawValue(node.message.value)
|
||||
} else {
|
||||
return <ReactJson src={json} />
|
||||
const theme = this.props.theme.palette.type === 'dark' ? 'monokai' : 'bright:inverted'
|
||||
return <ReactJson
|
||||
style={{ width: '100%' }}
|
||||
src={json}
|
||||
theme={theme}
|
||||
onEdit={(val) => {
|
||||
console.log(val)
|
||||
}} />
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +75,8 @@ export class ValueRenderer extends React.Component<Props, State> {
|
||||
padding: '12px 5px 12px 5px',
|
||||
}
|
||||
|
||||
return <pre><code style={style}>{value}</code></pre>
|
||||
return <pre style={style}><code>{value}</code></pre>
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme()(ValueRenderer)
|
||||
3
app/src/components/Sidebar/index.ts
Normal file
3
app/src/components/Sidebar/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import Sidebar from './Sidebar'
|
||||
|
||||
export { Sidebar }
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react'
|
||||
import * as io from 'socket.io-client'
|
||||
import * as q from '../../../backend/src/Model'
|
||||
import { TreeNode } from './TreeNode'
|
||||
import TreeNode from './TreeNode'
|
||||
import List from '@material-ui/core/List'
|
||||
|
||||
const throttle = require('lodash.throttle')
|
||||
@@ -18,10 +18,14 @@ class TreeState {
|
||||
export interface TreeNodeProps {
|
||||
didSelectNode?: (node: q.TreeNode) => void
|
||||
}
|
||||
declare const performance: any
|
||||
|
||||
export class Tree extends React.Component<TreeNodeProps, TreeState> {
|
||||
private socket: SocketIOClient.Socket
|
||||
private renderDuration: number = 300
|
||||
private renderDuration: number = 200
|
||||
private updateTimer?: any
|
||||
private lastUpdate: number = 0
|
||||
private perf:number = 0
|
||||
|
||||
constructor(props: any) {
|
||||
super(props)
|
||||
@@ -30,21 +34,36 @@ export class Tree extends React.Component<TreeNodeProps, TreeState> {
|
||||
this.socket = io('http://localhost:3000')
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
let updateState = throttle((state: any) => {
|
||||
this.setState(state)
|
||||
updateState.cancel()
|
||||
updateState = throttle(() => {
|
||||
this.setState(state)
|
||||
}, Math.max(this.renderDuration * 5, 300), { trailing: true })
|
||||
}, 1000)
|
||||
public time(): number {
|
||||
const time = performance.now() - this.perf
|
||||
this.perf = performance.now()
|
||||
|
||||
return time
|
||||
}
|
||||
|
||||
public throttledStateUpdate(state: any) {
|
||||
if (this.updateTimer) {
|
||||
return
|
||||
}
|
||||
|
||||
const updateInterval = Math.max(this.renderDuration * 5, 200)
|
||||
const timeUntilNextUpdate = updateInterval - (performance.now() - this.lastUpdate)
|
||||
|
||||
this.updateTimer = setTimeout(() => {
|
||||
this.lastUpdate = performance.now()
|
||||
this.updateTimer && clearTimeout(this.updateTimer)
|
||||
this.updateTimer = undefined
|
||||
this.setState(state)
|
||||
}, Math.max(0, timeUntilNextUpdate))
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.socket.on('message', (msg: any) => {
|
||||
const edges = msg.topic.split('/')
|
||||
const node = q.TreeNodeFactory.fromEdgesAndValue(edges, Buffer.from(msg.payload, 'base64').toString())
|
||||
this.state.tree.updateWithNode(node.firstNode())
|
||||
|
||||
updateState({ msg, tree: this.state.tree })
|
||||
this.throttledStateUpdate({ msg, tree: this.state.tree })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -56,10 +75,14 @@ export class Tree extends React.Component<TreeNodeProps, TreeState> {
|
||||
return <div>
|
||||
<List>
|
||||
<TreeNode
|
||||
isRoot={true}
|
||||
didSelectNode={this.props.didSelectNode}
|
||||
treeNode={this.state.tree}
|
||||
name="/" collapsed={false}
|
||||
performanceCallback={ms => this.renderDuration = ms}
|
||||
key="rootNode"
|
||||
performanceCallback={(ms: number) => {
|
||||
this.renderDuration = ms
|
||||
}}
|
||||
/>
|
||||
</List>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import * as React from 'react'
|
||||
import * as q from '../../../backend/src/Model'
|
||||
import List from '@material-ui/core/List'
|
||||
import ListItem from '@material-ui/core/ListItem'
|
||||
import Collapse from '@material-ui/core/Collapse'
|
||||
import { List, ListItem, Collapse, Typography } from '@material-ui/core'
|
||||
import { withTheme, Theme } from '@material-ui/core/styles'
|
||||
const throttle = require('lodash.throttle')
|
||||
import Slide from '@material-ui/core/Slide'
|
||||
import { isElementInViewport } from './helper/isElementInViewport'
|
||||
const collapseLimit = 3
|
||||
declare var performance: any
|
||||
|
||||
export interface TreeNodeProps {
|
||||
isRoot?: boolean
|
||||
treeNode: q.TreeNode
|
||||
name?: string | undefined
|
||||
collapsed?: boolean | undefined
|
||||
performanceCallback?: ((ms: number) => void) | undefined
|
||||
didSelectNode?: (node: q.TreeNode) => void
|
||||
theme: Theme
|
||||
}
|
||||
|
||||
interface TreeNodeState {
|
||||
@@ -19,28 +25,59 @@ interface TreeNodeState {
|
||||
edgeCount: number
|
||||
}
|
||||
|
||||
const collapseLimit = 0
|
||||
declare var performance: any
|
||||
|
||||
export class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
||||
class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
||||
private dirty: boolean = true
|
||||
private willUpdateTime: number = performance.now()
|
||||
private titleRef = React.createRef<HTMLElement>()
|
||||
private markAsDirty = () => {
|
||||
this.dirty = true
|
||||
if (!this.props.isRoot) {
|
||||
this.indicateUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
constructor(props: TreeNodeProps, state: TreeNodeState) {
|
||||
super(props, state)
|
||||
private indicateUpdate = throttle(() => {
|
||||
const title: any = this.titleRef.current
|
||||
if (title && isElementInViewport(title)) {
|
||||
title.style.animation = 'example 0.5s'
|
||||
setTimeout(() => {
|
||||
title.style.animation = ''
|
||||
}, 500)
|
||||
}
|
||||
}, 500)
|
||||
|
||||
constructor(props: TreeNodeProps) {
|
||||
super(props)
|
||||
const edgeCount = Object.keys(props.treeNode.edges).length
|
||||
const collapsed = edgeCount > collapseLimit
|
||||
|
||||
this.props.treeNode.on('update', () => {
|
||||
this.dirty = true
|
||||
})
|
||||
|
||||
this.state = { collapsed, edgeCount, collapsedOverride: props.collapsed, title: props.name }
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.props.treeNode.on('update', this.markAsDirty)
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.props.treeNode.removeListener('update', this.markAsDirty)
|
||||
}
|
||||
|
||||
private getStyles() {
|
||||
const { theme } = this.props
|
||||
return {
|
||||
collapsedSubnodes: {
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
container: {
|
||||
display: 'block',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
public setState(state: any) {
|
||||
this.dirty = true
|
||||
this.dirty = this.state.collapsed !== state.collapsed
|
||||
|| this.state.collapsedOverride !== state.collapsedOverride
|
||||
|| this.state.edgeCount !== state.edgeCount
|
||||
|
||||
super.setState(state)
|
||||
}
|
||||
|
||||
@@ -49,7 +86,6 @@ export class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
this.dirty = false
|
||||
if (this.props.performanceCallback) {
|
||||
const renderTime = performance.now() - this.willUpdateTime
|
||||
this.props.performanceCallback(renderTime)
|
||||
@@ -73,18 +109,25 @@ export class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
||||
private renderNodes() {
|
||||
const edges = Object.values(this.props.treeNode.edges)
|
||||
const listItemStyle = {
|
||||
padding: '3px 8px 3px 8px',
|
||||
padding: '3px 8px 0px 8px',
|
||||
}
|
||||
|
||||
const listStyle = {
|
||||
padding: '3px 8px 3px 16px',
|
||||
padding: '3px 8px 0px 16px',
|
||||
}
|
||||
|
||||
if (edges.length > 0) {
|
||||
const listItems = edges
|
||||
.map(edge => edge.target)
|
||||
.map(node => <ListItem style={listItemStyle} button key={node.hash()}>
|
||||
<TreeNode didSelectNode={this.props.didSelectNode} treeNode={node} />
|
||||
</ListItem>)
|
||||
.map(node => (
|
||||
<ListItem key={node.hash()} style={listItemStyle} button>
|
||||
<TreeNode
|
||||
theme={this.props.theme}
|
||||
didSelectNode={this.props.didSelectNode}
|
||||
treeNode={node}
|
||||
/>
|
||||
</ListItem>
|
||||
))
|
||||
|
||||
return <Collapse in={!this.collapsed()} timeout="auto" unmountOnExit>
|
||||
<List style={listStyle}>{listItems}</List>
|
||||
@@ -100,13 +143,10 @@ export class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
||||
}
|
||||
const name = this.state.title || (this.props.treeNode.sourceEdge && this.props.treeNode.sourceEdge.name)
|
||||
|
||||
return <span style={style} onClick={() => this.toggle()}>{name}</span>
|
||||
}
|
||||
|
||||
private getStyle(): React.CSSProperties {
|
||||
return {
|
||||
display: 'block',
|
||||
}
|
||||
return <span style={style} onClick={() => {
|
||||
this.toggle()
|
||||
this.props.didSelectNode && this.props.didSelectNode(this.props.treeNode)
|
||||
}>{name}</span>
|
||||
}
|
||||
|
||||
public componentWillReceiveProps() {
|
||||
@@ -128,7 +168,7 @@ export class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
||||
? <span
|
||||
style={style}
|
||||
onMouseOver={() => this.props.didSelectNode && this.props.didSelectNode(this.props.treeNode)}
|
||||
> = {this.props.treeNode.message.toString()}</span>
|
||||
> = {this.props.treeNode.message.value.toString()}</span>
|
||||
: null
|
||||
}
|
||||
|
||||
@@ -137,23 +177,33 @@ export class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
||||
}
|
||||
|
||||
private renderTitleLine() {
|
||||
const style = {
|
||||
const style: React.CSSProperties = {
|
||||
lineHeight: '1em',
|
||||
whiteSpace: 'nowrap',
|
||||
width: '15em',
|
||||
}
|
||||
return <div style={style}>{this.renderExpander()} {this.renderSourceEdge()} {this.renderCollapsedSubnodes()} {this.renderValue()}</div>
|
||||
return <div
|
||||
ref={this.titleRef}
|
||||
>
|
||||
<Typography style={style}>
|
||||
{this.renderExpander()} {this.renderSourceEdge()} {this.renderCollapsedSubnodes()} {this.renderValue()}
|
||||
</Typography>
|
||||
</div>
|
||||
}
|
||||
|
||||
public render() {
|
||||
this.dirty = false
|
||||
|
||||
const nodeStyle: React.CSSProperties = {
|
||||
display: 'block',
|
||||
}
|
||||
|
||||
return <div style={this.getStyle()}>
|
||||
{this.renderTitleLine()}
|
||||
<div style={nodeStyle}>
|
||||
{this.clear()}
|
||||
<div style={this.subnodesStyle()}>
|
||||
{this.collapsed() ? null : this.renderNodes()}
|
||||
return <div key={this.props.treeNode.hash()} style={ this.getStyles().container }>
|
||||
{ this.renderTitleLine() }
|
||||
<div style = { nodeStyle }>
|
||||
{ this.clear() }
|
||||
<div style = { this.subnodesStyle() }>
|
||||
{ this.collapsed() ? null : this.renderNodes() }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -178,10 +228,8 @@ export class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
||||
return null
|
||||
}
|
||||
|
||||
const style = {
|
||||
color: '#333',
|
||||
}
|
||||
return <span style={style}>({this.props.treeNode.leafes().length} nodes)</span>
|
||||
const messages = this.props.treeNode.leafes().map(leaf => leaf.messages).reduce((a, b) => a + b)
|
||||
return <span style={this.getStyles().collapsedSubnodes}>({this.props.treeNode.leafes().length} nodes, {messages} messages)</span>
|
||||
}
|
||||
|
||||
private subnodesStyle(): React.CSSProperties {
|
||||
@@ -190,3 +238,5 @@ export class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme()(TreeNode)
|
||||
|
||||
12
app/src/components/helper/isElementInViewport.ts
Normal file
12
app/src/components/helper/isElementInViewport.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
declare const window: any
|
||||
declare const document: any
|
||||
|
||||
export function isElementInViewport(el: any) {
|
||||
const rect = el.getBoundingClientRect()
|
||||
return (
|
||||
rect.top >= 0 &&
|
||||
rect.left >= 0 &&
|
||||
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
|
||||
rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user