Make topics selectable

This commit is contained in:
Thomas Nordquist
2019-01-25 13:06:01 +01:00
parent 370dbdb483
commit 72a3c5953f
23 changed files with 256 additions and 181 deletions

View File

@@ -22,7 +22,7 @@ interface Props {
settingsVisible: boolean
}
class App extends React.Component<Props, {}> {
class App extends React.PureComponent<Props, {}> {
constructor(props: any) {
super(props)
this.state = { }

19
app/src/TopicViewModel.ts Normal file
View File

@@ -0,0 +1,19 @@
import { EventDispatcher } from '../../events'
export class TopicViewModel {
private selected: boolean
public change = new EventDispatcher<void, TopicViewModel>(this)
public constructor() {
this.selected = false
}
public isSelected() {
return this.selected
}
public setSelected(selected: boolean) {
this.selected = selected
this.change.dispatch()
}
}

View File

@@ -6,6 +6,7 @@ import { AppState } from '../reducers'
import * as q from '../../../backend/src/Model'
import { showTree } from './Tree'
import * as url from 'url'
import { TopicViewModel } from '../TopicViewModel'
export const connect = (options: MqttOptions, connectionId: string) => (dispatch: Dispatch<any>, getState: () => AppState) => {
dispatch(connecting(connectionId))
@@ -15,7 +16,7 @@ export const connect = (options: MqttOptions, connectionId: string) => (dispatch
rendererEvents.subscribe(event, (dataSourceState) => {
if (dataSourceState.connected) {
const tree = new q.Tree()
const tree = new q.Tree<TopicViewModel>()
tree.updateWithConnection(rendererEvents, connectionId)
dispatch(connected(tree, host!))
dispatch(showTree(tree))
@@ -26,7 +27,7 @@ export const connect = (options: MqttOptions, connectionId: string) => (dispatch
})
}
export const connected: (tree: q.Tree, host: string) => Action = (tree: q.Tree, host: string) => ({
export const connected: (tree: q.Tree<TopicViewModel>, host: string) => Action = (tree: q.Tree<TopicViewModel>, host: string) => ({
tree,
host,
type: ActionTypes.CONNECTION_SET_CONNECTED,

View File

@@ -6,6 +6,7 @@ import { AppState } from '../reducers'
import * as q from '../../../backend/src/Model'
import { batchActions } from 'redux-batched-actions'
import { autoExpandLimitSet } from '../components/Settings'
import { TopicViewModel } from '../TopicViewModel'
export const setAutoExpandLimit = (autoExpandLimit: number = 0): Action => {
return {
@@ -42,7 +43,7 @@ export const filterTopics = (filterStr: string) => (dispatch: Dispatch<any>, get
const topicFilter = filterStr.toLowerCase()
const nodeFilter = (node: q.TreeNode): boolean => {
const nodeFilter = (node: q.TreeNode<TopicViewModel>): boolean => {
const topicMatches = node.path().toLowerCase().indexOf(topicFilter) !== -1
if (topicMatches) {
return true
@@ -54,17 +55,17 @@ export const filterTopics = (filterStr: string) => (dispatch: Dispatch<any>, get
const resultTree = tree.childTopics()
.filter(nodeFilter)
.map((node) => {
.map((node: q.TreeNode<TopicViewModel>) => {
const clone = node.unconnectedClone()
q.TreeNodeFactory.insertNodeAtPosition(node.path().split('/'), clone)
return clone.firstNode()
})
.reduce((a: q.TreeNode, b: q.TreeNode) => {
.reduce((a: q.TreeNode<TopicViewModel>, b: q.TreeNode<TopicViewModel>) => {
a.updateWithNode(b)
return a
}, new q.Tree())
}, new q.Tree<TopicViewModel>())
const nextTree: q.Tree = resultTree as q.Tree
const nextTree: q.Tree<TopicViewModel> = resultTree as q.Tree<TopicViewModel>
if (tree.updateSource && tree.connectionId) {
nextTree.updateWithConnection(tree.updateSource, tree.connectionId, nodeFilter)
}
@@ -72,7 +73,7 @@ export const filterTopics = (filterStr: string) => (dispatch: Dispatch<any>, get
dispatch(batchActions([setAutoExpandLimit(autoExpandLimitForTree(nextTree)), (showTree(nextTree) as any)]))
}
function autoExpandLimitForTree(tree: q.Tree) {
function autoExpandLimitForTree(tree: q.Tree<TopicViewModel>) {
if (!tree) {
return 0
}

View File

@@ -3,22 +3,42 @@ import { ActionTypes } from '../reducers/Tree'
import * as q from '../../../backend/src/Model'
import { Dispatch, AnyAction } from 'redux'
import { setTopic } from './Publish'
import { TopicViewModel } from '../TopicViewModel'
import { batchActions } from 'redux-batched-actions'
export const selectTopic = (topic: q.TreeNode) => (dispatch: Dispatch<any>, getState: () => AppState): AnyAction => {
export const selectTopic = (topic: q.TreeNode<TopicViewModel>) => (dispatch: Dispatch<any>, getState: () => AppState) => {
const { selectedTopic } = getState().tree
// Update publish topic
if (selectedTopic && (selectedTopic.path() === getState().publish.topic || !getState().publish.topic)) {
dispatch(setTopic(topic.path()))
if (selectedTopic === topic) {
return
}
return dispatch({
// Update publish topic
let setTopicDispatch: any | undefined
if (selectedTopic && (selectedTopic.path() === getState().publish.topic || !getState().publish.topic)) {
setTopicDispatch = setTopic(topic.path())
}
if (selectedTopic && selectedTopic.viewModel) {
selectedTopic.viewModel.setSelected(false)
}
if (topic.viewModel) {
topic.viewModel.setSelected(true)
}
const selectTreeTopicDispatch = {
selectedTopic: topic,
type: ActionTypes.TREE_SELECT_TOPIC,
})
}
if (setTopicDispatch) {
dispatch(batchActions([selectTreeTopicDispatch, setTopicDispatch]))
} else {
dispatch(selectTreeTopicDispatch)
}
}
export const showTree = (tree?: q.Tree) => (dispatch: Dispatch<any>, getState: () => AppState): AnyAction => {
export const showTree = (tree?: q.Tree<TopicViewModel>) => (dispatch: Dispatch<any>, getState: () => AppState): AnyAction => {
const visibleTree = getState().tree.tree
const connectionTree = getState().connection.tree

View File

@@ -6,6 +6,7 @@ import { Typography } from '@material-ui/core'
import { StyleRulesCallback, withStyles } from '@material-ui/core/styles'
import { connect } from 'react-redux'
import { TopicViewModel } from '../TopicViewModel'
const abbreviate = require('number-abbreviate')
interface Stats {
@@ -30,7 +31,7 @@ const styles: StyleRulesCallback = theme => ({
interface Props {
classes: any
tree?: q.Tree
tree?: q.Tree<TopicViewModel>
}
class BrokerStatistics extends React.Component<Props, {}> {
@@ -95,7 +96,7 @@ class BrokerStatistics extends React.Component<Props, {}> {
)
}
private renderPair(tree: q.Tree, a: Stats, b: Stats) {
private renderPair(tree: q.Tree<TopicViewModel>, a: Stats, b: Stats) {
return (
<div className={this.props.classes.flex}>
<div style={{ flex: 1 }}>{this.renderStat(tree, a)}</div>
@@ -104,7 +105,7 @@ class BrokerStatistics extends React.Component<Props, {}> {
)
}
public renderStat(tree: q.Tree, stat: Stats) {
public renderStat(tree: q.Tree<TopicViewModel>, stat: Stats) {
const node = tree.findNode(stat.topic)
if (!node) {
return null

View File

@@ -4,12 +4,13 @@ import * as q from '../../../../backend/src/Model'
import BarChart from '@material-ui/icons/BarChart'
import DateFormatter from '../helper/DateFormatter'
import History from './History'
import { TopicViewModel } from '../../TopicViewModel'
const PlotHistory = React.lazy(() => import('./PlotHistory'))
const throttle = require('lodash.throttle')
interface Props {
node?: q.TreeNode
node?: q.TreeNode<TopicViewModel>
onSelect: (message: q.Message) => void
}

View File

@@ -2,9 +2,10 @@ import * as React from 'react'
import * as q from '../../../../backend/src/Model'
import { Typography } from '@material-ui/core'
import { TopicViewModel } from '../../TopicViewModel'
interface Props {
node: q.TreeNode
node: q.TreeNode<TopicViewModel>
}
class NodeStats extends React.Component<Props, {}> {

View File

@@ -13,7 +13,6 @@ import {
Radio,
RadioGroup,
TextField,
IconButton,
FormControl,
InputLabel,
Input,
@@ -32,10 +31,11 @@ import Clear from '@material-ui/icons/Clear'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { publishActions } from '../../../actions'
import ClearAdornment from '../../helper/ClearAdornment';
import ClearAdornment from '../../helper/ClearAdornment'
import { TopicViewModel } from '../../../TopicViewModel'
interface Props {
node?: q.TreeNode
node?: q.TreeNode<TopicViewModel>
connectionId?: string
topic?: string
payload?: string

View File

@@ -28,18 +28,19 @@ import Topic from './Topic'
const ValueRenderer = React.lazy(() => import('./ValueRenderer'))
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { TopicViewModel } from '../../TopicViewModel'
const throttle = require('lodash.throttle')
interface Props {
node?: q.TreeNode,
node?: q.TreeNode<TopicViewModel>,
actions: typeof sidebarActons,
classes: any,
connectionId?: string,
}
interface State {
node: q.TreeNode,
node: q.TreeNode<TopicViewModel>
compareMessage?: q.Message
}
@@ -69,12 +70,12 @@ class Sidebar extends React.Component<Props, State> {
this.props.node && this.removeUpdateListener(this.props.node)
}
private registerUpdateListener(node: q.TreeNode) {
private registerUpdateListener(node: q.TreeNode<TopicViewModel>) {
node.onMerge.subscribe(this.updateNode)
node.onMessage.subscribe(this.updateNode)
}
private removeUpdateListener(node: q.TreeNode) {
private removeUpdateListener(node: q.TreeNode<TopicViewModel>) {
node.onMerge.unsubscribe(this.updateNode)
node.onMessage.unsubscribe(this.updateNode)
}

View File

@@ -5,14 +5,15 @@ import { withStyles, Theme, StyleRulesCallback } from '@material-ui/core/styles'
import { treeActions } from '../../actions'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { TopicViewModel } from '../../TopicViewModel'
interface Props {
classes: any
theme: Theme
node?: q.TreeNode
selected?: q.TreeNode
node?: q.TreeNode<TopicViewModel>
selected?: q.TreeNode<TopicViewModel>
actions: typeof treeActions
didSelectNode: (node: q.TreeNode) => void
didSelectNode: (node: q.TreeNode<TopicViewModel>) => void
}
const styles: StyleRulesCallback<string> = (theme: Theme) => ({

View File

@@ -4,30 +4,38 @@ import * as q from '../../../../backend/src/Model'
import { AppState } from '../../reducers'
import TreeNode from './TreeNode'
import { connect } from 'react-redux'
import { TopicOrder } from '../../reducers/Settings'
import { TopicViewModel } from '../../TopicViewModel'
const MovingAverage = require('moving-average')
const timeInterval = 10 * 1000
const average = MovingAverage(timeInterval)
const averagingTimeInterval = 10 * 1000
const average = MovingAverage(averagingTimeInterval)
declare var window: any
interface Props {
autoExpandLimit: number
connectionId?: string
tree?: q.Tree
tree?: q.Tree<TopicViewModel>
filter: string
host?: string
topicOrder: TopicOrder
autoExpandLimit: number
}
class Tree extends React.Component<Props, {}> {
interface State {
lastUpdate: number
}
class Tree extends React.PureComponent<Props, State> {
private updateTimer?: any
private lastUpdate: number = 0
private perf: number = 0
private renderTime = 0
constructor(props: any) {
super(props)
this.state = { }
this.state = { lastUpdate: 0 }
}
public time(): number {
@@ -40,17 +48,17 @@ class Tree extends React.Component<Props, {}> {
public componentWillReceiveProps(nextProps: Props) {
if (this.props.tree !== nextProps.tree) {
if (this.props.tree) {
this.props.tree.onMerge.unsubscribe(this.throttledTreeUpdate)
this.props.tree.didReceive.unsubscribe(this.throttledTreeUpdate)
}
if (nextProps.tree) {
nextProps.tree.onMerge.subscribe(this.throttledTreeUpdate)
nextProps.tree.didReceive.subscribe(this.throttledTreeUpdate)
}
this.setState(this.state)
}
}
public componentWillUnmount() {
this.props.tree && this.props.tree.onMerge.unsubscribe(this.throttledTreeUpdate)
this.props.tree && this.props.tree.didReceive.unsubscribe(this.throttledTreeUpdate)
}
public throttledTreeUpdate = () => {
@@ -60,14 +68,17 @@ class Tree extends React.Component<Props, {}> {
const expectedRenderTime = average.forecast()
const updateInterval = Math.max(expectedRenderTime * 7, 300)
const timeUntilNextUpdate = updateInterval - (performance.now() - this.lastUpdate)
const timeUntilNextUpdate = updateInterval - (performance.now() - this.renderTime)
this.updateTimer = setTimeout(() => {
window.requestIdleCallback(() => {
this.lastUpdate = performance.now()
this.updateTimer && clearTimeout(this.updateTimer)
this.updateTimer = undefined
this.setState(this.state)
this.renderTime = performance.now()
this.props.tree && this.props.tree.applyUnmergedChanges()
window.requestIdleCallback(() => {
this.setState({ lastUpdate: this.renderTime })
}, { timeout: 100 })
}, { timeout: 500 })
}, Math.max(0, timeUntilNextUpdate))
}
@@ -91,9 +102,11 @@ class Tree extends React.Component<Props, {}> {
isRoot={true}
treeNode={tree}
name={this.props.host}
lastUpdate={tree.lastUpdate}
collapsed={false}
performanceCallback={this.performanceCallback}
autoExpandLimit={this.props.autoExpandLimit}
topicOrder={this.props.topicOrder}
lastUpdate={tree.lastUpdate}
/>
</div>
)
@@ -106,10 +119,11 @@ class Tree extends React.Component<Props, {}> {
const mapStateToProps = (state: AppState) => {
return {
autoExpandLimit: state.settings.autoExpandLimit,
tree: state.tree.tree,
filter: state.tree.filter,
host: state.connection.host,
autoExpandLimit: state.settings.autoExpandLimit,
topicOrder: state.settings.topicOrder,
}
}

View File

@@ -3,7 +3,6 @@ import * as q from '../../../../backend/src/Model'
import { Theme, withStyles } from '@material-ui/core/styles'
import LabelImportant from '@material-ui/icons/LabelImportant'
import TreeNodeSubnodes from './TreeNodeSubnodes'
import TreeNodeTitle from './TreeNodeTitle'
import { bindActionCreators } from 'redux'
@@ -11,6 +10,9 @@ import { connect } from 'react-redux'
import { isElementInViewport } from '../helper/isElementInViewport'
import { treeActions } from '../../actions'
import { AppState } from '../../reducers'
import { TopicOrder } from '../../reducers/Settings'
import { TopicViewModel } from '../../TopicViewModel'
const debounce = require('lodash.debounce')
declare var performance: any
@@ -26,36 +28,40 @@ const styles = (theme: Theme) => {
display: 'block',
marginLeft: '10px',
},
// hover: {
// '&:hover': {
// backgroundColor: 'rgba(80, 80, 80, 0.35)',
// },
// },
topicSelect: {
float: 'right' as 'right',
opacity: 0,
cursor: 'pointer',
marginTop: '-1px',
},
selected: {
backgroundColor: 'rgba(120, 120, 120, 0.55)',
},
hover: {
backgroundColor: 'rgba(80, 80, 80, 0.55)',
},
}
}
interface Props {
actions: typeof treeActions
lastUpdate: number
animateChages: boolean
isRoot?: boolean
treeNode: q.TreeNode
treeNode: q.TreeNode<TopicViewModel>
name?: string | undefined
collapsed?: boolean | undefined
performanceCallback?: ((ms: number) => void) | undefined
autoExpandLimit: number
classes: any
className?: string
topicOrder: TopicOrder
autoExpandLimit: number
lastUpdate: number
}
interface State {
collapsedOverride: boolean | undefined
mouseOver: boolean
selected: boolean
}
class TreeNode extends React.Component<Props, State> {
@@ -63,13 +69,13 @@ class TreeNode extends React.Component<Props, State> {
private dirtyEdges: boolean = true
private dirtyMessage: boolean = true
private animationDirty: boolean = false
private lastRenderTime = 0
private cssAnimationWasSetAt?: number
private willUpdateTime: number = performance.now()
private titleRef?: React.RefObject<HTMLDivElement> = React.createRef<HTMLDivElement>()
private nodeRef?: React.RefObject<HTMLDivElement> = React.createRef<HTMLDivElement>()
private topicSelectRef?: React.RefObject<HTMLDivElement> = React.createRef<HTMLDivElement>()
private subnodesDidchange = () => {
this.dirtySubnodes = true
@@ -88,6 +94,8 @@ class TreeNode extends React.Component<Props, State> {
this.state = {
collapsedOverride: props.collapsed,
mouseOver: false,
selected: false,
}
}
@@ -96,13 +104,21 @@ class TreeNode extends React.Component<Props, State> {
this.addSubscriber(treeNode)
}
private addSubscriber(treeNode: q.TreeNode) {
private addSubscriber(treeNode: q.TreeNode<TopicViewModel>) {
treeNode.viewModel = new TopicViewModel()
treeNode.viewModel.change.subscribe(this.viewStateHasChanged)
treeNode.onMerge.subscribe(this.subnodesDidchange)
treeNode.onEdgesChange.subscribe(this.edgesDidChange)
treeNode.onMessage.subscribe(this.messageDidChange)
}
private removeSubscriber(treeNode: q.TreeNode) {
private viewStateHasChanged = (msg: void, viewModel: TopicViewModel) => {
this.setState({ selected: viewModel.isSelected() })
}
private removeSubscriber(treeNode: q.TreeNode<TopicViewModel>) {
treeNode.viewModel && treeNode.viewModel.change.unsubscribe(this.viewStateHasChanged)
treeNode.viewModel = undefined
treeNode.onMerge.unsubscribe(this.subnodesDidchange)
treeNode.onEdgesChange.unsubscribe(this.edgesDidChange)
treeNode.onMessage.unsubscribe(this.messageDidChange)
@@ -118,13 +134,14 @@ class TreeNode extends React.Component<Props, State> {
public componentWillUnmount() {
const { treeNode } = this.props
this.removeSubscriber(treeNode)
this.topicSelectRef = undefined
this.titleRef = undefined
this.nodeRef = undefined
}
private stateHasChanged(newState: State) {
return this.state.collapsedOverride !== newState.collapsedOverride
|| this.state.mouseOver !== newState.mouseOver
|| this.state.selected !== newState.selected
}
private propsHasChanged(newProps: Props) {
@@ -135,9 +152,7 @@ class TreeNode extends React.Component<Props, State> {
const shouldRenderToRemoveCssAnimation = this.cssAnimationWasSetAt !== undefined
return this.stateHasChanged(nextState)
|| this.propsHasChanged(nextProps)
|| this.dirtyEdges
|| this.dirtyMessage
|| this.dirtySubnodes
|| (this.dirtyEdges || this.dirtyMessage || this.dirtySubnodes)
|| this.animationDirty
|| shouldRenderToRemoveCssAnimation
}
@@ -176,10 +191,11 @@ class TreeNode extends React.Component<Props, State> {
const animation = shouldStartAnimation ? { willChange: 'auto', translateZ: 0, animation: 'example 0.5s' } : {}
this.animationDirty = shouldStartAnimation
const highlightClass = this.state.selected ? this.props.classes.selected : (this.state.mouseOver ? this.props.classes.hover : '')
return (
<div
key={this.props.treeNode.hash()}
className={`${classes.node} ${this.props.className}`}
className={`${classes.node} ${this.props.className} ${highlightClass}`}
onClick={this.didClickNode}
onMouseOver={this.mouseOver}
onMouseOut={this.mouseOut}
@@ -190,7 +206,7 @@ class TreeNode extends React.Component<Props, State> {
collapsed={this.collapsed()}
treeNode={this.props.treeNode}
name={this.props.name}
lastUpdate={this.props.treeNode.lastUpdate}
didSelectNode={this.didSelectTopic}
/>
</span>
{this.renderNodes()}
@@ -198,31 +214,27 @@ class TreeNode extends React.Component<Props, State> {
)
}
private didSelectTopic = () => {
this.props.actions.selectTopic(this.props.treeNode)
}
private mouseOver = (event: React.MouseEvent) => {
event.stopPropagation()
if (this.nodeRef && this.nodeRef.current) {
this.nodeRef.current.style.backgroundColor = 'rgba(100, 100, 100, 0.55)'
}
if (this.topicSelectRef && this.topicSelectRef.current) {
this.topicSelectRef.current.style.opacity = '1'
}
this.setHover(true)
}
private mouseOut = (event: React.MouseEvent) => {
event.stopPropagation()
if (this.nodeRef && this.nodeRef.current) {
this.nodeRef.current.style.backgroundColor = 'inherit'
}
if (this.topicSelectRef && this.topicSelectRef.current) {
this.topicSelectRef.current.style.opacity = '0'
}
this.setHover(false)
}
private setHover = debounce((hover: boolean) => {
this.setState({ mouseOver: hover })
}, 5)
private didSelectNode = (event: React.MouseEvent) => {
event.stopPropagation()
if (this.topicSelectRef && this.topicSelectRef.current) {
this.topicSelectRef.current.style.opacity = '1'
}
this.props.actions.selectTopic(this.props.treeNode)
this.didSelectTopic()
}
private didClickNode = (event: React.MouseEvent) => {
@@ -236,8 +248,9 @@ class TreeNode extends React.Component<Props, State> {
<TreeNodeSubnodes
animateChanges={this.props.animateChages}
collapsed={this.collapsed()}
autoExpandLimit={this.props.autoExpandLimit}
treeNode={this.props.treeNode}
autoExpandLimit={this.props.autoExpandLimit}
topicOrder={this.props.topicOrder}
lastUpdate={this.props.treeNode.lastUpdate}
/>
)
@@ -250,10 +263,4 @@ const mapDispatchToProps = (dispatch: any) => {
}
}
const mapStateToProps = (state: AppState) => {
return {
autoExpandLimit: state.settings.autoExpandLimit,
}
}
export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(TreeNode))
export default withStyles(styles)(connect(null, mapDispatchToProps)(TreeNode))

View File

@@ -7,17 +7,20 @@ import TreeNode from './TreeNode'
import { connect } from 'react-redux'
import { TopicOrder } from '../../reducers/Settings'
import { Theme, withStyles } from '@material-ui/core'
import { TopicViewModel } from '../../TopicViewModel'
export interface Props {
lastUpdate: number
topicOrder?: TopicOrder
animateChanges: boolean
treeNode: q.TreeNode
autoExpandLimit: number
treeNode: q.TreeNode<TopicViewModel>
filter?: string
collapsed?: boolean | undefined
didSelectNode?: (node: q.TreeNode) => void
classes: any
lastUpdate: number
topicOrder: TopicOrder
selectedTopic?: q.TreeNode<TopicViewModel>
autoExpandLimit: number
}
interface State {
@@ -31,7 +34,7 @@ class TreeNodeSubnodes extends React.Component<Props, State> {
this.state = { alreadyAdded: 10 }
}
private sortedNodes(): q.TreeNode[] {
private sortedNodes(): q.TreeNode<TopicViewModel>[] {
const { topicOrder, treeNode } = this.props
let edges = treeNode.edgeArray
@@ -72,15 +75,19 @@ class TreeNodeSubnodes extends React.Component<Props, State> {
}
const nodes = this.sortedNodes().slice(0, this.state.alreadyAdded)
const listItems = nodes.map(node => (
<TreeNode
key={`${node.hash()}-${this.props.filter}`}
animateChages={this.props.animateChanges}
treeNode={node}
lastUpdate={node.lastUpdate}
className={this.props.classes.listItem}
/>
))
const listItems = nodes.map((node) => {
return (
<TreeNode
key={`${node.hash()}-${this.props.filter}`}
animateChages={this.props.animateChanges}
treeNode={node}
className={this.props.classes.listItem}
topicOrder={this.props.topicOrder}
autoExpandLimit={this.props.autoExpandLimit}
lastUpdate={node.lastUpdate}
/>
)
})
return (
<span className={this.props.classes.list}>
@@ -90,13 +97,6 @@ class TreeNodeSubnodes extends React.Component<Props, State> {
}
}
const mapStateToProps = (state: AppState) => {
return {
topicOrder: state.settings.topicOrder,
filter: state.tree.filter,
}
}
const styles = (theme: Theme) => ({
list: {
display: 'block' as 'block',
@@ -107,4 +107,4 @@ const styles = (theme: Theme) => ({
},
})
export default withStyles(styles)(connect(mapStateToProps)(TreeNodeSubnodes))
export default withStyles(styles)(TreeNodeSubnodes)

View File

@@ -1,29 +1,33 @@
import * as React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { treeActions } from '../../actions'
import * as q from '../../../../backend/src/Model'
import { withStyles, Theme } from '@material-ui/core'
import { TopicViewModel } from '../../TopicViewModel'
const debounce = require('lodash.debounce')
export interface TreeNodeProps extends React.HTMLAttributes<HTMLElement> {
treeNode: q.TreeNode
actions: any
treeNode: q.TreeNode<TopicViewModel>
name?: string | undefined
collapsed?: boolean | undefined
lastUpdate: number
classes: any
didSelectNode: any
}
class TreeNodeTitle extends React.Component<TreeNodeProps, {}> {
private mouseOver = (event: React.MouseEvent) => {
if (this.props.treeNode.message) {
this.props.actions.selectTopic(this.props.treeNode)
}
event.preventDefault()
this.selectTopic()
}
private selectTopic = debounce(() => {
if (this.props.treeNode.message) {
this.props.didSelectNode(this.props.treeNode)
}
}, 5)
public render() {
return (
<span className={this.props.classes.title} onMouseOver={this.mouseOver}>
<span className={this.props.classes.title} onMouseOver={this.props.treeNode.message ? this.mouseOver : undefined}>
{this.renderExpander()} {this.renderSourceEdge()} {this.renderCollapsedSubnodes()} {this.renderValue()}
</span>
)
@@ -59,12 +63,6 @@ class TreeNodeTitle extends React.Component<TreeNodeProps, {}> {
}
}
const mapDispatchToProps = (dispatch: any) => {
return {
actions: bindActionCreators(treeActions, dispatch),
}
}
const styles = (theme: Theme) => ({
value: {
whiteSpace: 'nowrap' as 'nowrap',
@@ -88,4 +86,4 @@ const styles = (theme: Theme) => ({
},
})
export default withStyles(styles)(connect(null, mapDispatchToProps)(TreeNodeTitle))
export default withStyles(styles)(TreeNodeTitle)

View File

@@ -3,7 +3,7 @@ import './tracking'
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import reduxThunk from 'redux-thunk'
import { batchDispatchMiddleware } from 'redux-batched-actions';
import { batchDispatchMiddleware } from 'redux-batched-actions'
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'
import reducers from './reducers'

View File

@@ -2,10 +2,11 @@ import { Action } from 'redux'
import { createReducer } from './lib'
import * as q from '../../../backend/src/Model'
import { MqttOptions } from '../../../backend/src/DataSource'
import { TopicViewModel } from '../TopicViewModel'
export interface ConnectionState {
host?: string
tree?: q.Tree
tree?: q.Tree<TopicViewModel>
connectionOptions?: MqttOptions
connectionId?: string
error?: string
@@ -30,7 +31,7 @@ export interface SetConnecting {
export interface SetConnected {
type: ActionTypes.CONNECTION_SET_CONNECTED
host: string
tree: q.Tree
tree: q.Tree<TopicViewModel>
}
export interface SetDisconnected {

View File

@@ -1,10 +1,11 @@
import * as q from '../../../backend/src/Model'
import { Action } from 'redux'
import { createReducer } from './lib'
import { TopicViewModel } from '../TopicViewModel'
export interface TreeState {
tree?: q.Tree
selectedTopic?: q.TreeNode
tree?: q.Tree<TopicViewModel>
selectedTopic?: q.TreeNode<TopicViewModel>
filter?: string
}
@@ -17,13 +18,13 @@ export enum ActionTypes {
export interface ShowTree {
type: ActionTypes.TREE_SHOW_TREE
tree?: q.Tree
tree?: q.Tree<TopicViewModel>
filter?: string
}
export interface SelectTopic {
type: ActionTypes.TREE_SELECT_TOPIC
selectedTopic?: q.TreeNode
selectedTopic?: q.TreeNode<TopicViewModel>
}
const initialState: TreeState = { }

View File

@@ -1,5 +1,3 @@
import * as q from '../../../backend/src/Model'
import { Action, Reducer, combineReducers } from 'redux'
import { trackEvent } from '../tracking'