Compare commits

..
Author SHA1 Message Date
Thomas Nordquist 0ead83463c Add app veyor build badge 2019-02-26 23:43:00 +01:00
Thomas Nordquist 2d8253ef31 WiP 2019-02-26 23:38:46 +01:00
Thomas Nordquist d4a709f0d5 build only appx 2019-02-26 11:56:34 +01:00
Thomas Nordquist d090197fe5 WiP 2019-02-26 11:43:27 +01:00
Thomas Nordquist 63cdc53075 Remove publisher 2019-02-25 18:12:53 +01:00
Thomas Nordquist dca35c1ed7 Merge branch 'master' into add-appveyor-support 2019-02-25 18:11:50 +01:00
Thomas Nordquist 4c58d4af69 WiP 2019-02-21 17:42:18 +01:00
Thomas Nordquist 766a283b01 WiP 2019-02-21 12:17:14 +01:00
Thomas Nordquist 870043bde6 WiP 2019-02-21 11:59:22 +01:00
Thomas Nordquist d2fea71bd5 Rewrite rpepare-release in typescript 2019-02-21 11:58:41 +01:00
Thomas Nordquist 208c379898 WiP 2019-02-21 11:27:07 +01:00
Thomas Nordquist 55e3c827c4 WiP 2019-02-21 10:44:33 +01:00
Thomas Nordquist 8ac285846a WiP 2019-02-21 09:17:06 +01:00
Thomas Nordquist 2cb4084ed6 WiP 2019-02-21 09:15:34 +01:00
Thomas Nordquist 0bcae7912c WiP 2019-02-21 09:09:49 +01:00
Thomas Nordquist 1868a08237 WiP 2019-02-21 09:08:16 +01:00
Thomas Nordquist ba919c19fa WiP 2019-02-21 08:59:38 +01:00
Thomas Nordquist 701477f32e WiP 2019-02-21 08:56:00 +01:00
Thomas Nordquist fc59c46121 WiP 2019-02-21 08:52:24 +01:00
Thomas Nordquist eb67c69fad WiP 2019-02-21 08:51:19 +01:00
68 changed files with 982 additions and 1478 deletions
+1 -2
View File
@@ -24,7 +24,6 @@ services:
install:
- yarn install
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get update && sudo apt-get -y install snap squashfs-tools && sudo snap install snapcraft --classic; fi;
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker build docker --tag uitest; fi;
script:
@@ -32,6 +31,6 @@ script:
- yarn test
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker run -e GH_TOKEN=$GH_TOKEN -e GIT_TAG=$TRAVIS_TAG --rm -v `pwd`:/app uitest sh -c "cd app && docker/testMounted.sh"; fi
- if [[ "$TRAVIS_TAG" != "" ]]; then yarn run prepare-release; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]] && [[ "$TRAVIS_TAG" != "" ]]; then yarn run package linux; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]] && [[ "$TRAVIS_TAG" != "" ]]; then yarn run package -- linux; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]] && [[ "$TRAVIS_TAG" != "" ]]; then yarn run package -- mac; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]] && [[ "$TRAVIS_TAG" != "" ]]; then yarn run package -- win; fi
+4 -10
View File
@@ -1,12 +1,6 @@
# 0.2.1
- Highlight differences in the last received message with a "diff" view
# next release
- Magnify app content with "Ctrl +", "Ctrl -", "Ctrl 0"
- Make user interactions more predictable by removing "select topic on mouse over"
- Add "Quick Preview" setting, to enable topic selection on mouse over
- Add JSON formatter
- Show error when connection to the mqtt broker is lost
- Fix bug where mqtt explorer resets the tree after a reconnect
- Fix MQTT-Client id
- Add message diff view
- Make user interactions more predictable
# 0.2.0
# 0.2.0
-1
View File
@@ -2,7 +2,6 @@
[![Downloads](https://img.shields.io/github/release/thomasnordquist/mqtt-explorer.svg)](https://travis-ci.org/thomasnordquist/MQTT-Explorer/releases)
[![Downloads](https://img.shields.io/github/downloads/thomasnordquist/mqtt-explorer/total.svg)](https://travis-ci.org/thomasnordquist/MQTT-Explorer/releases)
[![Build_Status](https://travis-ci.org/thomasnordquist/MQTT-Explorer.svg?branch=master)](https://travis-ci.org/thomasnordquist/MQTT-Explorer)
[![Build status](https://ci.appveyor.com/api/projects/status/c35tkm29rm4m5364/branch/master?svg=true)](https://ci.appveyor.com/project/thomasnordquist/mqtt-explorer/branch/master)
### Version
+2 -2
View File
@@ -24,9 +24,9 @@ The hierarchical view allows for a quick understanding what is going on on your
| Platform | | Downloads |
|:----------:|:-------------:|:------:|
| ![windows](https://user-images.githubusercontent.com/7721625/51445407-b4172080-1d04-11e9-8c70-d8413d1d6d8b.png) | Windows | <a href="https://www.microsoft.com/store/apps/9PP8SFM082WD?ocid=badge"><img src="https://assets.windowsphone.com/85864462-9c82-451e-9355-a3d5f874397a/English_get-it-from-MS_InvariantCulture_Default.png" width="150" /></a><br/>{{windowsTargets}} |
| ![windows](https://user-images.githubusercontent.com/7721625/51445407-b4172080-1d04-11e9-8c70-d8413d1d6d8b.png) | Windows | {{windowsTargets}} |
| ![linux](https://user-images.githubusercontent.com/7721625/51445392-947ff800-1d04-11e9-8c7f-a30efb755651.png) | Linux | {{linuxTargets}}<br>*Run AppImage:<br>Make it executable and double-click it.* |
| ![ubuntu](https://user-images.githubusercontent.com/7721625/51445401-a5306e00-1d04-11e9-9b9b-20e196b82142.png) | Ubuntu | <a href="https://snapcraft.io/mqtt-explorer" title="Get it from the Snap Store"><img src="https://snapcraft.io/static/images/badges/en/snap-store-black.svg" width="150" /><br/>[Ubuntu Store](snap://mqtt-explorer) |
| ![ubuntu](https://user-images.githubusercontent.com/7721625/51445401-a5306e00-1d04-11e9-9b9b-20e196b82142.png) | Ubuntu | [![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/mqtt-explorer)<br>[Ubuntu Store](snap://mqtt-explorer) |
| ![mac](https://user-images.githubusercontent.com/7721625/51445390-921d9e00-1d04-11e9-8339-351469ef20ae.png) | Mac | {{macTargets}} |
[More Downloads](https://github.com/thomasnordquist/MQTT-Explorer/releases)
+3 -5
View File
@@ -9,10 +9,9 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@material-ui/core": "^4.0.0-alpha.1",
"@material-ui/icons": "^3.0.2",
"@material-ui/lab": "^3.0.0-alpha.30",
"@material-ui/styles": "^3.0.0-alpha.10",
"@material-ui/core": "^4.0.0-alpha.0",
"@material-ui/icons": "^3.0.1",
"@material-ui/styles": "^3.0.0-alpha.8",
"@types/diff": "^4.0.1",
"@types/node": "^10.12.18",
"@types/prismjs": "^1.9.1",
@@ -48,7 +47,6 @@
"react-vis": "^1.11.6",
"redux": "^4.0.1",
"redux-batched-actions": "^0.4.1",
"redux-thunk": "^2.3.0",
"sha1": "^1.1.1",
"socket.io-client": "^2.2.0",
"source-map-loader": "^0.2.4",
+3 -1
View File
@@ -1,4 +1,5 @@
import * as React from 'react'
import CodeDiff from './components/CodeDiff'
import ConnectionSetup from './components/ConnectionSetup/ConnectionSetup'
import CssBaseline from '@material-ui/core/CssBaseline'
import ErrorBoundary from './ErrorBoundary'
@@ -17,6 +18,7 @@ import { Theme, withStyles } from '@material-ui/core/styles'
const Settings = React.lazy(() => import('./components/Settings'))
interface Props {
name: string
connectionId: string
classes: any
settingsVisible: boolean
@@ -88,7 +90,7 @@ class App extends React.PureComponent<Props, {}> {
<UpdateNotifier />
<ConnectionSetup />
</ErrorBoundary>
</div>
</div >
)
}
}
+11 -20
View File
@@ -1,19 +1,18 @@
import * as q from '../../../backend/src/Model'
import * as url from 'url'
import { Action, ActionTypes } from '../reducers/Connection'
import { AppState } from '../reducers'
import { DataSourceState, MqttOptions } from '../../../backend/src/DataSource'
import { Dispatch } from 'redux'
import { globalActions } from '.'
import { showError } from './Global'
import { showTree } from './Tree'
import { TopicViewModel } from '../TopicViewModel'
import {
addMqttConnectionEvent,
makeConnectionStateEvent,
removeConnection,
rendererEvents,
} from '../../../events'
import { AppState } from '../reducers'
import { Dispatch } from 'redux'
import { MqttOptions, DataSourceState } from '../../../backend/src/DataSource'
import { showTree } from './Tree'
import { TopicViewModel } from '../TopicViewModel'
import { showError } from './Global'
export const connect = (options: MqttOptions, connectionId: string) => (dispatch: Dispatch<any>, getState: () => AppState) => {
dispatch(connecting(connectionId))
@@ -24,13 +23,10 @@ export const connect = (options: MqttOptions, connectionId: string) => (dispatch
rendererEvents.subscribe(event, (dataSourceState) => {
console.log(dataSourceState)
if (dataSourceState.connected) {
const didReconnect = Boolean(getState().connection.tree)
if (!didReconnect) {
const tree = new q.Tree<TopicViewModel>()
tree.updateWithConnection(rendererEvents, connectionId)
dispatch(showTree(tree))
dispatch(connected(tree, host!))
}
const tree = new q.Tree<TopicViewModel>()
tree.updateWithConnection(rendererEvents, connectionId)
dispatch(connected(tree, host!))
dispatch(showTree(tree))
} else if (dataSourceState.error) {
dispatch(showError(dataSourceState.error))
dispatch(disconnect())
@@ -45,7 +41,6 @@ const updateHealth = (dataSourceState: DataSourceState) => (dispatch: Dispatch<a
state = 'connecting'
} else if (!dataSourceState.connected) {
state = 'offline'
dispatch(globalActions.showError('Disconnected from server'))
} else if (dataSourceState.connected) {
state = 'online'
} else {
@@ -73,11 +68,7 @@ export const connecting: (connectionId: string) => Action = (connectionId: strin
export const disconnect = () => (dispatch: Dispatch<any>, getState: () => AppState) => {
const { connectionId, tree } = getState().connection
if (connectionId) {
rendererEvents.emit(removeConnection, connectionId)
rendererEvents.unsubscribeAll(makeConnectionStateEvent(connectionId))
}
rendererEvents.emit(removeConnection, connectionId)
tree && tree.stopUpdating()
dispatch(showTree(undefined))
+2 -2
View File
@@ -1,7 +1,7 @@
import { Action, ActionTypes } from '../reducers/Publish'
import { ActionTypes, Action } from '../reducers/Publish'
import { AppState } from '../reducers'
import { Dispatch } from 'redux'
import { makePublishEvent, rendererEvents } from '../../../events'
import { rendererEvents, makePublishEvent } from '../../../events'
export const setTopic = (topic?: string): Action => {
return {
-16
View File
@@ -51,22 +51,6 @@ export const setAutoExpandLimit = (autoExpandLimit: number = 0) => (dispatch: Di
dispatch(storeSettings())
}
export const selectTopicWithMouseOver = (selectTopicWithMouseOver: boolean) => (dispatch: Dispatch<any>, getState: () => AppState) => {
dispatch({
selectTopicWithMouseOver,
type: ActionTypes.SETTINGS_SET_SELECT_TOPIC_WITH_MOUSE_OVER,
})
dispatch(storeSettings())
}
export const setValueDisplayMode = (valueRendererDisplayMode: 'diff' | 'raw') => (dispatch: Dispatch<any>, getState: () => AppState) => {
dispatch({
valueRendererDisplayMode,
type: ActionTypes.SETTINGS_SET_VALUE_RENDERER_DISPLAY_MODE,
})
dispatch(storeSettings())
}
export const toggleSettingsVisibility = () => (dispatch: Dispatch<any>, _getState: () => AppState) => {
dispatch({
type: ActionTypes.SETTINGS_TOGGLE_VISIBILITY,
+2 -2
View File
@@ -1,7 +1,7 @@
import * as q from '../../../backend/src/Model'
import { Action, Dispatch } from 'redux'
import { Dispatch, Action } from 'redux'
import { AppState } from '../reducers'
import { makePublishEvent, rendererEvents } from '../../../events'
import * as q from '../../../backend/src/Model'
export const clearRetainedTopic = () => (dispatch: Dispatch<any>, getState: () => AppState) => {
const { selectedTopic } = getState().tree
+4 -4
View File
@@ -1,10 +1,10 @@
import * as q from '../../../backend/src/Model'
import { ActionTypes } from '../reducers/Tree'
import { AnyAction, Dispatch } from 'redux'
import { AppState } from '../reducers'
import { batchActions } from 'redux-batched-actions'
import { ActionTypes } from '../reducers/Tree'
import * as q from '../../../backend/src/Model'
import { Dispatch, AnyAction } from 'redux'
import { setTopic } from './Publish'
import { TopicViewModel } from '../TopicViewModel'
import { batchActions } from 'redux-batched-actions'
const debounce = require('lodash.debounce')
export const selectTopic = (topic: q.TreeNode<TopicViewModel>) => (dispatch: Dispatch<any>, getState: () => AppState) => {
+5 -5
View File
@@ -1,10 +1,10 @@
import * as connectionActions from './Connection'
import * as connectionManagerActions from './ConnectionManager'
import * as globalActions from './Global'
import * as publishActions from './Publish'
import * as settingsActions from './Settings'
import * as sidebarActons from './Sidebar'
import * as publishActions from './Publish'
import * as treeActions from './Tree'
import * as updateNotifierActions from './UpdateNotifier'
import * as connectionActions from './Connection'
import * as sidebarActons from './Sidebar'
import * as connectionManagerActions from './ConnectionManager'
import * as globalActions from './Global'
export { settingsActions, treeActions, publishActions, updateNotifierActions, connectionActions, sidebarActons, connectionManagerActions, globalActions }
+7 -6
View File
@@ -1,11 +1,12 @@
import * as q from '../../../backend/src/Model'
import * as React from 'react'
import { AppState } from '../reducers'
import { connect } from 'react-redux'
import { StyleRulesCallback, withStyles } from '@material-ui/core/styles'
import { TopicViewModel } from '../TopicViewModel'
import { Typography } from '@material-ui/core'
import * as q from '../../../backend/src/Model'
import { AppState } from '../reducers'
import { Typography } from '@material-ui/core'
import { StyleRulesCallback, withStyles } from '@material-ui/core/styles'
import { connect } from 'react-redux'
import { TopicViewModel } from '../TopicViewModel'
const abbreviate = require('number-abbreviate')
interface Stats {
+7 -74
View File
@@ -8,7 +8,6 @@ import 'prismjs/themes/prism-tomorrow.css'
interface Props {
previous: string
current: string
nameOfCompareMessage: string
language?: 'json'
classes: any
}
@@ -20,6 +19,7 @@ class CodeDiff extends React.Component<Props, {}> {
public render() {
const changes = diff.diffLines(this.props.previous, this.props.current)
const styledLines = Prism.highlight(this.props.current, Prism.languages.json).split('\n')
let lineNumber = 0
@@ -27,59 +27,22 @@ class CodeDiff extends React.Component<Props, {}> {
const hasStyledCode = change.removed !== true
const changedLines = change.count || 0
if (hasStyledCode && this.props.language === 'json') {
const currentLines = styledLines.slice(lineNumber, lineNumber + changedLines)
const lines = currentLines.map((l, idx) => {
return <div key={`${key}-${idx}`} className={this.props.classes.line}><span className={this.cssClassForChange(change)} dangerouslySetInnerHTML={{ __html: l }} /></div>
})
const html = styledLines.slice(lineNumber, lineNumber + changedLines).join('\n')
lineNumber += changedLines
return <div key={key}>{lines}</div>
return <div key={key}><span className={this.cssClassForChange(change)} dangerouslySetInnerHTML={{ __html: html }} /></div>
}
return this.trimNewlineRight(change.value)
.split('\n')
.map((line, idx) => {
return <div key={`${key}-${idx}`} className={this.props.classes.line}><span className={this.cssClassForChange(change)}>{line}</span></div>
})
return <div key={key}><span className={this.cssClassForChange(change)}>{change.value}</span></div>
})
return (
<div>
<pre className={`language-json ${this.props.classes.codeBlock}`}>
{code}
</pre>
{this.renderChangeAmount(changes)}
</div>
<pre className={`language-json ${this.props.classes.codeBlock}`}>
{code}
</pre>
)
}
private renderChangeAmount(changes: Diff.Change[]) {
const additions = changes.map(change => (change.added === true) ? (change.count || 0) : 0).reduce((a, b) => a + b)
const deletions = changes.map(change => (change.removed === true) ? (change.count || 0) : 0).reduce((a, b) => a + b)
if (additions === 0 && deletions === 0) {
return null
}
return (
<span style={{ display: 'block', marginBottom: '8px', float: 'right' }}>
<span>
Comparing with <b>{this.props.nameOfCompareMessage}</b> message:&nbsp;
<span className={this.props.classes.additions}>+ {additions} line{additions === 1 ? '' : 's'}</span>
, <span className={this.props.classes.deletions}>- {deletions} line{deletions === 1 ? '' : 's'}</span>
</span>
</span>
)
}
private trimNewlineRight(str: string) {
if (str.slice(-1) === '\n') {
return str.slice(0, -1)
}
return str
}
private cssClassForChange(change: diff.Change) {
if (change.added === true) {
return this.props.classes.addition
@@ -97,51 +60,21 @@ const style = (theme: Theme) => {
const baseStyle = {
width: '100%',
}
const before = {
margin: '0 2px 0 -9px',
}
return {
additions: {
color: 'rgb(10, 255, 10)',
},
deletions: {
color: 'rgb(255, 10, 10)',
},
line: {
lineHeight: 'normal',
},
codeBlock: {
fontSize: '12px',
maxHeight: '200px',
marginLeft: '-16px',
},
noChange: {
...baseStyle,
'&:before': {
...before,
},
},
deletion: {
...baseStyle,
backgroundColor: 'rgba(255, 10, 10, 0.3)',
'&:before': {
...before,
content: "'-'",
},
'&:hover': {
backgroundColor: 'rgba(255, 10, 10, 0.6)',
},
},
addition: {
...baseStyle,
backgroundColor: 'rgba(10, 255, 10, 0.3)',
'&:hover': {
backgroundColor: 'rgba(10, 255, 10, 0.5)',
},
'&:before': {
...before,
content: "'+'",
},
},
}
}
@@ -66,7 +66,7 @@ class ConnectionSettings extends React.Component<Props, State> {
</Grid>
<Grid item={true} xs={12} style={{ padding: 0 }}>
<List
className={`${classes.topicList} advanced-connection-settings-topic-list`}
className={classes.topicList}
component="nav"
>
<div className={classes.list}>
+5 -5
View File
@@ -1,10 +1,10 @@
import * as React from 'react'
import Check from '@material-ui/icons/Check'
import CustomIconButton from './CustomIconButton'
import FileCopy from '@material-ui/icons/FileCopy'
import green from '@material-ui/core/colors/green'
import { Snackbar, SnackbarContent, Tooltip } from '@material-ui/core'
import { Theme, withStyles } from '@material-ui/core/styles'
import FileCopy from '@material-ui/icons/FileCopy'
import Check from '@material-ui/icons/Check'
import green from '@material-ui/core/colors/green'
import { withStyles, Theme } from '@material-ui/core/styles'
import CustomIconButton from './CustomIconButton';
const copy = require('copy-text-to-clipboard')
+1 -1
View File
@@ -1,6 +1,6 @@
import * as React from 'react'
import { IconButton } from '@material-ui/core'
import { Theme, withStyles } from '@material-ui/core/styles'
import { withStyles, Theme } from '@material-ui/core/styles'
interface Props {
onClick: any,
+22 -59
View File
@@ -1,14 +1,6 @@
import * as React from 'react'
import BrokerStatistics from './BrokerStatistics'
import ChevronRight from '@material-ui/icons/ChevronRight'
import { AppState } from '../reducers'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { settingsActions } from '../actions'
import { shell } from 'electron'
import { StyleRulesCallback, withStyles } from '@material-ui/core/styles'
import { TopicOrder } from '../reducers/Settings'
import { AppState } from '../reducers'
import {
Divider,
Drawer,
@@ -21,7 +13,15 @@ import {
Switch,
Tooltip,
} from '@material-ui/core'
const sha1 = require('sha1')
import { StyleRulesCallback, withStyles } from '@material-ui/core/styles'
import ChevronRight from '@material-ui/icons/ChevronRight'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { settingsActions } from '../actions'
import { TopicOrder } from '../reducers/Settings'
import BrokerStatistics from './BrokerStatistics'
import { shell } from 'electron';
export const autoExpandLimitSet = [{
limit: 0,
@@ -44,7 +44,6 @@ const styles: StyleRulesCallback = theme => ({
drawer: {
backgroundColor: theme.palette.background.default,
flexShrink: 0,
userSelect: 'none' as 'none',
},
paper: {
width: '300px',
@@ -63,20 +62,16 @@ const styles: StyleRulesCallback = theme => ({
color: theme.palette.text.hint,
cursor: 'pointer' as 'pointer',
},
switchBase: {
height: theme.spacing(4),
},
})
interface Props {
actions: typeof settingsActions
autoExpandLimit: number
classes: any
highlightTopicUpdates: boolean
selectTopicWithMouseOver: boolean
visible: boolean
store?: any
topicOrder: TopicOrder
visible: boolean
classes: any
actions: typeof settingsActions
}
class Settings extends React.Component<Props, {}> {
@@ -110,8 +105,7 @@ class Settings extends React.Component<Props, {}> {
{this.renderAutoExpand()}
{this.renderNodeOrder()}
{this.renderHighlightTopicUpdates()}
{this.selectTopicsOnMouseOver()}
{this.renderhighlightTopicUpdates()}
</div>
<Tooltip placement="top" title="App Author">
<Typography className={classes.author} onClick={this.openGithubPage}>
@@ -127,52 +121,22 @@ class Settings extends React.Component<Props, {}> {
shell.openExternal('https://github.com/thomasnordquist')
}
private renderHighlightTopicUpdates() {
private renderhighlightTopicUpdates() {
const { highlightTopicUpdates, actions } = this.props
return this.renderSwitch('Show Activity', highlightTopicUpdates, actions.togglehighlightTopicUpdates, 'Topics blink when a new message arrives')
}
private renderSwitch(title: string, checked: boolean, action: any, tooltip: string) {
const { classes } = this.props
const clickHandler = (e: React.MouseEvent) => {
e.stopPropagation()
e.preventDefault()
action()
}
return (
<div style={{ padding: '8px', display: 'flex' }}>
<Tooltip title={tooltip}>
<InputLabel
htmlFor={`toggle-${sha1(title)}`}
onClick={clickHandler}
style={{ flex: '1', paddingTop: '8px' }}
>
{title}
</InputLabel>
</Tooltip>
<Tooltip title={tooltip}>
<Switch
name={`toggle-${sha1(title)}`}
checked={checked}
onChange={action}
color="primary"
classes={{ switchBase: classes.switchBase }}
/>
</Tooltip>
<InputLabel htmlFor="toggle-highlight-activity" style={{ flex: '1', marginTop: '8px' }}>Show Activity</InputLabel>
<Switch
name="toggle-highlight-activity"
checked={highlightTopicUpdates}
onChange={actions.togglehighlightTopicUpdates}
color="primary"
/>
</div>
)
}
private selectTopicsOnMouseOver() {
const { actions, selectTopicWithMouseOver } = this.props
const toggle = () => actions.selectTopicWithMouseOver(!selectTopicWithMouseOver)
return this.renderSwitch('Quick Preview', selectTopicWithMouseOver, toggle, 'Select topics on mouse over')
}
private renderAutoExpand() {
const { classes, autoExpandLimit } = this.props
@@ -232,7 +196,6 @@ const mapStateToProps = (state: AppState) => {
topicOrder: state.settings.topicOrder,
visible: state.settings.visible,
highlightTopicUpdates: state.settings.highlightTopicUpdates,
selectTopicWithMouseOver: state.settings.selectTopicWithMouseOver,
}
}
@@ -1,10 +1,10 @@
import * as q from '../../../../backend/src/Model'
import * as React from 'react'
import * as q from '../../../../backend/src/Model'
import BarChart from '@material-ui/icons/BarChart'
import DateFormatter from '../helper/DateFormatter'
import History from './History'
import { TopicViewModel } from '../../TopicViewModel'
const PlotHistory = React.lazy(() => import('./PlotHistory'))
const throttle = require('lodash.throttle')
+3 -2
View File
@@ -1,7 +1,8 @@
import * as q from '../../../../backend/src/Model'
import * as React from 'react'
import { TopicViewModel } from '../../TopicViewModel'
import * as q from '../../../../backend/src/Model'
import { Typography } from '@material-ui/core'
import { TopicViewModel } from '../../TopicViewModel'
interface Props {
node: q.TreeNode<TopicViewModel>
+5 -4
View File
@@ -1,9 +1,10 @@
import * as q from '../../../../backend/src/Model'
import * as React from 'react'
import DateFormatter from '../helper/DateFormatter'
const { XYPlot, XAxis, LineMarkSeries, Hint, YAxis, HorizontalGridLines, LineSeries } = require('react-vis')
import { default as ReactResizeDetector } from 'react-resize-detector'
import DateFormatter from '../helper/DateFormatter'
import * as React from 'react'
import * as q from '../../../../backend/src/Model'
import 'react-vis/dist/style.css'
const { XYPlot, LineMarkSeries, Hint, YAxis, HorizontalGridLines } = require('react-vis')
interface Props {
messages: q.Message[]
+2 -31
View File
@@ -19,7 +19,6 @@ import {
Checkbox,
MenuItem,
Tooltip,
Fab,
} from '@material-ui/core'
// tslint:disable-next-line
@@ -28,10 +27,10 @@ import { AppState } from '../../../reducers'
import History from '../History'
import Message from './Model/Message'
import Navigation from '@material-ui/icons/Navigation'
import FormatAlignLeft from '@material-ui/icons/FormatAlignLeft'
import Clear from '@material-ui/icons/Clear'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { publishActions, globalActions } from '../../../actions'
import { publishActions } from '../../../actions'
import ClearAdornment from '../../helper/ClearAdornment'
import { TopicViewModel } from '../../../TopicViewModel'
@@ -41,7 +40,6 @@ interface Props {
topic?: string
payload?: string
actions: typeof publishActions
globalActions: typeof globalActions
retain: boolean
editorMode: string
qos: 0 | 1 | 2
@@ -153,37 +151,11 @@ class Publish extends React.Component<Props, State> {
)
}
private formatJson = () => {
if (this.props.payload) {
try {
const str = JSON.stringify(JSON.parse(this.props.payload), undefined, ' ')
this.updatePayload(str)
} catch (error) {
this.props.globalActions.showError(`Format error: ${error.message}`)
}
}
}
private renderFormatJson() {
if (this.props.editorMode !== 'json') {
return null
}
return (
<Tooltip title="Format JSON">
<Fab style={{ width: '36px', height: '36px', marginLeft: '8px' }} onClick={this.formatJson} id="sidebar-publish-format-json">
<FormatAlignLeft style={{ fontSize: '20px' }} />
</Fab>
</Tooltip>
)
}
private editorMode() {
return (
<div style={{ marginTop: '16px' }}>
<div style={{ width: '100%', lineHeight: '64px' }}>
{this.renderEditorModeSelection()}
{this.renderFormatJson()}
<div style={{ float: 'right', marginRight: '16px' }}>
{this.publishButton()}
</div>
@@ -327,7 +299,6 @@ class Publish extends React.Component<Props, State> {
const mapDispatchToProps = (dispatch: any) => {
return {
actions: bindActionCreators(publishActions, dispatch),
globalActions: bindActionCreators(globalActions, dispatch),
}
}
+23 -63
View File
@@ -1,31 +1,31 @@
import * as q from '../../../../backend/src/Model'
import * as React from 'react'
import Clear from '@material-ui/icons/Clear'
import Code from '@material-ui/icons/Code'
import Copy from '../Copy'
import CustomIconButton from '../CustomIconButton'
import DateFormatter from '../helper/DateFormatter'
import Delete from '@material-ui/icons/Delete'
import DeleteForever from '@material-ui/icons/DeleteForever'
import ExpandMore from '@material-ui/icons/ExpandMore'
import MessageHistory from './MessageHistory'
import NodeStats from './NodeStats'
import Reorder from '@material-ui/icons/Reorder'
import ToggleButton from '@material-ui/lab/ToggleButton'
import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup'
import Topic from './Topic'
import { AppState } from '../../reducers'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { settingsActions, sidebarActons } from '../../actions'
import { default as ReactResizeDetector } from 'react-resize-detector'
import { sidebarActons } from '../../actions'
import { StyleRulesCallback, Theme, withStyles } from '@material-ui/core/styles'
import { TopicViewModel } from '../../TopicViewModel'
import { ValueRendererDisplayMode } from '../../reducers/Settings'
import {
Button,
ExpansionPanel,
ExpansionPanelDetails,
ExpansionPanelSummary,
Fade,
Paper,
Popper,
Typography,
Tooltip,
Badge,
@@ -37,12 +37,10 @@ const Publish = React.lazy(() => import('./Publish/Publish'))
const ValueRenderer = React.lazy(() => import('./ValueRenderer'))
interface Props {
node?: q.TreeNode<TopicViewModel>
actions: typeof sidebarActons
settingsActions: typeof settingsActions
valueRendererDisplayMode: ValueRendererDisplayMode
classes: any
connectionId?: string
node?: q.TreeNode<TopicViewModel>,
actions: typeof sidebarActons,
classes: any,
connectionId?: string,
}
interface State {
@@ -89,7 +87,7 @@ class Sidebar extends React.Component<Props, State> {
public render() {
return (
<div id="Sidebar" className={this.props.classes.drawer}>
<div className={this.props.classes.drawer}>
{this.renderNode()}
</div>
)
@@ -98,7 +96,7 @@ class Sidebar extends React.Component<Props, State> {
private detailsStyle = { padding: '0px 16px 8px 8px', display: 'block' }
private renderTopicDeleteButton() {
if (!this.props.node || (!this.props.node.message || !this.props.node.message.value)) {
if (!this.props.node) {
return null
}
@@ -114,7 +112,7 @@ class Sidebar extends React.Component<Props, State> {
private renderRecursiveTopicDeleteButton() {
const deleteLimit = 50
const topicCount = this.props.node ? this.props.node.childTopicCount() : 0
if ((!this.props.node || topicCount === 0) || (this.props.node.message && topicCount === 1)) {
if (!this.props.node || topicCount === 0) {
return null
}
@@ -126,7 +124,7 @@ class Sidebar extends React.Component<Props, State> {
badgeContent={<span style={{ whiteSpace: 'nowrap' }}>{topicCount >= deleteLimit ? '50+' : topicCount}</span>}
color="secondary"
>
<Delete color="action" />
<DeleteForever color="action" />
</Badge>
</Tooltip>
</CustomIconButton>
@@ -141,27 +139,6 @@ class Sidebar extends React.Component<Props, State> {
this.props.actions.clearTopic(topic, recursive, maxCount)
}
private renderActionButtons() {
const handleValue = (_e: React.MouseEvent, value: any) => {
this.props.settingsActions.setValueDisplayMode(value)
}
return (
<ToggleButtonGroup id="valueRendererDisplayMode" value={this.props.valueRendererDisplayMode} exclusive={true} onChange={handleValue}>
<ToggleButton value="diff" id="valueRendererDisplayMode-diff">
<Tooltip title="Show difference between the current and the last message">
<Code />
</Tooltip>
</ToggleButton>
<ToggleButton value="raw" id="valueRendererDisplayMode-raw">
<Tooltip title="Raw value">
<Reorder />
</Tooltip>
</ToggleButton>
</ToggleButtonGroup>
)
}
private renderNode() {
const { classes, node } = this.props
@@ -169,11 +146,11 @@ class Sidebar extends React.Component<Props, State> {
const deleteTopic = this.renderTopicDeleteButton()
const deleteRecursiveTopic = this.renderRecursiveTopicDeleteButton()
const copyValue = node && node.message ? <Copy value={node.message.value} /> : null
const summaryStyle = { minHeight: '0' }
const summeryStyle = { minHeight: '0' }
return (
<div>
<ExpansionPanel key="topic" defaultExpanded={true} disabled={!Boolean(this.props.node)}>
<ExpansionPanelSummary expandIcon={<ExpandMore />} style={summaryStyle}>
<ExpansionPanelSummary expandIcon={<ExpandMore />} style={summeryStyle}>
<Typography className={classes.heading}>Topic {copyTopic} {deleteTopic} {deleteRecursiveTopic}</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails style={this.detailsStyle}>
@@ -181,21 +158,21 @@ class Sidebar extends React.Component<Props, State> {
</ExpansionPanelDetails>
</ExpansionPanel>
<ExpansionPanel key="value" defaultExpanded={true}>
<ExpansionPanelSummary expandIcon={<ExpandMore />} style={summaryStyle}>
<ExpansionPanelSummary expandIcon={<ExpandMore />} style={summeryStyle}>
<Typography className={classes.heading}>Value {copyValue}</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails style={this.detailsStyle}>
{this.messageMetaInfo()}
<div ref={this.valueRef}>
<React.Suspense fallback={<div>Loading...</div>}>
{this.renderValue()}
</React.Suspense>
<React.Suspense fallback={<div>Loading...</div>}>
<ValueRenderer node={this.props.node} compareWith={this.state.compareMessage} />
</React.Suspense>
</div>
<div><MessageHistory onSelect={this.handleMessageHistorySelect} selected={this.state.compareMessage} node={this.props.node} /></div>
</ExpansionPanelDetails>
</ExpansionPanel>
<ExpansionPanel defaultExpanded={true}>
<ExpansionPanelSummary expandIcon={<ExpandMore />} style={summaryStyle}>
<ExpansionPanelSummary expandIcon={<ExpandMore />} style={summeryStyle}>
<Typography className={classes.heading}>Publish</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails style={this.detailsStyle}>
@@ -205,7 +182,7 @@ class Sidebar extends React.Component<Props, State> {
</ExpansionPanelDetails>
</ExpansionPanel>
<ExpansionPanel defaultExpanded={Boolean(this.props.node)}>
<ExpansionPanelSummary expandIcon={<ExpandMore />} style={summaryStyle}>
<ExpansionPanelSummary expandIcon={<ExpandMore />} style={summeryStyle}>
<Typography className={classes.heading}>Stats</Typography>
</ExpansionPanelSummary>
{this.renderNodeStats()}
@@ -214,20 +191,6 @@ class Sidebar extends React.Component<Props, State> {
)
}
private renderValue() {
const node = this.props.node
if (!node || !node.message) {
return null
}
return (
<ValueRenderer
message={node.message}
messageHistory={node.messageHistory}
compareWith={this.state.compareMessage} />
)
}
private messageMetaInfo() {
if (!this.props.node || !this.props.node.message || !this.props.node.mqttMessage) {
return null
@@ -248,9 +211,8 @@ class Sidebar extends React.Component<Props, State> {
)
return (
<div style={{ width: '100%', display: 'flex', paddingLeft: '8px' }}>
<div style={{ width: '100%', display: 'flex' }}>
<div style={{ flex: 6 }}><Typography>QoS: {this.props.node.mqttMessage.qos}</Typography></div>
<span style={{ marginTop: '-8px' }}>{this.renderActionButtons()}</span>
<div style={{ flex: 8, textAlign: 'center' }}>
{this.props.node.mqttMessage.retain ? retainedButton : null}
</div>
@@ -283,14 +245,12 @@ class Sidebar extends React.Component<Props, State> {
const mapStateToProps = (state: AppState) => {
return {
node: state.tree.selectedTopic,
valueRendererDisplayMode: state.settings.valueRendererDisplayMode,
}
}
const mapDispatchToProps = (dispatch: any) => {
return {
actions: bindActionCreators(sidebarActons, dispatch),
settingsActions: bindActionCreators(settingsActions, dispatch),
}
}
+16 -21
View File
@@ -1,16 +1,13 @@
import * as q from '../../../../backend/src/Model'
import * as React from 'react'
import CodeDiff from '../CodeDiff'
import { AppState } from '../../reducers'
import { connect } from 'react-redux'
import { default as ReactResizeDetector } from 'react-resize-detector'
import { ValueRendererDisplayMode } from '../../reducers/Settings'
import { Theme, withTheme } from '@material-ui/core/styles'
interface Props {
message: q.Message
messageHistory: q.RingBuffer<q.Message>
node?: q.TreeNode<any>,
compareWith?: q.Message
renderMode: ValueRendererDisplayMode
theme: Theme
}
interface State {
@@ -24,6 +21,10 @@ class ValueRenderer extends React.Component<Props, State> {
}
public render() {
if (!this.props.node) {
return null
}
return (
<div style={{ padding: '8px 0px 8px 8px' }}>
<ReactResizeDetector handleWidth={true} onResize={this.updateWidth} />
@@ -33,15 +34,16 @@ class ValueRenderer extends React.Component<Props, State> {
}
public renderValue() {
const { message, messageHistory, compareWith, renderMode } = this.props
const previousMessages = messageHistory.toArray()
const previousMessage = previousMessages[previousMessages.length - 2]
let compareMessage = compareWith || previousMessage || message
if (renderMode === 'raw') {
compareMessage = message
const { node } = this.props
if (!node || !node.message) {
return <span key="empty" />
}
const message = node.message
const previousMessages = node.messageHistory.toArray()
const previousMessage = previousMessages[previousMessages.length - 2]
const compareMessage = this.props.compareWith || previousMessage || message
let json
try {
json = JSON.parse(message.value)
@@ -70,7 +72,6 @@ class ValueRenderer extends React.Component<Props, State> {
previous={previous}
current={current}
language={language}
nameOfCompareMessage={this.props.compareWith ? 'selected' : 'previous'}
/>
)
}
@@ -99,10 +100,4 @@ class ValueRenderer extends React.Component<Props, State> {
}
}
const mapStateToProps = (state: AppState) => {
return {
renderMode: state.settings.valueRendererDisplayMode,
}
}
export default connect(mapStateToProps)(ValueRenderer)
export default withTheme()(ValueRenderer)
+7 -7
View File
@@ -1,15 +1,8 @@
import * as React from 'react'
import ClearAdornment from './helper/ClearAdornment'
import CloudOff from '@material-ui/icons/CloudOff'
import ConnectionHealthIndicator from './ConnectionHealthIndicator'
import Menu from '@material-ui/icons/Menu'
import Search from '@material-ui/icons/Search'
import { AppState } from '../reducers'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { connectionActions, settingsActions } from '../actions'
import { fade } from '@material-ui/core/styles/colorManipulator'
import { StyleRulesCallback, withStyles } from '@material-ui/core/styles'
import {
AppBar,
Button,
@@ -18,6 +11,13 @@ import {
Toolbar,
Typography,
} from '@material-ui/core'
import { AppState } from '../reducers'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { connectionActions, settingsActions } from '../actions'
import { fade } from '@material-ui/core/styles/colorManipulator'
import { StyleRulesCallback, withStyles } from '@material-ui/core/styles'
import ConnectionHealthIndicator from './ConnectionHealthIndicator';
const styles: StyleRulesCallback = theme => ({
title: {
+4 -6
View File
@@ -1,12 +1,13 @@
import * as q from '../../../../backend/src/Model'
import * as React from 'react'
import TreeNode from './TreeNode'
import * as q from '../../../../backend/src/Model'
import { AppState } from '../../reducers'
import { bindActionCreators } from 'redux'
import TreeNode from './TreeNode'
import { connect } from 'react-redux'
import { TopicOrder } from '../../reducers/Settings'
import { TopicViewModel } from '../../TopicViewModel'
import { treeActions } from '../../actions'
import { bindActionCreators } from 'redux'
const MovingAverage = require('moving-average')
@@ -24,7 +25,6 @@ interface Props {
topicOrder: TopicOrder
autoExpandLimit: number
highlightTopicUpdates: boolean
selectTopicWithMouseOver: boolean
}
interface State {
@@ -112,7 +112,6 @@ class Tree extends React.PureComponent<Props, State> {
lastUpdate={tree.lastUpdate}
didSelectTopic={this.props.actions.selectTopic}
highlightTopicUpdates={this.props.highlightTopicUpdates}
selectTopicWithMouseOver={this.props.selectTopicWithMouseOver}
/>
</div>
)
@@ -131,7 +130,6 @@ const mapStateToProps = (state: AppState) => {
autoExpandLimit: state.settings.autoExpandLimit,
topicOrder: state.settings.topicOrder,
highlightTopicUpdates: state.settings.highlightTopicUpdates,
selectTopicWithMouseOver: state.settings.selectTopicWithMouseOver,
}
}
+4 -8
View File
@@ -1,11 +1,12 @@
import * as q from '../../../../backend/src/Model'
import * as React from 'react'
import * as q from '../../../../backend/src/Model'
import { Theme, withStyles } from '@material-ui/core/styles'
import TreeNodeSubnodes from './TreeNodeSubnodes'
import TreeNodeTitle from './TreeNodeTitle'
import { Theme, withStyles } from '@material-ui/core/styles'
import { TopicOrder } from '../../reducers/Settings'
import { TopicViewModel } from '../../TopicViewModel'
const debounce = require('lodash.debounce')
declare var performance: any
@@ -54,7 +55,6 @@ interface Props {
lastUpdate: number
didSelectTopic: any
highlightTopicUpdates: boolean
selectTopicWithMouseOver: boolean
}
interface State {
@@ -229,9 +229,6 @@ class TreeNode extends React.Component<Props, State> {
private mouseOver = (event: React.MouseEvent) => {
event.stopPropagation()
this.setHover(true)
if (this.props.selectTopicWithMouseOver && this.props.treeNode && this.props.treeNode.message && this.props.treeNode.message.value) {
this.props.didSelectTopic(this.props.treeNode)
}
}
private mouseOut = (event: React.MouseEvent) => {
@@ -263,7 +260,6 @@ class TreeNode extends React.Component<Props, State> {
lastUpdate={this.props.treeNode.lastUpdate}
didSelectTopic={this.props.didSelectTopic}
highlightTopicUpdates={this.props.highlightTopicUpdates}
selectTopicWithMouseOver={this.props.selectTopicWithMouseOver}
/>
)
}
+2 -4
View File
@@ -18,7 +18,6 @@ export interface Props {
autoExpandLimit: number
didSelectTopic: any
highlightTopicUpdates: boolean
selectTopicWithMouseOver: boolean
}
interface State {
@@ -52,13 +51,13 @@ class TreeNodeSubnodes extends React.Component<Props, State> {
}
private renderMore() {
this.renderMoreAnimationFrame = (window as any).requestIdleCallback(() => {
this.renderMoreAnimationFrame = window.requestIdleCallback(() => {
this.setState({ ...this.state, alreadyAdded: this.state.alreadyAdded * 1.5 })
}, { timeout: 500 })
}
public componentWillUnmount() {
(window as any).cancelIdleCallback(this.renderMoreAnimationFrame)
window.cancelIdleCallback(this.renderMoreAnimationFrame)
}
public render() {
@@ -84,7 +83,6 @@ class TreeNodeSubnodes extends React.Component<Props, State> {
lastUpdate={node.lastUpdate}
didSelectTopic={this.props.didSelectTopic}
highlightTopicUpdates={this.props.highlightTopicUpdates}
selectTopicWithMouseOver={this.props.selectTopicWithMouseOver}
/>
)
})
-44
View File
@@ -1,44 +0,0 @@
import * as React from 'react'
import { Theme, withStyles } from '@material-ui/core'
interface Props {
keyboardKey: string
classes: any
}
class Key extends React.Component<Props, {}> {
constructor(props: any) {
super(props)
this.state = { location: 'bottom' }
}
public render() {
return (
<div className={this.props.classes.keyStyle}>
<div className={this.props.classes.keyTextStyle}>{this.props.keyboardKey}</div>
</div>
)
}
}
const style = (theme: Theme) => ({
keyStyle: {
display: 'inline-block' as 'inline-block',
width: '1em',
height: '1em',
backgroundColor: '#bbb',
borderRadius: '10%',
verticalAlign: 'middle' as 'middle',
textAlign: 'center' as 'center',
textShadow: '1px 1px rgba(255,255,255,0.45)',
boxShadow: '0.08em 0.15em 0.01em 0px rgba(100,100,100,0.75)',
},
keyTextStyle: {
marginTop: '0.65em',
fontSize: '0.4em',
fontWeight: 'bold' as 'bold',
},
})
export default withStyles(style)(Key)
-78
View File
@@ -1,78 +0,0 @@
import * as React from 'react'
import { Theme, withStyles } from '@material-ui/core'
interface State {
enabled: boolean
target: {x: number, y: number}
position: {x: number, y: number}
stepSizeX: number,
stepSizeY: number,
}
class Demo extends React.Component<{}, State> {
private timer: any
private frameInterval = 20
constructor(props: any) {
super(props)
this.state = { enabled: false, target: { x: 0, y: 0 }, position: { x: 0, y: 0 }, stepSizeX: 1, stepSizeY: 1 }
}
public componentDidMount() {
(window as any).demo.enableMouse = () => {
this.setState({ enabled: true })
}
(window as any).demo.moveMouse = (x: number, y: number, animationTime: number) => {
const stepSizeX = Math.abs(this.state.position.x - x) / (animationTime / this.frameInterval)
const stepSizeY = Math.abs(this.state.position.y - y) / (animationTime / this.frameInterval)
this.setState({ stepSizeX, stepSizeY, enabled: true, target: { x, y } })
this.moveCloser()
}
}
private moveCloser(steps: number = 0) {
const steSizeX = Math.min(this.state.stepSizeX, Math.abs(this.state.position.x - this.state.target.x))
const steSizeY = Math.min(this.state.stepSizeY, Math.abs(this.state.position.y - this.state.target.y))
const dirX = this.state.position.x > this.state.target.x ? -1 : 1
const dirY = this.state.position.y > this.state.target.y ? -1 : 1
if (steSizeX <= 0.1 && steSizeY <= 0.1) {
this.timer && clearTimeout(this.timer)
return
}
this.setState({
position: {
x: this.state.position.x + (dirX * steSizeX),
y: this.state.position.y + (dirY * steSizeY),
},
})
this.timer = setTimeout(() => {
this.moveCloser(steps + 1)
}, this.frameInterval)
}
public render() {
if (!this.state.enabled) {
return null
}
const style = {
width: '32px',
height: '32px',
position: 'fixed' as 'fixed',
zIndex: 1000000,
filter: 'invert(100%)',
left: this.state.position.x + 2,
top: this.state.position.y + 2,
}
return (
<img src="../cursor.png" style={style} />
)
}
}
const style = (theme: Theme) => {}
export default withStyles(style)(Demo)
-91
View File
@@ -1,91 +0,0 @@
import * as React from 'react'
import { Theme, withStyles } from '@material-ui/core'
import Key from './Key';
interface State {
message?: string
keys: string[]
location: string
}
class Demo extends React.Component<{classes: any}, State> {
private timer: any
constructor(props: any) {
super(props)
this.state = { location: 'bottom', keys: [] }
}
private clearTimer() {
this.timer && clearTimeout(this.timer)
}
public componentDidMount() {
(window as any).demo.showMessage = (message: string, location: string, duration: number, keys: string[] = []) => {
this.clearTimer()
this.setState({ message, location, keys })
this.timer = setTimeout(() => this.setState({ message: undefined }), duration)
}
(window as any).demo.hideMessage = () => {
this.clearTimer()
this.setState({ message: undefined })
}
}
public render() {
const positions: {[s: string]: number} = {
top: 0,
bottom: -65,
middle: -32,
}
const style = {
position: 'fixed' as 'fixed',
left: '5vw',
zIndex: 1000000,
margin: '30vw auto 50vw',
right: '5vw',
bottom: `${positions[this.state.location]}vh`,
}
const style2 = {
textAlign: 'center' as 'center',
fontSize: '4em',
color: 'white',
backgroundColor: 'rgba(0, 0, 0, 0.8)',
borderRadius: '16px',
}
if (!this.state.message) {
return null
}
let keys: any[] = []
if (this.state.keys.length > 0) {
keys = this.state.keys.map(key => [<Key key={key} keyboardKey={key} />])
.reduce((prev, current) => {
return [prev, '+' as any, current]
})
}
return (
<div style={style}>
<div style={style2}>
<span>{this.state.message}</span>
{keys.length > 0 ? <div className={this.props.classes.keysStyle}>{keys}</div> : null}
</div>
</div>
)
}
}
const style = (theme: Theme) => ({
keysStyle: {
fontSize: '1em',
display: 'inline-block' as 'inline-block',
transform: 'translateY(0.3em) translateX(0.8em)',
},
})
export default withStyles(style)(Demo)
-14
View File
@@ -1,14 +0,0 @@
import * as React from 'react'
import ShowText from './ShowText'
import Mouse from './Mouse';
(window as any).demo = {}
export default function render(props: any) {
return (
<span>
<ShowText />
<Mouse />
</span>
)
}
+1 -1
View File
@@ -1,6 +1,6 @@
import * as React from 'react'
import Clear from '@material-ui/icons/Clear'
import { IconButton } from '@material-ui/core'
import Clear from '@material-ui/icons/Clear'
interface Props {
value?: string
+11 -12
View File
@@ -1,17 +1,16 @@
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import App from './App'
import Demo from './components/demo'
import reducers from './reducers'
import reduxThunk from 'redux-thunk'
import { applyMiddleware, compose, createStore } from 'redux'
import { batchDispatchMiddleware } from 'redux-batched-actions'
import { createMuiTheme, MuiThemeProvider } from '@material-ui/core/styles'
import { Provider } from 'react-redux'
import './tracking'
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import reduxThunk from 'redux-thunk'
import { batchDispatchMiddleware } from 'redux-batched-actions'
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'
import reducers from './reducers'
import App from './App'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware, compose } from 'redux'
const composeEnhancers = /*(window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || */ compose
const store = createStore(
@@ -28,6 +27,7 @@ const theme = createMuiTheme({
palette: {
type: 'dark',
},
typography: { useNextVariants: true },
})
setTimeout(() => {
@@ -41,8 +41,7 @@ setTimeout(() => {
ReactDOM.render(
<MuiThemeProvider theme={theme}>
<Provider store={store}>
<App />
<Demo />
<App name="" />
</Provider>
</MuiThemeProvider>,
document.getElementById('app'),
+1 -1
View File
@@ -1,6 +1,6 @@
import * as q from '../../../backend/src/Model'
import { Action } from 'redux'
import { createReducer } from './lib'
import * as q from '../../../backend/src/Model'
import { MqttOptions } from '../../../backend/src/DataSource'
import { TopicViewModel } from '../TopicViewModel'
+1 -1
View File
@@ -44,7 +44,7 @@ export interface ToggleRetain {
}
const initialState: PublishState = {
editorMode: 'json',
editorMode: 'text',
retain: false,
qos: 0,
}
+1 -37
View File
@@ -8,19 +8,14 @@ export enum TopicOrder {
topics = '#topics',
}
export type ValueRendererDisplayMode = 'diff' | 'raw'
export interface SettingsState {
autoExpandLimit: number
visible: boolean
topicOrder: TopicOrder
topicFilter?: string
highlightTopicUpdates: boolean
valueRendererDisplayMode: ValueRendererDisplayMode
selectTopicWithMouseOver: boolean
}
export type Action = SetAutoExpandLimit | ToggleVisibility | SetTopicOrder | FilterTopics | TogglehighlightTopicUpdates | SetValueRendererDisplayMode
export type Action = SetAutoExpandLimit | ToggleVisibility | SetTopicOrder | FilterTopics | TogglehighlightTopicUpdates
export enum ActionTypes {
SETTINGS_SET_AUTO_EXPAND_LIMIT = 'SETTINGS_SET_AUTO_EXPAND_LIMIT',
@@ -29,9 +24,6 @@ export enum ActionTypes {
SETTINGS_FILTER_TOPICS = 'SETTINGS_FILTER_TOPICS',
SETTINGS_TOGGLE_HIGHLIGHT_ACTIVITY = 'SETTINGS_TOGGLE_HIGHLIGHT_ACTIVITY',
SETTINGS_DID_LOAD_SETTINGS = 'SETTINGS_DID_LOAD_SETTINGS',
SETTINGS_SET_VALUE_RENDERER_DISPLAY_MODE = 'SETTINGS_SET_VALUE_RENDERER_DISPLAY_MODE',
SETTINGS_SET_SELECT_TOPIC_WITH_MOUSE_OVER = 'SETTINGS_SET_SELECT_TOPIC_WITH_MOUSE_OVER',
}
const initialState: SettingsState = {
@@ -39,8 +31,6 @@ const initialState: SettingsState = {
topicOrder: TopicOrder.none,
visible: false,
highlightTopicUpdates: true,
valueRendererDisplayMode: 'diff',
selectTopicWithMouseOver: false,
}
export const settingsReducer = createReducer(initialState, {
@@ -50,8 +40,6 @@ export const settingsReducer = createReducer(initialState, {
SETTINGS_FILTER_TOPICS: filterTopics,
SETTINGS_TOGGLE_HIGHLIGHT_ACTIVITY: togglehighlightTopicUpdates,
SETTINGS_DID_LOAD_SETTINGS: didLoadSettings,
SETTINGS_SET_VALUE_RENDERER_DISPLAY_MODE: setValueRendererDisplayMode,
SETTINGS_SET_SELECT_TOPIC_WITH_MOUSE_OVER: setSelectTopicWithMouseOver,
})
export interface DidLoadSettings {
@@ -66,30 +54,6 @@ function didLoadSettings(state: SettingsState, action: DidLoadSettings) {
}
}
export interface SetSelectTopicWithMouseOver {
type: ActionTypes.SETTINGS_SET_SELECT_TOPIC_WITH_MOUSE_OVER
selectTopicWithMouseOver: boolean
}
export function setSelectTopicWithMouseOver(state: SettingsState, action: SetSelectTopicWithMouseOver) {
return {
...state,
selectTopicWithMouseOver: action.selectTopicWithMouseOver,
}
}
export interface SetValueRendererDisplayMode {
type: ActionTypes.SETTINGS_SET_VALUE_RENDERER_DISPLAY_MODE
valueRendererDisplayMode: ValueRendererDisplayMode
}
export function setValueRendererDisplayMode(state: SettingsState, action: SetValueRendererDisplayMode) {
return {
...state,
valueRendererDisplayMode: action.valueRendererDisplayMode,
}
}
export interface SetAutoExpandLimit {
type: ActionTypes.SETTINGS_SET_AUTO_EXPAND_LIMIT
autoExpandLimit: number
+7 -7
View File
@@ -1,11 +1,11 @@
import { Action, combineReducers, Reducer } from 'redux'
import { connectionManagerReducer, ConnectionManagerState } from './ConnectionManager'
import { connectionReducer, ConnectionState } from './Connection'
import { publishReducer, PublishState } from './Publish'
import { settingsReducer, SettingsState } from './Settings'
import { trackEvent } from '../tracking'
import { treeReducer, TreeState } from './Tree'
import { Action, Reducer, combineReducers } from 'redux'
import { trackEvent } from '../tracking'
import { PublishState, publishReducer } from './Publish'
import { ConnectionState, connectionReducer } from './Connection'
import { SettingsState, settingsReducer } from './Settings'
import { TreeState, treeReducer } from './Tree'
import { ConnectionManagerState, connectionManagerReducer } from './ConnectionManager'
export enum ActionTypes {
showUpdateNotification = 'SHOW_UPDATE_NOTIFICATION',
+547 -502
View File
File diff suppressed because it is too large Load Diff
+11 -2
View File
@@ -12,12 +12,21 @@ install:
build_script:
- yarn
- yarn build
- yarn prepare-release
- yarn package appx
test_script:
- yarn test
before_deploy:
- yarn prepare-release
- yarn package appx
deploy:
- provider: Environment
name: production
on:
branch: master # only this will work
APPVEYOR_REPO_TAG: true # condition will never be evaluated
artifacts:
- path: 'build\clean\build\MQTT-Explorer*.appx'
+5 -6
View File
@@ -1,6 +1,6 @@
{
"name": "MQTT-Explorer",
"version": "0.2.1-alpha6",
"version": "0.2.0",
"description": "Explore your message queues",
"main": "dist/src/electron.js",
"scripts": {
@@ -36,15 +36,13 @@
"applicationId": "mqttexplorer",
"identityName": "51031thomas.nordquist.MQTT-Explorer",
"publisherDisplayName": "Thomas Nordquist",
"publisher": "CN=0A6DE643-4AA2-4FF2-9729-6935C9ED8C13",
"backgroundColor": "#FFFFFF"
"publisher": "CN=0A6DE643-4AA2-4FF2-9729-6935C9ED8C13"
},
"directories": {
"app": "./",
"buildResources": "res",
"output": "build"
},
"afterAllArtifactBuild": "./dist/scripts/afterPack.js"
}
},
"author": "Thomas Nordquist",
"email": "xxnerowingerxx@gmail.com",
@@ -62,7 +60,7 @@
"@types/sha1": "^1.1.1",
"axios": "^0.18.0",
"chai": "^4.2.0",
"electron": "^4.0.6",
"electron": "^4.0.2",
"electron-builder": "^20.38.5",
"fs-extra": "^7.0.1",
"mime": "^2.4.0",
@@ -82,6 +80,7 @@
},
"dependencies": {
"about-window": "^1.12.1",
"electron-debug": "^2.0.0",
"electron-is-dev": "^1.0.1",
"electron-log": "^2.2.17",
"electron-telemetry": "git+https://github.com/thomasnordquist/electron-telemetry.git#dist",
+20 -37
View File
@@ -1,12 +1,11 @@
import * as builder from 'electron-builder'
import * as fs from 'fs'
import * as path from 'path'
const linuxAppImage: builder.CliOptions = {
const linux: builder.CliOptions = {
x64: true,
ia32: true,
armv7l: true,
arm64: false,
linux: ['AppImage'],
projectDir: './build/clean',
publish: 'always',
}
@@ -16,24 +15,17 @@ const linuxSnap: builder.CliOptions = {
ia32: false,
armv7l: false,
arm64: false,
linux: ['snap'],
projectDir: './build/clean',
publish: 'always',
}
const winPortable: builder.CliOptions = {
x64: true,
ia32: true,
armv7l: false,
arm64: false,
projectDir: './build/clean',
publish: 'always',
}
const winNsis: builder.CliOptions = {
const win: builder.CliOptions = {
x64: true,
ia32: true,
armv7l: false,
arm64: false,
win: ['portable', 'nsis'],
projectDir: './build/clean',
publish: 'always',
}
@@ -43,8 +35,9 @@ const winAppx: builder.CliOptions = {
ia32: true,
armv7l: false,
arm64: false,
win: ['appx'],
projectDir: './build/clean',
publish: 'onTag',
publish: 'never',
}
const mac: builder.CliOptions = {
@@ -52,6 +45,7 @@ const mac: builder.CliOptions = {
ia32: true,
armv7l: false,
arm64: false,
mac: ['dmg'],
projectDir: './build/clean',
publish: 'always',
}
@@ -59,40 +53,29 @@ const mac: builder.CliOptions = {
async function executeBuild() {
switch (process.argv[2]) {
case 'win':
await buildWithOptions(winPortable, { platform: 'win', package: 'portable' })
await buildWithOptions(winNsis, { platform: 'win', package: 'nsis' })
await builder.build(win)
break
case 'appx':
await buildWithOptions(winAppx, { platform: 'win', package: 'appx' })
await builder.build(winAppx)
break
case 'linux':
await buildWithOptions(linuxAppImage, { platform: 'linux', package: 'AppImage' })
await buildWithOptions(linuxSnap, { platform: 'linux', package: 'snap' })
await builder.build(linux)
break
case 'snap':
try {
await builder.build(linuxSnap)
} catch {
// ignore
}
break
case 'mac':
await buildWithOptions(mac, { platform: 'mac', package: 'mas' })
await buildWithOptions(mac, { platform: 'mac', package: 'dmg' })
await buildWithOptions(mac, { platform: 'mac', package: 'zip' })
await builder.build(mac)
break
default:
throw new Error('No target selected')
await builder.build(mac)
}
}
export interface BuildInfo {
platform: 'win' | 'linux' | 'mac'
package: 'portable' | 'nsis' | 'appx' | 'AppImage' | 'snap' | 'dmg' | 'zip' | 'mas'
}
async function buildWithOptions(options: builder.CliOptions, buildInfo: BuildInfo) {
fs.writeFileSync(path.join(options.projectDir!, 'buildOptions.json'), JSON.stringify(buildInfo))
await builder.build({
...options,
[buildInfo.platform]: [buildInfo.package],
})
}
function build() {
try {
executeBuild()
Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.
-22
View File
@@ -1,22 +0,0 @@
import * as fs from 'fs-extra'
import * as path from 'path'
import { chdir } from 'process'
import { exec } from './util'
export default async function (info: any) {
for (const snapFile of info.artifactPaths) {
if (/\.snap$/.test(snapFile)) {
const originalDir = __dirname
const dirname = path.dirname(snapFile)
chdir(dirname)
await exec('sudo', ['unsquashfs', snapFile])
await fs.remove(snapFile)
await exec('sudo', ['chmod', '-R', 'g-s', 'squashfs-root'])
await exec('sudo', ['snap', 'run', 'snapcraft', 'pack', 'squashfs-root', '--output', snapFile])
await fs.remove('squashfs-root')
chdir(originalDir)
}
}
}
+30 -1
View File
@@ -1,7 +1,36 @@
import * as fs from 'fs-extra'
import * as path from 'path'
import { spawn, ChildProcess } from 'child_process'
import { chdir } from 'process'
import { exec } from './util'
async function exec(cmd: string, args: string[] = []) {
const child = spawn(cmd, args, { shell: true })
redirectOutputFor(child)
await waitFor(child)
}
function redirectOutputFor(child: ChildProcess) {
const printStdout = (data: Buffer) => {
process.stdout.write(data.toString())
}
const printStderr = (data: Buffer) => {
process.stderr.write(data.toString())
}
child.stdout.on('data', printStdout)
child.stderr.on('data', printStderr)
child.once('close', () => {
child.stdout.off('data', printStdout)
child.stderr.off('data', printStderr)
})
}
async function waitFor(child: ChildProcess) {
return new Promise((resolve) => {
child.once('close', () => resolve())
})
}
const targetDir = path.join('build', 'clean')
async function prepareRelease() {
+3 -4
View File
@@ -1,6 +1,5 @@
#!/bin/bash
DIMENSIONS="1024x720"
GIF_SCALE="1024"
DIMENSIONS="1280x720"
ffmpeg -s:v $DIMENSIONS -r 20 -f rawvideo -pix_fmt yuv420p -i qrawvideorgb24.yuv app2.mp4
@@ -15,10 +14,10 @@ END_OF_BLACK=`awk "BEGIN {print $END_OF_BLACK+0.8; exit}"`
ffmpeg -s:v $DIMENSIONS -r 20 -f rawvideo -pix_fmt yuv420p -i qrawvideorgb24.yuv -ss $END_OF_BLACK app.mp4
# Generate gif palette
ffmpeg -y -s:v $DIMENSIONS -r 20 -f rawvideo -pix_fmt yuv420p -i qrawvideorgb24.yuv -vf "fps=10,scale=$GIF_SCALE:-1:flags=lanczos,palettegen" palette1024.png
ffmpeg -y -s:v $DIMENSIONS -r 20 -f rawvideo -pix_fmt yuv420p -i qrawvideorgb24.yuv -vf fps=10,scale=1280:-1:flags=lanczos,palettegen palette1024.png
# Create gif
ffmpeg -s:v $DIMENSIONS -r 20 -f rawvideo -pix_fmt yuv420p -i qrawvideorgb24.yuv -i palette1024.png -ss $END_OF_BLACK -filter_complex "fps=10,scale=$GIF_SCALE:-1:flags=lanczos[x];[x][1:v]paletteuse" app720.gif
ffmpeg -s:v $DIMENSIONS -r 20 -f rawvideo -pix_fmt yuv420p -i qrawvideorgb24.yuv -i palette1024.png -ss $END_OF_BLACK -filter_complex "fps=10,scale=1280:-1:flags=lanczos[x];[x][1:v]paletteuse" app720.gif
# Clean up
rm ffmpeg_info palette*.png qrawvideorgb24.yuv
+1 -1
View File
@@ -9,7 +9,7 @@ function finish {
trap finish EXIT
DIMENSIONS="1024x720"
DIMENSIONS="1280x720"
SCR=99
# Start new window manager
Xvfb :$SCR -screen 0 "$DIMENSIONS"x24 -ac &
-29
View File
@@ -1,29 +0,0 @@
import { spawn, ChildProcess } from 'child_process'
export async function exec(cmd: string, args: string[] = []) {
const child = spawn(cmd, args, { shell: true })
redirectOutputFor(child)
await waitFor(child)
}
function redirectOutputFor(child: ChildProcess) {
const printStdout = (data: Buffer) => {
process.stdout.write(data.toString())
}
const printStderr = (data: Buffer) => {
process.stderr.write(data.toString())
}
child.stdout.on('data', printStdout)
child.stderr.on('data', printStderr)
child.once('close', () => {
child.stdout.off('data', printStdout)
child.stderr.off('data', printStderr)
})
}
async function waitFor(child: ChildProcess) {
return new Promise((resolve) => {
child.once('close', () => resolve())
})
}
+13 -13
View File
@@ -1,8 +1,8 @@
import { Menu, app, BrowserWindow, webContents, MenuItemConstructorOptions, MenuItem } from 'electron'
import { Menu, app, BrowserWindow, webContents } from 'electron'
import openAboutWindow from 'about-window'
import * as path from 'path'
const applicationMenu: MenuItemConstructorOptions = {
const applicationMenu = {
label: 'Application',
submenu: [
{
@@ -23,7 +23,7 @@ const applicationMenu: MenuItemConstructorOptions = {
{
label: 'Dev Tools',
accelerator: 'CmdOrCtrl+Alt+I',
role: 'toggledevtools' as 'toggledevtools',
role: 'toggleDevTools',
},
{
label: 'Quit',
@@ -35,46 +35,46 @@ const applicationMenu: MenuItemConstructorOptions = {
],
}
const editMenu: MenuItemConstructorOptions = {
const editMenu = {
label: 'Edit',
submenu: [
{
label: 'Undo',
accelerator: 'CmdOrCtrl+Z',
role: 'undo',
selector: 'undo:',
},
{
label: 'Redo',
accelerator: 'Shift+CmdOrCtrl+Z',
role: 'redo',
selector: 'redo:',
},
{
type: 'separator',
type: 'separator' as 'separator',
},
{
label: 'Cut',
accelerator: 'CmdOrCtrl+X',
role: 'cut',
selector: 'cut:',
},
{
label: 'Copy',
accelerator: 'CmdOrCtrl+C',
role: 'copy',
selector: 'copy:',
},
{
label: 'Paste',
accelerator: 'CmdOrCtrl+V',
role: 'paste',
selector: 'paste:',
},
{
label: 'Select All',
accelerator: 'CmdOrCtrl+A',
role: 'selectall',
selector: 'selectAll:',
},
],
}
const viewMenu: MenuItemConstructorOptions = {
const viewMenu = {
label: 'View',
submenu: [
{
@@ -114,7 +114,7 @@ const viewMenu: MenuItemConstructorOptions = {
],
}
const template: any = [
const template = [
applicationMenu,
editMenu,
viewMenu,
+7 -18
View File
@@ -1,4 +1,3 @@
import * as fs from 'fs'
import * as log from 'electron-log'
import * as path from 'path'
import ConfigStorage from '../backend/src/ConfigStorage'
@@ -8,28 +7,18 @@ import { ConnectionManager, updateNotifier } from '../backend/src/index'
import { electronTelemetryFactory } from 'electron-telemetry'
import { menuTemplate } from './MenuTemplate'
import { UpdateInfo } from '../events'
import { BuildInfo } from 'electron-telemetry/build/Model';
const isDev = require('electron-is-dev')
let electronTelemetry: any
if (!isDev) {
let buildOptions: BuildInfo = ({ platform: 'unknown', package: 'unknown' } as any)
try {
const options = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'buildOptions.json')).toString())
if (typeof options.platform === 'string' && typeof options.package === 'string') {
buildOptions = options
}
} catch (error) {
console.log(error)
}
console.log(buildOptions)
const electronTelemetry = electronTelemetryFactory('9b0c8ca04a361eb8160d98c5', buildOptions)
electronTelemetry = electronTelemetryFactory('9b0c8ca04a361eb8160d98c5')
}
// const isDebugEnabled = Boolean(process.argv.find(arg => arg === 'debug'))
const isDebugEnabled = Boolean(process.argv.find(arg => arg === 'debug'))
const runningUiTestOnCi = Boolean(process.argv.find(arg => arg === '--runningUiTestOnCi'))
require('electron-debug')({ enabled: isDebugEnabled })
autoUpdater.logger = log
log.info('App starting...')
@@ -47,8 +36,8 @@ function createWindow() {
const iconPath = path.join(__dirname, 'icon.png')
// Create the browser window.
mainWindow = new BrowserWindow({
width: 1024,
height: 720,
width: runningUiTestOnCi ? 1280 : 1024,
height: runningUiTestOnCi ? 720 : 700,
show: false,
webPreferences: {
nodeIntegration: true,
-3
View File
@@ -1,3 +0,0 @@
export const Control = '\uE009'
export const Unknown = '\uE000'
export const Plus = '\uE025'
+10 -12
View File
@@ -87,19 +87,17 @@ function generateData(client: mqtt.MqttClient) {
let state = true
intervals.push(setInterval(() => {
state = !state
const js = {
tags:{
entityId: 33512,
entityType: 'person',
host: 'd44ad81e10f9',
server: 'http://localhost/dataActuality',
status: state ? 'live' : 'inactive',
},
timestamp: Date.now(),
}
const enitityId = Math.round(Math.random() * 3000)
client.publish(
'actuality/showcase',
JSON.stringify(js),
'actuality/showcase', `{
"tags":{
"entityId":${enitityId},
"entityType":"person",
"host":"d44ad81e10f9",
"server":"${state ? 'http://localhost/dataActuality' : 'http://localhost/dataStorage' }",
"status":"${state ? 'live' : 'inactive'}"},
"timestamp":${Date.now()}
}`.replace(/\s/g, ''),
{ retain: true, qos: 0 },
)
}, 2102))
@@ -0,0 +1,14 @@
import { clickOn, sleep, writeText, expandTopic, hideText } from '../util'
import { Browser } from 'webdriverio'
// Expects a topic with at least two messages to be selected
export async function compareJsonSideBySide(browser: Browser<void>) {
const firstEntry = await browser.$('//span[contains(text(), "History")]/../../div/div[1]/div')
const secondEntry = await browser.$('//span[contains(text(), "History")]/../../div/div[2]/div')
await clickOn(secondEntry, browser)
await sleep(2000)
await clickOn(firstEntry, browser)
await sleep(2000)
await clickOn(firstEntry, browser)
await sleep(1000)
}
+10 -1
View File
@@ -1,4 +1,4 @@
import { clickOn, writeTextToInput } from '../util'
import { clickOn, sleep, writeText } from '../util'
import { Browser } from 'webdriverio'
export async function connectTo(host: string, browser: Browser<void>) {
@@ -9,3 +9,12 @@ export async function connectTo(host: string, browser: Browser<void>) {
const connectButton = await browser.$('//button/span[contains(text(),"Connect")]')
clickOn(connectButton, browser)
}
async function writeTextToInput(name: string, text: string, browser: Browser<void>, wait: boolean = true) {
const input = await browser.$(`//label[contains(text(), "${name}")]/..//input`)
await clickOn(input, browser, 1)
wait && await sleep(500)
input.clearValue()
wait && await sleep(300)
await writeText(text, browser)
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { clickOn } from '../util'
import { clickOn, sleep, writeText, expandTopic } from '../util'
import { Browser } from 'webdriverio'
export async function copyTopicToClipboard(browser: Browser<void>) {
@@ -1,29 +0,0 @@
import { clickOn, writeTextToInput, sleep } from '../util'
import { Browser } from 'webdriverio'
export async function showAdvancedConnectionSettings(browser: Browser<void>) {
const advancedSettingsButton = await browser.$('//button/span[contains(text(),"Advanced")]')
const addButton = await browser.$('//button/span[contains(text(),"Add")]')
await clickOn(advancedSettingsButton, browser)
await writeTextToInput('Subscription', 'garden/#', browser, false)
await clickOn(addButton, browser)
await writeTextToInput('Subscription', 'livingroom/#', browser, false)
await clickOn(addButton, browser)
await deleteFirstSubscribedTopic(browser)
await deleteFirstSubscribedTopic(browser)
await sleep(1000)
const backButton = await browser.$('//button/span[contains(text(),"Back")]')
await clickOn(backButton, browser)
const connectButton = await browser.$('//button/span[contains(text(),"Connect")]')
await clickOn(connectButton, browser)
}
async function deleteFirstSubscribedTopic(browser: Browser<void>) {
const deleteButton = await browser.$('.advanced-connection-settings-topic-list button')
await clickOn(deleteButton, browser)
}
-31
View File
@@ -1,31 +0,0 @@
import { Browser } from 'webdriverio'
import {
clickOn,
sleep,
} from '../util'
export async function showJsonFormatting(browser: Browser<void>) {
const editor = await browser.$('//*[contains(@class, "ace_editor")]')
const formatJsonButton = await browser.$('#sidebar-publish-format-json')
const payloadInput = await browser.$('//*[contains(@class, "ace_text-input")]')
await clickOn(editor, browser)
await browser.keys(['\uE009', 'A']) // Ctrl + A
await browser.keys(['\uE000']) // End keyboard modifier
await browser.keys(['\uE003']) // Backspace
await sleep(500)
await writeTextPayload(payloadInput, '{"action": "setState", "state": "on" }')
await sleep(300)
await clickOn(formatJsonButton, browser)
await sleep(1200)
const sidebarDrawer = await browser.$('#Sidebar')
await sidebarDrawer.scrollIntoView()
}
async function writeTextPayload(payloadInput: any, text: string) {
const chars = text.split('')
for (const char of chars) {
await payloadInput.setValue(char)
await sleep(10)
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
import { clickOn, sleep, writeText, expandTopic } from '../util'
import { Browser } from 'webdriverio'
import { expandTopic, sleep } from '../util'
export async function showJsonPreview(browser: Browser<void>) {
await expandTopic('actuality/showcase', browser)
@@ -1,21 +0,0 @@
import { clickOn, sleep, showText } from '../util'
import { Browser } from 'webdriverio'
// Expects a topic with at least two messages to be selected
export async function showOffDiffCapability(browser: Browser<void>) {
await showText('Compare messages', 2000, browser, 'top')
await showText('Show raw message', 2000, browser, 'bottom')
const rawMessage = await browser.$('#valueRendererDisplayMode-raw')
await clickOn(rawMessage, browser)
await sleep(1000)
await showText('Compare with others', 2000, browser, 'bottom')
const diffMessages = await browser.$('#valueRendererDisplayMode-diff')
await clickOn(diffMessages, browser)
// const firstEntry = await browser.$('//span[contains(text(), "History")]/../../div/div[1]/div')
const secondEntry = await browser.$('//span[contains(text(), "History")]/../../div/div[2]/div')
await clickOn(secondEntry, browser)
await sleep(2000)
}
-11
View File
@@ -1,11 +0,0 @@
import { sleep, showKeys, showText } from '../util'
import { Browser } from 'webdriverio'
export async function showZoomLevel(browser: Browser<void>) {
await showKeys('Zoom in', 2000, browser, 'top', ['Ctrl', '+'])
await sleep(2000)
await showKeys('Zoom out', 2000, browser, 'middle', ['Ctrl', '-'])
await sleep(2000)
await showKeys('Reset zoom level', 2000, browser, 'bottom', ['Ctrl', 'Zero'])
await sleep(2000)
}
+69 -28
View File
@@ -1,4 +1,4 @@
import { Browser, Element } from 'webdriverio'
import { Element, Browser } from 'webdriverio'
export { expandTopic } from './expandTopic'
export function sleep(ms: number, required = false) {
@@ -26,28 +26,47 @@ export async function delteTextWithBackspaces(element: Element<void>, browser: B
}
}
export async function writeTextToInput(name: string, text: string, browser: Browser<void>, wait: boolean = true) {
const input = await browser.$(`//label[contains(text(), "${name}")]/..//input`)
await clickOn(input, browser, 1)
wait && await sleep(500)
input.clearValue()
wait && await sleep(300)
await writeText(text, browser)
}
export async function moveToCenterOfElement(element: Element<void>, browser: Browser<void>) {
const { x, y } = await element.getLocation()
const { width, height } = await element.getSize()
const targetX = x + width / 2
const targetY = y + height / 2
const js = `{
const targetX = ${x + width / 2}
const targetY = ${y + height / 2}
const duration = 500
const duration = 500
const maxStepSize = 10
const e = document.getElementById("bier")
const top = parseFloat(e.style.top)
const left = parseFloat(e.style.left)
const deltaY = targetY - top
const deltaX = targetX - left
const js = `window.demo.moveMouse(${targetX}, ${targetY}, ${duration});`
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY)
const steps = Math.ceil(distance / maxStepSize)
const stepX = deltaX / steps
const stepY = deltaY / steps
let currentStep = 0
function getCloser() {
e.style.left = String(left + (stepX * currentStep)) + 'px'
e.style.top = String(top + (stepY * currentStep)) + 'px'
if (currentStep < steps) {
setTimeout(() => {
currentStep += 1
if (currentStep === steps) {
e.style.left = String(targetX + 5) + 'px'
e.style.top = String(targetY + 1) + 'px'
} else {
getCloser()
}
}, duration/steps)
}
}
getCloser()
}`
await browser.execute(js)
await sleep(duration + 500, true)
await sleep(550, true)
await element.moveTo()
}
@@ -65,25 +84,47 @@ export async function clickOn(element: Element<void>, browser: Browser<void>, cl
}
export async function createFakeMousePointer(browser: Browser<void>) {
const js = 'window.demo.enableMouse();'
const addCursorImage = 'const i=document.createElement("img");'
+ 'i.src="../cursor.png";'
+ 'i.width="32";'
+ 'i.height="32";'
+ 'i.id="bier";'
+ 'i.style="position: fixed; z-index:10000000; filter: invert(100%);left: 0px; top: 0px;";'
+ 'document.body.appendChild(i)'
await browser.execute(js)
await browser.execute(addCursorImage)
}
export async function showText(text: string, duration: number = 0, browser: Browser<void>, location: 'top' | 'bottom' | 'middle' = 'bottom', keys = []) {
const js = `window.demo.showMessage('${text}', '${location}', ${duration});`
browser.execute(js)
}
export async function showKeys(text: string, duration: number = 0, browser: Browser<void>, location: 'top' | 'bottom' | 'middle' = 'bottom', keys: string[] = []) {
const js = `window.demo.showMessage('${text}', '${location}', ${duration}, ${JSON.stringify(keys)});`
export async function showText(text: string, duration: number = 0, browser: Browser<void>, location: 'top' | 'bottom' | 'middle' = 'bottom') {
const positions = {
top: 0,
bottom: -65,
middle: -32,
}
const js = `
const postition = "${positions[location]}vh"
let previousDiv = document.getElementById('tests-text-overlay')
previousDiv && previousDiv.remove()
let div = document.createElement('div')
div.id = "tests-text-overlay"
div.style = "background-color: rgba(0, 0, 0, 0.8);position: fixed;left: 5vw;z-index: 1000000;margin: 30vw auto 50vw;border-radius: 16px;right: 5vw;bottom: "+ postition +";"
let div2 = document.createElement('div')
div2.style = "text-align: center;font-size: 4em;color: white;"
div2.innerHTML = \`${text}\`
div.appendChild(div2)
document.body.appendChild(div)
if (${duration} > 0) {
setTimeout(() => div.remove(), ${duration})
}
`
browser.execute(js)
}
export async function hideText(browser: Browser<void>) {
const js = 'window.demo.hideMessage();'
const js = `
let previousDiv = document.getElementById('tests-text-overlay')
previousDiv && previousDiv.remove()
`
browser.execute(js)
await sleep(600)
}
+26 -49
View File
@@ -1,43 +1,35 @@
import * as os from 'os'
import * as webdriverio from 'webdriverio'
import mockMqtt, { stop } from './mock-mqtt'
import { clearOldTopics } from './scenarios/clearOldTopics'
import { clearSearch, searchTree } from './scenarios/searchTree'
import { connectTo } from './scenarios/connect'
import { copyTopicToClipboard } from './scenarios/copyTopicToClipboard'
import { copyValueToClipboard } from './scenarios/copyValueToClipboard'
import { disconnect } from './scenarios/disconnect'
import { publishTopic } from './scenarios/publishTopic'
import { showJsonFormatting } from './scenarios/showJsonFormatting'
import { showJsonPreview } from './scenarios/showJsonPreview'
import { showMenu } from './scenarios/showMenu'
import { showNumericPlot } from './scenarios/showNumericPlot'
import { showOffDiffCapability } from './scenarios/showOffDiffCapability'
import { showZoomLevel } from './scenarios/showZoomLevel'
import { showAdvancedConnectionSettings } from './scenarios/showAdvancedConnectionSettings'
import {
clickOnHistory,
createFakeMousePointer,
hideText,
showText,
sleep,
} from './util'
process.on('unhandledRejection', (error: Error) => {
console.error('unhandledRejection', error.message, error.stack)
process.exit(1)
})
import * as webdriverio from 'webdriverio'
import * as os from 'os'
import mockMqtt, { stop } from './mock-mqtt'
import { connectTo } from './scenarios/connect'
import { disconnect } from './scenarios/disconnect'
import { showNumericPlot } from './scenarios/showNumericPlot'
import { showJsonPreview } from './scenarios/showJsonPreview'
import { compareJsonSideBySide } from './scenarios/compareJsonSideBySide'
import { searchTree, clearSearch } from './scenarios/searchTree'
import { copyTopicToClipboard } from './scenarios/copyTopicToClipboard'
import { copyValueToClipboard } from './scenarios/copyValueToClipboard'
import { publishTopic } from './scenarios/publishTopic'
import { clearOldTopics } from './scenarios/clearOldTopics'
import { showMenu } from './scenarios/showMenu'
import { createFakeMousePointer, sleep, showText, hideText, clickOnHistory } from './util'
const binary = os.platform() === 'darwin' ? 'Electron.app/Contents/MacOS/Electron' : 'electron'
const runningUiTestOnCi = os.platform() === 'darwin' ? [] : ['--runningUiTestOnCi']
console.log(`${__dirname}/../../../node_modules/.bin/electron`)
const options = {
host: '127.0.0.1', // Use localhost as chrome driver server
host: 'localhost', // Use localhost as chrome driver server
port: 9515, // "9515" is the port opened by chrome driver.
capabilities: {
browserName: 'electron',
chromeOptions: {
binary: `${__dirname}/../../../node_modules/.bin/electron`,
binary: `${__dirname}/../../../node_modules/electron/dist/${binary}`,
args: [`--app=${__dirname}/../../..`, '--force-device-scale-factor=1', '--no-sandbox', '--disable-dev-shm-usage', '--disable-extensions'].concat(runningUiTestOnCi),
},
windowTypes: ['app', 'webview'],
@@ -45,19 +37,15 @@ const options = {
}
async function doStuff() {
console.log('mock mqtt')
await mockMqtt()
console.log('start webdriver')
const browser = await webdriverio.remote(options)
await createFakeMousePointer(browser)
await connectTo('127.0.0.1', browser)
await sleep(1000)
await sleep(1000)
await showText('Topic overview', 2000, browser, 'top')
await showText('Topics overview', 2000, browser, 'top')
await sleep(2000)
await showText('Indicate topic updates', 2000, browser, 'bottom')
await sleep(3000)
@@ -66,11 +54,12 @@ async function doStuff() {
await showNumericPlot(browser)
await sleep(2000)
await showText('Formatted messages', 2000, browser, 'top')
await showJsonPreview(browser)
await showText('Formatted messages', 1500, browser, 'top')
await sleep(1500)
await sleep(2000)
await showOffDiffCapability(browser)
await showText('Compare messages', 2000, browser, 'top')
await compareJsonSideBySide(browser)
await hideText(browser)
await showText('Publish topics', 2000, browser, 'top')
@@ -78,10 +67,6 @@ async function doStuff() {
await publishTopic(browser)
await sleep(1000)
await showText('Write JSON with ease', 2000, browser, 'top')
await showJsonFormatting(browser)
await sleep(1000)
await showText('Copy to Clipboard', 2000, browser)
await copyTopicToClipboard(browser)
await hideText(browser)
@@ -106,18 +91,10 @@ async function doStuff() {
await sleep(2000)
await disconnect(browser)
await showText('Customize Subscriptions', 3000, browser, 'top')
await showAdvancedConnectionSettings(browser)
await showText('Keyboard shortcuts', 1750, browser, 'middle')
await sleep(1750)
await showZoomLevel(browser)
await showText('The End', 3000, browser, 'middle')
await sleep(3000)
browser.closeWindow()
stop()
}
+1 -1
View File
@@ -11,5 +11,5 @@
"lib": ["es2017", "dom"],
"sourceMap": true
},
"include": ["src/electron.ts", "src/spec/electron.ts", "src/spec/webdriverio.ts", "scripts/*.ts"]
"include": ["src/electron.ts", "src/spec/electron.ts", "src/spec/webdriverio.ts"]
}
+43 -5
View File
@@ -1095,6 +1095,14 @@ electron-chromedriver@~3.0.0:
electron-download "^4.1.0"
extract-zip "^1.6.5"
electron-debug@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/electron-debug/-/electron-debug-2.0.0.tgz#3059a6557acbfb091f138d83875f57bac80cea6d"
integrity sha512-orGlw9uErUztdD7cgdKz78txq3czpOnKG/zvvsINkUsugqL+dn77UFrbwRGVgPwuLJ7Ejbjjk9EcxIcgTivMbA==
dependencies:
electron-is-dev "^0.3.0"
electron-localshortcut "^3.0.0"
electron-download@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.1.tgz#02e69556705cc456e520f9e035556ed5a015ebe8"
@@ -1110,11 +1118,31 @@ electron-download@^4.1.0:
semver "^5.4.1"
sumchecker "^2.0.2"
electron-is-accelerator@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/electron-is-accelerator/-/electron-is-accelerator-0.1.2.tgz#509e510c26a56b55e17f863a4b04e111846ab27b"
integrity sha1-UJ5RDCala1Xhf4Y6SwThEYRqsns=
electron-is-dev@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-0.3.0.tgz#14e6fda5c68e9e4ecbeff9ccf037cbd7c05c5afe"
integrity sha1-FOb9pcaOnk7L7/nM8DfL18BcWv4=
electron-is-dev@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-1.0.1.tgz#6e0a184736fe7aea77d18210b0b0f6a02402c4bc"
integrity sha512-iwM3EotA9HTXqMGpQRkR/kT8OZqBbdfHTnlwcxsjSLYqY8svvsq0MuujsWCn3/vtgRmDv/PC/gKUUpoZvi5C1w==
electron-localshortcut@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/electron-localshortcut/-/electron-localshortcut-3.1.0.tgz#10c1ffd537b8d39170aaf6e1551341f7780dd2ce"
integrity sha512-MgL/j5jdjW7iA0R6cI7S045B0GlKXWM1FjjujVPjlrmyXRa6yH0bGSaIAfxXAF9tpJm3pLEiQzerYHkRh9JG/A==
dependencies:
debug "^2.6.8"
electron-is-accelerator "^0.1.0"
keyboardevent-from-electron-accelerator "^1.1.0"
keyboardevents-areequal "^0.2.1"
electron-log@^2.2.17:
version "2.2.17"
resolved "https://registry.yarnpkg.com/electron-log/-/electron-log-2.2.17.tgz#e71e2ebb949fc96ded7cdb99eeee7202e48981d2"
@@ -1147,7 +1175,7 @@ electron-publish@20.38.5:
"electron-telemetry@git+https://github.com/thomasnordquist/electron-telemetry.git#dist":
version "1.0.0"
resolved "git+https://github.com/thomasnordquist/electron-telemetry.git#3d6c0c5e3fd9d8101dcb7a0435256572ed58b2b6"
resolved "git+https://github.com/thomasnordquist/electron-telemetry.git#24792a0d8d6c2b047855acfda7d740ee4e51b97f"
dependencies:
axios "^0.18.0"
pako "^1.0.8"
@@ -1168,10 +1196,10 @@ electron-updater@^4.0.6:
semver "^5.6.0"
source-map-support "^0.5.9"
electron@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/electron/-/electron-4.0.6.tgz#f936772ddb5eaa026591ea14ef806e1385fbbda9"
integrity sha512-r2ow/EmDibjoCNJp35mB1CcPs2xBF9fp2eoWuUOJmpVQyzdba6EnqsSD1BxwXzF9vQ5WkQ2UbfXBIvpomrdioQ==
electron@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/electron/-/electron-4.0.2.tgz#b5e074c149841490825d0785aa2bc4f5e29bbea4"
integrity sha512-H0pmSvOVuC+Mq/+cYNXbCDBmqq5d1xYUVdBOjqGJuwuwJeP7qDHF35JA3cq+ARzq/CbwlXq98zdW6i6+x3U24g==
dependencies:
"@types/node" "^10.12.18"
electron-download "^4.1.0"
@@ -2062,6 +2090,16 @@ jsprim@^1.2.2:
json-schema "0.2.3"
verror "1.10.0"
keyboardevent-from-electron-accelerator@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/keyboardevent-from-electron-accelerator/-/keyboardevent-from-electron-accelerator-1.1.0.tgz#324614f6e33490c37ffc5be5876b3e85fe223c84"
integrity sha512-VDC4vKWGrR3VgIKCE4CsXnvObGgP8C2idnTKEMUkuEuvDGE1GEBX9FtNdJzrD00iQlhI3xFxRaeItsUmlERVng==
keyboardevents-areequal@^0.2.1:
version "0.2.2"
resolved "https://registry.yarnpkg.com/keyboardevents-areequal/-/keyboardevents-areequal-0.2.2.tgz#88191ec738ce9f7591c25e9056de928b40277194"
integrity sha512-Nv+Kr33T0mEjxR500q+I6IWisOQ0lK1GGOncV0kWE6n4KFmpcu7RUX5/2B0EUtX51Cb0HjZ9VJsSY3u4cBa0kw==
latest-version@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15"