diff --git a/app/src/actions/Tree.ts b/app/src/actions/Tree.ts index d6fa567..b78f1e1 100644 --- a/app/src/actions/Tree.ts +++ b/app/src/actions/Tree.ts @@ -11,7 +11,6 @@ const debounce = require('lodash.debounce') export { clearTopic } from './clearTopic' export { moveSelectionUpOrDownwards, moveInward, moveOutward } from './visibleTreeTraversal' -import { moveSelectionUpOrDownwards } from './visibleTreeTraversal' export const selectTopic = (topic: q.TreeNode) => ( dispatch: Dispatch, diff --git a/app/src/components/Sidebar/HistoryDrawer.tsx b/app/src/components/Sidebar/HistoryDrawer.tsx index ccbb474..7468891 100644 --- a/app/src/components/Sidebar/HistoryDrawer.tsx +++ b/app/src/components/Sidebar/HistoryDrawer.tsx @@ -34,6 +34,12 @@ class HistoryDrawer extends React.Component { this.setState({ collapsed: !this.state.collapsed }) } + private createSelectionHandler = (index: number) => (event: React.MouseEvent) => { + this.props.onClick && this.props.onClick(index, event.target) + event.preventDefault() + event.stopPropagation() + } + private handleCtrlA = selectTextWithCtrlA({ targetSelector: 'pre' }) public renderHistory() { @@ -51,7 +57,7 @@ class HistoryDrawer extends React.Component {
this.props.onClick && this.props.onClick(index, event.target)} + onClick={this.createSelectionHandler(index)} tabIndex={0} onKeyDown={this.handleCtrlA} > diff --git a/app/src/components/Sidebar/NodeStats.tsx b/app/src/components/Sidebar/NodeStats.tsx index 5e60902..90bfd40 100644 --- a/app/src/components/Sidebar/NodeStats.tsx +++ b/app/src/components/Sidebar/NodeStats.tsx @@ -4,7 +4,7 @@ import { TopicViewModel } from '../../model/TopicViewModel' import { Typography } from '@material-ui/core' interface Props { - node: q.TreeNode + node?: q.TreeNode } class NodeStats extends React.Component { @@ -14,6 +14,9 @@ class NodeStats extends React.Component { public render() { const { node } = this.props + if (!node) { + return null + } return (
diff --git a/app/src/components/Sidebar/Panel.tsx b/app/src/components/Sidebar/Panel.tsx new file mode 100644 index 0000000..3bc6851 --- /dev/null +++ b/app/src/components/Sidebar/Panel.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import ExpandMore from '@material-ui/icons/ExpandMore' +import { ExpansionPanel, ExpansionPanelDetails, ExpansionPanelSummary, Typography, Theme } from '@material-ui/core' +import { withStyles } from '@material-ui/styles' + +const styles = (theme: Theme) => ({ + summary: { minHeight: '0' }, + details: { padding: '0px 16px 8px 8px', display: 'block' }, + heading: { + fontSize: theme.typography.pxToRem(15), + fontWeight: theme.typography.fontWeightRegular, + }, +}) + +const Panel = (props: { + classes: any + children: [React.ReactElement, React.ReactElement] + disabled?: boolean + detailsHidden?: boolean +}) => { + return ( + + } className={props.classes.summary}> + {props.children[0]} + + {props.detailsHidden ? null : ( + {props.children[1]} + )} + + ) +} + +export default withStyles(styles)(Panel) diff --git a/app/src/components/Sidebar/Publish/Publish.tsx b/app/src/components/Sidebar/Publish/Publish.tsx index 8d8cc86..cfc698f 100644 --- a/app/src/components/Sidebar/Publish/Publish.tsx +++ b/app/src/components/Sidebar/Publish/Publish.tsx @@ -1,30 +1,18 @@ -import * as React from 'react' -import ClearAdornment from '../../helper/ClearAdornment' import Editor from './Editor' import FormatAlignLeft from '@material-ui/icons/FormatAlignLeft' -import History from '../HistoryDrawer' import Message from './Model/Message' import Navigation from '@material-ui/icons/Navigation' -import QosSelect from './QosSelect' +import PublishHistory from './PublishHistory' +import React, { useCallback, useState, useMemo } from 'react' +import TopicInput from './TopicInput' import { AppState } from '../../../reducers' import { bindActionCreators } from 'redux' +import { Button, Fab, Theme, Tooltip, withTheme } from '@material-ui/core' import { connect } from 'react-redux' import { EditorModeSelect } from './EditorModeSelect' import { globalActions, publishActions } from '../../../actions' import { KeyCodes } from '../../../utils/KeyCodes' -import { - Button, - FormControlLabel, - FormControl, - InputLabel, - Input, - Checkbox, - Tooltip, - Fab, - Theme, - withTheme, -} from '@material-ui/core' -const sha1 = require('sha1') +import RetainSwitch from './RetainSwitch' interface Props { connectionId?: string @@ -37,108 +25,73 @@ interface Props { theme: Theme } -interface State { - history: Array +function useHistory(): [Array, (topic: string, payload?: string) => void] { + const [history, setHistory] = useState>([]) + const amendToHistory = useCallback( + (topic: string, payload?: string) => { + // Remove duplicates + let filteredHistory = history.filter(e => e.payload !== payload || e.topic !== topic) + filteredHistory = filteredHistory.slice(-7) + setHistory([...filteredHistory, { topic, payload, sent: new Date() }]) + }, + [history] + ) + + return [history, amendToHistory] } -class Publish extends React.Component { - constructor(props: any) { - super(props) - this.state = { history: [] } - } +function Publish(props: Props) { + console.log(props.connectionId) + const updatePayload = props.actions.setPayload + const [history, amendToHistory] = useHistory() - private updatePayload = (payload: string) => { - this.props.actions.setPayload(payload) - } + const updateMode = useCallback((e: React.ChangeEvent<{}>, value: string) => { + props.actions.setEditorMode(value) + }, []) - private updateTopic = (e: React.ChangeEvent) => { - this.props.actions.setTopic(e.target.value) - } - - private updateMode = (e: React.ChangeEvent<{}>, value: string) => { - this.props.actions.setEditorMode(value) - } - - private handleClickPublish = (e: React.MouseEvent) => { - e.stopPropagation() - this.publish() - } - - private publish() { - if (!this.props.connectionId) { + const publish = useCallback(() => { + if (!props.connectionId) { return } - this.props.actions.publish(this.props.connectionId) + props.actions.publish(props.connectionId) - const topic = this.props.topic || '' - const payload = this.props.payload - if (this.props.connectionId && topic) { - this.addMessageToHistory(topic, payload) + const topic = props.topic || '' + const payload = props.payload + if (props.connectionId && topic) { + amendToHistory(topic, payload) } - } + }, [props, props.connectionId, props.topic, props.payload, amendToHistory]) - private addMessageToHistory(topic: string, payload?: string) { - // Remove duplicates - let filteredHistory = this.state.history.filter(e => e.payload !== payload || e.topic !== topic) - filteredHistory = filteredHistory.slice(-7) - const history: Array = [...filteredHistory, { topic, payload, sent: new Date() }] - this.setState({ history }) - } - - private clearTopic = () => { - this.props.actions.setTopic('') - } - - private topic() { - const topicStr = this.props.topic || '' + const handleClickPublish = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation() + publish() + }, + [publish] + ) + const PublishButton = () => { return ( -
- - Topic - } - endAdornment={} - onBlur={this.onTopicBlur} - onChange={this.updateTopic} - multiline={true} - placeholder="example/topic" - /> - -
- ) - } - - private onTopicBlur = (e: React.FocusEvent) => { - if (!e.target.value) { - this.props.actions.setTopic(undefined) - } - } - - private publishButton() { - return ( - ) } - private formatJson = () => { - if (this.props.payload) { + const formatJson = () => { + if (props.payload) { try { - const str = JSON.stringify(JSON.parse(this.props.payload), undefined, ' ') - this.updatePayload(str) + const str = JSON.stringify(JSON.parse(props.payload), undefined, ' ') + updatePayload(str) } catch (error) { - this.props.globalActions.showError(`Format error: ${error.message}`) + props.globalActions.showError(`Format error: ${error.message}`) } } } - private renderFormatJson() { - if (this.props.editorMode !== 'json') { + const renderFormatJson = () => { + if (props.editorMode !== 'json') { return null } @@ -146,7 +99,7 @@ class Publish extends React.Component { @@ -155,80 +108,45 @@ class Publish extends React.Component { ) } - private editorMode() { + function EditorMode() { return (
- - {this.renderFormatJson()} -
{this.publishButton()}
+ + {renderFormatJson()} +
+ +
) } - private publishMode() { - const labelStyle = { margin: '0 8px 0 8px' } + const handleSubmit = useCallback( + (e: React.KeyboardEvent) => { + if (e.keyCode === KeyCodes.enter && (e.metaKey || e.ctrlKey)) { + e.preventDefault() + e.stopPropagation() + publish() + } + }, + [publish] + ) - return ( -
-
- } label="QoS" labelPlacement="start" /> - - - } - label="retain" - labelPlacement="end" - /> - -
-
- ) - } - - private history() { - const items = [...this.state.history].reverse().map(message => ({ - key: sha1(message.topic + message.payload), - title: message.topic, - value: message.payload || '', - })) - return - } - - private didSelectHistoryEntry = (index: number) => { - const message = this.state.history[index] - this.props.actions.setTopic(message.topic) - this.props.actions.setPayload(message.payload) - } - - private handleSubmit = (e: React.KeyboardEvent) => { - if (e.keyCode === KeyCodes.enter && (e.metaKey || e.ctrlKey)) { - e.preventDefault() - e.stopPropagation() - this.publish() - } - } - - public render() { - return ( -
- {this.topic()} + return useMemo( + () => ( +
+
- {this.editorMode()} - - {this.publishMode()} + + +
- {this.history()} +
- ) - } + ), + [props.payload, props.editorMode, history, handleSubmit, updatePayload] + ) } const mapDispatchToProps = (dispatch: any) => { diff --git a/app/src/components/Sidebar/Publish/PublishHistory.tsx b/app/src/components/Sidebar/Publish/PublishHistory.tsx new file mode 100644 index 0000000..abd4a38 --- /dev/null +++ b/app/src/components/Sidebar/Publish/PublishHistory.tsx @@ -0,0 +1,40 @@ +import History from '../HistoryDrawer' +import Message from './Model/Message' +import React, { useCallback, useMemo } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' +import { publishActions } from '../../../actions' +const sha1 = require('sha1') + +function PublishHistory(props: { history: Array; actions: typeof publishActions }) { + const didSelectHistoryEntry = useCallback( + (index: number) => { + const items = [...props.history].reverse() + const message = items[index] + props.actions.setTopic(message.topic) + props.actions.setPayload(message.payload) + }, + [props.history] + ) + + return useMemo(() => { + const items = [...props.history].reverse().map(message => ({ + key: sha1(message.topic + message.payload), + title: message.topic, + value: message.payload || '', + })) + + return + }, [props.history]) +} + +const mapDispatchToProps = (dispatch: any) => { + return { + actions: bindActionCreators(publishActions, dispatch), + } +} + +export default connect( + undefined, + mapDispatchToProps +)(PublishHistory) diff --git a/app/src/components/Sidebar/Publish/RetainSwitch.tsx b/app/src/components/Sidebar/Publish/RetainSwitch.tsx new file mode 100644 index 0000000..4855e69 --- /dev/null +++ b/app/src/components/Sidebar/Publish/RetainSwitch.tsx @@ -0,0 +1,47 @@ +import QosSelect from './QosSelect' +import React from 'react' +import { Checkbox, FormControlLabel, Tooltip } from '@material-ui/core' +import { publishActions } from '../../../actions' +import { bindActionCreators } from 'redux' +import { AppState } from '../../../reducers' +import { connect } from 'react-redux' + +export function RetainSwitch(props: { retain: boolean; actions: typeof publishActions }) { + const labelStyle = { margin: '0 8px 0 8px' } + return ( +
+
+ } label="QoS" labelPlacement="start" /> + + } + label="retain" + labelPlacement="end" + /> + +
+
+ ) +} + +const mapDispatchToProps = (dispatch: any) => { + return { + actions: bindActionCreators(publishActions, dispatch), + } +} + +const mapStateToProps = (state: AppState) => { + return { + retain: state.publish.retain, + } +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(RetainSwitch) diff --git a/app/src/components/Sidebar/Publish/TopicInput.tsx b/app/src/components/Sidebar/Publish/TopicInput.tsx new file mode 100644 index 0000000..1ac1eb2 --- /dev/null +++ b/app/src/components/Sidebar/Publish/TopicInput.tsx @@ -0,0 +1,60 @@ +import ClearAdornment from '../../helper/ClearAdornment' +import React, { useCallback } from 'react' +import { FormControl, Input, InputLabel } from '@material-ui/core' +import { publishActions } from '../../../actions' +import { bindActionCreators } from 'redux' +import { AppState } from '../../../reducers' +import { connect } from 'react-redux' + +function TopicInput(props: { actions: typeof publishActions; topic?: string }) { + console.log(props.topic) + const updateTopic = useCallback((e: React.ChangeEvent) => { + props.actions.setTopic(e.target.value) + }, []) + + const clearTopic = useCallback(() => { + props.actions.setTopic('') + }, []) + + const onTopicBlur = useCallback((e: React.FocusEvent) => { + if (!e.target.value) { + props.actions.setTopic(undefined) + } + }, []) + + const topicStr = props.topic || '' + return ( +
+ + Topic + } + endAdornment={} + onBlur={onTopicBlur} + onChange={updateTopic} + multiline={true} + placeholder="example/topic" + /> + +
+ ) +} + +const mapDispatchToProps = (dispatch: any) => { + return { + actions: bindActionCreators(publishActions, dispatch), + } +} + +const mapStateToProps = (state: AppState) => { + return { + topic: state.publish.topic, + } +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(TopicInput) diff --git a/app/src/components/Sidebar/Sidebar.tsx b/app/src/components/Sidebar/Sidebar.tsx index ed92661..85f9056 100644 --- a/app/src/components/Sidebar/Sidebar.tsx +++ b/app/src/components/Sidebar/Sidebar.tsx @@ -1,11 +1,7 @@ import * as q from '../../../../backend/src/Model' -import * as React from 'react' -import Copy from '../helper/Copy' -import CustomIconButton from '../helper/CustomIconButton' -import Delete from '@material-ui/icons/Delete' +import React, { useState, useEffect, useCallback } from 'react' import ExpandMore from '@material-ui/icons/ExpandMore' import NodeStats from './NodeStats' -import Topic from './Topic' import ValuePanel from './ValueRenderer/ValuePanel' import { AppState } from '../../reducers' import { Badge, ExpansionPanel, ExpansionPanelDetails, ExpansionPanelSummary, Typography } from '@material-ui/core' @@ -14,6 +10,8 @@ import { connect } from 'react-redux' import { settingsActions, sidebarActions } from '../../actions' import { Theme, withStyles } from '@material-ui/core/styles' import { TopicViewModel } from '../../model/TopicViewModel' +import TopicPanel from './TopicPanel/TopicPanel' +import Panel from './Panel' const throttle = require('lodash.throttle') @@ -27,148 +25,48 @@ interface Props { connectionId?: string } -interface State { - compareMessage?: q.Message - valueRenderWidth: number -} +function Sidebar(props: Props) { + const { classes, node } = props + const [lastUpdate, setLastUpdate] = useState(0) -class Sidebar extends React.Component { - private updateNode = throttle(() => { - this.setState(this.state) - }, 300) + const updateNode = useCallback( + throttle(() => { + setLastUpdate(node ? node.lastUpdate : 0) + }, 300), + [node] + ) - private detailsStyle = { padding: '0px 16px 8px 8px', display: 'block' } + useEffect(() => { + const updateCallback = updateNode + node && node.onMerge.subscribe(updateCallback) + node && node.onMessage.subscribe(updateCallback) - constructor(props: any) { - super(props) - this.state = { valueRenderWidth: 300 } - } - - private registerUpdateListener(node: q.TreeNode) { - node.onMerge.subscribe(this.updateNode) - node.onMessage.subscribe(this.updateNode) - } - - private removeUpdateListener(node: q.TreeNode) { - node.onMerge.unsubscribe(this.updateNode) - node.onMessage.unsubscribe(this.updateNode) - } - - private renderTopicDeleteButton() { - if (!this.props.node || (!this.props.node.message || !this.props.node.message.value)) { - return null + return function cleanup() { + node && node.onMerge.unsubscribe(updateCallback) + node && node.onMessage.unsubscribe(updateCallback) } + }, [node]) - return ( - this.deleteTopic(this.props.node)} tooltip="Clear this topic"> - - - ) - } - - private renderRecursiveTopicDeleteButton() { - const deleteLimit = 50 - const topicCount = this.props.node ? this.props.node.childTopicCount() : 0 - if (!this.props.node || topicCount === 0 || (this.props.node.message && topicCount === 1)) { - return null - } - - return ( - {topicCount >= deleteLimit ? '50+' : topicCount}} - color="secondary" - > - this.deleteTopic(this.props.node, true, deleteLimit)} - tooltip={`Deletes up to ${deleteLimit} sub-topics with a single click`} - > - - - - ) - } - - private deleteTopic = (topic?: q.TreeNode, recursive: boolean = false, maxCount = 50) => { - if (!topic) { - return - } - - this.props.actions.clearTopic(topic, recursive, maxCount) - } - - private renderNode() { - const { classes, node } = this.props - - const copyTopic = node ? : null - const deleteTopic = this.renderTopicDeleteButton() - const deleteRecursiveTopic = this.renderRecursiveTopicDeleteButton() - const summaryStyle = { minHeight: '0' } - return ( + return ( + }> - - - - - - } style={summaryStyle}> - Stats - - {this.renderNodeStats()} - +
- ) - } - - private renderNodeStats() { - if (!this.props.node) { - return null - } - - return ( - - - - ) - } - - public componentWillReceiveProps(nextProps: Props) { - this.props.node && this.removeUpdateListener(this.props.node) - nextProps.node && this.registerUpdateListener(nextProps.node) - - if (this.props.node !== nextProps.node) { - this.setState({ compareMessage: undefined }) - } - } - - public componentWillUnmount() { - this.props.node && this.removeUpdateListener(this.props.node) - } - - public render() { - return ( - - ) - } +
+ ) } const mapStateToProps = (state: AppState) => { @@ -188,13 +86,11 @@ const styles = (theme: Theme) => ({ drawer: { display: 'block' as 'block', }, - badge: { - top: '3px', - right: '3px', - }, valuePaper: { margin: theme.spacing(1), }, + summary: { minHeight: '0' }, + details: { padding: '0px 16px 8px 8px', display: 'block' }, heading: { fontSize: theme.typography.pxToRem(15), fontWeight: theme.typography.fontWeightRegular, diff --git a/app/src/components/Sidebar/TopicPanel/RecursiveTopicDeleteButton.tsx b/app/src/components/Sidebar/TopicPanel/RecursiveTopicDeleteButton.tsx new file mode 100644 index 0000000..dc3e130 --- /dev/null +++ b/app/src/components/Sidebar/TopicPanel/RecursiveTopicDeleteButton.tsx @@ -0,0 +1,38 @@ +import * as q from '../../../../../backend/src/Model' +import CustomIconButton from '../../helper/CustomIconButton' +import Delete from '@material-ui/icons/Delete' +import React, { useCallback } from 'react' +import { Badge } from '@material-ui/core' + +export const RecursiveTopicDeleteButton = (props: { + node?: q.TreeNode + deleteTopicAction: (node: q.TreeNode, a: boolean, limit: number) => void +}) => { + const onClick = useCallback(() => { + if (props.node) { + props.deleteTopicAction(props.node, true, deleteLimit) + } + }, [props.node]) + if (!props.node) { + return null + } + const deleteLimit = 50 + const topicCount = props.node ? props.node.childTopicCount() : 0 + if (topicCount === 0 || (props.node.message && topicCount === 1)) { + return null + } + return ( + {topicCount >= deleteLimit ? '50+' : topicCount}} + color="secondary" + > + + + + + ) +} diff --git a/app/src/components/Sidebar/Topic.tsx b/app/src/components/Sidebar/TopicPanel/Topic.tsx similarity index 90% rename from app/src/components/Sidebar/Topic.tsx rename to app/src/components/Sidebar/TopicPanel/Topic.tsx index 30ebc95..9099edd 100644 --- a/app/src/components/Sidebar/Topic.tsx +++ b/app/src/components/Sidebar/TopicPanel/Topic.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' -import * as q from '../../../../backend/src/Model' +import React from 'react' +import * as q from '../../../../../backend/src/Model' import Button from '@material-ui/core/Button' import { withStyles, Theme } from '@material-ui/core/styles' -import { treeActions } from '../../actions' +import { treeActions } from '../../../actions' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' -import { TopicViewModel } from '../../model/TopicViewModel' +import { TopicViewModel } from '../../../model/TopicViewModel' interface Props { classes: any diff --git a/app/src/components/Sidebar/TopicPanel/TopicDeleteButton.tsx b/app/src/components/Sidebar/TopicPanel/TopicDeleteButton.tsx new file mode 100644 index 0000000..9b41110 --- /dev/null +++ b/app/src/components/Sidebar/TopicPanel/TopicDeleteButton.tsx @@ -0,0 +1,19 @@ +import * as q from '../../../../../backend/src/Model' +import CustomIconButton from '../../helper/CustomIconButton' +import Delete from '@material-ui/icons/Delete' +import React from 'react' + +export const TopicDeleteButton = (props: { + node?: q.TreeNode + deleteTopicAction: (node: q.TreeNode) => void +}) => { + const { node } = props + if (!node || !node.message || !node.message.value) { + return null + } + return ( + props.deleteTopicAction(node)} tooltip="Clear this topic"> + + + ) +} diff --git a/app/src/components/Sidebar/TopicPanel/TopicPanel.tsx b/app/src/components/Sidebar/TopicPanel/TopicPanel.tsx new file mode 100644 index 0000000..a5a3165 --- /dev/null +++ b/app/src/components/Sidebar/TopicPanel/TopicPanel.tsx @@ -0,0 +1,48 @@ +import * as q from '../../../../../backend/src/Model' +import Copy from '../../helper/Copy' +import Panel from '../Panel' +import React, { useMemo } from 'react' +import Topic from './Topic' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' +import { RecursiveTopicDeleteButton } from './RecursiveTopicDeleteButton' +import { sidebarActions } from '../../../actions' +import { TopicDeleteButton } from './TopicDeleteButton' + +const TopicPanel = (props: { node?: q.TreeNode; actions: typeof sidebarActions; updateNode: () => void }) => { + const { node, updateNode } = props + const copyTopic = node ? : null + + const deleteTopic = (topic?: q.TreeNode, recursive: boolean = false, maxCount = 50) => { + if (!topic) { + return + } + + props.actions.clearTopic(topic, recursive, maxCount) + } + + return useMemo( + () => ( + + + Topic {copyTopic} + + + + + + ), + [node, node && node.childTopicCount()] + ) +} + +const mapDispatchToProps = (dispatch: any) => { + return { + actions: bindActionCreators(sidebarActions, dispatch), + } +} + +export default connect( + undefined, + mapDispatchToProps +)(TopicPanel) diff --git a/app/src/components/Tree/TreeNode/TreeNodeSubnodes.tsx b/app/src/components/Tree/TreeNode/TreeNodeSubnodes.tsx index a4aa6ed..aa81c81 100644 --- a/app/src/components/Tree/TreeNode/TreeNodeSubnodes.tsx +++ b/app/src/components/Tree/TreeNode/TreeNodeSubnodes.tsx @@ -63,7 +63,7 @@ function TreeNodeSubnodes(props: Props) { }) return {listItems} - }, [alreadyAdded, props.lastUpdate, props.theme]) + }, [alreadyAdded, props.treeNode.lastUpdate, props.theme]) } const styles = (theme: Theme) => ({ diff --git a/app/src/components/Tree/TreeNode/index.tsx b/app/src/components/Tree/TreeNode/index.tsx index 5e7988a..d1bb507 100644 --- a/app/src/components/Tree/TreeNode/index.tsx +++ b/app/src/components/Tree/TreeNode/index.tsx @@ -133,7 +133,7 @@ function TreeNodeComponent(props: Props) { {renderNodes()}
) - }, [lastUpdate, treeNode, name, isCollapsed, selected, theme]) + }, [treeNode.lastUpdate, treeNode, name, isCollapsed, selected, theme]) } export default withStyles(styles, { withTheme: true })(TreeNodeComponent) diff --git a/app/src/components/Tree/index.tsx b/app/src/components/Tree/index.tsx index a40029f..42ba1d0 100644 --- a/app/src/components/Tree/index.tsx +++ b/app/src/components/Tree/index.tsx @@ -99,8 +99,8 @@ class TreeComponent extends React.PureComponent { this.updateTimer = undefined this.renderTime = performance.now() - if (!this.props.paused) { - this.props.tree && this.props.tree.applyUnmergedChanges() + if (!this.props.paused && this.props.tree) { + this.props.tree.applyUnmergedChanges() } window.requestIdleCallback( () => {