Refactor communication

Add QoS andd retain flag
Refactor reducer
This commit is contained in:
Thomas Nordquist
2019-01-20 05:30:21 +01:00
parent 1839b551c0
commit f893d5ce60
21 changed files with 433 additions and 166 deletions

View File

@@ -380,10 +380,10 @@ class Connection extends React.Component<Props, State> {
const mapStateToProps = (state: AppState) => {
return {
visible: !state.connected,
connected: state.connected,
connecting: state.connecting,
error: state.error,
visible: !state.tooBigReducer.connected,
connected: state.tooBigReducer.connected,
connecting: state.tooBigReducer.connecting,
error: state.tooBigReducer.error,
}
}

View File

@@ -138,9 +138,9 @@ class Settings extends React.Component<Props, {}> {
const mapStateToProps = (state: AppState) => {
return {
autoExpandLimit: state.settings.autoExpandLimit,
nodeOrder: state.settings.nodeOrder,
visible: state.settings.visible,
autoExpandLimit: state.tooBigReducer.settings.autoExpandLimit,
nodeOrder: state.tooBigReducer.settings.nodeOrder,
visible: state.tooBigReducer.settings.visible,
}
}

View File

@@ -1,4 +1,3 @@
// tslint:disable-next-line
import 'react-ace'
import 'brace/mode/json'
import 'brace/mode/text'
@@ -6,7 +5,6 @@ import 'brace/mode/xml'
import 'brace/theme/monokai'
import * as React from 'react'
import * as brace from 'brace'
import * as q from '../../../../../backend/src/Model'
import {
@@ -15,14 +13,16 @@ import {
Radio,
RadioGroup,
TextField,
Typography,
IconButton,
FormControl,
InputLabel,
Input,
Checkbox,
MenuItem,
Tooltip,
} from '@material-ui/core'
import { makePublishEvent, rendererEvents } from '../../../../../events'
// tslint:disable-next-line
import { default as AceEditor } from 'react-ace'
import { AppState } from '../../../reducers'
import History from '../History'
@@ -31,47 +31,53 @@ import Navigation from '@material-ui/icons/Navigation'
import Clear from '@material-ui/icons/Clear'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { sidebarActions } from '../../../actions'
import { publishActions } from '../../../actions'
interface Props {
node?: q.TreeNode
connectionId?: string
topic?: string
payload?: string
actions: any
actions: typeof publishActions
emptyPayload: boolean
retain: boolean
editorMode: string
qos: 0 | 1 | 2
}
interface State {
mode: string
history: Message[]
}
class Publish extends React.Component<Props, State> {
constructor(props: any) {
super(props)
this.state = { mode: 'json', history: [] }
this.state = { history: [] }
}
private updatePayload = (value: string, event?: any) => {
this.props.actions.setPublishPayload(value)
private updatePayload = (payload: string, event?: any) => {
this.props.actions.setPayload(payload)
}
private updateTopic = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value)
this.props.actions.setPublishTopic(e.target.value)
this.props.actions.setTopic(e.target.value)
}
private updateMode = (e: React.ChangeEvent<{}>, value: string) => {
this.setState({ mode: value })
this.props.actions.setEditorMode(value)
}
private publish = (e: React.MouseEvent) => {
e.stopPropagation()
if (!this.props.connectionId) {
return
}
this.props.actions.publish(this.props.connectionId)
const topic = this.currentTopic() || ''
const payload = this.props.payload
if (this.props.connectionId && topic) {
rendererEvents.emit(makePublishEvent(this.props.connectionId), { topic, payload })
this.addMessageToHistory(topic, payload)
}
}
@@ -102,7 +108,7 @@ class Publish extends React.Component<Props, State> {
}
private clearTopic = () => {
this.props.actions.setPublishTopic(undefined)
this.props.actions.setTopic('')
}
private topic() {
@@ -129,7 +135,7 @@ class Publish extends React.Component<Props, State> {
private onTopicBlur = (e: React.FocusEvent<HTMLInputElement>) => {
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() {
const labelStyle = { margin: '0 8px 0 8px' }
return (
<div style={{ marginTop: '16px' }}>
<div style={{ width: '100%', lineHeight: '64px' }}>
<RadioGroup
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>
{this.renderEditorModeSelection()}
<div style={{ float: 'right', marginRight: '16px' }}>
{this.publishButton()}
</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() {
const items = this.state.history.reverse().map(message => ({
title: message.topic,
@@ -203,41 +278,56 @@ class Publish extends React.Component<Props, State> {
private didSelectHistoryEntry = (index: number) => {
const message = this.state.history[index]
this.props.actions.setPublishTopic(message.topic)
this.props.actions.setPublishPayload(message.payload)
this.props.actions.setTopic(message.topic)
this.props.actions.setPayload(message.payload)
}
private editor() {
return (
<div style={{ width: '100%', display: 'block' }}>
{this.editorMode()}
<AceEditor
mode={this.state.mode}
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 }}
/>
{this.renderEditor()}
{this.publishMode()}
</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) => {
return {
actions: bindActionCreators(sidebarActions, dispatch),
actions: bindActionCreators(publishActions, dispatch),
}
}
const mapStateToProps = (state: AppState) => {
return {
topic: state.sidebar.publishTopic,
payload: state.sidebar.publishPayload,
topic: state.publish.topic,
payload: state.publish.payload,
emptyPayload: state.publish.emptyPayload,
editorMode: state.publish.editorMode,
retain: state.publish.retain,
qos: state.publish.qos,
}
}

View File

@@ -116,21 +116,13 @@ class Sidebar extends React.Component<Props, State> {
<Typography className={classes.heading}>Value {copyValue}</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails style={this.detailsStyle}>
<div style={{ width: '100%', textAlign:'right' }}>
{this.props.node && this.props.node.message && <i><DateFormatter date={this.props.node.message.received} /></i>}
</div>
{this.messageMetaInfo()}
<div ref={this.valueRef}>
<ValueRenderer message={this.props.node && this.props.node.message} />
</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}>
{({ TransitionProps }) => (
<Fade {...TransitionProps} timeout={350}>
<Paper>
<ValueRenderer message={this.state.compareMessage} />
</Paper>
</Fade>
)}
{this.showValueComparison}
</Popper>
</ExpansionPanelDetails>
</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) => {
if (message !== this.state.compareMessage) {
this.setState({ compareMessage: message })
@@ -175,7 +191,7 @@ class Sidebar extends React.Component<Props, State> {
const mapStateToProps = (state: AppState) => {
return {
node: state.selectedTopic,
node: state.tooBigReducer.selectedTopic,
}
}

View File

@@ -66,7 +66,10 @@ const styles: StyleRulesCallback = theme => ({
interface Props {
classes: any
actions: any
actions: {
settings: typeof settingsActions,
connection: typeof connectionActions,
}
}
interface State {
@@ -91,11 +94,11 @@ class TitleBar extends React.Component<Props, State> {
return (
<AppBar position="static">
<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 />
</IconButton>
<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' }}/>
</Button>
</Toolbar>
@@ -122,7 +125,10 @@ class TitleBar extends React.Component<Props, State> {
const mapDispatchToProps = (dispatch: any) => {
return {
actions: { ...bindActionCreators(connectionActions, dispatch), ...bindActionCreators(settingsActions, dispatch) },
actions: {
settings: bindActionCreators(settingsActions, dispatch),
connection: bindActionCreators(connectionActions, dispatch),
},
}
}

View File

@@ -1,7 +1,7 @@
import * as React from 'react'
import * as q from '../../../../backend/src/Model'
import { makeConnectionMessageEvent, rendererEvents } from '../../../../events'
import { makeConnectionMessageEvent, rendererEvents, MqttMessage } from '../../../../events'
import { AppState } from '../../reducers'
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 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.throttledStateUpdate({ msg, tree: this.state.tree })
@@ -128,8 +129,8 @@ class Tree extends React.Component<Props, TreeState> {
const mapStateToProps = (state: AppState) => {
return {
autoExpandLimit: state.settings.autoExpandLimit,
connected: state.connected,
autoExpandLimit: state.tooBigReducer.settings.autoExpandLimit,
connected: state.tooBigReducer.connected,
}
}

View File

@@ -72,7 +72,7 @@ class TreeNodeSubnodes extends React.Component<Props, {}> {
const mapStateToProps = (state: AppState) => {
return {
nodeOrder: state.settings.nodeOrder,
nodeOrder: state.tooBigReducer.settings.nodeOrder,
}
}