diff --git a/app/src/App.tsx b/app/src/App.tsx index ce8ed0c..4a3aba6 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -87,7 +87,7 @@ class App extends React.Component {
- +
@@ -105,7 +105,7 @@ class App extends React.Component { const mapStateToProps = (state: AppState) => { return { settingsVisible: state.tooBigReducer.settings.visible, - connectionId: state.tooBigReducer.connectionId, + connectionId: state.connection.connectionId, } } diff --git a/app/src/actions/Connection.ts b/app/src/actions/Connection.ts index 60f76c7..e1cb023 100644 --- a/app/src/actions/Connection.ts +++ b/app/src/actions/Connection.ts @@ -1,7 +1,9 @@ -import { ActionTypes, NodeOrder, CustomAction, AppState } from '../reducers' +import { ActionTypes, Action, ConnectionState } from '../reducers/Connection' import { MqttOptions } from '../../../backend/src/DataSource' import { Dispatch } from 'redux' import { rendererEvents, addMqttConnectionEvent, makeConnectionStateEvent, removeConnection } from '../../../events' +import { AppState } from '../reducers' +import * as q from '../../../backend/src/Model' export const connect = (options: MqttOptions, connectionId: string) => (dispatch: Dispatch, getState: () => AppState) => { dispatch(connecting(connectionId)) @@ -9,7 +11,9 @@ export const connect = (options: MqttOptions, connectionId: string) => (dispatch const event = makeConnectionStateEvent(connectionId) rendererEvents.subscribe(event, (dataSourceState) => { if (dataSourceState.connected) { - dispatch(connected()) + const tree = new q.Tree() + tree.updateWithConnection(rendererEvents, connectionId) + dispatch(connected(tree)) } else if (dataSourceState.error) { dispatch(showError(dataSourceState.error)) dispatch(disconnect()) @@ -17,24 +21,27 @@ export const connect = (options: MqttOptions, connectionId: string) => (dispatch }) } -export const connected: () => CustomAction = () => ({ - type: ActionTypes.connected, +export const connected: (tree: q.Tree) => Action = (tree: q.Tree) => ({ + tree, + type: ActionTypes.CONNECTION_SET_CONNECTED, }) -export const connecting: (connectionId: string) => CustomAction = (connectionId: string) => ({ +export const connecting: (connectionId: string) => Action = (connectionId: string) => ({ connectionId, - type: ActionTypes.connecting, + type: ActionTypes.CONNECTION_SET_CONNECTING, }) export const showError = (error?: string) => ({ error, - type: ActionTypes.showError, + type: ActionTypes.CONNECTION_SET_SHOW_ERROR, }) -export const disconnect = () => (dispatch: Dispatch, getState: () => AppState) => { - rendererEvents.emit(removeConnection, getState().tooBigReducer.connectionId) +export const disconnect = () => (dispatch: Dispatch, getState: () => AppState) => { + const { connectionId, tree } = getState().connection + rendererEvents.emit(removeConnection, connectionId) + tree && tree.stopUpdating() dispatch({ - type: ActionTypes.disconnect, + type: ActionTypes.CONNECTION_SET_DISCONNECTED, }) } diff --git a/app/src/actions/Sidebar.ts b/app/src/actions/Sidebar.ts index 25f8948..a353904 100644 --- a/app/src/actions/Sidebar.ts +++ b/app/src/actions/Sidebar.ts @@ -3,7 +3,9 @@ import { AppState } from '../reducers' import { makePublishEvent, rendererEvents } from '../../../events' export const clearRetainedTopic = () => (dispatch: Dispatch, getState: () => AppState) => { - const { selectedTopic, connectionId } = getState().tooBigReducer + const { selectedTopic } = getState().tooBigReducer + const { connectionId } = getState().connection + if (!selectedTopic || !connectionId) { return } diff --git a/app/src/components/ConnectionSetup/Connection.tsx b/app/src/components/ConnectionSetup/Connection.tsx index ba08abf..a795a8c 100644 --- a/app/src/components/ConnectionSetup/Connection.tsx +++ b/app/src/components/ConnectionSetup/Connection.tsx @@ -380,10 +380,10 @@ class Connection extends React.Component { const mapStateToProps = (state: AppState) => { return { - visible: !state.tooBigReducer.connected, - connected: state.tooBigReducer.connected, - connecting: state.tooBigReducer.connecting, - error: state.tooBigReducer.error, + visible: !state.connection.connected, + connected: state.connection.connected, + connecting: state.connection.connecting, + error: state.connection.error, } } diff --git a/app/src/components/Sidebar/History.tsx b/app/src/components/Sidebar/History.tsx index b6b055b..bba83e1 100644 --- a/app/src/components/Sidebar/History.tsx +++ b/app/src/components/Sidebar/History.tsx @@ -1,5 +1,4 @@ import * as React from 'react' -import * as q from '../../../../backend/src/Model' import { Badge, Typography } from '@material-ui/core' import { Theme, withStyles } from '@material-ui/core/styles' diff --git a/app/src/components/Tree/Tree.tsx b/app/src/components/Tree/Tree.tsx index 99071e2..10f2397 100644 --- a/app/src/components/Tree/Tree.tsx +++ b/app/src/components/Tree/Tree.tsx @@ -18,22 +18,17 @@ interface Props { autoExpandLimit: number didSelectNode?: (node: q.TreeNode) => void connectionId?: string - connected: boolean + tree?: q.Tree } -interface TreeState { - tree: q.Tree - msg: any -} - -class Tree extends React.Component { +class Tree extends React.Component { private updateTimer?: any private lastUpdate: number = 0 private perf: number = 0 constructor(props: any) { super(props) - this.state = { tree: new q.Tree(), msg: {} } + this.state = { } } public time(): number { @@ -43,11 +38,18 @@ class Tree extends React.Component { return time } - private performanceCallback = (ms: number) => { - average.push(Date.now(), ms) + public componentWillReceiveProps(nextProps: Props) { + if (this.props.tree !== nextProps.tree) { + if (this.props.tree) { + this.props.tree.onMerge.unsubscribe(this.throttledTreeUpdate) + } + if (nextProps.tree) { + nextProps.tree.onMerge.subscribe(this.throttledTreeUpdate) + } + } } - public throttledTreeUpdate() { + public throttledTreeUpdate = () => { if (this.updateTimer) { return } @@ -66,23 +68,6 @@ class Tree extends React.Component { }, Math.max(0, timeUntilNextUpdate)) } - public componentWillReceiveProps(nextProps: Props) { - this.registerAndUnregisterEventSubscriptionsForNewProps(nextProps) - } - - private registerAndUnregisterEventSubscriptionsForNewProps(nextProps: Props) { - if (this.props.connectionId !== nextProps.connectionId) { - if (this.props.connectionId) { - this.setState({ tree: new q.Tree() }) - rendererEvents.unsubscribeAll(makeConnectionMessageEvent(this.props.connectionId)) - this.updateTimer && clearTimeout(this.updateTimer) - } - if (nextProps.connectionId) { - rendererEvents.subscribe(makeConnectionMessageEvent(nextProps.connectionId), this.handleNewData) - } - } - } - public componentWillUnmount() { if (this.props.connectionId) { const event = makeConnectionMessageEvent(this.props.connectionId) @@ -90,17 +75,8 @@ class Tree extends React.Component { } } - private handleNewData = (msg: MqttMessage) => { - const edges = msg.topic.split('/') - const node = q.TreeNodeFactory.fromEdgesAndValue(edges, msg.payload) - node.mqttMessage = msg - this.state.tree.updateWithNode(node.firstNode()) - - this.throttledTreeUpdate() - } - public render() { - if (!this.props.connected) { + if (!this.props.tree) { return null } @@ -116,22 +92,26 @@ class Tree extends React.Component { autoExpandLimit={this.props.autoExpandLimit} isRoot={true} didSelectNode={this.props.didSelectNode} - treeNode={this.state.tree} + treeNode={this.props.tree} name="/" collapsed={false} key="rootNode" - lastUpdate={this.state.tree.lastUpdate} + lastUpdate={this.props.tree.lastUpdate} performanceCallback={this.performanceCallback} />
) } + + private performanceCallback = (ms: number) => { + average.push(Date.now(), ms) + } } const mapStateToProps = (state: AppState) => { return { autoExpandLimit: state.tooBigReducer.settings.autoExpandLimit, - connected: state.tooBigReducer.connected, + tree: state.connection.tree, } } diff --git a/app/src/reducers/Connection.ts b/app/src/reducers/Connection.ts new file mode 100644 index 0000000..87cfb0a --- /dev/null +++ b/app/src/reducers/Connection.ts @@ -0,0 +1,88 @@ +import { Action } from 'redux' +import { createReducer } from './lib' +import * as q from '../../../backend/src/Model' +import { MqttOptions } from '../../../backend/src/DataSource' + +export interface ConnectionState { + tree?: q.Tree + connectionOptions?: MqttOptions + connectionId?: string + error?: string + connected: boolean + connecting: boolean +} + +export type Action = SetConnecting | SetConnected | SetDisconnected | ShowError + +export enum ActionTypes { + CONNECTION_SET_CONNECTING = 'CONNECTION_SET_CONNECTING', + CONNECTION_SET_CONNECTED = 'CONNECTION_SET_CONNECTED', + CONNECTION_SET_DISCONNECTED = 'CONNECTION_SET_DISCONNECTED', + CONNECTION_SET_SHOW_ERROR = 'CONNECTION_SET_SHOW_ERROR', +} + +export interface SetConnecting { + type: ActionTypes.CONNECTION_SET_CONNECTING, + connectionId: string +} + +export interface SetConnected { + type: ActionTypes.CONNECTION_SET_CONNECTED + tree: q.Tree +} + +export interface SetDisconnected { + type: ActionTypes.CONNECTION_SET_DISCONNECTED +} + +export interface ShowError { + type: ActionTypes.CONNECTION_SET_SHOW_ERROR + error?: Error +} + +const initialState: ConnectionState = { + connected: false, + connecting: false, +} + +export const connectionReducer = createReducer(initialState, { + CONNECTION_SET_CONNECTING: setConnecting, + CONNECTION_SET_CONNECTED: setConnected, + CONNECTION_SET_DISCONNECTED: setDisconnected, + CONNECTION_SET_SHOW_ERROR: showError, +}) + +function setConnecting(state: ConnectionState, action: SetConnecting) { + return { + ...state, + connecting: true, + connected: false, + connectionId: action.connectionId, + } +} + +function setConnected(state: ConnectionState, action: SetConnected) { + return { + ...state, + connecting: false, + connected: true, + tree: action.tree, + } +} + +function setDisconnected(state: ConnectionState, action: SetDisconnected) { + return { + ...state, + connecting: false, + connected: false, + connectionId: undefined, + tree: undefined, + } +} + +function showError(state: ConnectionState, action: ShowError) { + return { + ...state, + error: action.error, + } +} diff --git a/app/src/reducers/index.ts b/app/src/reducers/index.ts index 020530a..923d72c 100644 --- a/app/src/reducers/index.ts +++ b/app/src/reducers/index.ts @@ -3,20 +3,18 @@ import * as q from '../../../backend/src/Model' import { Action, Reducer, combineReducers } from 'redux' import { trackEvent } from '../tracking' -import { MqttOptions } from '../../../backend/src/DataSource' import { PublishState, publishReducer } from './Publish' +import { ConnectionState, connectionReducer } from './Connection' export enum ActionTypes { - disconnect = 'DISCONNECT', - showError = 'SHOW_ERROR', + setAutoExpandLimit = 'SET_AUTO_EXPAND_LIMIT', toggleSettingsVisibility = 'TOGGLE_SETTINGS_VISIBILITY', setNodeOrder = 'SET_NODE_ORDER', selectTopic = 'SELECT_TOPIC', showUpdateNotification = 'SHOW_UPDATE_NOTIFICATION', showUpdateDetails = 'SHOW_UPDATE_DETAILS', - connecting = 'CONNECTING', - connected = 'CONNECTED', + } export interface CustomAction extends Action { @@ -26,14 +24,12 @@ export interface CustomAction extends Action { selectedTopic?: q.TreeNode showUpdateNotification?: boolean showUpdateDetails?: boolean - connectionOptions?: MqttOptions - connectionId?: string - error?: string } export interface AppState { tooBigReducer: TooBigOfState publish: PublishState + connection: ConnectionState } export interface TooBigOfState { @@ -41,10 +37,6 @@ export interface TooBigOfState { selectedTopic?: q.TreeNode showUpdateNotification?: boolean showUpdateDetails: boolean - connecting: boolean - connected: boolean - error?: string - connectionId?: string } export interface SettingsState { @@ -68,9 +60,6 @@ const initialBigState: TooBigOfState = { }, selectedTopic: undefined, showUpdateDetails: false, - connected: false, - connecting: false, - error: undefined, } const tooBigReducer: Reducer = (state = initialBigState, action) => { @@ -134,38 +123,6 @@ const tooBigReducer: Reducer = (state = showUpdateDetails: action.showUpdateDetails, } - case ActionTypes.connecting: - if (!action.connectionId) { - return state - } - return { - ...state, - connected: false, - connecting: true, - connectionId: action.connectionId, - } - - case ActionTypes.disconnect: - return { - ...state, - connected: false, - connecting: false, - connectionId: undefined, - } - - case ActionTypes.connected: - return { - ...state, - connected: true, - connecting: false, - } - - case ActionTypes.showError: - return { - ...state, - error: action.error, - } - default: return state } @@ -174,6 +131,7 @@ const tooBigReducer: Reducer = (state = const reducer = combineReducers({ tooBigReducer, publish: publishReducer, + connection: connectionReducer, }) export default reducer diff --git a/backend/src/Model/Tree.ts b/backend/src/Model/Tree.ts index 2f8bdf0..7fc6c4b 100644 --- a/backend/src/Model/Tree.ts +++ b/backend/src/Model/Tree.ts @@ -1,7 +1,29 @@ import { Edge, TreeNode } from './' +import { EventBusInterface, makeConnectionMessageEvent, MqttMessage } from '../../../events' +import { TreeNodeFactory } from './TreeNodeFactory' export class Tree extends TreeNode { + private connectionId?: string + private updateSource?: EventBusInterface constructor() { super(undefined, undefined) } + + public updateWithConnection(emitter: EventBusInterface, connectionId: string) { + this.updateSource = emitter + this.updateSource.subscribe(makeConnectionMessageEvent(connectionId), this.handleNewData) + } + + private handleNewData = (msg: MqttMessage) => { + const edges = msg.topic.split('/') + const node = TreeNodeFactory.fromEdgesAndValue(edges, msg.payload) + node.mqttMessage = msg + this.updateWithNode(node.firstNode()) + } + + public stopUpdating() { + if (this.updateSource && this.connectionId) { + this.updateSource.unsubscribeAll(makeConnectionMessageEvent(this.connectionId)) + } + } } diff --git a/events/EventBus.ts b/events/EventBus.ts index e821550..8059d56 100644 --- a/events/EventBus.ts +++ b/events/EventBus.ts @@ -2,7 +2,7 @@ import { IpcMain, IpcRenderer, ipcMain, ipcRenderer } from 'electron' import { Event } from './Events' -interface EventBusInterface { +export interface EventBusInterface { subscribe(event: Event, callback:(msg: MessageType) => void): void unsubscribeAll(event: Event): void emit(event: Event, msg: MessageType): void