diff --git a/app/package-lock.json b/app/package-lock.json index 862331b..ad081f5 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -748,6 +748,11 @@ "multicast-dns-service-types": "^1.1.0" } }, + "brace": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/brace/-/brace-0.11.1.tgz", + "integrity": "sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg=" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1451,6 +1456,11 @@ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==" }, + "diff-match-patch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.4.tgz", + "integrity": "sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg==" + }, "diffie-hellman": { "version": "5.0.3", "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -3300,6 +3310,16 @@ "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, "lodash.throttle": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", @@ -4183,6 +4203,18 @@ "scheduler": "^0.12.0" } }, + "react-ace": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/react-ace/-/react-ace-6.3.2.tgz", + "integrity": "sha512-eSk0fWvrBe2oqYIYX0njLddLG5H0hemWv5VVoQi5yDSPTjGlSSnzFwdgPyfuwRe8mSARZuRdprPQa5p61hKirw==", + "requires": { + "brace": "^0.11.1", + "diff-match-patch": "^1.0.4", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "prop-types": "^15.6.2" + } + }, "react-base16-styling": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.6.0.tgz", diff --git a/app/package.json b/app/package.json index 1022bdd..88ad967 100644 --- a/app/package.json +++ b/app/package.json @@ -32,6 +32,7 @@ "jquery": "^3.3.1", "lodash.throttle": "^4.1.1", "moving-average": "^1.0.0", + "react-ace": "^6.3.2", "react-json-view": "^1.19.1", "sha1": "^1.1.1", "socket.io-client": "^2.2.0", diff --git a/app/src/App.tsx b/app/src/App.tsx index 1941fe9..23bd9f1 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -91,7 +91,7 @@ class App extends React.Component { }} />
- +
diff --git a/app/src/actions/Tree.ts b/app/src/actions/Tree.ts new file mode 100644 index 0000000..eff6247 --- /dev/null +++ b/app/src/actions/Tree.ts @@ -0,0 +1,9 @@ +import { ActionTypes } from '../reducers' +import * as q from '../../../backend/src/Model' + +export const selectTopic = (topic: q.TreeNode) => { + return { + selectedTopic: topic, + type: ActionTypes.selectTopic, + } +} diff --git a/app/src/components/Sidebar/Publisher.tsx b/app/src/components/Sidebar/Publisher.tsx new file mode 100644 index 0000000..0feb2be --- /dev/null +++ b/app/src/components/Sidebar/Publisher.tsx @@ -0,0 +1,165 @@ +import * as React from 'react' +import * as q from '../../../../backend/src/Model' +import { makePublishEvent, rendererEvents } from '../../../../events' +import Navigation from '@material-ui/icons/Navigation' +import { + Button, Fab, InputAdornment, FormControlLabel, Radio, + RadioGroup, TextField, Typography, +} from '@material-ui/core' + +import * as brace from 'brace' +import { default as AceEditor } from 'react-ace' +// tslint:disable-next-line +import 'react-ace' +import 'brace/mode/java' +import 'brace/mode/text' +import 'brace/mode/xml' +import 'brace/theme/monokai' + +interface Props { + node?: q.TreeNode + connectionId?: string +} + +interface State { + customTopic?: string + payload?: string + mode: string +} + +class Publisher extends React.Component { + constructor(props: any) { + super(props) + this.state = { mode: 'json' } + } + + private updatePayload = (value: string, event?: any) => { + this.setState({ payload: value }) + } + + private updateTopic = (e: React.ChangeEvent) => { + this.setState({ customTopic: e.target.value }) + } + + private updateMode = (e: React.ChangeEvent<{}>, value: string) => { + this.setState({ mode: value }) + } + + private publish = (e: React.MouseEvent) => { + e.stopPropagation() + if (!this.props.connectionId || !this.state.customTopic) { + return + } + + rendererEvents.emit(makePublishEvent(this.props.connectionId), { + topic: this.state.customTopic, + payload: this.state.payload, + }) + } + + public render() { + return ( +
+ {this.topic()} + {this.editor()} +
+ ) + } + + private topic() { + const { node } = this.props + const topicStr = this.state.customTopic || (node ? node.path() : '') + return ( +
+ Topic + +
+ ) + } + + private editorOptions = { + enableBasicAutocompletion: true, + enableLiveAutocompletion: true, + enableSnippets: true, + showLineNumbers: false, + tabSize: 2, + } + + private publishButton() { + return ( + + ) + } + + private editorMode() { + const labelStyle = { margin: '0 8px 0 8px' } + return ( +
+ + + } + label="raw" + labelPlacement="top" + /> + } + label="xml" + labelPlacement="top" + /> + } + label="json" + labelPlacement="top" + /> + +
+ {this.publishButton()} +
+
+
+ ) + } + + private editor() { + return ( +
+ {this.editorMode()} + +
+ ) + } +} + +export default Publisher diff --git a/app/src/components/Sidebar/Sidebar.tsx b/app/src/components/Sidebar/Sidebar.tsx index 9ecfcfc..0fd7c50 100644 --- a/app/src/components/Sidebar/Sidebar.tsx +++ b/app/src/components/Sidebar/Sidebar.tsx @@ -5,6 +5,7 @@ import * as q from '../../../../backend/src/Model' import { ExpansionPanel, ExpansionPanelDetails, ExpansionPanelSummary, Typography } from '@material-ui/core' import { withStyles, Theme, StyleRulesCallback } from '@material-ui/core/styles' import ExpandMore from '@material-ui/icons/ExpandMore' +import Publisher from './Publisher' import Copy from '../Copy' import ValueRenderer from './ValueRenderer' @@ -15,10 +16,11 @@ interface Props { node?: q.TreeNode, classes: any, theme: Theme, + connectionId?: string, } interface State { - node: q.TreeNode + node: q.TreeNode, } class Sidebar extends React.Component { @@ -77,30 +79,39 @@ class Sidebar extends React.Component { const copyTopic = node ? : null const copyValue = node && node.message ? : null - + const summeryStyle = { minHeight: '0' } + const detailsStyle = { padding: '0px 8px 8px' } return (
- }> + } style={summeryStyle}> Topic {copyTopic} - + - }> + } style={summeryStyle}> Value {copyValue} - + - }> + } style={summeryStyle}> + Publish + + + + + + + } style={summeryStyle}> Stats - + {this.props.node ? : null} diff --git a/backend/src/DataSource/DataSource.ts b/backend/src/DataSource/DataSource.ts index b1a7711..56bc650 100644 --- a/backend/src/DataSource/DataSource.ts +++ b/backend/src/DataSource/DataSource.ts @@ -7,6 +7,7 @@ interface DataSource { connect(options: DataSourceOptions): DataSourceStateMachine disconnect(): void onMessage(messageCallback: MessageCallback): void + publish(topic: string, payload: any): void topicSeparator: string stateMachine: DataSourceStateMachine } diff --git a/backend/src/DataSource/MqttSource.ts b/backend/src/DataSource/MqttSource.ts index 0d6cf5c..4fc7d72 100644 --- a/backend/src/DataSource/MqttSource.ts +++ b/backend/src/DataSource/MqttSource.ts @@ -69,6 +69,10 @@ export class MqttSource implements DataSource { return this.stateMachine } + public publish(topic: string, payload: any) { + this.client && this.client.publish(topic, payload) + } + public disconnect() { this.client && this.client.end() } diff --git a/backend/src/index.ts b/backend/src/index.ts index 1ba4644..d0c3dd0 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,7 +1,7 @@ import { addMqttConnectionEvent, backendEvents, makeConnectionStateEvent, removeConnection, - makeConnectionMessageEvent, AddMqttConnection, + makeConnectionMessageEvent, makePublishEvent, AddMqttConnection, Message, } from '../../events' import { MqttSource, DataSource } from './DataSource' @@ -26,6 +26,9 @@ class ConnectionManager { connection.connect(options) this.handleNewMessagesForConnection(connectionId, connection) + backendEvents.subscribe(makePublishEvent(connectionId), (msg: Message) => { + this.connections[connectionId].publish(msg.topic, msg.payload) + }) } private handleNewMessagesForConnection(connectionId: string, connection: MqttSource) { diff --git a/events/EventBus.ts b/events/EventBus.ts index c6cf1c2..efa97f3 100644 --- a/events/EventBus.ts +++ b/events/EventBus.ts @@ -46,7 +46,6 @@ class IpcRendererEventBus implements EventBusInterface { } public emit(event: Event, msg: MessageType) { - console.log(event.topic, msg) this.ipc.send(event.topic, msg) } } diff --git a/events/Events.ts b/events/Events.ts index 5b02cee..69ebfc2 100644 --- a/events/Events.ts +++ b/events/Events.ts @@ -23,7 +23,7 @@ export function makeConnectionStateEvent(connectionId: string): Event topic: `conn/${connectionId}`, } } + +export function makePublishEvent(connectionId: string): Event { + return { + topic: `conn/publish/${connectionId}`, + } +} diff --git a/package-lock.json b/package-lock.json index 047ef47..b7e902a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -621,6 +621,11 @@ } } }, + "brace": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/brace/-/brace-0.11.1.tgz", + "integrity": "sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg=" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1083,6 +1088,11 @@ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, + "diff-match-patch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.4.tgz", + "integrity": "sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg==" + }, "dmg-builder": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-6.5.3.tgz", @@ -2160,8 +2170,7 @@ "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, "js-yaml": { "version": "3.12.0", @@ -2277,11 +2286,24 @@ "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -4078,6 +4100,15 @@ "through2": "~0.2.3" } }, + "prop-types": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", + "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "requires": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -4140,6 +4171,18 @@ "strip-json-comments": "~2.0.1" } }, + "react-ace": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/react-ace/-/react-ace-6.3.2.tgz", + "integrity": "sha512-eSk0fWvrBe2oqYIYX0njLddLG5H0hemWv5VVoQi5yDSPTjGlSSnzFwdgPyfuwRe8mSARZuRdprPQa5p61hKirw==", + "requires": { + "brace": "^0.11.1", + "diff-match-patch": "^1.0.4", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "prop-types": "^15.6.2" + } + }, "read-config-file": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-3.2.0.tgz", diff --git a/package.json b/package.json index 9b36716..9f636eb 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,9 @@ "appId": "mqtt-explorer", "mac": { "category": "de.t7n.apps.mq-explorer", - "publish": ["github"] + "publish": [ + "github" + ] }, "linux": { "category": "Development", @@ -59,6 +61,7 @@ "electron-log": "^2.2.17", "electron-updater": "^4.0.6", "mqtt": "^2.18.8", + "react-ace": "^6.3.2", "sha1": "^1.1.1", "socket.io": "^2.2.0" }