From 209899c3b81bf1222abc8fcbba7258b921c07b50 Mon Sep 17 00:00:00 2001 From: Thomas Nordquist Date: Sun, 16 Jun 2019 19:10:37 +0200 Subject: [PATCH] Add numeric chart panel --- .vscode/settings.json | 3 +- app/index.html | 266 +++++++++++------- app/package.json | 2 + app/src/actions/Charts.ts | 101 +++++++ app/src/actions/Connection.ts | 1 - app/src/actions/ConnectionManager.ts | 7 +- app/src/actions/Settings.ts | 6 +- app/src/actions/Sidebar.ts | 1 - app/src/actions/index.ts | 2 + app/src/components/App.tsx | 5 +- app/src/components/ChartPanel/Chart.tsx | 107 +++++++ app/src/components/ChartPanel/index.tsx | 124 ++++++++ app/src/components/ErrorBoundary.tsx | 1 + app/src/components/Layout/ContentView.tsx | 54 ++-- .../Sidebar/CodeDiff/ChartPreview.tsx | 82 ++++++ .../components/Sidebar/CodeDiff/Gutters.tsx | 65 +---- app/src/components/Sidebar/CodeDiff/index.tsx | 4 +- app/src/components/Sidebar/Sidebar.tsx | 1 - .../Sidebar/ValueRenderer/MessageHistory.tsx | 2 +- .../Sidebar/ValueRenderer/ValuePanel.tsx | 8 +- .../Sidebar/ValueRenderer/ValueRenderer.tsx | 9 +- .../components/{Sidebar => }/TopicPlot.tsx | 9 +- app/src/components/Tree/Tree.tsx | 4 + .../components/helper/CustomIconButton.tsx | 5 +- app/src/reducers/Charts.ts | 61 ++++ app/src/reducers/Settings.ts | 1 - app/src/reducers/index.ts | 3 + app/yarn.lock | 4 +- 28 files changed, 719 insertions(+), 219 deletions(-) create mode 100644 app/src/actions/Charts.ts create mode 100644 app/src/components/ChartPanel/Chart.tsx create mode 100644 app/src/components/ChartPanel/index.tsx create mode 100644 app/src/components/Sidebar/CodeDiff/ChartPreview.tsx rename app/src/components/{Sidebar => }/TopicPlot.tsx (82%) create mode 100644 app/src/reducers/Charts.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 85aaba8..7ec1a9d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "editor.formatOnSave": true, "files.exclude": { - "**/node_modules": true + "**/node_modules": true, + "build/": true } } diff --git a/app/index.html b/app/index.html index 6fd8026..128906a 100644 --- a/app/index.html +++ b/app/index.html @@ -1,125 +1,181 @@ - - - - MQTT Explorer - + + + + MQTT Explorer + - - + - - -
- - <% _.forEach(htmlWebpackPlugin.files.js, function(file) { %><% }); %> - + .Resizer.vertical:hover { + border-left: 4px solid rgba(130, 130, 130, 0.3); + border-right: 4px solid rgba(140, 140, 140, 0.3); + } + + .Resizer.disabled { + cursor: not-allowed; + } + .Resizer.disabled:hover { + border-color: transparent; + } + + .example-enter { + opacity: 0; + } + .example-enter-active { + opacity: 1; + transition: opacity 300ms ease-in; + } + .example-exit { + opacity: 1; + } + .example-exit-active { + opacity: 0; + transition: opacity 300ms ease-in; + } + + + +
+ + <% _.forEach(htmlWebpackPlugin.files.js, function(file) { %><% }); %> + diff --git a/app/package.json b/app/package.json index 932eea6..124ebfd 100644 --- a/app/package.json +++ b/app/package.json @@ -15,6 +15,7 @@ "@material-ui/icons": "^4", "@material-ui/lab": "^4.0.0-alpha", "@material-ui/styles": "^4", + "@types/react-transition-group": "^2.9.2", "axios": "^0.19.0", "brace": "^0.11.1", "compare-versions": "^3.4.0", @@ -41,6 +42,7 @@ "react-redux": "^7.0.3", "react-resize-detector": "^4.1.4", "react-split-pane": "^0.1.85", + "react-transition-group": "^4.1.1", "react-vis": "^1.11.6", "redux": "^4.0.1", "redux-batched-actions": "^0.4.1", diff --git a/app/src/actions/Charts.ts b/app/src/actions/Charts.ts new file mode 100644 index 0000000..d12fe73 --- /dev/null +++ b/app/src/actions/Charts.ts @@ -0,0 +1,101 @@ +import { Action, ActionTypes, ChartParameters } from '../reducers/Charts' +import { AppState } from '../reducers' +import { default as persistentStorage, StorageIdentifier } from '../utils/PersistentStorage' +import { Dispatch } from 'redux' +import { showError } from './Global' + +interface ConnectionViewState { + charts: Array +} + +interface ConnectionViewStateDictionary { + [s: string]: ConnectionViewState +} +const connectionViewStateIdentifier: StorageIdentifier = { + id: 'connection_view_state', +} + +export const loadCharts = () => async (dispatch: Dispatch, getState: () => AppState) => { + const connectionId = getState().connection.connectionId + if (!connectionId) { + return + } + + let viewStates: ConnectionViewStateDictionary | undefined + try { + viewStates = await persistentStorage.load(connectionViewStateIdentifier) + } catch (error) { + dispatch(showError(error)) + } + + if (!viewStates || !viewStates[connectionId]) { + dispatch(setCharts([])) + return + } + + const viewState = viewStates[connectionId] + if (viewState) { + dispatch(setCharts(viewState.charts)) + } +} + +export const saveCharts = () => async (dispatch: Dispatch, getState: () => AppState) => { + const connectionId = getState().connection.connectionId + if (!connectionId) { + return + } + + const charts = getState() + .charts.get('charts') + .toArray() + + let viewStates: ConnectionViewStateDictionary | undefined + try { + viewStates = (await persistentStorage.load(connectionViewStateIdentifier)) || {} + const state: ConnectionViewState = viewStates[connectionId] || { charts: [] } + state.charts = charts + + viewStates[connectionId] = state + await persistentStorage.store(connectionViewStateIdentifier, viewStates) + } catch (error) { + dispatch(showError(error)) + } +} + +export const addChart = (chartParameters: ChartParameters) => async ( + dispatch: Dispatch, + getState: () => AppState +) => { + let chartExists = Boolean( + getState() + .charts.get('charts') + .find(chart => chart.topic === chartParameters.topic && chart.dotPath === chartParameters.dotPath) + ) + if (chartExists) { + return + } + + dispatch({ + type: ActionTypes.CHARTS_ADD, + chart: chartParameters, + }) + dispatch(saveCharts()) +} + +export const removeChart = (chartParameters: ChartParameters) => async ( + dispatch: Dispatch, + getState: () => AppState +) => { + dispatch({ + chart: chartParameters, + type: ActionTypes.CHARTS_REMOVE, + }) + dispatch(saveCharts()) +} + +export const setCharts = (charts: Array): Action => { + return { + charts, + type: ActionTypes.CHARTS_SET, + } +} diff --git a/app/src/actions/Connection.ts b/app/src/actions/Connection.ts index 4c4a266..a2e0d23 100644 --- a/app/src/actions/Connection.ts +++ b/app/src/actions/Connection.ts @@ -21,7 +21,6 @@ export const connect = (options: MqttOptions, connectionId: string) => ( const host = url.parse(options.url).hostname rendererEvents.subscribe(event, dataSourceState => { - console.log(dataSourceState) if (dataSourceState.connected) { const didReconnect = Boolean(getState().connection.tree) if (!didReconnect) { diff --git a/app/src/actions/ConnectionManager.ts b/app/src/actions/ConnectionManager.ts index b6b4c3a..9325072 100644 --- a/app/src/actions/ConnectionManager.ts +++ b/app/src/actions/ConnectionManager.ts @@ -15,9 +15,8 @@ import * as path from 'path' import { ActionTypes, Action } from '../reducers/ConnectionManager' -const storedConnectionsIdentifier: StorageIdentifier<{ - [s: string]: ConnectionOptions -}> = { +type ConnectionDictionary = { [s: string]: ConnectionOptions } +const storedConnectionsIdentifier: StorageIdentifier = { id: 'ConnectionManager_connections', } @@ -47,14 +46,12 @@ export const selectCertificate = (connectionId: string) => async ( ) => { try { const certificate = await openCertificate() - console.log(certificate) dispatch( updateConnection(connectionId, { selfSignedCertificate: certificate, }) ) } catch (error) { - console.log(error) dispatch(showError(error)) } } diff --git a/app/src/actions/Settings.ts b/app/src/actions/Settings.ts index 915ef1f..e69a178 100644 --- a/app/src/actions/Settings.ts +++ b/app/src/actions/Settings.ts @@ -1,15 +1,15 @@ import * as q from '../../../backend/src/Model' +import { ActionTypes, SettingsState, TopicOrder } from '../reducers/Settings' import { AppState } from '../reducers' import { autoExpandLimitSet } from '../components/SettingsDrawer/Settings' +import { Base64Message } from '../../../backend/src/Model/Base64Message' import { batchActions } from 'redux-batched-actions' import { default as persistentStorage, StorageIdentifier } from '../utils/PersistentStorage' import { Dispatch } from 'redux' +import { globalActions } from './' import { showError } from './Global' import { showTree } from './Tree' import { TopicViewModel } from '../model/TopicViewModel' -import { ActionTypes, SettingsState, TopicOrder } from '../reducers/Settings' -import { Base64Message } from '../../../backend/src/Model/Base64Message' -import { globalActions } from '.' const settingsIdentifier: StorageIdentifier> = { id: 'Settings', diff --git a/app/src/actions/Sidebar.ts b/app/src/actions/Sidebar.ts index 5bf0332..4d2dbd5 100644 --- a/app/src/actions/Sidebar.ts +++ b/app/src/actions/Sidebar.ts @@ -44,7 +44,6 @@ export const clearTopic = (topic: q.TreeNode, recursive: boolean, subtopicC .filter(topic => Boolean(topic.message && topic.message.value)) .slice(0, subtopicClearLimit) .forEach(topic => { - console.log('deleting', topic.path()) const mqttMessage = { topic: topic.path(), payload: null, diff --git a/app/src/actions/index.ts b/app/src/actions/index.ts index 8e9aef5..c5eab9d 100644 --- a/app/src/actions/index.ts +++ b/app/src/actions/index.ts @@ -1,3 +1,4 @@ +import * as chartActions from './Charts' import * as connectionActions from './Connection' import * as connectionManagerActions from './ConnectionManager' import * as globalActions from './Global' @@ -10,6 +11,7 @@ import * as updateNotifierActions from './UpdateNotifier' export { settingsActions, treeActions, + chartActions, publishActions, updateNotifierActions, connectionActions, diff --git a/app/src/components/App.tsx b/app/src/components/App.tsx index 09a0954..e4c5931 100644 --- a/app/src/components/App.tsx +++ b/app/src/components/App.tsx @@ -97,19 +97,16 @@ const styles = (theme: Theme) => { const drawerWidth = 300 const contentBaseStyle = { width: '100vw', - overflow: 'hidden' as 'hidden', backgroundColor: theme.palette.background.default, } return { heightProperty: { - height: 'calc(100vh - 64px) !important', + height: '100%', // 'calc(100vh - 64px) !important', }, paneDefaults: { backgroundColor: theme.palette.background.default, color: theme.palette.text.primary, - overflowY: 'scroll' as 'scroll', - overflowX: 'hidden' as 'hidden', display: 'block' as 'block', height: 'calc(100vh - 64px)', }, diff --git a/app/src/components/ChartPanel/Chart.tsx b/app/src/components/ChartPanel/Chart.tsx new file mode 100644 index 0000000..d76ccd6 --- /dev/null +++ b/app/src/components/ChartPanel/Chart.tsx @@ -0,0 +1,107 @@ +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, Fade } from '@material-ui/core' + +interface Props { + parameters: ChartParameters + tree?: q.Tree + classes: any + actions: { + chart: typeof chartActions + } +} + +function Chart(props: Props) { + if (!props.tree) { + return null + } + + const { tree, parameters } = props + const initialTreeNode = tree.findNode(parameters.topic) + const [treeNode, setTreeNode] = React.useState | undefined>(initialTreeNode) + const [lastUpdate, setLastUpdate] = React.useState(0) + + /** If a node is not available when the plot is shown, keep polling until it has been created */ + function pollForTreeNode() { + const onUpdateCallback = () => setLastUpdate(treeNode ? treeNode.lastUpdate : 0) + 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) + + const onClick = React.useCallback(() => { + props.actions.chart.removeChart(props.parameters) + }, [props.parameters]) + + return ( + +
+ + + +
+ + {parameters.dotPath ? parameters.dotPath : ''} + +
+ + {parameters.topic} + +
+ {treeNode ? : No data} +
+ ) +} + +const mapStateToProps = (state: AppState) => { + return { + tree: state.tree.get('tree'), + } +} + +const mapDispatchToProps = (dispatch: any) => { + return { + actions: { + chart: bindActionCreators(chartActions, dispatch), + }, + } +} + +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, + mapDispatchToProps +)(withStyles(styles)(Chart)) diff --git a/app/src/components/ChartPanel/index.tsx b/app/src/components/ChartPanel/index.tsx new file mode 100644 index 0000000..826fc42 --- /dev/null +++ b/app/src/components/ChartPanel/index.tsx @@ -0,0 +1,124 @@ +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 { connect } from 'react-redux' +import { Grid, Theme, Typography, withStyles } from '@material-ui/core' +import { List } from 'immutable' +const { TransitionGroup, CSSTransition } = require('react-transition-group/esm') + +interface Props { + charts: List + connectionId?: string + actions: { + chart: typeof chartActions + } +} + +function spacingForChartCount(count: number): 4 | 6 | 12 { + if (count >= 5) { + return 4 + } else if (count >= 2) { + return 6 + } else { + return 12 + } +} + +// function FadingChart(props: { chartParameters: ChartParameters; chartsInView: number; key: any }) { +// const { chartsInView, chartParameters } = props +// const [spacing, setSpacing] = React.useState(spacingForChartCount(chartsInView)) + +// // Update spacing after animations have completed +// React.useEffect(() => { +// const newSpacing = spacingForChartCount(chartsInView) +// if (spacing !== newSpacing) { +// setSpacing(newSpacing) +// // setTimeout(() => , 500) +// } +// }) + +// return ( + +// ) +// } + +function ChartPanel(props: Props) { + const chartsInView = props.charts.count() + + const [spacing, setSpacing] = React.useState(spacingForChartCount(chartsInView)) + + React.useEffect(() => { + props.actions.chart.loadCharts() + }, [props.connectionId]) + + // Update spacing after animations have completed + React.useEffect(() => { + const newSpacing = spacingForChartCount(chartsInView) + if (newSpacing > spacing) { + setTimeout(() => setSpacing(newSpacing), 500) + } else { + setSpacing(newSpacing) + } + }, [chartsInView]) + + const charts = props.charts.map(chartParameters => ( + + + + + + )) + + return ( +
+ + + {charts} + + {chartsInView === 0 ? : null} + +
+ ) +} + +function NoCharts() { + return ( +
+ No charts selected + Select a numeric values from the value preview. + + Click on to add a topic / value to this panel. + +
+ ) +} + +const mapStateToProps = (state: AppState) => { + return { + charts: state.charts.get('charts'), + connectionId: state.connection.connectionId, + } +} + +const mapDispatchToProps = (dispatch: any) => { + return { + actions: { + chart: bindActionCreators(chartActions, dispatch), + }, + } +} + +const styles = (theme: Theme) => ({}) + +export default connect( + mapStateToProps, + mapDispatchToProps +)(withStyles(styles)(ChartPanel)) diff --git a/app/src/components/ErrorBoundary.tsx b/app/src/components/ErrorBoundary.tsx index dfb1591..6e9a5db 100644 --- a/app/src/components/ErrorBoundary.tsx +++ b/app/src/components/ErrorBoundary.tsx @@ -104,6 +104,7 @@ const styles = (theme: Theme) => ({ }, textColor: { color: theme.palette.text.primary, + userSelect: 'all' as 'all', }, centered: { textAlign: 'center' as 'center', diff --git a/app/src/components/Layout/ContentView.tsx b/app/src/components/Layout/ContentView.tsx index 03afed4..30d1152 100644 --- a/app/src/components/Layout/ContentView.tsx +++ b/app/src/components/Layout/ContentView.tsx @@ -1,27 +1,43 @@ import * as React from 'react' import ReactSplitPane from 'react-split-pane' -import { Sidebar } from '../Sidebar' import Tree from '../Tree/Tree' +import ChartPanel from '../ChartPanel' +import { Sidebar } from '../Sidebar' export default function ContentView(props: { heightProperty: any; paneDefaults: any; connectionId: any }) { + const [height, setHeight] = React.useState(0) return ( - -
- -
-
- -
-
+
+ + + +
+ +
+
+ +
+
) } diff --git a/app/src/components/Sidebar/CodeDiff/ChartPreview.tsx b/app/src/components/Sidebar/CodeDiff/ChartPreview.tsx new file mode 100644 index 0000000..f0f1243 --- /dev/null +++ b/app/src/components/Sidebar/CodeDiff/ChartPreview.tsx @@ -0,0 +1,82 @@ +import * as q from '../../../../../backend/src/Model' +import * as React from 'react' +import ShowChart from '@material-ui/icons/ShowChart' +import TopicPlot from '../../TopicPlot' +import { bindActionCreators } from 'redux' +import { chartActions } from '../../../actions' +import { connect } from 'react-redux' +import { Fade, Paper, Popper, Tooltip } from '@material-ui/core' +import { JsonPropertyLocation } from '../../../../../backend/src/JsonAstParser' + +interface Props { + treeNode: q.TreeNode + classes: any + literal: JsonPropertyLocation + actions: { + chart: typeof chartActions + } +} + +function ChartPreview(props: Props) { + const chartIconRef = React.useRef(null) + const [open, setOpen] = React.useState(false) + + const onClick = React.useCallback(() => { + props.actions.chart.addChart({ + topic: props.treeNode.path(), + dotPath: props.literal.path, + }) + }, [props.literal.path, props.treeNode]) + + const mouseOver = React.useCallback(() => { + setOpen(true) + }, []) + + const mouseOut = React.useCallback(() => { + setOpen(false) + }, []) + + const hasEnoughDataToDisplayDiagrams = props.treeNode.messageHistory.count() > 1 + + let preview = hasEnoughDataToDisplayDiagrams ? ( + + + + ) : ( + + + + ) + + return ( + + {preview} + + + + {open ? : } + + + + + ) +} + +const mapDispatchToProps = (dispatch: any) => { + return { + actions: { + chart: bindActionCreators(chartActions, dispatch), + }, + } +} + +export default connect( + undefined, + mapDispatchToProps +)(ChartPreview) diff --git a/app/src/components/Sidebar/CodeDiff/Gutters.tsx b/app/src/components/Sidebar/CodeDiff/Gutters.tsx index e74ff6e..129b10b 100644 --- a/app/src/components/Sidebar/CodeDiff/Gutters.tsx +++ b/app/src/components/Sidebar/CodeDiff/Gutters.tsx @@ -2,10 +2,10 @@ import * as diff from 'diff' import * as q from '../../../../../backend/src/Model' import * as React from 'react' import Add from '@material-ui/icons/Add' +import ChartPreview from './ChartPreview' import Remove from '@material-ui/icons/Remove' import ShowChart from '@material-ui/icons/ShowChart' -import TopicPlot from '../TopicPlot' -import { Fade, Paper, Popper, Theme, Tooltip } from '@material-ui/core' +import { Theme, Tooltip } from '@material-ui/core' import { JsonPropertyLocation } from '../../../../../backend/src/JsonAstParser' import { lineChangeStyle, trimNewlineRight } from './util' import { withStyles } from '@material-ui/styles' @@ -15,7 +15,7 @@ interface Props { literalPositions: Array classes: any className: string - messageHistory: q.MessageHistory + treeNode: q.TreeNode } const style = (theme: Theme) => { @@ -29,12 +29,9 @@ const style = (theme: Theme) => { return { icon, - iconDisabled: { - ...icon, - color: theme.palette.text.disabled, - }, iconButton: { ...icon, + marginTop: '0px', width: '16px', height: '16px', padding: '2px', @@ -52,66 +49,24 @@ const style = (theme: Theme) => { } } -function ChartIcon(props: { messageHistory: q.MessageHistory; classes: any; literal: JsonPropertyLocation }) { - const chartIconRef = React.useRef(null) - const [open, setOpen] = React.useState(false) - - const mouseOver = React.useCallback( - (event: React.MouseEvent) => { - setOpen(true) - }, - [props.literal.path] - ) - - const mouseOut = React.useCallback(() => { - setOpen(false) - }, []) - - return ( - - - - - - {open ? : } - - - - - ) -} - function tokensForLine(change: diff.Change, line: number, props: Props) { const { classes, literalPositions } = props - const hasEnoughDataToDisplayDiagrams = props.messageHistory.count() > 1 const literal = literalPositions[line] - let chartIcon = null + let chartPreview = null if (literal) { - if (hasEnoughDataToDisplayDiagrams) { - chartIcon = ( - - ) - } else { - chartIcon = ( - - - - ) - } + chartPreview = ( + + ) } if (change.added) { - return [chartIcon, ] + return [chartPreview, ] } else if (change.removed) { return [] } else { return [ - chartIcon, + chartPreview,
previous: string current: string nameOfCompareMessage: string @@ -93,7 +93,7 @@ class CodeDiff extends React.Component {
{code}
diff --git a/app/src/components/Sidebar/Sidebar.tsx b/app/src/components/Sidebar/Sidebar.tsx index 0f87c75..f48b1a9 100644 --- a/app/src/components/Sidebar/Sidebar.tsx +++ b/app/src/components/Sidebar/Sidebar.tsx @@ -188,7 +188,6 @@ const mapDispatchToProps = (dispatch: any) => { const styles = (theme: Theme) => ({ drawer: { display: 'block' as 'block', - height: '100%', }, badge: { top: '3px', diff --git a/app/src/components/Sidebar/ValueRenderer/MessageHistory.tsx b/app/src/components/Sidebar/ValueRenderer/MessageHistory.tsx index 799d5bb..6e6fc5f 100644 --- a/app/src/components/Sidebar/ValueRenderer/MessageHistory.tsx +++ b/app/src/components/Sidebar/ValueRenderer/MessageHistory.tsx @@ -4,7 +4,7 @@ import BarChart from '@material-ui/icons/BarChart' import Copy from '../../helper/Copy' import DateFormatter from '../../helper/DateFormatter' import History from '../HistoryDrawer' -import TopicPlot from '../TopicPlot' +import TopicPlot from '../../TopicPlot' import { Base64Message } from '../../../../../backend/src/Model/Base64Message' import { isPlottable } from '../CodeDiff/util' import { TopicViewModel } from '../../../model/TopicViewModel' diff --git a/app/src/components/Sidebar/ValueRenderer/ValuePanel.tsx b/app/src/components/Sidebar/ValueRenderer/ValuePanel.tsx index 85672c7..bea9c44 100644 --- a/app/src/components/Sidebar/ValueRenderer/ValuePanel.tsx +++ b/app/src/components/Sidebar/ValueRenderer/ValuePanel.tsx @@ -53,13 +53,7 @@ class ValuePanel extends React.Component { return null } - return ( - - ) + return } private renderViewOptions() { diff --git a/app/src/components/Sidebar/ValueRenderer/ValueRenderer.tsx b/app/src/components/Sidebar/ValueRenderer/ValueRenderer.tsx index e01c9aa..a113003 100644 --- a/app/src/components/Sidebar/ValueRenderer/ValueRenderer.tsx +++ b/app/src/components/Sidebar/ValueRenderer/ValueRenderer.tsx @@ -9,7 +9,7 @@ import { ValueRendererDisplayMode } from '../../../reducers/Settings' interface Props { message: q.Message - messageHistory: q.MessageHistory + treeNode: q.TreeNode compareWith?: q.Message renderMode: ValueRendererDisplayMode } @@ -27,7 +27,7 @@ class ValueRenderer extends React.Component { private renderDiff(current: string = '', previous: string = '', language?: 'json') { return ( { } public renderValue() { - const { message, messageHistory, compareWith, renderMode } = this.props - - const previousMessages = messageHistory.toArray() + const { message, treeNode, compareWith, renderMode } = this.props + const previousMessages = treeNode.messageHistory.toArray() const previousMessage = previousMessages[previousMessages.length - 2] let compareMessage = compareWith || previousMessage || message if (renderMode === 'raw') { diff --git a/app/src/components/Sidebar/TopicPlot.tsx b/app/src/components/TopicPlot.tsx similarity index 82% rename from app/src/components/Sidebar/TopicPlot.tsx rename to app/src/components/TopicPlot.tsx index 21b4d11..35a4f5d 100644 --- a/app/src/components/Sidebar/TopicPlot.tsx +++ b/app/src/components/TopicPlot.tsx @@ -1,9 +1,9 @@ import * as dotProp from 'dot-prop' -import * as q from '../../../../backend/src/Model' +import * as q from '../../../backend/src/Model' import * as React from 'react' -import PlotHistory from './PlotHistory' -import { Base64Message } from '../../../../backend/src/Model/Base64Message' -import { toPlottableValue } from './CodeDiff/util' +import PlotHistory from './Sidebar/PlotHistory' +import { Base64Message } from '../../../backend/src/Model/Base64Message' +import { toPlottableValue } from './Sidebar/CodeDiff/util' interface Props { history: q.MessageHistory @@ -38,7 +38,6 @@ function nodeDotPathToHistory(history: q.MessageHistory, dotPath: string) { function render(props: Props) { const data = props.dotPath ? nodeDotPathToHistory(props.history, props.dotPath) : nodeToHistory(props.history) - console.log(props.dotPath, data) return } diff --git a/app/src/components/Tree/Tree.tsx b/app/src/components/Tree/Tree.tsx index 0878a16..e4c7055 100644 --- a/app/src/components/Tree/Tree.tsx +++ b/app/src/components/Tree/Tree.tsx @@ -107,6 +107,10 @@ class TreeComponent extends React.PureComponent { const style: React.CSSProperties = { lineHeight: '1.1', cursor: 'default', + overflowY: 'scroll', + overflowX: 'hidden', + height: '100%', + paddingBottom: '16px', // avoid conflict with chart panel Resizer } return ( diff --git a/app/src/components/helper/CustomIconButton.tsx b/app/src/components/helper/CustomIconButton.tsx index 93a0067..aee1ee2 100644 --- a/app/src/components/helper/CustomIconButton.tsx +++ b/app/src/components/helper/CustomIconButton.tsx @@ -15,6 +15,9 @@ const styles = (theme: Theme) => ({ width: '32px', height: '32px', }, + label: { + marginTop: '-2px', + }, }) class CustomIconButton extends React.Component { @@ -30,7 +33,7 @@ class CustomIconButton extends React.Component { public render() { return ( - + {this.props.children} diff --git a/app/src/reducers/Charts.ts b/app/src/reducers/Charts.ts new file mode 100644 index 0000000..642137e --- /dev/null +++ b/app/src/reducers/Charts.ts @@ -0,0 +1,61 @@ +import { Action } from 'redux' +import { createReducer } from './lib' +import { Record, List } from 'immutable' + +export interface ChartParameters { + topic: string + dotPath?: string +} + +export interface ChartsStateModel { + charts: List +} + +export type ChartsState = Record + +export type Action = AddChart | RemoveChart | SetCharts + +export enum ActionTypes { + CHARTS_ADD = 'CHARTS_ADD', + CHARTS_REMOVE = 'CHARTS_REMOVE', + CHARTS_SET = 'CHARTS_SET', +} + +export interface AddChart { + type: ActionTypes.CHARTS_ADD + chart: ChartParameters +} + +export interface RemoveChart { + type: ActionTypes.CHARTS_REMOVE + chart: ChartParameters +} + +export interface SetCharts { + type: ActionTypes.CHARTS_SET + charts: Array +} + +const initialState = Record({ + charts: List(), +}) + +export const chartsReducer = createReducer(initialState(), { + CHARTS_ADD: addChart, + CHARTS_REMOVE: removeChart, + CHARTS_SET: setCharts, +}) + +function addChart(state: ChartsState, action: AddChart) { + return state.set('charts', state.get('charts').push(action.chart)) +} + +function removeChart(state: ChartsState, action: RemoveChart) { + const charts = state.get('charts') + const newCharts = charts.filter(chart => chart.topic !== action.chart.topic || chart.dotPath !== action.chart.dotPath) + return state.set('charts', newCharts) +} + +function setCharts(state: ChartsState, action: SetCharts) { + return state.set('charts', List(action.charts)) +} diff --git a/app/src/reducers/Settings.ts b/app/src/reducers/Settings.ts index 340881c..d1fa74c 100644 --- a/app/src/reducers/Settings.ts +++ b/app/src/reducers/Settings.ts @@ -1,4 +1,3 @@ -import * as moment from 'moment' import { createReducer } from './lib' import { Record } from 'immutable' diff --git a/app/src/reducers/index.ts b/app/src/reducers/index.ts index 2e58933..1226b64 100644 --- a/app/src/reducers/index.ts +++ b/app/src/reducers/index.ts @@ -1,3 +1,4 @@ +import { chartsReducer, ChartsState } from './Charts' import { combineReducers } from 'redux' import { connectionManagerReducer, ConnectionManagerState } from './ConnectionManager' import { connectionReducer, ConnectionState } from './Connection' @@ -13,6 +14,7 @@ export interface AppState { tree: TreeState settings: Record publish: PublishState + charts: ChartsState sidebar: SidebarState connection: ConnectionState connectionManager: ConnectionManagerState @@ -20,6 +22,7 @@ export interface AppState { export default combineReducers({ globalState, + charts: chartsReducer, publish: publishReducer, sidebar: sidebarReducer, connection: connectionReducer, diff --git a/app/yarn.lock b/app/yarn.lock index 532c7cf..08eab78 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -414,7 +414,7 @@ dependencies: "@types/react" "*" -"@types/react-transition-group@^2.0.16": +"@types/react-transition-group@^2.0.16", "@types/react-transition-group@^2.9.2": version "2.9.2" resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-2.9.2.tgz#c48cf2a11977c8b4ff539a1c91d259eaa627028d" integrity sha512-5Fv2DQNO+GpdPZcxp2x/OQG/H19A01WlmpjVD9cKvVFmoVLOZ9LvBgSWG6pSXIU4og5fgbvGPaCV5+VGkWAEHA== @@ -4729,7 +4729,7 @@ react-style-proptype@^3.0.0: dependencies: prop-types "^15.5.4" -react-transition-group@^4.0.0: +react-transition-group@^4.0.0, react-transition-group@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.1.1.tgz#16efe9ac8c68306f6bef59c7da5a96b4dfd9fb32" integrity sha512-K/N1wqJ2GRP2yj3WBqEUYa0KV5fiaAWpUfU9SpHOHefeKvyrO+VrnMBML21M19QZoVbDZKmuQFHZYoMMi1xuJA==