From e82c8c4eb08b6dd2d971e1f8d64fbd63095a57ea Mon Sep 17 00:00:00 2001 From: Thomas Nordquist Date: Mon, 17 Jun 2019 21:31:54 +0200 Subject: [PATCH] Refactor --- app/src/components/ChartPanel/Chart.tsx | 124 +++++++----------- .../components/ChartPanel/ChartActions.tsx | 26 ++++ app/src/components/ChartPanel/ChartTitle.tsx | 29 ++++ .../ChartPanel/ChartWithTreeNode.tsx | 50 +++++++ app/src/components/ChartPanel/index.tsx | 7 +- app/src/components/Layout/ContentView.tsx | 7 + app/src/components/Layout/PauseButton.tsx | 2 +- 7 files changed, 162 insertions(+), 83 deletions(-) create mode 100644 app/src/components/ChartPanel/ChartActions.tsx create mode 100644 app/src/components/ChartPanel/ChartTitle.tsx create mode 100644 app/src/components/ChartPanel/ChartWithTreeNode.tsx diff --git a/app/src/components/ChartPanel/Chart.tsx b/app/src/components/ChartPanel/Chart.tsx index 4e5a6c0..0d5862c 100644 --- a/app/src/components/ChartPanel/Chart.tsx +++ b/app/src/components/ChartPanel/Chart.tsx @@ -1,35 +1,51 @@ import * as q from '../../../../backend/src/Model' import * as React from 'react' -import Clear from '@material-ui/icons/Clear' -import CustomIconButton from '../helper/CustomIconButton' import TopicPlot from '../TopicPlot' import { AppState } from '../../reducers' import { bindActionCreators } from 'redux' import { chartActions } from '../../actions' import { ChartParameters } from '../../reducers/Charts' import { connect } from 'react-redux' -import { Paper, Theme, Typography, withStyles } from '@material-ui/core' -import { SettingsButton } from './ChartSettings/SettingsButton' +import { Paper } from '@material-ui/core' +import ChartTitle from './ChartTitle' +import { ChartActions } from './ChartActions' const throttle = require('lodash.throttle') interface Props { parameters: ChartParameters - tree?: q.Tree - classes: any + treeNode?: q.TreeNode actions: { chart: typeof chartActions } } -function Chart(props: Props) { - if (!props.tree) { - return null - } +/** + * Subscribes to onMessage of treeNode + */ +function useMessageSubscriptionToUpdate(treeNode?: q.TreeNode) { + const [lastUpdated, setLastUpdate] = React.useState(0) + function subscribeToMessageUpdates() { + const onUpdateCallback = throttle(() => setLastUpdate(treeNode ? treeNode.lastUpdate : 0), 300) + treeNode && treeNode.onMessage.subscribe(onUpdateCallback) - const { tree, parameters } = props - const initialTreeNode = tree.findNode(parameters.topic) - const [treeNode, setTreeNode] = React.useState | undefined>(initialTreeNode) - usePollingToFetchTreeNode(treeNode, tree, parameters, setTreeNode) + return function cleanup() { + treeNode && treeNode.onMessage.unsubscribe(onUpdateCallback) + } + } + React.useEffect(subscribeToMessageUpdates, [treeNode]) +} + +function Chart(props: Props) { + const { parameters, treeNode } = props + const [freezedHistory, setHistory] = React.useState() + useMessageSubscriptionToUpdate(treeNode) + + const togglePause = React.useCallback(() => { + if (!props.treeNode) { + return + } + setHistory(freezedHistory ? undefined : props.treeNode.messageHistory.clone()) + }, [props.treeNode, freezedHistory]) const onRemove = React.useCallback(() => { props.actions.chart.removeChart(props.parameters) @@ -37,26 +53,23 @@ function Chart(props: Props) { return ( -
- - - - +
+
+ + +
- - {parameters.dotPath ? parameters.dotPath : ''} - -
- - {parameters.topic} - -
- {treeNode ? ( + {props.treeNode ? ( ) : ( @@ -66,12 +79,6 @@ function Chart(props: Props) { ) } -const mapStateToProps = (state: AppState) => { - return { - tree: state.connection.tree, - } -} - const mapDispatchToProps = (dispatch: any) => { return { actions: { @@ -80,50 +87,7 @@ const mapDispatchToProps = (dispatch: any) => { } } -const styles = (theme: Theme) => ({ - topic: { - wordBreak: 'break-all' as 'break-all', - whiteSpace: 'nowrap' as 'nowrap', - overflow: 'hidden' as 'hidden', - textOverflow: 'ellipsis' as 'ellipsis', - }, -}) - export default connect( - mapStateToProps, + undefined, mapDispatchToProps -)(withStyles(styles)(Chart)) - -/** - * If a node is not available when the plot is shown, keep polling until it has been created - */ -function usePollingToFetchTreeNode( - treeNode: q.TreeNode | undefined, - tree: q.Tree, - parameters: ChartParameters, - setTreeNode: React.Dispatch | undefined>> -) { - const [lastUpdate, setLastUpdate] = React.useState(0) - - function pollForTreeNode() { - const onUpdateCallback = throttle(() => setLastUpdate(treeNode ? treeNode.lastUpdate : 0), 300) - let intervalTimer: any - if (!treeNode) { - intervalTimer = setInterval(() => { - const node = tree.findNode(parameters.topic) - if (node) { - setTreeNode(node) - node.onMessage.subscribe(onUpdateCallback) - clearInterval(intervalTimer) - } - }, 500) - } else { - treeNode.onMessage.subscribe(onUpdateCallback) - } - return function cleanup() { - treeNode && treeNode.onMessage.unsubscribe(onUpdateCallback) - intervalTimer && clearInterval(intervalTimer) - } - } - React.useEffect(pollForTreeNode) -} +)(Chart) diff --git a/app/src/components/ChartPanel/ChartActions.tsx b/app/src/components/ChartPanel/ChartActions.tsx new file mode 100644 index 0000000..1b25ec3 --- /dev/null +++ b/app/src/components/ChartPanel/ChartActions.tsx @@ -0,0 +1,26 @@ +import * as React from 'react' +import Play from '@material-ui/icons/PlayArrow' +import Pause from '@material-ui/icons/PauseCircleFilled' +import Clear from '@material-ui/icons/Clear' +import CustomIconButton from '../helper/CustomIconButton' +import { ChartParameters } from '../../reducers/Charts' +import { SettingsButton } from './ChartSettings/SettingsButton' + +export function ChartActions(props: { + paused: boolean + togglePause: () => void + parameters: ChartParameters + onRemove: () => void +}) { + return ( +
+ + {props.paused ? : } + + + + + +
+ ) +} diff --git a/app/src/components/ChartPanel/ChartTitle.tsx b/app/src/components/ChartPanel/ChartTitle.tsx new file mode 100644 index 0000000..63cbc2b --- /dev/null +++ b/app/src/components/ChartPanel/ChartTitle.tsx @@ -0,0 +1,29 @@ +import * as React from 'react' +import { ChartParameters } from '../../reducers/Charts' +import { Typography, Theme, withStyles } from '@material-ui/core' + +function ChartTitle(props: { parameters: ChartParameters; classes: any }) { + const { classes, parameters } = props + return ( +
+ + {parameters.dotPath ? parameters.dotPath : parameters.topic} + +
+ + {parameters.dotPath ? parameters.topic : ''} + +
+ ) +} + +const styles = (theme: Theme) => ({ + topic: { + wordBreak: 'break-all' as 'break-all', + whiteSpace: 'nowrap' as 'nowrap', + overflow: 'hidden' as 'hidden', + textOverflow: 'ellipsis' as 'ellipsis', + }, +}) + +export default withStyles(styles)(ChartTitle) diff --git a/app/src/components/ChartPanel/ChartWithTreeNode.tsx b/app/src/components/ChartPanel/ChartWithTreeNode.tsx new file mode 100644 index 0000000..2fd812c --- /dev/null +++ b/app/src/components/ChartPanel/ChartWithTreeNode.tsx @@ -0,0 +1,50 @@ +import * as q from '../../../../backend/src/Model' +import * as React from 'react' +import Chart from './Chart' +import { ChartParameters } from '../../reducers/Charts' + +interface Props { + tree?: q.Tree + parameters: ChartParameters +} + +export function ChartWithTreeNode(props: Props) { + const { tree, parameters } = props + if (!tree) { + return null + } + + const initialTreeNode = tree.findNode(parameters.topic) + const [treeNode, setTreeNode] = React.useState | undefined>(initialTreeNode) + + usePollingToFetchTreeNode(treeNode, tree, parameters.topic, setTreeNode) + return +} + +/** + * If a node is not available when the plot is shown, keep polling until it has been created + */ +function usePollingToFetchTreeNode( + treeNode: q.TreeNode | undefined, + tree: q.Tree, + path: string, + setTreeNode: React.Dispatch | undefined>> +) { + function pollUntilTreeNodeHasBeenFound() { + let intervalTimer: any + if (!treeNode) { + intervalTimer = setInterval(() => { + const node = tree.findNode(path) + if (node) { + setTreeNode(node) + clearInterval(intervalTimer) + } + }, 500) + } + return function cleanup() { + intervalTimer && clearInterval(intervalTimer) + } + } + + React.useEffect(pollUntilTreeNodeHasBeenFound, []) +} diff --git a/app/src/components/ChartPanel/index.tsx b/app/src/components/ChartPanel/index.tsx index 7c599c8..9b65479 100644 --- a/app/src/components/ChartPanel/index.tsx +++ b/app/src/components/ChartPanel/index.tsx @@ -1,10 +1,11 @@ +import * as q from '../../../../backend/src/Model' import * as React from 'react' -import Chart from './Chart' import ShowChart from '@material-ui/icons/ShowChart' import { AppState } from '../../reducers' import { bindActionCreators } from 'redux' import { chartActions } from '../../actions' import { ChartParameters } from '../../reducers/Charts' +import { ChartWithTreeNode } from './ChartWithTreeNode' import { connect } from 'react-redux' import { Grid, Theme, Typography, withStyles } from '@material-ui/core' import { List } from 'immutable' @@ -13,6 +14,7 @@ const { TransitionGroup, CSSTransition } = require('react-transition-group/esm') interface Props { charts: List connectionId?: string + tree?: q.Tree actions: { chart: typeof chartActions } @@ -67,7 +69,7 @@ function ChartPanel(props: Props) { classNames="example" > - + )) @@ -100,6 +102,7 @@ const mapStateToProps = (state: AppState) => { return { charts: state.charts.get('charts'), connectionId: state.connection.connectionId, + tree: state.connection.tree, } } diff --git a/app/src/components/Layout/ContentView.tsx b/app/src/components/Layout/ContentView.tsx index 46feec7..6383502 100644 --- a/app/src/components/Layout/ContentView.tsx +++ b/app/src/components/Layout/ContentView.tsx @@ -23,6 +23,12 @@ function ContentView(props: Props) { setDetectedHeight(newHeight) }, []) + const closeDrawerCompletelyIfItSitsOnTheEdge = React.useCallback(() => { + if (detectedHeight < 30) { + setHeight('100%') + } + }, [detectedHeight]) + // Open chart panel on start and when a new chart is added but the panel is closed React.useEffect(() => { const almostClosed = !isNaN(height as any) && detectedHeight < 30 @@ -48,6 +54,7 @@ function ContentView(props: Props) { pane1Style={{ maxHeight: '100%' }} pane2Style={{ borderTop: '1px solid #999', display: 'flex' }} onChange={setHeight} + onDragFinished={closeDrawerCompletelyIfItSitsOnTheEdge} >