Refactor communication
Add QoS andd retain flag Refactor reducer
This commit is contained in:
@@ -14,11 +14,6 @@ import UpdateNotifier from './UpdateNotifier'
|
|||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import ErrorBoundary from './ErrorBoundary'
|
import ErrorBoundary from './ErrorBoundary'
|
||||||
|
|
||||||
interface State {
|
|
||||||
selectedNode?: q.TreeNode,
|
|
||||||
connectionId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name: string
|
name: string
|
||||||
connectionId: string
|
connectionId: string
|
||||||
@@ -26,12 +21,10 @@ interface Props {
|
|||||||
settingsVisible: boolean
|
settingsVisible: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
class App extends React.Component<Props, State> {
|
class App extends React.Component<Props, {}> {
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = { }
|
||||||
selectedNode: undefined,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getStyles(): {[s: string]: React.CSSProperties} {
|
private getStyles(): {[s: string]: React.CSSProperties} {
|
||||||
@@ -94,9 +87,7 @@ class App extends React.Component<Props, State> {
|
|||||||
<TitleBar />
|
<TitleBar />
|
||||||
<div style={centerContent}>
|
<div style={centerContent}>
|
||||||
<div style={this.getStyles().left}>
|
<div style={this.getStyles().left}>
|
||||||
<Tree connectionId={this.props.connectionId} didSelectNode={(node: q.TreeNode) => {
|
<Tree connectionId={this.props.connectionId} />
|
||||||
this.setState({ selectedNode: node })
|
|
||||||
}} />
|
|
||||||
</div>
|
</div>
|
||||||
<div style={this.getStyles().right}>
|
<div style={this.getStyles().right}>
|
||||||
<Sidebar connectionId={this.props.connectionId} />
|
<Sidebar connectionId={this.props.connectionId} />
|
||||||
@@ -113,8 +104,8 @@ class App extends React.Component<Props, State> {
|
|||||||
|
|
||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
return {
|
return {
|
||||||
settingsVisible: state.settings.visible,
|
settingsVisible: state.tooBigReducer.settings.visible,
|
||||||
connectionId: state.connectionId,
|
connectionId: state.tooBigReducer.connectionId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -212,8 +212,8 @@ const styles = (theme: Theme) => ({
|
|||||||
|
|
||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
return {
|
return {
|
||||||
showUpdateNotification: state.showUpdateNotification,
|
showUpdateNotification: state.tooBigReducer.showUpdateNotification,
|
||||||
showUpdateDetails: state.showUpdateDetails,
|
showUpdateDetails: state.tooBigReducer.showUpdateDetails,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const showError = (error?: string) => ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const disconnect = () => (dispatch: Dispatch<CustomAction>, getState: () => AppState) => {
|
export const disconnect = () => (dispatch: Dispatch<CustomAction>, getState: () => AppState) => {
|
||||||
rendererEvents.emit(removeConnection, getState().connectionId)
|
rendererEvents.emit(removeConnection, getState().tooBigReducer.connectionId)
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ActionTypes.disconnect,
|
type: ActionTypes.disconnect,
|
||||||
|
|||||||
61
app/src/actions/Publish.ts
Normal file
61
app/src/actions/Publish.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { ActionTypes, Action } from '../reducers/Publish'
|
||||||
|
import { AppState } from '../reducers'
|
||||||
|
import { Dispatch } from 'redux'
|
||||||
|
import { rendererEvents, makePublishEvent } from '../../../events'
|
||||||
|
|
||||||
|
export const setTopic = (topic?: string): Action => {
|
||||||
|
return {
|
||||||
|
topic,
|
||||||
|
type: ActionTypes.PUBLISH_SET_TOPIC,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setPayload = (payload?: string): Action => {
|
||||||
|
return {
|
||||||
|
payload,
|
||||||
|
type: ActionTypes.PUBLISH_SET_PAYLOAD,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const toggleEmptyPayload = (): Action => {
|
||||||
|
return {
|
||||||
|
type: ActionTypes.PUBLISH_TOGGLE_EMPTY_PAYLOAD,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setQoS = (qos: 0 | 1 | 2): Action => {
|
||||||
|
return {
|
||||||
|
qos,
|
||||||
|
type: ActionTypes.PUBLISH_SET_QOS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setEditorMode = (editorMode: string): Action => {
|
||||||
|
return {
|
||||||
|
editorMode,
|
||||||
|
type: ActionTypes.PUBLISH_SET_EDITOR_MODE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const publish = (connectionId: string) => (dispatch: Dispatch<Action>, getState: () => AppState) => {
|
||||||
|
const state = getState()
|
||||||
|
const topic = state.publish.topic
|
||||||
|
|
||||||
|
if (!topic) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const publishEvent = makePublishEvent(connectionId)
|
||||||
|
rendererEvents.emit(publishEvent, {
|
||||||
|
topic,
|
||||||
|
payload: state.publish.payload,
|
||||||
|
retain: state.publish.retain,
|
||||||
|
qos: state.publish.qos,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const toggleRetain = (): Action => {
|
||||||
|
return {
|
||||||
|
type: ActionTypes.PUBLISH_TOGGLE_RETAIN,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { ActionTypes, CustomAction } from '../reducers'
|
|
||||||
|
|
||||||
export const setPublishTopic = (topic: string): CustomAction => {
|
|
||||||
return {
|
|
||||||
publishTopic: topic,
|
|
||||||
type: ActionTypes.setPublishTopic,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setPublishPayload = (payload: string): CustomAction => {
|
|
||||||
return {
|
|
||||||
publishPayload: payload,
|
|
||||||
type: ActionTypes.setPublishPayload,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as settingsActions from './Settings'
|
import * as settingsActions from './Settings'
|
||||||
import * as sidebarActions from './Sidebar'
|
import * as publishActions from './Publish'
|
||||||
import * as treeActions from './Tree'
|
import * as treeActions from './Tree'
|
||||||
import * as updateNotifierActions from './UpdateNotifier'
|
import * as updateNotifierActions from './UpdateNotifier'
|
||||||
import * as connectionActions from './Connection'
|
import * as connectionActions from './Connection'
|
||||||
|
|
||||||
export { settingsActions, treeActions, sidebarActions, updateNotifierActions, connectionActions }
|
export { settingsActions, treeActions, publishActions, updateNotifierActions, connectionActions }
|
||||||
|
|||||||
@@ -380,10 +380,10 @@ class Connection extends React.Component<Props, State> {
|
|||||||
|
|
||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
return {
|
return {
|
||||||
visible: !state.connected,
|
visible: !state.tooBigReducer.connected,
|
||||||
connected: state.connected,
|
connected: state.tooBigReducer.connected,
|
||||||
connecting: state.connecting,
|
connecting: state.tooBigReducer.connecting,
|
||||||
error: state.error,
|
error: state.tooBigReducer.error,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -138,9 +138,9 @@ class Settings extends React.Component<Props, {}> {
|
|||||||
|
|
||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
return {
|
return {
|
||||||
autoExpandLimit: state.settings.autoExpandLimit,
|
autoExpandLimit: state.tooBigReducer.settings.autoExpandLimit,
|
||||||
nodeOrder: state.settings.nodeOrder,
|
nodeOrder: state.tooBigReducer.settings.nodeOrder,
|
||||||
visible: state.settings.visible,
|
visible: state.tooBigReducer.settings.visible,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// tslint:disable-next-line
|
|
||||||
import 'react-ace'
|
import 'react-ace'
|
||||||
import 'brace/mode/json'
|
import 'brace/mode/json'
|
||||||
import 'brace/mode/text'
|
import 'brace/mode/text'
|
||||||
@@ -6,7 +5,6 @@ import 'brace/mode/xml'
|
|||||||
import 'brace/theme/monokai'
|
import 'brace/theme/monokai'
|
||||||
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import * as brace from 'brace'
|
|
||||||
import * as q from '../../../../../backend/src/Model'
|
import * as q from '../../../../../backend/src/Model'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -15,14 +13,16 @@ import {
|
|||||||
Radio,
|
Radio,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
TextField,
|
TextField,
|
||||||
Typography,
|
|
||||||
IconButton,
|
IconButton,
|
||||||
FormControl,
|
FormControl,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
Input,
|
Input,
|
||||||
|
Checkbox,
|
||||||
|
MenuItem,
|
||||||
|
Tooltip,
|
||||||
} from '@material-ui/core'
|
} from '@material-ui/core'
|
||||||
import { makePublishEvent, rendererEvents } from '../../../../../events'
|
|
||||||
|
|
||||||
|
// tslint:disable-next-line
|
||||||
import { default as AceEditor } from 'react-ace'
|
import { default as AceEditor } from 'react-ace'
|
||||||
import { AppState } from '../../../reducers'
|
import { AppState } from '../../../reducers'
|
||||||
import History from '../History'
|
import History from '../History'
|
||||||
@@ -31,47 +31,53 @@ import Navigation from '@material-ui/icons/Navigation'
|
|||||||
import Clear from '@material-ui/icons/Clear'
|
import Clear from '@material-ui/icons/Clear'
|
||||||
import { bindActionCreators } from 'redux'
|
import { bindActionCreators } from 'redux'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { sidebarActions } from '../../../actions'
|
import { publishActions } from '../../../actions'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
node?: q.TreeNode
|
node?: q.TreeNode
|
||||||
connectionId?: string
|
connectionId?: string
|
||||||
topic?: string
|
topic?: string
|
||||||
payload?: string
|
payload?: string
|
||||||
actions: any
|
actions: typeof publishActions
|
||||||
|
emptyPayload: boolean
|
||||||
|
retain: boolean
|
||||||
|
editorMode: string
|
||||||
|
qos: 0 | 1 | 2
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
mode: string
|
|
||||||
history: Message[]
|
history: Message[]
|
||||||
}
|
}
|
||||||
|
|
||||||
class Publish extends React.Component<Props, State> {
|
class Publish extends React.Component<Props, State> {
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = { mode: 'json', history: [] }
|
this.state = { history: [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
private updatePayload = (value: string, event?: any) => {
|
private updatePayload = (payload: string, event?: any) => {
|
||||||
this.props.actions.setPublishPayload(value)
|
this.props.actions.setPayload(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateTopic = (e: React.ChangeEvent<HTMLInputElement>) => {
|
private updateTopic = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
console.log(e.target.value)
|
this.props.actions.setTopic(e.target.value)
|
||||||
this.props.actions.setPublishTopic(e.target.value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateMode = (e: React.ChangeEvent<{}>, value: string) => {
|
private updateMode = (e: React.ChangeEvent<{}>, value: string) => {
|
||||||
this.setState({ mode: value })
|
this.props.actions.setEditorMode(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
private publish = (e: React.MouseEvent) => {
|
private publish = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
if (!this.props.connectionId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.actions.publish(this.props.connectionId)
|
||||||
const topic = this.currentTopic() || ''
|
const topic = this.currentTopic() || ''
|
||||||
const payload = this.props.payload
|
const payload = this.props.payload
|
||||||
|
|
||||||
if (this.props.connectionId && topic) {
|
if (this.props.connectionId && topic) {
|
||||||
rendererEvents.emit(makePublishEvent(this.props.connectionId), { topic, payload })
|
|
||||||
this.addMessageToHistory(topic, payload)
|
this.addMessageToHistory(topic, payload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,7 +108,7 @@ class Publish extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private clearTopic = () => {
|
private clearTopic = () => {
|
||||||
this.props.actions.setPublishTopic(undefined)
|
this.props.actions.setTopic('')
|
||||||
}
|
}
|
||||||
|
|
||||||
private topic() {
|
private topic() {
|
||||||
@@ -129,7 +135,7 @@ class Publish extends React.Component<Props, State> {
|
|||||||
|
|
||||||
private onTopicBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
private onTopicBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||||
if (!e.target.value) {
|
if (!e.target.value) {
|
||||||
this.props.actions.setPublishTopic(undefined)
|
this.props.actions.setTopic(undefined)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,38 +158,10 @@ class Publish extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private editorMode() {
|
private editorMode() {
|
||||||
const labelStyle = { margin: '0 8px 0 8px' }
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginTop: '16px' }}>
|
<div style={{ marginTop: '16px' }}>
|
||||||
<div style={{ width: '100%', lineHeight: '64px' }}>
|
<div style={{ width: '100%', lineHeight: '64px' }}>
|
||||||
<RadioGroup
|
{this.renderEditorModeSelection()}
|
||||||
style={{ display: 'inline-block', float: 'left' }}
|
|
||||||
value={this.state.mode}
|
|
||||||
onChange={this.updateMode}
|
|
||||||
row={true}
|
|
||||||
>
|
|
||||||
<FormControlLabel
|
|
||||||
value="text"
|
|
||||||
style={labelStyle}
|
|
||||||
control={<Radio color="primary" />}
|
|
||||||
label="raw"
|
|
||||||
labelPlacement="top"
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
|
||||||
value="xml"
|
|
||||||
style={labelStyle}
|
|
||||||
control={<Radio color="primary" />}
|
|
||||||
label="xml"
|
|
||||||
labelPlacement="top"
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
|
||||||
value="json"
|
|
||||||
style={labelStyle}
|
|
||||||
control={<Radio color="primary" />}
|
|
||||||
label="json"
|
|
||||||
labelPlacement="top"
|
|
||||||
/>
|
|
||||||
</RadioGroup>
|
|
||||||
<div style={{ float: 'right', marginRight: '16px' }}>
|
<div style={{ float: 'right', marginRight: '16px' }}>
|
||||||
{this.publishButton()}
|
{this.publishButton()}
|
||||||
</div>
|
</div>
|
||||||
@@ -192,6 +170,103 @@ class Publish extends React.Component<Props, State> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderEditorModeSelection() {
|
||||||
|
if (this.props.emptyPayload) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const labelStyle = { margin: '0 8px 0 8px' }
|
||||||
|
return (
|
||||||
|
<RadioGroup
|
||||||
|
style={{ display: 'inline-block', float: 'left' }}
|
||||||
|
value={this.props.editorMode}
|
||||||
|
onChange={this.updateMode}
|
||||||
|
row={true}
|
||||||
|
>
|
||||||
|
<FormControlLabel
|
||||||
|
value="text"
|
||||||
|
style={labelStyle}
|
||||||
|
control={<Radio color="primary" />}
|
||||||
|
label="raw"
|
||||||
|
labelPlacement="top"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="xml"
|
||||||
|
style={labelStyle}
|
||||||
|
control={<Radio color="primary" />}
|
||||||
|
label="xml"
|
||||||
|
labelPlacement="top"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="json"
|
||||||
|
style={labelStyle}
|
||||||
|
control={<Radio color="primary" />}
|
||||||
|
label="json"
|
||||||
|
labelPlacement="top"
|
||||||
|
/>
|
||||||
|
</RadioGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private onChangeQoS = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = parseInt(event.target.value, 10)
|
||||||
|
if (value !== 0 && value !== 1 && value !== 2) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.actions.setQoS(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private publishMode() {
|
||||||
|
const labelStyle = { margin: '0 8px 0 8px' }
|
||||||
|
const itemStyle = { padding: '0' }
|
||||||
|
const tooltipStyle = { textAlign: 'center' as 'center', width: '100%' }
|
||||||
|
const qosSelect = (
|
||||||
|
<TextField
|
||||||
|
select={true}
|
||||||
|
value={this.props.qos}
|
||||||
|
margin="normal"
|
||||||
|
style={{ margin: '8px 0 8px 8px' }}
|
||||||
|
onChange={this.onChangeQoS}
|
||||||
|
>
|
||||||
|
<MenuItem key={0} value={0} style={itemStyle}>
|
||||||
|
<Tooltip title="At most once"><div style={tooltipStyle}>0</div></Tooltip>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key={1} value={1} style={itemStyle}>
|
||||||
|
<Tooltip title="At least once"><div style={tooltipStyle}>1</div></Tooltip>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key={2} value={2} style={itemStyle}>
|
||||||
|
<Tooltip title="Exactly once"><div style={tooltipStyle}>2</div></Tooltip>
|
||||||
|
</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<div style={{ marginTop: '8px', clear: 'both' }}>
|
||||||
|
<div style={{ width: '100%' }}>
|
||||||
|
<FormControlLabel
|
||||||
|
style={labelStyle}
|
||||||
|
control={qosSelect}
|
||||||
|
label="QoS"
|
||||||
|
labelPlacement="start"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="empty"
|
||||||
|
style={labelStyle}
|
||||||
|
control={<Checkbox color="primary" checked={this.props.emptyPayload} onChange={this.props.actions.toggleEmptyPayload} />}
|
||||||
|
label="no message"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="retain"
|
||||||
|
style={labelStyle}
|
||||||
|
control={<Checkbox color="primary" checked={this.props.retain} onChange={this.props.actions.toggleRetain} />}
|
||||||
|
label="retain"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private history() {
|
private history() {
|
||||||
const items = this.state.history.reverse().map(message => ({
|
const items = this.state.history.reverse().map(message => ({
|
||||||
title: message.topic,
|
title: message.topic,
|
||||||
@@ -203,41 +278,56 @@ class Publish extends React.Component<Props, State> {
|
|||||||
|
|
||||||
private didSelectHistoryEntry = (index: number) => {
|
private didSelectHistoryEntry = (index: number) => {
|
||||||
const message = this.state.history[index]
|
const message = this.state.history[index]
|
||||||
this.props.actions.setPublishTopic(message.topic)
|
this.props.actions.setTopic(message.topic)
|
||||||
this.props.actions.setPublishPayload(message.payload)
|
this.props.actions.setPayload(message.payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
private editor() {
|
private editor() {
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '100%', display: 'block' }}>
|
<div style={{ width: '100%', display: 'block' }}>
|
||||||
{this.editorMode()}
|
{this.editorMode()}
|
||||||
<AceEditor
|
{this.renderEditor()}
|
||||||
mode={this.state.mode}
|
{this.publishMode()}
|
||||||
theme="monokai"
|
|
||||||
name="UNIQUE_ID_OF_DIV"
|
|
||||||
width="100%"
|
|
||||||
height="200px"
|
|
||||||
showGutter={true}
|
|
||||||
value={this.props.payload}
|
|
||||||
onChange={this.updatePayload}
|
|
||||||
setOptions={this.editorOptions}
|
|
||||||
editorProps={{ $blockScrolling: true }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderEditor() {
|
||||||
|
if (this.props.emptyPayload) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AceEditor
|
||||||
|
mode={this.props.editorMode}
|
||||||
|
theme="monokai"
|
||||||
|
name="UNIQUE_ID_OF_DIV"
|
||||||
|
width="100%"
|
||||||
|
height="200px"
|
||||||
|
showGutter={true}
|
||||||
|
value={this.props.payload}
|
||||||
|
onChange={this.updatePayload}
|
||||||
|
setOptions={this.editorOptions}
|
||||||
|
editorProps={{ $blockScrolling: true }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: any) => {
|
const mapDispatchToProps = (dispatch: any) => {
|
||||||
return {
|
return {
|
||||||
actions: bindActionCreators(sidebarActions, dispatch),
|
actions: bindActionCreators(publishActions, dispatch),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
return {
|
return {
|
||||||
topic: state.sidebar.publishTopic,
|
topic: state.publish.topic,
|
||||||
payload: state.sidebar.publishPayload,
|
payload: state.publish.payload,
|
||||||
|
emptyPayload: state.publish.emptyPayload,
|
||||||
|
editorMode: state.publish.editorMode,
|
||||||
|
retain: state.publish.retain,
|
||||||
|
qos: state.publish.qos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,21 +116,13 @@ class Sidebar extends React.Component<Props, State> {
|
|||||||
<Typography className={classes.heading}>Value {copyValue}</Typography>
|
<Typography className={classes.heading}>Value {copyValue}</Typography>
|
||||||
</ExpansionPanelSummary>
|
</ExpansionPanelSummary>
|
||||||
<ExpansionPanelDetails style={this.detailsStyle}>
|
<ExpansionPanelDetails style={this.detailsStyle}>
|
||||||
<div style={{ width: '100%', textAlign:'right' }}>
|
{this.messageMetaInfo()}
|
||||||
{this.props.node && this.props.node.message && <i><DateFormatter date={this.props.node.message.received} /></i>}
|
|
||||||
</div>
|
|
||||||
<div ref={this.valueRef}>
|
<div ref={this.valueRef}>
|
||||||
<ValueRenderer message={this.props.node && this.props.node.message} />
|
<ValueRenderer message={this.props.node && this.props.node.message} />
|
||||||
</div>
|
</div>
|
||||||
<div><MessageHistory onSelect={this.handleMessageHistorySelect} node={this.props.node} /></div>
|
<div><MessageHistory onSelect={this.handleMessageHistorySelect} node={this.props.node} /></div>
|
||||||
<Popper open={Boolean(this.state.compareMessage)} anchorEl={this.valueRef.current} placement="left" transition={true}>
|
<Popper open={Boolean(this.state.compareMessage)} anchorEl={this.valueRef.current} placement="left" transition={true}>
|
||||||
{({ TransitionProps }) => (
|
{this.showValueComparison}
|
||||||
<Fade {...TransitionProps} timeout={350}>
|
|
||||||
<Paper>
|
|
||||||
<ValueRenderer message={this.state.compareMessage} />
|
|
||||||
</Paper>
|
|
||||||
</Fade>
|
|
||||||
)}
|
|
||||||
</Popper>
|
</Popper>
|
||||||
</ExpansionPanelDetails>
|
</ExpansionPanelDetails>
|
||||||
</ExpansionPanel>
|
</ExpansionPanel>
|
||||||
@@ -152,6 +144,30 @@ class Sidebar extends React.Component<Props, State> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private showValueComparison = (a: any) => (
|
||||||
|
<Fade {...a.TransitionProps} timeout={350}>
|
||||||
|
<Paper>
|
||||||
|
<ValueRenderer message={this.state.compareMessage} />
|
||||||
|
</Paper>
|
||||||
|
</Fade>
|
||||||
|
)
|
||||||
|
|
||||||
|
private messageMetaInfo() {
|
||||||
|
if (!this.props.node || !this.props.node.message || !this.props.node.mqttMessage) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ width: '100%', display: 'flex' }}>
|
||||||
|
<div style={{ flex: 1 }}><Typography>QoS: {this.props.node.mqttMessage.qos}</Typography></div>
|
||||||
|
<div style={{ flex: 1, textAlign: 'center' }}>
|
||||||
|
<Typography style={{ color: this.props.theme.palette.secondary.main }}><b>{this.props.node.mqttMessage.retain ? 'retained' : null}</b></Typography>
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: 1, textAlign: 'right' }}><Typography><i><DateFormatter date={this.props.node.message.received} /></i></Typography></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private handleMessageHistorySelect = (message: q.Message) => {
|
private handleMessageHistorySelect = (message: q.Message) => {
|
||||||
if (message !== this.state.compareMessage) {
|
if (message !== this.state.compareMessage) {
|
||||||
this.setState({ compareMessage: message })
|
this.setState({ compareMessage: message })
|
||||||
@@ -175,7 +191,7 @@ class Sidebar extends React.Component<Props, State> {
|
|||||||
|
|
||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
return {
|
return {
|
||||||
node: state.selectedTopic,
|
node: state.tooBigReducer.selectedTopic,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,10 @@ const styles: StyleRulesCallback = theme => ({
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
classes: any
|
classes: any
|
||||||
actions: any
|
actions: {
|
||||||
|
settings: typeof settingsActions,
|
||||||
|
connection: typeof connectionActions,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
@@ -91,11 +94,11 @@ class TitleBar extends React.Component<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<AppBar position="static">
|
<AppBar position="static">
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<IconButton className={classes.menuButton} color="inherit" aria-label="Menu" onClick={this.props.actions.toggleSettingsVisibility}>
|
<IconButton className={classes.menuButton} color="inherit" aria-label="Menu" onClick={actions.settings.toggleSettingsVisibility}>
|
||||||
<Menu />
|
<Menu />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography className={classes.title} variant="h6" color="inherit">MQTT-Explorer</Typography>
|
<Typography className={classes.title} variant="h6" color="inherit">MQTT-Explorer</Typography>
|
||||||
<Button style={{ margin: 'auto 8px auto auto' }} onClick={actions.disconnect}>
|
<Button style={{ margin: 'auto 8px auto auto' }} onClick={actions.connection.disconnect}>
|
||||||
Disconnect <CloudOff style={{ marginRight: '8px', paddingLeft: '8px' }}/>
|
Disconnect <CloudOff style={{ marginRight: '8px', paddingLeft: '8px' }}/>
|
||||||
</Button>
|
</Button>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
@@ -122,7 +125,10 @@ class TitleBar extends React.Component<Props, State> {
|
|||||||
|
|
||||||
const mapDispatchToProps = (dispatch: any) => {
|
const mapDispatchToProps = (dispatch: any) => {
|
||||||
return {
|
return {
|
||||||
actions: { ...bindActionCreators(connectionActions, dispatch), ...bindActionCreators(settingsActions, dispatch) },
|
actions: {
|
||||||
|
settings: bindActionCreators(settingsActions, dispatch),
|
||||||
|
connection: bindActionCreators(connectionActions, dispatch),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import * as q from '../../../../backend/src/Model'
|
import * as q from '../../../../backend/src/Model'
|
||||||
|
|
||||||
import { makeConnectionMessageEvent, rendererEvents } from '../../../../events'
|
import { makeConnectionMessageEvent, rendererEvents, MqttMessage } from '../../../../events'
|
||||||
|
|
||||||
import { AppState } from '../../reducers'
|
import { AppState } from '../../reducers'
|
||||||
import TreeNode from './TreeNode'
|
import TreeNode from './TreeNode'
|
||||||
@@ -89,9 +89,10 @@ class Tree extends React.Component<Props, TreeState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleNewData = (msg: any) => {
|
private handleNewData = (msg: MqttMessage) => {
|
||||||
const edges = msg.topic.split('/')
|
const edges = msg.topic.split('/')
|
||||||
const node = q.TreeNodeFactory.fromEdgesAndValue(edges, Buffer.from(msg.payload, 'base64').toString())
|
const node = q.TreeNodeFactory.fromEdgesAndValue(edges, msg.payload)
|
||||||
|
node.mqttMessage = msg
|
||||||
this.state.tree.updateWithNode(node.firstNode())
|
this.state.tree.updateWithNode(node.firstNode())
|
||||||
|
|
||||||
this.throttledStateUpdate({ msg, tree: this.state.tree })
|
this.throttledStateUpdate({ msg, tree: this.state.tree })
|
||||||
@@ -128,8 +129,8 @@ class Tree extends React.Component<Props, TreeState> {
|
|||||||
|
|
||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
return {
|
return {
|
||||||
autoExpandLimit: state.settings.autoExpandLimit,
|
autoExpandLimit: state.tooBigReducer.settings.autoExpandLimit,
|
||||||
connected: state.connected,
|
connected: state.tooBigReducer.connected,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class TreeNodeSubnodes extends React.Component<Props, {}> {
|
|||||||
|
|
||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
return {
|
return {
|
||||||
nodeOrder: state.settings.nodeOrder,
|
nodeOrder: state.tooBigReducer.settings.nodeOrder,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
108
app/src/reducers/Publish.ts
Normal file
108
app/src/reducers/Publish.ts
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { Action } from 'redux'
|
||||||
|
import { createReducer } from './lib'
|
||||||
|
|
||||||
|
export interface PublishState {
|
||||||
|
topic?: string
|
||||||
|
payload?: string
|
||||||
|
emptyPayload: boolean
|
||||||
|
retain: boolean
|
||||||
|
editorMode: string
|
||||||
|
qos: 0 | 1 | 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Action = SetPayload | SetTopic | ToggleEmptyPayload | ToggleRetain | SetEditorMode | SetQoS
|
||||||
|
|
||||||
|
export enum ActionTypes {
|
||||||
|
PUBLISH_SET_TOPIC = 'PUBLISH_SET_TOPIC',
|
||||||
|
PUBLISH_SET_PAYLOAD = 'PUBLISH_SET_PAYLOAD',
|
||||||
|
PUBLISH_TOGGLE_EMPTY_PAYLOAD = 'PUBLISH_TOGGLE_EMPTY_PAYLOAD',
|
||||||
|
PUBLISH_TOGGLE_RETAIN = 'PUBLISH_TOGGLE_RETAIN',
|
||||||
|
PUBLISH_SET_EDITOR_MODE = 'PUBLISH_SET_EDITOR_MODE',
|
||||||
|
PUBLISH_SET_QOS = 'PUBLISH_SET_QOS',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetPayload {
|
||||||
|
type: ActionTypes.PUBLISH_SET_PAYLOAD
|
||||||
|
payload?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetTopic {
|
||||||
|
type: ActionTypes.PUBLISH_SET_TOPIC
|
||||||
|
topic?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetQoS {
|
||||||
|
type: ActionTypes.PUBLISH_SET_QOS
|
||||||
|
qos: 0 | 1 | 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToggleEmptyPayload {
|
||||||
|
type: ActionTypes.PUBLISH_TOGGLE_EMPTY_PAYLOAD
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetEditorMode {
|
||||||
|
type: ActionTypes.PUBLISH_SET_EDITOR_MODE
|
||||||
|
editorMode: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToggleRetain {
|
||||||
|
type: ActionTypes.PUBLISH_TOGGLE_RETAIN
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: PublishState = {
|
||||||
|
editorMode: 'text',
|
||||||
|
emptyPayload: false,
|
||||||
|
retain: false,
|
||||||
|
qos: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const publishReducer = createReducer(initialState, {
|
||||||
|
PUBLISH_SET_TOPIC: setTopic,
|
||||||
|
PUBLISH_SET_PAYLOAD: setPayload,
|
||||||
|
PUBLISH_TOGGLE_EMPTY_PAYLOAD: toggleEmptyPayload,
|
||||||
|
PUBLISH_TOGGLE_RETAIN: toggleRetain,
|
||||||
|
PUBLISH_SET_EDITOR_MODE: setEditorMode,
|
||||||
|
PUBLISH_SET_QOS: setQoS,
|
||||||
|
})
|
||||||
|
|
||||||
|
function setTopic(state: PublishState, action: SetTopic) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
topic: action.topic,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPayload(state: PublishState, action: SetPayload) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
payload: action.payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setQoS(state: PublishState, action: SetQoS) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
qos: action.qos,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setEditorMode(state: PublishState, action: SetEditorMode) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
editorMode: action.editorMode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleEmptyPayload(state: PublishState) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
emptyPayload: !state.emptyPayload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleRetain(state: PublishState) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
retain: !state.retain,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import * as q from '../../../backend/src/Model'
|
import * as q from '../../../backend/src/Model'
|
||||||
|
|
||||||
import { Action, Reducer } from 'redux'
|
import { Action, Reducer, combineReducers } from 'redux'
|
||||||
|
|
||||||
import { trackEvent } from '../tracking'
|
import { trackEvent } from '../tracking'
|
||||||
import { MqttOptions, DataSourceStateMachine } from '../../../backend/src/DataSource'
|
import { MqttOptions } from '../../../backend/src/DataSource'
|
||||||
import { rendererEvents, addMqttConnectionEvent, makeConnectionStateEvent } from '../../../events'
|
import { PublishState, publishReducer } from './Publish'
|
||||||
|
|
||||||
export enum ActionTypes {
|
export enum ActionTypes {
|
||||||
disconnect = 'DISCONNECT',
|
disconnect = 'DISCONNECT',
|
||||||
@@ -13,8 +13,6 @@ export enum ActionTypes {
|
|||||||
toggleSettingsVisibility = 'TOGGLE_SETTINGS_VISIBILITY',
|
toggleSettingsVisibility = 'TOGGLE_SETTINGS_VISIBILITY',
|
||||||
setNodeOrder = 'SET_NODE_ORDER',
|
setNodeOrder = 'SET_NODE_ORDER',
|
||||||
selectTopic = 'SELECT_TOPIC',
|
selectTopic = 'SELECT_TOPIC',
|
||||||
setPublishTopic = 'SET_PUBLISH_TOPIC',
|
|
||||||
setPublishPayload = 'SET_PUBLISH_PAYLOAD',
|
|
||||||
showUpdateNotification = 'SHOW_UPDATE_NOTIFICATION',
|
showUpdateNotification = 'SHOW_UPDATE_NOTIFICATION',
|
||||||
showUpdateDetails = 'SHOW_UPDATE_DETAILS',
|
showUpdateDetails = 'SHOW_UPDATE_DETAILS',
|
||||||
connecting = 'CONNECTING',
|
connecting = 'CONNECTING',
|
||||||
@@ -26,8 +24,6 @@ export interface CustomAction extends Action {
|
|||||||
autoExpandLimit?: number
|
autoExpandLimit?: number
|
||||||
nodeOrder?: NodeOrder
|
nodeOrder?: NodeOrder
|
||||||
selectedTopic?: q.TreeNode
|
selectedTopic?: q.TreeNode
|
||||||
publishTopic?: string
|
|
||||||
publishPayload?: string
|
|
||||||
showUpdateNotification?: boolean
|
showUpdateNotification?: boolean
|
||||||
showUpdateDetails?: boolean
|
showUpdateDetails?: boolean
|
||||||
connectionOptions?: MqttOptions
|
connectionOptions?: MqttOptions
|
||||||
@@ -35,15 +31,14 @@ export interface CustomAction extends Action {
|
|||||||
error?: string
|
error?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SidebarState {
|
export interface AppState {
|
||||||
publishTopic?: string
|
tooBigReducer: TooBigOfState
|
||||||
publishPayload?: string
|
publish: PublishState
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppState {
|
export interface TooBigOfState {
|
||||||
settings: SettingsState,
|
settings: SettingsState,
|
||||||
selectedTopic?: q.TreeNode
|
selectedTopic?: q.TreeNode
|
||||||
sidebar: SidebarState
|
|
||||||
showUpdateNotification?: boolean
|
showUpdateNotification?: boolean
|
||||||
showUpdateDetails: boolean
|
showUpdateDetails: boolean
|
||||||
connecting: boolean
|
connecting: boolean
|
||||||
@@ -65,13 +60,12 @@ export enum NodeOrder {
|
|||||||
topics = '#topics',
|
topics = '#topics',
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialAppState: AppState = {
|
const initialBigState: TooBigOfState = {
|
||||||
settings: {
|
settings: {
|
||||||
autoExpandLimit: 0,
|
autoExpandLimit: 0,
|
||||||
nodeOrder: NodeOrder.none,
|
nodeOrder: NodeOrder.none,
|
||||||
visible: false,
|
visible: false,
|
||||||
},
|
},
|
||||||
sidebar: {},
|
|
||||||
selectedTopic: undefined,
|
selectedTopic: undefined,
|
||||||
showUpdateDetails: false,
|
showUpdateDetails: false,
|
||||||
connected: false,
|
connected: false,
|
||||||
@@ -79,7 +73,7 @@ const initialAppState: AppState = {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
const reducer: Reducer<AppState | undefined, CustomAction> = (state = initialAppState, action) => {
|
const tooBigReducer: Reducer<TooBigOfState | undefined, CustomAction> = (state = initialBigState, action) => {
|
||||||
if (!state) {
|
if (!state) {
|
||||||
throw Error('No initial state')
|
throw Error('No initial state')
|
||||||
}
|
}
|
||||||
@@ -97,16 +91,7 @@ const reducer: Reducer<AppState | undefined, CustomAction> = (state = initialApp
|
|||||||
autoExpandLimit: action.autoExpandLimit,
|
autoExpandLimit: action.autoExpandLimit,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case ActionTypes.setPublishTopic:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
sidebar: { ...state.sidebar, publishTopic: action.publishTopic },
|
|
||||||
}
|
|
||||||
case ActionTypes.setPublishPayload:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
sidebar: { ...state.sidebar, publishPayload: action.publishPayload },
|
|
||||||
}
|
|
||||||
case ActionTypes.toggleSettingsVisibility:
|
case ActionTypes.toggleSettingsVisibility:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@@ -115,6 +100,7 @@ const reducer: Reducer<AppState | undefined, CustomAction> = (state = initialApp
|
|||||||
visible: !state.settings.visible,
|
visible: !state.settings.visible,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
case ActionTypes.selectTopic:
|
case ActionTypes.selectTopic:
|
||||||
if (!action.selectedTopic) {
|
if (!action.selectedTopic) {
|
||||||
return state
|
return state
|
||||||
@@ -123,6 +109,7 @@ const reducer: Reducer<AppState | undefined, CustomAction> = (state = initialApp
|
|||||||
...state,
|
...state,
|
||||||
selectedTopic: action.selectedTopic,
|
selectedTopic: action.selectedTopic,
|
||||||
}
|
}
|
||||||
|
|
||||||
case ActionTypes.setNodeOrder:
|
case ActionTypes.setNodeOrder:
|
||||||
if (!action.nodeOrder) {
|
if (!action.nodeOrder) {
|
||||||
return state
|
return state
|
||||||
@@ -131,11 +118,13 @@ const reducer: Reducer<AppState | undefined, CustomAction> = (state = initialApp
|
|||||||
...state,
|
...state,
|
||||||
settings: { ...state.settings, nodeOrder: action.nodeOrder },
|
settings: { ...state.settings, nodeOrder: action.nodeOrder },
|
||||||
}
|
}
|
||||||
|
|
||||||
case ActionTypes.showUpdateNotification:
|
case ActionTypes.showUpdateNotification:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
showUpdateNotification: action.showUpdateNotification,
|
showUpdateNotification: action.showUpdateNotification,
|
||||||
}
|
}
|
||||||
|
|
||||||
case ActionTypes.showUpdateDetails:
|
case ActionTypes.showUpdateDetails:
|
||||||
if (action.showUpdateDetails === undefined) {
|
if (action.showUpdateDetails === undefined) {
|
||||||
return state
|
return state
|
||||||
@@ -144,6 +133,7 @@ const reducer: Reducer<AppState | undefined, CustomAction> = (state = initialApp
|
|||||||
...state,
|
...state,
|
||||||
showUpdateDetails: action.showUpdateDetails,
|
showUpdateDetails: action.showUpdateDetails,
|
||||||
}
|
}
|
||||||
|
|
||||||
case ActionTypes.connecting:
|
case ActionTypes.connecting:
|
||||||
if (!action.connectionId) {
|
if (!action.connectionId) {
|
||||||
return state
|
return state
|
||||||
@@ -181,4 +171,9 @@ const reducer: Reducer<AppState | undefined, CustomAction> = (state = initialApp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reducer = combineReducers({
|
||||||
|
tooBigReducer,
|
||||||
|
publish: publishReducer,
|
||||||
|
})
|
||||||
|
|
||||||
export default reducer
|
export default reducer
|
||||||
|
|||||||
9
app/src/reducers/lib.ts
Normal file
9
app/src/reducers/lib.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export const createReducer = (initialState: any, handlers: any) => {
|
||||||
|
return (state = initialState, action: any) => {
|
||||||
|
if (handlers.hasOwnProperty(action.type)) {
|
||||||
|
return handlers[action.type](state, action)
|
||||||
|
} else {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { DataSourceStateMachine } from './'
|
import { DataSourceStateMachine } from './'
|
||||||
|
import { MqttMessage } from '../../../events'
|
||||||
|
|
||||||
type MessageCallback = (topic: string, payload: Buffer) => void
|
type MessageCallback = (topic: string, payload: Buffer) => void
|
||||||
|
|
||||||
@@ -7,7 +8,7 @@ interface DataSource<DataSourceOptions> {
|
|||||||
connect(options: DataSourceOptions): DataSourceStateMachine
|
connect(options: DataSourceOptions): DataSourceStateMachine
|
||||||
disconnect(): void
|
disconnect(): void
|
||||||
onMessage(messageCallback: MessageCallback): void
|
onMessage(messageCallback: MessageCallback): void
|
||||||
publish(topic: string, payload: any): void
|
publish(msg: MqttMessage): void
|
||||||
topicSeparator: string
|
topicSeparator: string
|
||||||
stateMachine: DataSourceStateMachine
|
stateMachine: DataSourceStateMachine
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as Url from 'url'
|
|||||||
|
|
||||||
import { Client, connect as mqttConnect } from 'mqtt'
|
import { Client, connect as mqttConnect } from 'mqtt'
|
||||||
import { DataSource, DataSourceStateMachine } from './'
|
import { DataSource, DataSourceStateMachine } from './'
|
||||||
|
import { MqttMessage } from '../../../events'
|
||||||
|
|
||||||
export interface MqttOptions {
|
export interface MqttOptions {
|
||||||
url: string
|
url: string
|
||||||
@@ -15,11 +16,11 @@ export interface MqttOptions {
|
|||||||
export class MqttSource implements DataSource<MqttOptions> {
|
export class MqttSource implements DataSource<MqttOptions> {
|
||||||
public stateMachine: DataSourceStateMachine = new DataSourceStateMachine()
|
public stateMachine: DataSourceStateMachine = new DataSourceStateMachine()
|
||||||
private client: Client | undefined
|
private client: Client | undefined
|
||||||
private messageCallback?: (topic: string, message: Buffer) => void
|
private messageCallback?: (topic: string, message: Buffer, packet: any) => void
|
||||||
private rootSubscription = '#'
|
private rootSubscription = '#'
|
||||||
public topicSeparator = '/'
|
public topicSeparator = '/'
|
||||||
|
|
||||||
public onMessage(messageCallback: (topic: string, message: Buffer) => void) {
|
public onMessage(messageCallback: (topic: string, message: Buffer, packet: any) => void) {
|
||||||
this.messageCallback = messageCallback
|
this.messageCallback = messageCallback
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,14 +74,14 @@ export class MqttSource implements DataSource<MqttOptions> {
|
|||||||
})
|
})
|
||||||
|
|
||||||
client.on('message', (topic, message, packet) => {
|
client.on('message', (topic, message, packet) => {
|
||||||
this.messageCallback && this.messageCallback(topic, message)
|
this.messageCallback && this.messageCallback(topic, message, packet)
|
||||||
})
|
})
|
||||||
|
|
||||||
return this.stateMachine
|
return this.stateMachine
|
||||||
}
|
}
|
||||||
|
|
||||||
public publish(topic: string, payload: any) {
|
public publish(msg: MqttMessage) {
|
||||||
this.client && this.client.publish(topic, payload)
|
this.client && this.client.publish(msg.topic, msg.payload, { qos: msg.qos, retain: msg.retain })
|
||||||
}
|
}
|
||||||
|
|
||||||
public disconnect() {
|
public disconnect() {
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { Edge, Message, RingBuffer } from './'
|
import { Edge, Message, RingBuffer } from './'
|
||||||
import { EventDispatcher } from '../../../events'
|
import { EventDispatcher, MqttMessage } from '../../../events'
|
||||||
|
|
||||||
export class TreeNode {
|
export class TreeNode {
|
||||||
public sourceEdge?: Edge
|
public sourceEdge?: Edge
|
||||||
public message?: Message
|
public message?: Message
|
||||||
|
public mqttMessage?: MqttMessage
|
||||||
public messageHistory: RingBuffer<Message> = new RingBuffer<Message>(3000, 100)
|
public messageHistory: RingBuffer<Message> = new RingBuffer<Message>(3000, 100)
|
||||||
public edges: {[s: string]: Edge} = {}
|
public edges: {[s: string]: Edge} = {}
|
||||||
public collapsed = false
|
public collapsed = false
|
||||||
public messages: number = 0
|
public messages: number = 0
|
||||||
public lastUpdate: number = Date.now()
|
public lastUpdate: number = Date.now()
|
||||||
|
|
||||||
public onMerge = new EventDispatcher<void, TreeNode>(this)
|
public onMerge = new EventDispatcher<void, TreeNode>(this)
|
||||||
public onEdgesChange = new EventDispatcher<void, TreeNode>(this)
|
public onEdgesChange = new EventDispatcher<void, TreeNode>(this)
|
||||||
public onMessage = new EventDispatcher<Message, TreeNode>(this)
|
public onMessage = new EventDispatcher<Message, TreeNode>(this)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
AddMqttConnection,
|
AddMqttConnection,
|
||||||
EventDispatcher,
|
EventDispatcher,
|
||||||
Message,
|
MqttMessage,
|
||||||
addMqttConnectionEvent,
|
addMqttConnectionEvent,
|
||||||
backendEvents,
|
backendEvents,
|
||||||
checkForUpdates,
|
checkForUpdates,
|
||||||
@@ -36,19 +36,20 @@ export class ConnectionManager {
|
|||||||
|
|
||||||
connection.connect(options)
|
connection.connect(options)
|
||||||
this.handleNewMessagesForConnection(connectionId, connection)
|
this.handleNewMessagesForConnection(connectionId, connection)
|
||||||
backendEvents.subscribe(makePublishEvent(connectionId), (msg: Message) => {
|
backendEvents.subscribe(makePublishEvent(connectionId), (msg: MqttMessage) => {
|
||||||
this.connections[connectionId].publish(msg.topic, msg.payload)
|
this.connections[connectionId].publish(msg)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleNewMessagesForConnection(connectionId: string, connection: MqttSource) {
|
private handleNewMessagesForConnection(connectionId: string, connection: MqttSource) {
|
||||||
const messageEvent = makeConnectionMessageEvent(connectionId)
|
const messageEvent = makeConnectionMessageEvent(connectionId)
|
||||||
connection.onMessage((topic: string, payload: Buffer) => {
|
connection.onMessage((topic: string, payload: Buffer, packet: any) => {
|
||||||
let buffer = payload
|
let buffer = payload
|
||||||
if (buffer.length > 10000) {
|
if (buffer.length > 10000) {
|
||||||
buffer = buffer.slice(0, 10000)
|
buffer = buffer.slice(0, 10000)
|
||||||
}
|
}
|
||||||
backendEvents.emit(messageEvent, { topic, payload: buffer.toString('base64') })
|
|
||||||
|
backendEvents.emit(messageEvent, { topic, payload: buffer.toString(), qos: packet.qos, retain: packet.retain })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,18 +35,20 @@ export const updateAvailable: Event<UpdateInfo> = {
|
|||||||
topic: 'app/update/available',
|
topic: 'app/update/available',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Message {
|
export interface MqttMessage {
|
||||||
topic: string,
|
topic: string,
|
||||||
payload: any
|
payload: any,
|
||||||
|
qos: 0 | 1 | 2,
|
||||||
|
retain: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makePublishEvent(connectionId: string): Event<Message> {
|
export function makePublishEvent(connectionId: string): Event<MqttMessage> {
|
||||||
return {
|
return {
|
||||||
topic: `conn/publish/${connectionId}`,
|
topic: `conn/publish/${connectionId}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeConnectionMessageEvent(connectionId: string): Event<Message> {
|
export function makeConnectionMessageEvent(connectionId: string): Event<MqttMessage> {
|
||||||
return {
|
return {
|
||||||
topic: `conn/${connectionId}`,
|
topic: `conn/${connectionId}`,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user