Compare commits

..
Author SHA1 Message Date
Thomas Nordquist 91486718d8 Fix electron downgrade 2019-03-03 00:55:15 +01:00
Thomas Nordquist 55dd79aebd Change recording resolution 2019-03-03 00:13:56 +01:00
Thomas Nordquist 7141dfa9fc Downgrade electron 2019-03-02 23:59:52 +01:00
Thomas Nordquist f16d0cd271 Clean dependencies 2019-03-02 23:37:04 +01:00
Thomas Nordquist 838e2fa287 Update electron 2019-03-02 23:21:07 +01:00
Thomas Nordquist b9439c3244 Add switch for diffs 2019-03-02 23:20:02 +01:00
Thomas Nordquist 410855e8a0 Fix app build 2019-03-02 21:23:49 +01:00
Thomas Nordquist 71d606d7cd Fix compile errors 2019-03-02 19:57:29 +01:00
Thomas Nordquist a32810417d Fix store logos 2019-03-02 16:08:40 +01:00
Thomas Nordquist 4fcf767079 Change windows store color theme 2019-02-28 15:50:14 +01:00
Thomas Nordquist 1e6fa11f0f Remove Appveyor deploy rule 2019-02-27 17:23:27 +01:00
Thomas Nordquist 9644952d81 Always build appx 2019-02-27 16:15:44 +01:00
Thomas Nordquist d1cd299132 Add appx store logos 2019-02-27 16:14:32 +01:00
Thomas Nordquist e3539015f7 Add appveyor badge 2019-02-26 23:44:52 +01:00
Thomas Nordquist 874cedb422 Add appveyor support (#75)
* Add appveyor build badge
2019-02-26 23:43:53 +01:00
Thomas Nordquist 63530a41ac Fix topic selector xpath 2019-02-25 17:58:24 +01:00
Thomas Nordquist d3bf5e87e1 Clean up dependencies 2019-02-25 17:30:41 +01:00
Thomas Nordquist 10c6c3cecd Add changelog 2019-02-25 17:08:41 +01:00
Thomas Nordquist 4d8fa72491 Refactor 2019-02-25 17:07:49 +01:00
Thomas Nordquist b9c0a66948 Refactor value rendering 2019-02-25 17:07:29 +01:00
Thomas Nordquist 6b859d31e8 Fix loading client id conenction setting
fixes #71
2019-02-25 16:40:43 +01:00
Thomas Nordquist 4fd328e716 Add zoom menu and shortcuts 2019-02-25 13:52:07 +01:00
Thomas Nordquist b606f14836 Clean imports & modules 2019-02-25 12:04:41 +01:00
Thomas Nordquist ad49c5c819 Update diff view 2019-02-25 11:57:44 +01:00
Thomas Nordquist 749a591465 Fix message diffing 2019-02-24 11:46:35 +01:00
Thomas Nordquist a1b1c92648 Remove mouse-over to select topics 2019-02-24 11:38:00 +01:00
Thomas Nordquist 85c6c4ebbc Fix message history comparison selection 2019-02-23 23:26:52 +01:00
Thomas Nordquist de7c9479c9 Highlight selected history entry 2019-02-23 23:16:47 +01:00
Thomas Nordquist 138f51eb39 Add diff view for received messages 2019-02-23 22:49:42 +01:00
Thomas Nordquist 107ca83882 Remove obsolete import 2019-02-23 22:35:20 +01:00
Thomas Nordquist 839ad531ba Fix RingBuffer array type 2019-02-23 22:35:03 +01:00
Thomas Nordquist 174eb0c767 Fix leaking connections 2019-02-23 22:34:32 +01:00
Thomas Nordquist 953422dcd4 Show travis-ci master build status 2019-02-22 12:08:36 +01:00
Thomas Nordquist a4015141c3 Fix zoomFactor 2019-02-21 10:42:51 +01:00
Thomas Nordquist ca3bd0b22b Fix connection window location 2019-02-20 17:09:47 +01:00
Thomas Nordquist 797d11d58d Update video captions 2019-02-20 15:57:32 +01:00
Thomas Nordquist 8281a852ab Change ui test resolution 2019-02-20 15:54:38 +01:00
Thomas Nordquist e5c3f207d9 Update gif image scale 2019-02-20 15:49:13 +01:00
Thomas Nordquist 2efc8f4dda Record videos in 720p 2019-02-20 15:40:35 +01:00
Thomas Nordquist 60a06d8c82 Update Readme & Add snap store link 2019-02-20 15:35:51 +01:00
Thomas Nordquist 857500f9c9 Update electron builder 2019-02-20 11:27:07 +01:00
Thomas Nordquist 396e6985ce Update readme 2019-02-19 00:33:21 +01:00
Thomas Nordquist 6d75f29f03 Update readme 2019-02-19 00:09:25 +01:00
Thomas Nordquist f8cdf0837e Prepare alpha release 2019-02-18 23:48:58 +01:00
Thomas Nordquist 5bb96233d1 Fix asset upload 2019-02-18 23:48:58 +01:00
Thomas Nordquist f3ff6cde7b Fix about window icon 2019-02-18 23:48:58 +01:00
Thomas Nordquist ec88f9822d Fix tests 2019-02-18 23:48:58 +01:00
Thomas Nordquist 235e81826b Add about window 2019-02-18 23:48:58 +01:00
Thomas Nordquist 615ec17b96 Remove empty topics from tree 2019-02-18 23:48:58 +01:00
Thomas Nordquist ddc801fe93 WiP 2019-02-18 23:48:58 +01:00
Thomas Nordquist 24c6b7e7b3 Clean up 2019-02-18 23:48:58 +01:00
Thomas Nordquist 55ea381b3b WiP 2019-02-18 23:48:58 +01:00
37 changed files with 1159 additions and 884 deletions
+6
View File
@@ -0,0 +1,6 @@
# next release
- Magnify app content with "Ctrl +", "Ctrl -", "Ctrl 0"
- Add message diff view
- Make user interactions more predictable
# 0.2.0
+25 -20
View File
@@ -1,42 +1,47 @@
# [MQTT Explorer](https://thomasnordquist.github.io/MQTT-Explorer/)
[![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)](https://travis-ci.org/thomasnordquist/MQTT-Explorer)
[![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 0.1.3
### Version
See the whole picture of your broker.
- What topics are active?
- Which retained topics are abandoned?
- Where is this new device / service I connected to the broker?
A MQTT Tool to get a quick overview of your MQTT topics and integrate new devices/services more efficiently.
- Visualize topics and topic activity
- Delete retained topics
- Search/filter topics
- Delete topic recursively
- Publish topics
- Plot numeric topics
- Keep a history of each topic
MQTT-Explorer strives to be THE swiss-army-knife tool.
The perfect tool to integrate new services, IoT devices in your network.
The hierarchical view allows for a quick understanding what is going on on your broker.
The app is available for Windows ([portable](https://github.com/thomasnordquist/MQTT-Explorer/releases/download/v0.1.3/MQTT-Explorer-0.1.3.exe), [installer](https://github.com/thomasnordquist/MQTT-Explorer/releases/download/v0.1.3/MQTT-Explorer-Setup-0.1.3.exe)), Linux ([AppImage](https://github.com/thomasnordquist/MQTT-Explorer/releases/download/v0.1.3/MQTT-Explorer-0.1.3-x86_64.AppImage)) and Mac ([dmg](https://github.com/thomasnordquist/MQTT-Explorer/releases/download/v0.1.3/MQTT-Explorer-0.1.3.dmg)).
## Download
| Platform | | Downloads |
|:----------:|:-------------:|:------:|
| ![windows](https://user-images.githubusercontent.com/7721625/51445407-b4172080-1d04-11e9-8c70-d8413d1d6d8b.png) | Windows | [portable](https://github.com/thomasnordquist/MQTT-Explorer/releases/download/v0.2.0/MQTT-Explorer-0.2.0.exe), [installer](https://github.com/thomasnordquist/MQTT-Explorer/releases/download/v0.2.0/MQTT-Explorer-Setup-0.2.0.exe) |
| ![linux](https://user-images.githubusercontent.com/7721625/51445392-947ff800-1d04-11e9-8c7f-a30efb755651.png) | Linux | [AppImage](https:&#x2F;&#x2F;github.com&#x2F;thomasnordquist&#x2F;MQTT-Explorer&#x2F;releases&#x2F;download&#x2F;v0.2.0&#x2F;MQTT-Explorer-0.2.0-x86_64.AppImage)<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 | [![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 | [dmg](https:&#x2F;&#x2F;github.com&#x2F;thomasnordquist&#x2F;MQTT-Explorer&#x2F;releases&#x2F;download&#x2F;v0.2.0&#x2F;MQTT-Explorer-0.2.0.dmg) |
[More Downloads](https://github.com/thomasnordquist/MQTT-Explorer/releases)
## Screenshots
![screen1](https://user-images.githubusercontent.com/7721625/51770198-6c6a0d80-20e5-11e9-94d5-a0174634253c.png)
## Download
| Platform | | Downloads |
|:----------:|:-------------:|:------:|
| ![windows](https://user-images.githubusercontent.com/7721625/51445407-b4172080-1d04-11e9-8c70-d8413d1d6d8b.png) | Windows | [portable](https:&#x2F;&#x2F;github.com&#x2F;thomasnordquist&#x2F;MQTT-Explorer&#x2F;releases&#x2F;download&#x2F;v0.1.3&#x2F;MQTT-Explorer-0.1.3.exe), [installer](https:&#x2F;&#x2F;github.com&#x2F;thomasnordquist&#x2F;MQTT-Explorer&#x2F;releases&#x2F;download&#x2F;v0.1.3&#x2F;MQTT-Explorer-Setup-0.1.3.exe) |
| ![linux](https://user-images.githubusercontent.com/7721625/51445392-947ff800-1d04-11e9-8c7f-a30efb755651.png) | Linux | [AppImage](https:&#x2F;&#x2F;github.com&#x2F;thomasnordquist&#x2F;MQTT-Explorer&#x2F;releases&#x2F;download&#x2F;v0.1.3&#x2F;MQTT-Explorer-0.1.3-x86_64.AppImage)<br>*Run AppImage:<br>Make it executable and double-click it.* |
| ![mac](https://user-images.githubusercontent.com/7721625/51445390-921d9e00-1d04-11e9-8339-351469ef20ae.png) | Mac | [dmg](https:&#x2F;&#x2F;github.com&#x2F;thomasnordquist&#x2F;MQTT-Explorer&#x2F;releases&#x2F;download&#x2F;v0.1.3&#x2F;MQTT-Explorer-0.1.3.dmg) |
[More Downloads](https://github.com/thomasnordquist/MQTT-Explorer/releases)
## Demo
![screencast](https://user-images.githubusercontent.com/7721625/51978075-5bc0eb00-2489-11e9-953a-0965b154557b.gif)
![screencast](https://user-images.githubusercontent.com/7721625/52979302-3f073b80-33d5-11e9-9953-b70ebb349439.gif)
## Performance
This App is optimized to handle thousands of topics and at least hundreds of thousands messages per minute.
So don't worry if you got >=10k topics and >=100k messages per minute.
So don't worry if you got >=30k topics and >=100.000 messages per minute.
Future releases will allow for a more granular selection of subscribed topics to handle
One can also subscribe to custom topics insted of the "root wildcard #", therefore limiting the amount of messages.
## Develop
PRs and issues are welcome
+24 -20
View File
@@ -1,43 +1,47 @@
# [MQTT Explorer](https://thomasnordquist.github.io/MQTT-Explorer/)
[![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)](https://travis-ci.org/thomasnordquist/MQTT-Explorer)
![](https://img.shields.io/github/commits-since/thomasnordquist/mqtt-explorer/latest.svg?style=flat)
[![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 {{ version }}
See the whole picture of your broker.
- What topics are active?
- Which retained topics are abandoned?
- Where is this new device / service I connected to the broker?
A MQTT Tool to get a quick overview of your MQTT topics and integrate new devices/services more efficiently.
- Visualize topics and topic activity
- Delete retained topics
- Search/filter topics
- Delete topic recursively
- Publish topics
- Plot numeric topics
- Keep a history of each topic
MQTT-Explorer strives to be THE swiss-army-knife tool.
The perfect tool to integrate new services, IoT devices in your network.
The hierarchical view allows for a quick understanding what is going on on your broker.
The app is available for Windows ({{windowsTargets}}), Linux ({{linuxTargets}}) and Mac ({{macTargets}}).
## Download
| 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}} |
| ![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) |
| ![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)
## Screenshots
![screen1](https://user-images.githubusercontent.com/7721625/51770198-6c6a0d80-20e5-11e9-94d5-a0174634253c.png)
## Download
| Platform | | Downloads |
|:----------:|:-------------:|:------:|
| ![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.* |
| ![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)
## Demo
![screencast](https://user-images.githubusercontent.com/7721625/51978075-5bc0eb00-2489-11e9-953a-0965b154557b.gif)
![screencast](https://user-images.githubusercontent.com/7721625/52979302-3f073b80-33d5-11e9-9953-b70ebb349439.gif)
## Performance
This App is optimized to handle thousands of topics and at least hundreds of thousands messages per minute.
So don't worry if you got >=10k topics and >=100k messages per minute.
So don't worry if you got >=30k topics and >=100.000 messages per minute.
Future releases will allow for a more granular selection of subscribed topics to handle
One can also subscribe to custom topics insted of the "root wildcard #", therefore limiting the amount of messages.
## Develop
PRs and issues are welcome
+9 -4
View File
@@ -9,10 +9,13 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@material-ui/core": "^4.0.0-alpha.0",
"@material-ui/icons": "^3.0.1",
"@material-ui/styles": "^3.0.0-alpha.8",
"@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",
"@types/diff": "^4.0.1",
"@types/node": "^10.12.18",
"@types/prismjs": "^1.9.1",
"@types/react": "^16.7.18",
"@types/react-dom": "^16.0.11",
"@types/react-redux": "^6.0.12",
@@ -26,6 +29,7 @@
"compare-versions": "^3.4.0",
"copy-text-to-clipboard": "^1.0.4",
"css-loader": "^2.1.0",
"diff": "^4.0.1",
"electron-nucleus": "^1.11.0",
"electron-telemetry": "git+https://github.com/thomasnordquist/electron-telemetry.git#dist",
"html-webpack-plugin": "^4.0.0-beta.5",
@@ -34,16 +38,17 @@
"lodash.throttle": "^4.1.1",
"moving-average": "^1.0.0",
"number-abbreviate": "^2.0.0",
"prismjs": "^1.15.0",
"react": "16.8",
"react-ace": "^6.3.2",
"react-dom": "^16.7.0",
"react-json-view": "^1.19.1",
"react-redux": "^6.0.0",
"react-resize-detector": "^3.4.0",
"react-split-pane": "^0.1.85",
"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",
+1 -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,7 +18,6 @@ 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
+16
View File
@@ -51,6 +51,22 @@ 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,
+82
View File
@@ -0,0 +1,82 @@
import * as diff from 'diff'
import * as Prism from 'prismjs'
import * as React from 'react'
import { Theme, withStyles } from '@material-ui/core'
import 'prismjs/components/prism-json'
import 'prismjs/themes/prism-tomorrow.css'
interface Props {
previous: string
current: string
language?: 'json'
classes: any
}
class CodeDiff extends React.Component<Props, {}> {
constructor(props: Props) {
super(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
const code = changes.map((change, key) => {
const hasStyledCode = change.removed !== true
const changedLines = change.count || 0
if (hasStyledCode && this.props.language === 'json') {
const html = styledLines.slice(lineNumber, lineNumber + changedLines).join('\n')
lineNumber += changedLines
return <div key={key}><span className={this.cssClassForChange(change)} dangerouslySetInnerHTML={{ __html: html }} /></div>
}
return <div key={key}><span className={this.cssClassForChange(change)}>{change.value}</span></div>
})
return (
<pre className={`language-json ${this.props.classes.codeBlock}`}>
{code}
</pre>
)
}
private cssClassForChange(change: diff.Change) {
if (change.added === true) {
return this.props.classes.addition
}
if (change.removed === true) {
return this.props.classes.deletion
}
return this.props.classes.noChange
}
}
const style = (theme: Theme) => {
const baseStyle = {
width: '100%',
}
return {
codeBlock: {
fontSize: '12px',
maxHeight: '200px',
},
noChange: {
...baseStyle,
},
deletion: {
...baseStyle,
backgroundColor: 'rgba(255, 10, 10, 0.3)',
},
addition: {
...baseStyle,
backgroundColor: 'rgba(10, 255, 10, 0.3)',
},
}
}
export default withStyles(style)(CodeDiff)
@@ -69,7 +69,7 @@ class ConnectionSettings extends React.Component<Props, State> {
className={classes.topicList}
component="nav"
>
<div className={this.props.classes.list}>
<div className={classes.list}>
{this.renderSubscriptions()}
</div>
</List>
@@ -79,6 +79,7 @@ class ConnectionSettings extends React.Component<Props, State> {
className={classes.fullWidth}
label="MQTT Client ID"
margin="normal"
value={this.props.connection.clientId}
onChange={this.handleChange('clientId')}
/>
</Grid>
@@ -71,16 +71,17 @@ class ConnectionSetup extends React.Component<Props, {}> {
}
}
const connectionHeight = '440px'
const styles = (theme: Theme) => ({
title: {
color: theme.palette.text.primary,
whiteSpace: 'nowrap' as 'nowrap',
},
root: {
margin: '13vw auto 0 auto',
margin: `calc((100vh - ${connectionHeight}) / 2) auto 0 auto`,
minWidth: '800px',
maxWidth: '850px',
height: '440px',
height: connectionHeight,
outline: 'none' as 'none',
display: 'flex' as 'flex',
},
+5 -4
View File
@@ -6,6 +6,7 @@ import { Theme, withStyles } from '@material-ui/core/styles'
interface HistoryItem {
title: JSX.Element | string
value: string | any
selected?: boolean
}
interface Props {
@@ -28,18 +29,18 @@ class MessageHistory extends React.Component<Props, State> {
}
public renderHistory() {
const style = {
backgroundColor: 'rgba(80, 80, 80, 0.6)',
const style = (element: HistoryItem) => ({
backgroundColor: element.selected ? 'rgba(120, 120, 120, 0.6)' : 'rgba(80, 80, 80, 0.6)',
margin: '8px',
padding: '8px 8px 0 8px',
cursor: this.props.onClick ? 'pointer' : 'inherit',
}
})
const messageStyle: React.CSSProperties = { textOverflow: 'ellipsis', whiteSpace: 'nowrap', overflow: 'hidden' }
const elements = this.props.items.map((element, index) => (
<div
key={index}
style={style}
style={style(element)}
onClick={(event: React.MouseEvent) => this.props.onClick && this.props.onClick(index, event.target)}
>
<div><i>{element.title}</i></div>
@@ -11,6 +11,7 @@ const throttle = require('lodash.throttle')
interface Props {
node?: q.TreeNode<TopicViewModel>
selected?: q.Message
onSelect: (message: q.Message) => void
}
@@ -52,6 +53,7 @@ class MessageHistory extends React.Component<Props, State> {
const historyElements = history.reverse().map(message => ({
title: <DateFormatter date={message.received} />,
value: message.value,
selected: message && message === this.props.selected,
}))
const numericMessages = history.filter(message => !isNaN(parseFloat(message.value)))
@@ -79,8 +81,10 @@ class MessageHistory extends React.Component<Props, State> {
}
private displayMessage = (index: number, eventTarget: EventTarget) => {
const message = this.props.node && this.props.node.messageHistory.toArray()[index]
this.props.onSelect(message)
const message = this.props.node && this.props.node.messageHistory.toArray().reverse()[index]
if (message) {
this.props.onSelect(message)
}
}
}
+53 -28
View File
@@ -1,6 +1,7 @@
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'
@@ -9,23 +10,23 @@ 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 { default as ReactResizeDetector } from 'react-resize-detector'
import { sidebarActons } from '../../actions'
import { settingsActions, 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,10 +38,12 @@ const Publish = React.lazy(() => import('./Publish/Publish'))
const ValueRenderer = React.lazy(() => import('./ValueRenderer'))
interface Props {
node?: q.TreeNode<TopicViewModel>,
actions: typeof sidebarActons,
classes: any,
connectionId?: string,
node?: q.TreeNode<TopicViewModel>
actions: typeof sidebarActons
settingsActions: typeof settingsActions
valueRendererDisplayMode: ValueRendererDisplayMode
classes: any
connectionId?: string
}
interface State {
@@ -139,6 +142,27 @@ 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 value={this.props.valueRendererDisplayMode} exclusive={true} onChange={handleValue}>
<ToggleButton value="diff">
<Tooltip title="Show difference between the current and the last message">
<Code />
</Tooltip>
</ToggleButton>
<ToggleButton value="raw">
<Tooltip title="Raw value">
<Reorder />
</Tooltip>
</ToggleButton>
</ToggleButtonGroup>
)
}
private renderNode() {
const { classes, node } = this.props
@@ -164,15 +188,11 @@ class Sidebar extends React.Component<Props, State> {
<ExpansionPanelDetails style={this.detailsStyle}>
{this.messageMetaInfo()}
<div ref={this.valueRef}>
<React.Suspense fallback={<div>Loading...</div>}>
<ReactResizeDetector handleWidth={true} onResize={this.valueRenderWidthChange} />
<ValueRenderer message={this.props.node && this.props.node.message} />
</React.Suspense>
<React.Suspense fallback={<div>Loading...</div>}>
{this.renderValue()}
</React.Suspense>
</div>
<div><MessageHistory onSelect={this.handleMessageHistorySelect} node={this.props.node} /></div>
<Popper open={Boolean(this.state.compareMessage)} anchorEl={this.valueRef.current} placement="left" transition={true}>
{this.showValueComparison}
</Popper>
<div><MessageHistory onSelect={this.handleMessageHistorySelect} selected={this.state.compareMessage} node={this.props.node} /></div>
</ExpansionPanelDetails>
</ExpansionPanel>
<ExpansionPanel defaultExpanded={true}>
@@ -195,17 +215,19 @@ class Sidebar extends React.Component<Props, State> {
)
}
private valueRenderWidthChange = (width: number) => {
this.setState({ valueRenderWidth: width })
}
private renderValue() {
const node = this.props.node
if (!node || !node.message) {
return null
}
private showValueComparison = (a: any) => (
<Fade {...a.TransitionProps} timeout={350}>
<Paper style={{ maxWidth: this.state.valueRenderWidth }}>
<ValueRenderer message={this.state.compareMessage} />
</Paper>
</Fade>
)
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) {
@@ -227,8 +249,9 @@ class Sidebar extends React.Component<Props, State> {
)
return (
<div style={{ width: '100%', display: 'flex' }}>
<div style={{ width: '100%', display: 'flex', paddingLeft: '8px' }}>
<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>
@@ -261,12 +284,14 @@ 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),
}
}
+78 -37
View File
@@ -1,66 +1,107 @@
import * as React from 'react'
import * as q from '../../../../backend/src/Model'
import { Theme, withTheme } from '@material-ui/core/styles'
import { default as ReactJson } from 'react-json-view'
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'
interface Props {
message?: q.Message
theme: Theme
message: q.Message
messageHistory: q.RingBuffer<q.Message>
compareWith?: q.Message
renderMode: ValueRendererDisplayMode
}
class ValueRenderer extends React.Component<Props, {}> {
interface State {
width: number
}
class ValueRenderer extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { width: 0 }
}
public render() {
return <div style={{ padding: '8px 0px 8px 8px' }}>{this.renderValue()}</div>
return (
<div style={{ padding: '8px 0px 8px 8px' }}>
<ReactResizeDetector handleWidth={true} onResize={this.updateWidth} />
{this.renderValue()}
</div>
)
}
public renderValue() {
const { message } = this.props
if (!message) {
return null
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
}
let json
try {
json = JSON.parse(message.value)
} catch (error) {
return this.renderRawValue(message.value)
return this.renderRawValue(message.value, compareMessage.value)
}
if (typeof json === 'string') {
return this.renderRawValue(message.value)
return this.renderRawValue(message.value, compareMessage.value)
} else if (typeof json === 'number') {
return this.renderRawValue(message.value)
return this.renderRawValue(message.value, compareMessage.value)
} else if (typeof json === 'boolean') {
return this.renderRawValue(message.value)
return this.renderRawValue(message.value, compareMessage.value)
} else {
const theme = (this.props.theme.palette.type === 'dark') ? 'monokai' : 'bright:inverted'
return (
<ReactJson
style={{ width: '100%', overflowY: 'scroll', wordBreak: 'break-all' }}
src={json}
name={null}
theme={theme}
/>
)
const current = this.messageToPrettyJson(message) || message.value
const compare = this.messageToPrettyJson(compareMessage) || compareMessage.value
const language = current && compare ? 'json' : undefined
return this.renderDiff(current, compare, language)
}
}
private renderRawValue(value: string) {
const style: React.CSSProperties = {
padding: '8px 12px 8px 12px',
backgroundColor: 'rgba(100, 100, 100, 0.55)',
wordBreak: 'break-all',
width: '100%',
overflow: 'auto hidden',
display: 'block',
lineHeight: '1.2em',
color: this.props.theme.palette.text.primary,
private renderDiff(current: string = '', previous: string = '', language?: 'json') {
return (
<CodeDiff
previous={previous}
current={current}
language={language}
/>
)
}
private messageToPrettyJson(message?: q.Message): string | undefined {
if (!message || !message.value) {
return undefined
}
return <pre style={style}><code>{value}</code></pre>
try {
const json = JSON.parse(message.value)
return JSON.stringify(json, undefined, ' ')
} catch {
return undefined
}
}
private updateWidth = (width: number) => {
if (width !== this.state.width) {
this.setState({ width })
}
}
private renderRawValue(value: string, compare: string) {
return this.renderDiff(value, compare)
}
}
export default withTheme()(ValueRenderer)
const mapStateToProps = (state: AppState) => {
return {
renderMode: state.settings.valueRendererDisplayMode,
}
}
export default connect(mapStateToProps)(ValueRenderer)
+32 -19
View File
@@ -29,6 +29,9 @@ const styles = (theme: Theme) => {
cursor: 'pointer',
marginTop: '-1px',
},
subnodes: {
marginLeft: theme.spacing(1.5),
},
selected: {
backgroundColor: 'rgba(170, 170, 170, 0.55)',
},
@@ -187,23 +190,28 @@ class TreeNode extends React.Component<Props, State> {
const highlightClass = this.state.selected ? this.props.classes.selected : (this.state.mouseOver ? this.props.classes.hover : '')
return (
<div
key={this.props.treeNode.hash()}
className={`${classes.node} ${this.props.className}`}
onClick={this.didClickNode}
onMouseOver={this.mouseOver}
onMouseOut={this.mouseOut}
ref={this.nodeRef}
>
<TreeNodeTitle
style={animation}
collapsed={this.collapsed()}
treeNode={this.props.treeNode}
name={this.props.name}
didSelectNode={this.didSelectTopic}
className={highlightClass}
/>
{this.renderNodes()}
<div>
<div
key={this.props.treeNode.hash()}
className={`${classes.node} ${this.props.className}`}
onMouseOver={this.mouseOver}
onMouseOut={this.mouseOut}
onClick={this.didClickTitle}
ref={this.nodeRef}
>
<TreeNodeTitle
style={animation}
toggleCollapsed={this.toggleCollapsed}
didSelectNode={this.didSelectTopic}
collapsed={this.collapsed()}
treeNode={this.props.treeNode}
name={this.props.name}
className={highlightClass}
/>
</div>
<div className={classes.subnodes}>
{this.renderNodes()}
</div>
</div>
)
}
@@ -212,6 +220,12 @@ class TreeNode extends React.Component<Props, State> {
this.props.didSelectTopic(this.props.treeNode)
}
private didClickTitle = (event: React.MouseEvent) => {
event.preventDefault()
this.props.didSelectTopic(this.props.treeNode)
this.toggle()
}
private mouseOver = (event: React.MouseEvent) => {
event.stopPropagation()
this.setHover(true)
@@ -226,10 +240,9 @@ class TreeNode extends React.Component<Props, State> {
this.setState({ mouseOver: hover })
}, 45)
private didClickNode = (event: React.MouseEvent) => {
private toggleCollapsed = (event: React.MouseEvent) => {
event.stopPropagation()
this.toggle()
this.didSelectTopic()
}
private renderNodes() {
+2 -2
View File
@@ -51,13 +51,13 @@ class TreeNodeSubnodes extends React.Component<Props, State> {
}
private renderMore() {
this.renderMoreAnimationFrame = window.requestIdleCallback(() => {
this.renderMoreAnimationFrame = (window as any).requestIdleCallback(() => {
this.setState({ ...this.state, alreadyAdded: this.state.alreadyAdded * 1.5 })
}, { timeout: 500 })
}
public componentWillUnmount() {
window.cancelIdleCallback(this.renderMoreAnimationFrame)
(window as any).cancelIdleCallback(this.renderMoreAnimationFrame)
}
public render() {
+6 -14
View File
@@ -11,29 +11,19 @@ export interface TreeNodeProps extends React.HTMLAttributes<HTMLElement> {
collapsed?: boolean | undefined
classes: any
didSelectNode: any
toggleCollapsed: any
}
class TreeNodeTitle extends React.Component<TreeNodeProps, {}> {
private mouseOver = (event: React.MouseEvent) => {
event.preventDefault()
this.selectTopic()
}
private selectTopic = debounce(() => {
if (this.props.treeNode.message) {
this.props.didSelectNode(this.props.treeNode)
}
}, 5)
public render() {
const { classes, treeNode, style, className } = this.props
return (
<span
className={`${classes.title} ${className}`}
onMouseOver={treeNode.message ? this.mouseOver : undefined}
style={style}
>
<span className={classes.expander}>{this.renderExpander()}</span> {this.renderSourceEdge()} {this.renderCollapsedSubnodes()} {this.renderValue()}
<span className={classes.expander} onClick={this.props.toggleCollapsed}>{this.renderExpander()}</span>
{this.renderSourceEdge()} {this.renderCollapsedSubnodes()} {this.renderValue()}
</span>
)
}
@@ -81,6 +71,8 @@ const styles = (theme: Theme) => ({
},
expander: {
color: theme.palette.type === 'light' ? '#222' : '#eee',
cursor: 'pointer' as 'pointer',
paddingRight: theme.spacing(0.25),
},
title: {
borderRadius: '4px',
@@ -88,7 +80,7 @@ const styles = (theme: Theme) => ({
display: 'inline-block' as 'inline-block',
whiteSpace: 'nowrap' as 'nowrap',
padding: '1px 4px 0px 4px',
height: '16px',
height: '14px',
margin: '1px 0px 2px 0px',
},
collapsedSubnodes: {
+1 -2
View File
@@ -27,7 +27,6 @@ const theme = createMuiTheme({
palette: {
type: 'dark',
},
typography: { useNextVariants: true },
})
setTimeout(() => {
@@ -41,7 +40,7 @@ setTimeout(() => {
ReactDOM.render(
<MuiThemeProvider theme={theme}>
<Provider store={store}>
<App name="" />
<App />
</Provider>
</MuiThemeProvider>,
document.getElementById('app'),
+37 -1
View File
@@ -8,14 +8,19 @@ 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
export type Action = SetAutoExpandLimit | ToggleVisibility | SetTopicOrder | FilterTopics | TogglehighlightTopicUpdates | SetValueRendererDisplayMode
export enum ActionTypes {
SETTINGS_SET_AUTO_EXPAND_LIMIT = 'SETTINGS_SET_AUTO_EXPAND_LIMIT',
@@ -24,6 +29,9 @@ 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 = {
@@ -31,6 +39,8 @@ const initialState: SettingsState = {
topicOrder: TopicOrder.none,
visible: false,
highlightTopicUpdates: true,
valueRendererDisplayMode: 'diff',
selectTopicWithMouseOver: false,
}
export const settingsReducer = createReducer(initialState, {
@@ -40,6 +50,8 @@ 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 {
@@ -54,6 +66,30 @@ 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
+556 -610
View File
File diff suppressed because it is too large Load Diff
+23
View File
@@ -0,0 +1,23 @@
image: Visual Studio 2017
platform: x64
cache:
- node_modules
- '%LOCALAPPDATA%\electron\Cache'
- '%LOCALAPPDATA%\electron-builder\cache'
install:
- ps: Install-Product node 10
build_script:
- yarn
- yarn build
- yarn prepare-release
- yarn package appx
test_script:
- yarn test
artifacts:
- path: 'build\clean\build\MQTT-Explorer*.appx'
+1 -1
View File
@@ -6,7 +6,7 @@ export class RingBuffer<T extends Lengthwise> {
private capacity: number
private maxItems: number
private usage: number = 0
private items: (any)[] = []
private items: T[] = []
private start: number = 0
private end: number = 0
+12 -6
View File
@@ -11,8 +11,8 @@ import {
removeConnection,
updateAvailable,
} from '../../events'
import { DataSource, MqttSource } from './DataSource'
import ConfigStorage from './ConfigStorage'
import { UpdateInfo } from 'builder-util-runtime'
export class ConnectionManager {
@@ -21,13 +21,18 @@ export class ConnectionManager {
public manageConnections() {
backendEvents.subscribe(addMqttConnectionEvent, this.handleConnectionRequest)
backendEvents.subscribe(removeConnection, (connectionId: string) => {
backendEvents.unsubscribeAll(makePublishEvent(connectionId))
this.removeConnection(connectionId)
})
}
private handleConnectionRequest = (event: AddMqttConnection) => {
const connectionId = event.id
// Prevent double connections when reloading
if (this.connections[connectionId]) {
this.removeConnection(connectionId)
}
const options = event.options
const connection = new MqttSource()
this.connections[connectionId] = connection
@@ -56,18 +61,19 @@ export class ConnectionManager {
})
}
public removeConnection(hash: string) {
const connection = this.connections[hash]
public removeConnection(conenctionId: string) {
const connection = this.connections[conenctionId]
if (connection) {
backendEvents.unsubscribeAll(makePublishEvent(conenctionId))
connection.disconnect()
delete this.connections[hash]
delete this.connections[conenctionId]
connection.stateMachine.onUpdate.removeAllListeners()
}
}
public closeAllConnections() {
Object.keys(this.connections)
.forEach(hash => this.removeConnection(hash))
.forEach(conenctionId => this.removeConnection(conenctionId))
}
}
+14 -6
View File
@@ -1,15 +1,15 @@
{
"name": "MQTT-Explorer",
"version": "0.2.0-alpha",
"version": "0.2.0",
"description": "Explore your message queues",
"main": "dist/src/electron.js",
"scripts": {
"start": "electron .",
"test": "yarn run test-backend",
"install": "cd app; yarn; cd ..",
"install": "cd app && yarn && cd ..",
"build": "tsc && cd app && yarn run build && cd ..",
"test-backend": "cd backend && yarn run test && cd ..",
"prepare-release": "./scripts/prepare-release.sh",
"prepare-release": "ts-node scripts/prepare-release.ts",
"package": "ts-node package.ts",
"ui-test": "./scripts/uiTests.sh",
"upload-video-artifacts": "./scripts/uploadVideoAsset.ts ui-test.mp4 ui-test.gif",
@@ -32,6 +32,13 @@
"category": "Development",
"maintainer": "Thomas Nordquist"
},
"appx": {
"applicationId": "mqttexplorer",
"identityName": "51031thomas.nordquist.MQTT-Explorer",
"publisherDisplayName": "Thomas Nordquist",
"publisher": "CN=0A6DE643-4AA2-4FF2-9729-6935C9ED8C13",
"backgroundColor": "#FFFFFF"
},
"directories": {
"app": "./",
"buildResources": "res",
@@ -44,6 +51,7 @@
"license": "ISC",
"devDependencies": {
"@types/chai": "^4.1.7",
"@types/fs-extra": "^5.0.5",
"@types/lowdb": "^1.0.6",
"@types/mime": "^2.0.0",
"@types/mocha": "^5.2.5",
@@ -53,8 +61,9 @@
"@types/sha1": "^1.1.1",
"axios": "^0.18.0",
"chai": "^4.2.0",
"electron": "^4.0.2",
"electron-builder": "^20.38.4",
"electron": "^4.0.6",
"electron-builder": "^20.38.5",
"fs-extra": "^7.0.1",
"mime": "^2.4.0",
"mocha": "^5.2.0",
"mustache": "^3.0.1",
@@ -72,7 +81,6 @@
},
"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",
+13 -1
View File
@@ -30,6 +30,16 @@ const win: builder.CliOptions = {
publish: 'always',
}
const winAppx: builder.CliOptions = {
x64: true,
ia32: true,
armv7l: false,
arm64: false,
win: ['appx'],
projectDir: './build/clean',
publish: 'never',
}
const mac: builder.CliOptions = {
x64: true,
ia32: true,
@@ -41,11 +51,13 @@ const mac: builder.CliOptions = {
}
async function executeBuild() {
console.log(process.argv[2])
switch (process.argv[2]) {
case 'win':
await builder.build(win)
break
case 'appx':
await builder.build(winAppx)
break
case 'linux':
await builder.build(linux)
break
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

-26
View File
@@ -1,26 +0,0 @@
#!/bin/bash
set -e
ORIGINAL_DIR=`pwd`
DIR=build/clean
rm -rf "$DIR" || echo "Directory did not exist"
mkdir -p "$DIR"
git clone .git "$DIR"
cd $DIR
# App
cd app
yarn
cd ..
# Build
yarn
yarn build
rm -rf node_modules
yarn install --production
rm -rf app/node_modules
cd "$ORIGINAL_DIR"
+66
View File
@@ -0,0 +1,66 @@
import * as fs from 'fs-extra'
import * as path from 'path'
import { spawn, ChildProcess } from 'child_process'
import { chdir } from 'process'
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() {
const originalDir = __dirname
await fs.remove(targetDir)
await fs.mkdirp(targetDir)
// Create fresh clone of the local git repo
await exec('git', ['clone', '.git', targetDir])
// Enter git repo
chdir(targetDir)
// Install app dependencies
chdir('app')
await exec('yarn')
chdir('..')
// Install electron dependencies
await exec('yarn')
// Build App and Electron backend
await exec('yarn', ['build'])
// Clean up
await fs.remove('node_modules')
await exec('yarn', ['install', '--production'])
await fs.remove(path.join('app', 'node_modules'))
chdir(originalDir)
}
prepareRelease()
+8 -5
View File
@@ -1,5 +1,8 @@
#!/bin/bash
ffmpeg -s:v 1024x700 -r 20 -f rawvideo -pix_fmt yuv420p -i qrawvideorgb24.yuv app2.mp4
DIMENSIONS="1024x720"
GIF_SCALE="1024"
ffmpeg -s:v $DIMENSIONS -r 20 -f rawvideo -pix_fmt yuv420p -i qrawvideorgb24.yuv app2.mp4
# The video starts with a few blank frames, we want to know when the stop
ffprobe -f lavfi -i "movie=app2.mp4,blackdetect[out0]" -show_entries tags=lavfi.black_start,lavfi.black_end -of default=nw=1 -v quiet > ffmpeg_info
@@ -9,16 +12,16 @@ END_OF_BLACK=`cat ffmpeg_info | grep end | head -n1 | cut -d'=' -f2`
END_OF_BLACK=`awk "BEGIN {print $END_OF_BLACK+0.8; exit}"`
# Trim black frames at start
ffmpeg -s:v 1024x700 -r 20 -f rawvideo -pix_fmt yuv420p -i qrawvideorgb24.yuv -ss $END_OF_BLACK app.mp4
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 1024x700 -r 20 -f rawvideo -pix_fmt yuv420p -i qrawvideorgb24.yuv -vf fps=10,scale=1024:-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=$GIF_SCALE:-1:flags=lanczos,palettegen" palette1024.png
# Create gif
ffmpeg -s:v 1024x700 -r 20 -f rawvideo -pix_fmt yuv420p -i qrawvideorgb24.yuv -i palette1024.png -ss $END_OF_BLACK -filter_complex "fps=10,scale=1024:-1:flags=lanczos[x];[x][1:v]paletteuse" app1024.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
# Clean up
rm ffmpeg_info palette*.png qrawvideorgb24.yuv
mv app.mp4 ui-test.mp4
mv app1024.gif ui-test.gif
mv app720.gif ui-test.gif
+1 -1
View File
@@ -9,7 +9,7 @@ function finish {
trap finish EXIT
DIMENSIONS="1024x700"
DIMENSIONS="1024x720"
SCR=99
# Start new window manager
Xvfb :$SCR -screen 0 "$DIMENSIONS"x24 -ac &
+53 -13
View File
@@ -1,14 +1,13 @@
import { Menu, app, BrowserWindow, webContents } from 'electron'
import { Menu, app, BrowserWindow, webContents, MenuItemConstructorOptions, MenuItem } from 'electron'
import openAboutWindow from 'about-window'
import * as path from 'path'
const applicationMenu = {
const applicationMenu: MenuItemConstructorOptions = {
label: 'Application',
submenu: [
{
label: 'About Application',
click: () => {
console.log(path.join(__dirname, 'icon.png'))
openAboutWindow({
icon_path: path.join(__dirname, '..', '..', 'icon.png'),
license: 'AGPL-3.0',
@@ -24,7 +23,7 @@ const applicationMenu = {
{
label: 'Dev Tools',
accelerator: 'CmdOrCtrl+Alt+I',
role: 'toggleDevTools',
role: 'toggledevtools' as 'toggledevtools',
},
{
label: 'Quit',
@@ -36,48 +35,89 @@ const applicationMenu = {
],
}
const editMenu = {
const editMenu: MenuItemConstructorOptions = {
label: 'Edit',
submenu: [
{
label: 'Undo',
accelerator: 'CmdOrCtrl+Z',
selector: 'undo:',
role: 'undo',
},
{
label: 'Redo',
accelerator: 'Shift+CmdOrCtrl+Z',
selector: 'redo:',
role: 'redo',
},
{
type: 'separator' as 'separator',
type: 'separator',
},
{
label: 'Cut',
accelerator: 'CmdOrCtrl+X',
selector: 'cut:',
role: 'cut',
},
{
label: 'Copy',
accelerator: 'CmdOrCtrl+C',
selector: 'copy:',
role: 'copy',
},
{
label: 'Paste',
accelerator: 'CmdOrCtrl+V',
selector: 'paste:',
role: 'paste',
},
{
label: 'Select All',
accelerator: 'CmdOrCtrl+A',
selector: 'selectAll:',
role: 'selectall',
},
],
}
const template = [
const viewMenu: MenuItemConstructorOptions = {
label: 'View',
submenu: [
{
label: 'Default size',
accelerator: 'CmdOrCtrl+0',
click: () => {
const window = BrowserWindow.getFocusedWindow()
if (window) {
window.webContents.setZoomFactor(1)
}
},
},
{
label: 'Increase size',
accelerator: 'CmdOrCtrl+Plus',
click: () => {
const window = BrowserWindow.getFocusedWindow()
if (window) {
window.webContents.getZoomFactor((zoom) => {
window.webContents.setZoomFactor(Math.min(zoom + 0.1, 2.0))
})
}
},
},
{
label: 'Reduce size',
accelerator: 'CmdOrCtrl+-',
click: () => {
const window = BrowserWindow.getFocusedWindow()
if (window) {
window.webContents.getZoomFactor((zoom) => {
window.webContents.setZoomFactor(Math.max(zoom - 0.1, 0.5))
})
}
},
},
],
}
const template: any = [
applicationMenu,
editMenu,
viewMenu,
]
export const menuTemplate = Menu.buildFromTemplate(template)
+5 -8
View File
@@ -9,15 +9,12 @@ import { menuTemplate } from './MenuTemplate'
import { UpdateInfo } from '../events'
const isDev = require('electron-is-dev')
let electronTelemetry: any
if (!isDev) {
electronTelemetry = electronTelemetryFactory('9b0c8ca04a361eb8160d98c5')
const electronTelemetry = electronTelemetryFactory('9b0c8ca04a361eb8160d98c5')
}
const isDebugEnabled = Boolean(process.argv.find(arg => arg === 'debug'))
const isFullscreen = Boolean(process.argv.find(arg => arg === '--fullscreen'))
require('electron-debug')({ enabled: isDebugEnabled })
// const isDebugEnabled = Boolean(process.argv.find(arg => arg === 'debug'))
const runningUiTestOnCi = Boolean(process.argv.find(arg => arg === '--runningUiTestOnCi'))
autoUpdater.logger = log
log.info('App starting...')
@@ -37,7 +34,7 @@ function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 1024,
height: 700,
height: 720,
show: false,
webPreferences: {
nodeIntegration: true,
@@ -48,7 +45,7 @@ function createWindow() {
mainWindow.once('ready-to-show', () => {
if (mainWindow) {
isFullscreen && mainWindow.setFullScreen(true)
runningUiTestOnCi && mainWindow.setFullScreen(true)
mainWindow.show()
}
})
+1 -1
View File
@@ -24,6 +24,6 @@ async function topicMatches(topics: string[], browser: Browser<void>) {
}
function topicSelector(topics: string[]) {
const suffix = topics.map(topic => `*[contains(text(), "${topic}")]`).join('/../..//')
const suffix = topics.map(topic => `*[contains(text(), "${topic}")]`).join('/../../..//')
return `//${suffix}`
}
+5 -5
View File
@@ -21,7 +21,7 @@ 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 fullscreen = os.platform() === 'darwin' ? [] : ['--fullscreen']
const runningUiTestOnCi = os.platform() === 'darwin' ? [] : ['--runningUiTestOnCi']
const options = {
host: 'localhost', // Use localhost as chrome driver server
@@ -30,7 +30,7 @@ const options = {
browserName: 'electron',
chromeOptions: {
binary: `${__dirname}/../../../node_modules/electron/dist/${binary}`,
args: [`--app=${__dirname}/../../..`, '--force-device-scale-factor=1', '--no-sandbox', '--disable-dev-shm-usage', '--disable-extensions'].concat(fullscreen),
args: [`--app=${__dirname}/../../..`, '--force-device-scale-factor=1', '--no-sandbox', '--disable-dev-shm-usage', '--disable-extensions'].concat(runningUiTestOnCi),
},
windowTypes: ['app', 'webview'],
},
@@ -45,12 +45,12 @@ async function doStuff() {
await sleep(1000)
await sleep(1000)
await showText('Overview of topics', 2000, browser, 'top')
await showText('Topics overview', 2000, browser, 'top')
await sleep(2000)
await showText('Indicates which topics change', 2000, browser, 'bottom')
await showText('Indicate topic updates', 2000, browser, 'bottom')
await sleep(3000)
await showText('Plot topics', 2000, browser)
await showText('Plot topic history', 2000, browser)
await showNumericPlot(browser)
await sleep(2000)
+13 -44
View File
@@ -119,6 +119,13 @@
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.7.tgz#1b8e33b61a8c09cbe1f85133071baa0dbf9fa71a"
integrity sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==
"@types/fs-extra@^5.0.5":
version "5.0.5"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.5.tgz#080d90a792f3fa2c5559eb44bd8ef840aae9104b"
integrity sha512-w7iqhDH9mN8eLClQOYTkhdYUOSpp25eXxfc6VbFOGtzxW34JcvctH2bKjj4jD4++z4R5iO5D+pg48W2e03I65A==
dependencies:
"@types/node" "*"
"@types/lodash@*":
version "4.14.121"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.121.tgz#9327e20d49b95fc2bf983fc2f045b2c6effc80b9"
@@ -1061,7 +1068,7 @@ ejs@~2.5.6:
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.9.tgz#7ba254582a560d267437109a68354112475b0ce5"
integrity sha512-GJCAeDBKfREgkBtgrYSf9hQy9kTb3helv0zGdzqhM7iAkW8FA/ZF97VQDbwFiwIT8MQLLOe5VlPZOEvZAqtUAQ==
electron-builder@^20.38.4:
electron-builder@^20.38.5:
version "20.38.5"
resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-20.38.5.tgz#31b3913a68b4911afd4cfc7bcd2522c5808040cd"
integrity sha512-p88IDHhH2J4hA6KwRBJY+OfVZuFtFIShY3Uh/TwYAfbX0v1RhKZytuGdO8sty2zcWxDYX74xDBv+X9oN6qEIRQ==
@@ -1088,14 +1095,6 @@ 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"
@@ -1111,31 +1110,11 @@ 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"
@@ -1189,10 +1168,10 @@ electron-updater@^4.0.6:
semver "^5.6.0"
source-map-support "^0.5.9"
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==
electron@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/electron/-/electron-4.0.6.tgz#f936772ddb5eaa026591ea14ef806e1385fbbda9"
integrity sha512-r2ow/EmDibjoCNJp35mB1CcPs2xBF9fp2eoWuUOJmpVQyzdba6EnqsSD1BxwXzF9vQ5WkQ2UbfXBIvpomrdioQ==
dependencies:
"@types/node" "^10.12.18"
electron-download "^4.1.0"
@@ -1460,7 +1439,7 @@ fs-extra@^4.0.1:
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-extra@^7.0.0:
fs-extra@^7.0.0, fs-extra@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
@@ -2083,16 +2062,6 @@ 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"