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==