Compare commits

...
39 Commits
Author SHA1 Message Date
Thomas Nordquist 77654c7136 Bump version 2019-05-04 13:05:12 +02:00
Thomas Nordquist 3bfc59d89a Fix silent error in treenode 2019-05-04 13:03:36 +02:00
Thomas Nordquist 6bf84055d9 Update images in readme 2019-05-03 12:11:03 +02:00
Thomas Nordquist 12d7d3ab64 Fix download location 2019-05-03 12:07:58 +02:00
Thomas Nordquist b2913c7e0e Disable auto update for osx only 2019-05-03 12:07:58 +02:00
Thomas Nordquist 352948f212 Fix update notifier url 2019-05-03 12:07:58 +02:00
Thomas Nordquist 9a4dbe92a0 Fix connection health indicator in connection manager 2019-05-03 12:07:58 +02:00
Thomas Nordquist 397b95f9e5 Disable auto update
Auto update is buggy https://github.com/electron-userland/electron-builder/issues/3681
2019-05-03 12:07:58 +02:00
Thomas Nordquist a411d21133 Fix auto update 2019-05-03 12:07:58 +02:00
Thomas Nordquist 4f624cc130 Fix download url 2019-05-03 12:07:58 +02:00
Thomas Nordquist f10bfafa1d Delete me 2019-05-03 12:07:58 +02:00
Thomas Nordquist 10e37624a5 Improve demo video 2019-05-03 12:07:58 +02:00
Thomas Nordquist ebdfac39eb Fix copy to clipboard layout glitch 2019-05-03 12:07:58 +02:00
Thomas Nordquist 7cc032cdf4 Add afterPack script 2019-05-03 02:07:34 +02:00
Thomas Nordquist 4bc9039386 Fix sed command 2019-05-03 00:59:29 +02:00
Thomas Nordquist 43b997fb18 Add wrapper script to disable sandbox 2019-05-03 00:59:08 +02:00
Thomas Nordquist 6c883aa226 Disable sandbox for snap builds 2019-05-03 00:16:30 +02:00
Thomas Nordquist 20cde3e2a4 Merge branch 'master' of https://github.com/thomasnordquist/MQTT-Explorer 2019-05-01 02:46:06 +02:00
Thomas Nordquist ae7440f8ab Fix linux icon 2019-05-01 01:26:00 +02:00
Thomas Nordquist 03e228e5a0 Fix ci fullscreen flag 2019-05-01 00:58:04 +02:00
Thomas Nordquist 14f2f9ff1e Fix mac release artifact publish settings 2019-04-30 21:22:54 +02:00
Thomas Nordquist 86544c1334 Disable sandbox 2019-04-30 18:35:29 +02:00
Thomas Nordquist 9e71c3132e Use proper provisioning profile for OSX builds 2019-04-30 18:28:35 +02:00
Thomas Nordquist 817202fc20 Disable electron sandbox due to crashes 2019-04-30 17:44:53 +02:00
Thomas Nordquist 931ec0113c Fix ui glitch 2019-04-30 17:44:25 +02:00
Thomas Nordquist 365ebc78ab Fix ui tests 2019-04-30 16:25:37 +02:00
Thomas Nordquist 2a541a80dc Fix ui tests 2019-04-30 15:54:53 +02:00
Thomas Nordquist a1d3f32f73 Fix mouse-dummy pointer events for demo video 2019-04-27 10:32:45 +02:00
Thomas Nordquist e9e6ea618d Change leak acceptance level 2019-04-27 10:31:16 +02:00
Thomas Nordquist 6021df7150 Fix minor ui-bugs 2019-04-27 10:31:06 +02:00
Thomas Nordquist 070b72b304 Document license 2019-04-27 10:29:37 +02:00
Thomas Nordquist 1f65a0f316 Upgrade electron to v5.0.0 2019-04-26 17:45:35 +02:00
Thomas Nordquist 0f21f10c0d Fix backend tests 2019-04-26 14:57:12 +02:00
Thomas Nordquist af2ff0149d Add memory leak test-suite 2019-04-25 13:50:15 +02:00
Thomas Nordquist 8cd11cde3b Disable redux dev tools on default 2019-04-25 09:01:07 +02:00
Thomas Nordquist fdbe6344d9 Disable diff hover highlight 2019-04-25 09:00:44 +02:00
Thomas Nordquist a56b41635c Fix indentation 2019-04-25 00:28:48 +02:00
Thomas Nordquist 2c5c218fd1 Remove unsubscription since component would unount when node changes 2019-04-25 00:05:33 +02:00
Thomas Nordquist 749df70d5c Fix memory leaks 2019-04-24 23:40:28 +02:00
51 changed files with 478 additions and 203 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
| | | |
|:---:|:---:|:---:|
|[![screen1_small](https://user-images.githubusercontent.com/7721625/53954800-84b34c00-40d7-11e9-842d-bf655c569600.jpg)](https://user-images.githubusercontent.com/7721625/53954364-52551f00-40d6-11e9-93cf-d5a9601897ea.png)|[![screen2_small](https://user-images.githubusercontent.com/7721625/53954801-84b34c00-40d7-11e9-913a-42572e675620.jpg)](https://user-images.githubusercontent.com/7721625/53954365-52551f00-40d6-11e9-823f-afd66f19ed01.png)|[![screen3_small](https://user-images.githubusercontent.com/7721625/53954802-854be280-40d7-11e9-973c-08f23c8dbf89.jpg)](https://user-images.githubusercontent.com/7721625/53954366-52551f00-40d6-11e9-9738-74db830d03ac.png)|
|[![screen_composite](https://mqtt-explorer.com/img/screen-composite_small.png)](https://mqtt-explorer.com/img/screen-composite.png)|[![screen2_small](https://mqtt-explorer.com/img/screen2_small.png)](https://mqtt-explorer.com/img/screen2.png)|[![screen3_small](https://mqtt-explorer.com/img/screen3_small.png)](https://mqtt-explorer.com/img/screen3.png)|
# The App has moved to [mqtt-explorer.com](https://mqtt-explorer.com)
MQTT Explorer is a comprehensive and easy-to-use MQTT Client.
+6 -3
View File
@@ -4,11 +4,12 @@
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack --mode production",
"dev": "node_modules/.bin/webpack-dev-server --mode development --progress"
"build": "yarn rebuild && webpack --mode production",
"dev": "node_modules/.bin/webpack-dev-server --mode development --progress",
"rebuild": "cd node_modules/heapdump && node-gyp rebuild --target=5.0.0 --arch=x64 --dist-url=https://atom.io/download/electron || echo Could not build heapdump; cd -"
},
"author": "",
"license": "ISC",
"license": "CC-BY-ND-4.0",
"dependencies": {
"@material-ui/core": "^4.0.0-alpha.6",
"@material-ui/icons": "^4.0.0-alpha.1",
@@ -60,7 +61,9 @@
"awesome-typescript-loader": "^5.2.1",
"css-loader": "^2.1.0",
"hard-source-webpack-plugin": "^0.13.1",
"heapdump": "^0.3.12",
"html-webpack-plugin": "^4.0.0-beta.5",
"node-loader": "^0.6.0",
"source-map-loader": "^0.2.4",
"style-loader": "^0.23.1",
"typescript": "^3.2.2",
+3
View File
@@ -78,6 +78,8 @@ export const disconnect = () => (dispatch: Dispatch<any>, getState: () => AppSta
}
tree && tree.stopUpdating()
tree && tree.destroy()
// Clear topic filter
dispatch({
topicFilter: '',
@@ -88,4 +90,5 @@ export const disconnect = () => (dispatch: Dispatch<any>, getState: () => AppSta
dispatch({
type: ActionTypes.CONNECTION_SET_DISCONNECTED,
})
dispatch(showTree(undefined))
}
+16 -8
View File
@@ -53,20 +53,28 @@ const debouncedSelectTopic = debounce((topic: q.TreeNode<TopicViewModel>, dispat
}
}, 70)
export const resetStore = () => (dispatch: Dispatch<any>): AnyAction => {
function destroyUnreferencedTree(state: AppState) {
const visibleTree = state.tree.get('tree')
const connectionTree = state.connection.tree
// Stop updates of old tree
if (visibleTree && visibleTree !== connectionTree) {
console.warn('destroy')
visibleTree.stopUpdating()
visibleTree.destroy()
}
}
export const resetStore = () => (dispatch: Dispatch<any>, getState: () => AppState): AnyAction => {
destroyUnreferencedTree(getState())
return dispatch({
type: ActionTypes.TREE_RESET_STORE,
})
}
export const showTree = (tree: q.Tree<TopicViewModel> | undefined) => (dispatch: Dispatch<any>, getState: () => AppState): AnyAction => {
const visibleTree = getState().tree.get('tree')
const connectionTree = getState().connection.tree
// Stop updates of old tree
if (visibleTree !== connectionTree && visibleTree) {
visibleTree.stopUpdating()
}
destroyUnreferencedTree(getState())
return dispatch({
tree,
+8 -6
View File
@@ -89,6 +89,12 @@ class App extends React.PureComponent<Props, {}> {
const styles = (theme: Theme) => {
const drawerWidth = 300
const contentBaseStyle = {
width: '100vw',
overflow: 'hidden' as 'hidden',
backgroundColor: theme.palette.background.default,
}
return {
heightProperty: {
height: 'calc(100vh - 64px) !important',
@@ -106,9 +112,7 @@ const styles = (theme: Theme) => {
overflow: 'hidden' as 'hidden',
},
content: {
width: '100vw',
overflowX: 'hidden' as 'hidden',
backgroundColor: theme.palette.background.default,
...contentBaseStyle,
transition: theme.transitions.create('transform', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
@@ -116,9 +120,7 @@ const styles = (theme: Theme) => {
transform: 'translateX(0px)',
},
contentShift: {
overflowX: 'hidden' as 'hidden',
width: '100vw',
padding: 0,
...contentBaseStyle,
backgroundColor: theme.palette.background.default,
transition: theme.transitions.create('transform', {
easing: theme.transitions.easing.easeOut,
+1
View File
@@ -77,6 +77,7 @@ const style = (theme: Theme) => ({
position: 'fixed' as 'fixed',
zIndex: 1000000,
filter: theme.palette.type === 'light' ? undefined : 'invert(100%)',
pointerEvents: 'none' as 'none',
},
})
+13 -1
View File
@@ -1,8 +1,20 @@
import * as React from 'react'
import ShowText from './ShowText'
import Mouse from './Mouse'
let heapdump: any
(window as any).demo = {}
function writeHeapdump(path?: string) {
if (!heapdump) {
heapdump = require('heapdump')
}
heapdump.writeSnapshot(path || `${Date.now()}.heapsnapshot`)
return path
}
(window as any).demo = {
writeHeapdump,
}
export default function render(props: any) {
return (
+1 -1
View File
@@ -37,7 +37,7 @@ class Notification extends React.Component<Props, {}> {
<Snackbar
anchorOrigin={snackbarAnchor}
open={Boolean(this.props.message)}
autoHideDuration={this.props.type === 'error' ? 10000 : 3000}
autoHideDuration={this.props.type === 'error' ? 10000 : 2000}
onClose={this.props.onClose}
>
<SnackbarContent
-3
View File
@@ -78,9 +78,6 @@ const styles: StyleRulesCallback = theme => ({
paddingLeft: theme.spacing(6),
transition: theme.transitions.create('width'),
width: '100%',
[theme.breakpoints.up('md')]: {
width: 200,
},
},
menuButton: {
marginLeft: -12,
@@ -109,7 +109,7 @@ class Settings extends React.Component<Props, {}> {
private toggleTheme() {
const { actions, theme } = this.props
return <BooleanSwitch title="Dark Theme" tooltip="Enable dark theme" value={theme === 'light'} action={actions.settings.toggleTheme} />
return <BooleanSwitch title="Dark Mode" tooltip="Enable dark theme" value={theme === 'dark'} action={actions.settings.toggleTheme} />
}
private renderAutoExpand() {
@@ -120,12 +120,12 @@ class Settings extends React.Component<Props, {}> {
<div style={{ padding: '8px', display: 'flex' }}>
<InputLabel htmlFor="auto-expand" style={{ flex: '1', marginTop: '8px' }}>Auto Expand</InputLabel>
<Select
value={autoExpandLimit}
onChange={this.onChangeAutoExpand}
input={<Input name="auto-expand" id="auto-expand-label-placeholder" />}
name="auto-expand"
className={classes.input}
style={{ flex: '1' }}
value={autoExpandLimit}
onChange={this.onChangeAutoExpand}
input={<Input name="auto-expand" id="auto-expand-label-placeholder" />}
name="auto-expand"
className={classes.input}
style={{ flex: '1' }}
>
{limits}
</Select>
+6 -6
View File
@@ -182,16 +182,16 @@ const style = (theme: Theme) => {
deletion: {
...gutterBaseStyle,
backgroundColor: 'rgba(255, 10, 10, 0.3)',
'&:hover': {
backgroundColor: 'rgba(255, 10, 10, 0.6)',
},
// '&:hover': {
// backgroundColor: 'rgba(255, 10, 10, 0.3)',
// },
},
addition: {
...gutterBaseStyle,
backgroundColor: 'rgba(10, 255, 10, 0.3)',
'&:hover': {
backgroundColor: 'rgba(10, 255, 10, 0.5)',
},
// '&:hover': {
// backgroundColor: 'rgba(10, 255, 10, 0.5)',
// },
},
}
}
+1 -4
View File
@@ -35,7 +35,6 @@ interface Props {
}
interface State {
node: q.TreeNode<TopicViewModel>
compareMessage?: q.Message
valueRenderWidth: number
}
@@ -49,8 +48,7 @@ class Sidebar extends React.Component<Props, State> {
constructor(props: any) {
super(props)
console.error('Find and fix me #state')
this.state = { node: new q.Tree(), valueRenderWidth: 300 }
this.state = { valueRenderWidth: 300 }
}
private registerUpdateListener(node: q.TreeNode<TopicViewModel>) {
@@ -156,7 +154,6 @@ class Sidebar extends React.Component<Props, State> {
public componentWillReceiveProps(nextProps: Props) {
this.props.node && this.removeUpdateListener(this.props.node)
nextProps.node && this.registerUpdateListener(nextProps.node)
this.props.node && this.setState({ node: this.props.node })
if (this.props.node !== nextProps.node) {
this.setState({ compareMessage: undefined })
+2 -2
View File
@@ -30,7 +30,7 @@ interface State {
lastUpdate: number
}
class Tree extends React.PureComponent<Props, State> {
class TreeComponent extends React.PureComponent<Props, State> {
private updateTimer?: any
private perf: number = 0
private renderTime = 0
@@ -137,4 +137,4 @@ const mapDispatchToProps = (dispatch: any) => {
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Tree)
export default connect(mapStateToProps, mapDispatchToProps)(TreeComponent)
+4 -11
View File
@@ -70,7 +70,7 @@ interface State {
selected: boolean
}
class TreeNode extends React.Component<Props, State> {
class TreeNodeComponent extends React.Component<Props, State> {
private animationDirty: boolean = false
private cssAnimationWasSetAt?: number
@@ -97,8 +97,8 @@ class TreeNode extends React.Component<Props, State> {
treeNode.viewModel.change.subscribe(this.viewStateHasChanged)
}
private viewStateHasChanged = (msg: void, viewModel: TopicViewModel) => {
this.setState({ selected: viewModel.isSelected() })
private viewStateHasChanged = (msg: void) => {
this.props.treeNode.viewModel && this.setState({ selected: this.props.treeNode.viewModel.isSelected() })
}
private removeSubscriber(treeNode: q.TreeNode<TopicViewModel>) {
@@ -173,13 +173,6 @@ class TreeNode extends React.Component<Props, State> {
this.addSubscriber(treeNode)
}
public componentWillReceiveProps(nextProps: Props) {
if (nextProps.treeNode !== this.props.treeNode) {
this.removeSubscriber(this.props.treeNode)
this.addSubscriber(nextProps.treeNode)
}
}
public componentWillUnmount() {
const { treeNode } = this.props
this.removeSubscriber(treeNode)
@@ -243,4 +236,4 @@ class TreeNode extends React.Component<Props, State> {
}
}
export default withStyles(styles, { withTheme: true })(TreeNode)
export default withStyles(styles, { withTheme: true })(TreeNodeComponent)
+5 -4
View File
@@ -44,6 +44,7 @@ interface GithubAsset {
id: number
node_id: string
url: string
browser_download_url: string
name: string
label: string
}
@@ -170,7 +171,7 @@ class UpdateNotifier extends React.Component<Props, State> {
{this.renderDownloads()}
<Button
className={this.props.classes.download}
onClick={this.openGithub}
onClick={this.openHomePage}
>
Github Page
</Button>
@@ -180,8 +181,8 @@ class UpdateNotifier extends React.Component<Props, State> {
)
}
private openGithub = () => {
this.openUrl('https://github.com/thomasnordquist/MQTT-Explorer')
private openHomePage = () => {
this.openUrl('https://mqtt-explorer.com')
}
private openUrl = (url: string) => {
@@ -213,7 +214,7 @@ class UpdateNotifier extends React.Component<Props, State> {
<div>
<Button
className={this.props.classes.download}
onClick={() => this.openUrl(asset.url)}
onClick={() => this.openUrl(asset.browser_download_url)}
>
<CloudDownload />&nbsp;{asset.name}
</Button>
@@ -47,7 +47,7 @@ class ConnectionHealthIndicator extends React.Component<Props, {}> {
return (
<Tooltip title={`Connection health "${health}"`}>
<div>
<div style={{ display: 'inherit' }}>
<DeviceHubOutlined className={`${[classes[health]]} ${this.props.withBackground ? classes.icon : ''}`} />
</div>
</Tooltip>
+18 -29
View File
@@ -2,40 +2,35 @@ 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 { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { globalActions } from '../../actions'
const copy = require('copy-text-to-clipboard')
interface Props {
value: string
classes: any
actions: {
global: typeof globalActions
}
}
interface State {
didCopy: boolean
snackBarOpen: boolean
}
const styles = (theme: Theme) => ({
snackbar: {
backgroundColor: green[600],
color: theme.typography.button.color,
},
})
class Copy extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { didCopy: false, snackBarOpen: false }
this.state = { didCopy: false }
}
private handleClick = (event: React.MouseEvent) => {
event.stopPropagation()
copy(this.props.value)
this.setState({ didCopy: true, snackBarOpen: true })
this.props.actions.global.showNotification('Copied to clipboard')
this.setState({ didCopy: true })
setTimeout(() => {
this.setState({ didCopy: false })
}, 1500)
@@ -53,23 +48,17 @@ class Copy extends React.Component<Props, State> {
{icon}
</CustomIconButton>
</span>
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
open={this.state.snackBarOpen}
autoHideDuration={2000}
onClose={() => { this.setState({ snackBarOpen: false }) }}
>
<SnackbarContent
className={this.props.classes.snackbar}
message="Copied to clipboard"
/>
</Snackbar>
</span>
)
}
}
export default withStyles(styles)(Copy)
const mapDispatchToProps = (dispatch: any) => {
return {
actions: {
global: bindActionCreators(globalActions, dispatch),
},
}
}
export default connect(undefined, mapDispatchToProps)(Copy)
+1 -1
View File
@@ -2,7 +2,7 @@ import { EventDispatcher } from '../../../events'
export class TopicViewModel {
private selected: boolean
public change = new EventDispatcher<void, TopicViewModel>(this)
public change = new EventDispatcher<void, TopicViewModel>()
public constructor() {
this.selected = false
+16 -2
View File
@@ -14,6 +14,7 @@ module.exports = {
path: `${__dirname}/build`,
},
optimization: {
minimize: false,
splitChunks: {
chunks: 'all',
minSize: 30000,
@@ -47,7 +48,7 @@ module.exports = {
devtool: 'source-map',
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: ['.ts', '.tsx', '.js', '.json'],
extensions: ['.ts', '.tsx', '.js', '.json', '.node'],
},
module: {
rules: [
@@ -77,6 +78,15 @@ module.exports = {
},
],
},
{
test: /\.node$/,
use: {
loader: 'node-loader',
options: {
modules: true,
}
}
},
],
},
@@ -84,7 +94,11 @@ module.exports = {
new HtmlWebpackPlugin({ template: './index.html', file: './build/index.html', inject: false }),
// new BundleAnalyzerPlugin(),
new HardSourceWebpackPlugin(),
new webpack.HotModuleReplacementPlugin()
new webpack.HotModuleReplacementPlugin(),
new webpack.IgnorePlugin({
resourceRegExp: /\.\/build\/Debug\/addon/,
contextRegExp: /heapdump$/
}),
],
// When importing a module whose path matches one of the following, just
+13 -1
View File
@@ -2354,6 +2354,13 @@ he@1.2.x:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
heapdump@^0.3.12:
version "0.3.12"
resolved "https://registry.yarnpkg.com/heapdump/-/heapdump-0.3.12.tgz#5623be7816a8a92ab2d42b1b422f9e829a58da89"
integrity sha512-OSVdkxGd9xAJNEP0RA/ZvKEzGTEjCnkvmMEGF1ScR/87tH92v5Dpv6g/Fs8GvZA9UxdMpxU1G9NEC2D0OxZjJQ==
dependencies:
nan "^2.11.1"
hmac-drbg@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@@ -3346,7 +3353,7 @@ multicast-dns@^6.0.1:
dns-packet "^1.3.1"
thunky "^1.0.2"
nan@^2.9.2:
nan@^2.11.1, nan@^2.9.2:
version "2.13.2"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7"
integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==
@@ -3438,6 +3445,11 @@ node-libs-browser@^2.0.0:
util "^0.11.0"
vm-browserify "0.0.4"
node-loader@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/node-loader/-/node-loader-0.6.0.tgz#c797ef51095ed5859902b157f6384f6361e05ae8"
integrity sha1-x5fvUQle1YWZArFX9jhPY2HgWug=
node-object-hash@^1.2.0:
version "1.4.2"
resolved "https://registry.yarnpkg.com/node-object-hash/-/node-object-hash-1.4.2.tgz#385833d85b229902b75826224f6077be969a9e94"
+1 -1
View File
@@ -12,7 +12,7 @@
"postinstall": "yarn build"
},
"author": "",
"license": "ISC",
"license": "CC-BY-ND-4.0",
"nyc": {
"include": [
"src/**/*.ts",
+9 -3
View File
@@ -1,13 +1,15 @@
import * as FileAsync from 'lowdb/adapters/FileAsync'
import * as fs from 'fs-extra'
import * as lowdb from 'lowdb'
import * as path from 'path'
import { backendEvents } from '../../events'
import {
makeStorageAcknowledgementEvent,
makeStorageResponseEvent,
storageClearEvent,
storageLoadEvent,
storageStoreEvent,
makeStorageAcknowledgementEvent
} from '../../events/StorageEvents'
storageStoreEvent
} from '../../events/StorageEvents'
export default class ConfigStorage {
private file: string
@@ -17,6 +19,10 @@ export default class ConfigStorage {
}
private async getDb() {
const pathInfo = path.parse(this.file)
// Ensure that Settings dir exists
await fs.mkdirp(pathInfo.dir)
const adapter = new FileAsync(this.file)
if (!this.database) {
this.database = await lowdb(adapter)
+1 -1
View File
@@ -7,7 +7,7 @@ export interface DataSourceState {
}
export class DataSourceStateMachine {
public onUpdate = new EventDispatcher<DataSourceState, DataSourceStateMachine>(this)
public onUpdate = new EventDispatcher<DataSourceState, DataSourceStateMachine>()
private state: DataSourceState = {
error: undefined,
connected: false,
+8 -1
View File
@@ -16,7 +16,7 @@ export class Tree<ViewModel> extends TreeNode<ViewModel> {
public isTree = true
private cachedHash = `${Math.random()}`
private unmergedMessages: ChangeBuffer = new ChangeBuffer()
public didReceive = new EventDispatcher<void, Tree<ViewModel>>(this)
public didReceive = new EventDispatcher<void, Tree<ViewModel>>()
constructor() {
super(undefined, undefined)
@@ -27,6 +27,13 @@ export class Tree<ViewModel> extends TreeNode<ViewModel> {
this.didReceive.dispatch()
}
public destroy() {
super.destroy()
this.updateSource && this.updateSource.unsubscribe(this.subscriptionEvent, this.handleNewData)
this.updateSource = undefined
this.didReceive.removeAllListeners()
}
public updateWithConnection(emitter: EventBusInterface, connectionId: string, nodeFilter?: (node: TreeNode<ViewModel>) => boolean) {
this.updateSource = emitter
this.connectionId = connectionId
+18 -3
View File
@@ -12,9 +12,9 @@ export class TreeNode<ViewModel> {
public collapsed = false
public messages: number = 0
public lastUpdate: number = Date.now()
public onMerge = new EventDispatcher<void, TreeNode<ViewModel>>(this)
public onEdgesChange = new EventDispatcher<void, TreeNode<ViewModel>>(this)
public onMessage = new EventDispatcher<Message, TreeNode<ViewModel>>(this)
public onMerge = new EventDispatcher<void, TreeNode<ViewModel>>()
public onEdgesChange = new EventDispatcher<void, TreeNode<ViewModel>>()
public onMessage = new EventDispatcher<Message, TreeNode<ViewModel>>()
public isTree = false
private cachedPath?: string
@@ -99,6 +99,21 @@ export class TreeNode<ViewModel> {
}
}
public destroy() {
for (const edge of this.edgeArray) {
edge.target.destroy()
}
this.edgeArray = []
this.edges = {}
this.cachedChildTopics = []
this.sourceEdge = undefined
this.onMerge.removeAllListeners()
this.onEdgesChange.removeAllListeners()
this.onMessage.removeAllListeners()
this.messageHistory = new RingBuffer<Message>(1, 1)
this.message = undefined
}
public unconnectedClone() {
const node = new TreeNode<ViewModel>()
node.message = this.message
@@ -1,11 +1,10 @@
import 'mocha'
import { EventDispatcher } from '../../../../events'
import { expect } from 'chai'
import 'mocha'
describe('EventDispatcher', async () => {
it('should dispatch', async function () {
const dispatcher = new EventDispatcher<string, string>('me')
it('should dispatch', async function() {
const dispatcher = new EventDispatcher<string, string>()
this.timeout(300)
setTimeout(() => dispatcher.dispatch('hello'), 5)
@@ -18,8 +17,8 @@ describe('EventDispatcher', async () => {
expect(response).to.eq('hello')
})
it('should unsubscribe', async function () {
const dispatcher = new EventDispatcher<string, string>('me')
it('should unsubscribe', async function() {
const dispatcher = new EventDispatcher<string, string>()
this.timeout(300)
let incrementee = 0
const callback = (msg: any) => {
-18
View File
@@ -1,18 +1,14 @@
import { Base64Message } from './Model/Base64Message'
import { DataSource, MqttSource } from './DataSource'
import { UpdateInfo } from 'builder-util-runtime'
import {
AddMqttConnection,
EventDispatcher,
MqttMessage,
addMqttConnectionEvent,
backendEvents,
checkForUpdates,
makeConnectionMessageEvent,
makeConnectionStateEvent,
makePublishEvent,
removeConnection,
updateAvailable
} from '../../events'
export class ConnectionManager {
@@ -76,17 +72,3 @@ export class ConnectionManager {
.forEach(conenctionId => this.removeConnection(conenctionId))
}
}
class UpdateNotifier {
public onCheckUpdateRequest = new EventDispatcher<void, UpdateNotifier>(this)
constructor() {
backendEvents.subscribe(checkForUpdates, () => {
this.onCheckUpdateRequest.dispatch()
})
}
public notify(updateInfo: UpdateInfo) {
backendEvents.emit(updateAvailable, updateInfo)
}
}
export const updateNotifier = new UpdateNotifier()
+3 -8
View File
@@ -7,20 +7,15 @@ interface CallbackStore {
export class EventDispatcher<Message, Dispatcher> {
private emitter = new EventEmitter()
private dispatcher: Dispatcher
private callbacks: Array<CallbackStore> = []
constructor(dispatcher: Dispatcher) {
this.dispatcher = dispatcher
}
public dispatch(msg: Message) {
this.emitter.emit('event', msg)
}
public subscribe(callback: (msg: Message, dispatcher: Dispatcher) => void) {
public subscribe(callback: (msg: Message) => void) {
const wrappedCallback = (msg: Message) => {
callback(msg, this.dispatcher)
callback(msg)
}
this.emitter.on('event', wrappedCallback)
@@ -30,7 +25,7 @@ export class EventDispatcher<Message, Dispatcher> {
})
}
public unsubscribe(callback: (msg: Message, dispatcher: Dispatcher) => void) {
public unsubscribe(callback: (msg: Message) => void) {
const item = this.callbacks.find(store => store.callback === callback)
if (!item) {
return
-4
View File
@@ -28,10 +28,6 @@ export function makeConnectionStateEvent(connectionId: string): Event<DataSource
}
}
export const checkForUpdates: Event<void> = {
topic: 'app/update/check',
}
export const updateAvailable: Event<UpdateInfo> = {
topic: 'app/update/available',
}
+5 -4
View File
@@ -1,6 +1,6 @@
{
"name": "MQTT-Explorer",
"version": "0.2.5",
"version": "0.2.6",
"description": "Explore your message queues",
"main": "dist/src/electron.js",
"scripts": {
@@ -32,7 +32,6 @@
"publish": [
"github"
],
"provisioningProfile": "res/MQTT_Explorer_Store_Distribution_Profile.provisionprofile",
"entitlements": "res/entitlements.mas.plist"
},
"linux": {
@@ -51,7 +50,8 @@
"buildResources": "res",
"output": "build"
},
"afterAllArtifactBuild": "./dist/scripts/afterAllArtifactBuild.js"
"afterAllArtifactBuild": "./dist/scripts/afterAllArtifactBuild.js",
"afterPack": "./dist/scripts/afterPack.js"
},
"author": "Thomas Nordquist",
"email": "xxnerowingerxx@gmail.com",
@@ -70,7 +70,7 @@
"app-builder-lib": "https://github.com/thomasnordquist/app-builder-lib.git",
"axios": "^0.18.0",
"chai": "^4.2.0",
"electron": "4",
"electron": "5",
"electron-builder": "^20.38.5",
"fs-extra": "^7.0.1",
"mime": "^2.4.0",
@@ -91,6 +91,7 @@
},
"dependencies": {
"about-window": "^1.12.1",
"dot-prop": "^5.0.0",
"electron-log": "^2.2.17",
"electron-telemetry": "git+https://github.com/thomasnordquist/electron-telemetry.git#dist",
"electron-updater": "^4.0.6",
+28 -11
View File
@@ -1,6 +1,7 @@
import * as builder from 'electron-builder'
import * as fs from 'fs'
import * as path from 'path'
import * as dotProp from 'dot-prop'
const linuxAppImage: builder.CliOptions = {
x64: true,
@@ -88,20 +89,36 @@ type Packages = 'portable' | 'nsis' | 'appx' | 'AppImage' | 'snap' | 'dmg' | 'zi
async function buildWithOptions(options: builder.CliOptions, buildInfo: BuildInfo) {
fs.writeFileSync(path.join(options.projectDir!, 'buildOptions.json'), JSON.stringify(buildInfo))
ensureAppNameForPackage(options, buildInfo.package)
await builder.build({
...options,
[buildInfo.platform]: [buildInfo.package],
})
}
// AppX must hav a different name since the store name is already taken (but not used)
function ensureAppNameForPackage(options: builder.CliOptions, packageOption: Packages) {
const jsonLocation = path.join((options.projectDir as string), 'package.json')
const packageJsonStr = fs.readFileSync(jsonLocation).toString()
const packageJson = JSON.parse(fs.readFileSync(jsonLocation).toString())
packageJson.build.productName = packageOption === 'appx' ? 'MQTT-Explorer' : 'MQTT Explorer'
fs.writeFileSync(jsonLocation, JSON.stringify(packageJson, undefined, ' '))
// AppX must have a different name since the store name is already taken (but not used)
if (buildInfo.package === 'appx') {
dotProp.set(packageJson, 'build.productName', 'MQTT-Explorer')
}
if (buildInfo.platform === 'mac') {
console.log(buildInfo.package)
const provisioningProfile = (buildInfo.package === 'mas') ? 'res/MQTT_Explorer_Store_Distribution_Profile.provisionprofile' : 'res/MQTTExplorerdmg.provisionprofile'
dotProp.set(packageJson, 'build.mac.provisioningProfile', provisioningProfile)
}
try {
// Write modified package.json
fs.writeFileSync(jsonLocation, JSON.stringify(packageJson))
await builder.build({
...options,
[buildInfo.platform]: [buildInfo.package],
})
} catch (error) {
throw error
} finally {
// Roll back changes to package.json
fs.writeFileSync(jsonLocation, packageJsonStr)
}
}
function build() {
Binary file not shown.
+2
View File
@@ -13,6 +13,8 @@ export default async function(info: any) {
await exec('sudo', ['unsquashfs', snapFile])
await exec('sudo', ['rm', snapFile])
await exec('sudo', ['chmod', '-R', 'g-s', 'squashfs-root'])
// Add command line argument to disable the sandbox
await exec('sudo', ['snap', 'run', 'snapcraft', 'pack', 'squashfs-root', '--output', snapFile])
await exec('sudo', ['rm', '-rf', 'squashfs-root'])
+36
View File
@@ -0,0 +1,36 @@
import * as fs from 'fs-extra'
import * as path from 'path'
import { chdir } from 'process'
import { exec } from './util'
interface Target {
name: 'appImage' | string
}
interface Context {
appOutDir: string // .../build/clean/build/linux-unpacked
outDir: string // .../build/clean/build
targets: [Target]
}
export default async function(context: Context) {
console.log(context)
const isLinux = context.targets.find(target => target.name === 'appImage' || target.name === 'snap')
if (!isLinux) {
return
}
const originalDir = process.cwd()
const dirname = context.appOutDir
chdir(dirname)
await exec('mv', ['mqtt-explorer', 'mqtt-explorer.bin'])
const wrapperScript = `#!/bin/bash
"\${BASH_SOURCE%/*}"/mqtt-explorer.bin "$@" --no-sandbox
`
fs.writeFileSync('mqtt-explorer', wrapperScript)
await exec('chmod', ['+x', 'mqtt-explorer'])
chdir(originalDir)
}
+1 -1
View File
@@ -36,7 +36,7 @@ rm ./app.mp4 || echo no need to delete ./app.mp4
tmux new-session -d -s record ffmpeg -f x11grab -draw_mouse 0 -video_size $DIMENSIONS -i :$SCR -r 20 -vcodec rawvideo -pix_fmt yuv420p qrawvideorgb24.yuv
# Start tests
node dist/src/spec/webdriverio.js
node dist/src/spec/demoVideo.js
TEST_EXIT_CODE=$?
echo "Webriver exitet with $TEST_EXIT_CODE"
+4 -6
View File
@@ -93,9 +93,8 @@ const viewMenu: MenuItemConstructorOptions = {
click: () => {
const window = BrowserWindow.getFocusedWindow()
if (window) {
window.webContents.getZoomFactor((zoom) => {
window.webContents.setZoomFactor(Math.min(zoom + 0.1, 2.0))
})
const zoom = window.webContents.getZoomFactor()
window.webContents.setZoomFactor(Math.min(zoom + 0.1, 2.0))
}
},
},
@@ -105,9 +104,8 @@ const viewMenu: MenuItemConstructorOptions = {
click: () => {
const window = BrowserWindow.getFocusedWindow()
if (window) {
window.webContents.getZoomFactor((zoom) => {
window.webContents.setZoomFactor(Math.max(zoom - 0.1, 0.5))
})
const zoom = window.webContents.getZoomFactor()
window.webContents.setZoomFactor(Math.max(zoom - 0.1, 0.5))
}
},
},
+8 -11
View File
@@ -1,10 +1,9 @@
import { autoUpdater } from 'electron-updater'
import { BuildInfo } from 'electron-telemetry/build/Model'
import { UpdateInfo } from '../events'
import { updateNotifier } from '../backend/src/index'
export function shouldAutoUpdate(build: BuildInfo) {
return build.package !== 'portable'
return build.package !== 'portable' && build.platform !== 'mac'
}
export function handleAutoUpdate() {
@@ -12,15 +11,13 @@ export function handleAutoUpdate() {
console.log('There is an update available')
})
autoUpdater.on('error', () => {
console.log('could not update due to error')
autoUpdater.on('error', (error) => {
console.error('could not update due to error', error)
})
updateNotifier.onCheckUpdateRequest.subscribe(() => {
try {
autoUpdater.checkForUpdatesAndNotify()
} catch (error) {
console.error(error)
}
})
try {
autoUpdater.checkForUpdatesAndNotify()
} catch (error) {
console.error(error)
}
}
+3 -3
View File
@@ -18,9 +18,9 @@ export async function waitForDevServer() {
export function loadDevTools() {
// Redux
BrowserWindow.addDevToolsExtension(
path.join(os.homedir(), '/Library/Application Support/Google/Chrome/Default/Extensions/lmhkpmbekcpmknklioeibfkpmmfibljd/2.17.0_0/')
)
// BrowserWindow.addDevToolsExtension(
// path.join(os.homedir(), '/Library/Application Support/Google/Chrome/Default/Extensions/lmhkpmbekcpmknklioeibfkpmmfibljd/2.17.0_0/')
// )
}
export function isDev() {
+5 -2
View File
@@ -14,6 +14,8 @@ if (!isDev() && !runningUiTestOnCi()) {
const electronTelemetry = electronTelemetryFactory('9b0c8ca04a361eb8160d98c5', buildOptions)
}
app.commandLine.appendSwitch('--no-sandbox')
autoUpdater.logger = log
log.info('App starting...')
@@ -33,7 +35,7 @@ async function createWindow() {
loadDevTools()
}
const iconPath = path.join(__dirname, 'icon.png')
const iconPath = path.join(__dirname, '..', '..', 'icon.png')
// Create the browser window.
mainWindow = new BrowserWindow({
width: 1024,
@@ -42,13 +44,14 @@ async function createWindow() {
webPreferences: {
nodeIntegration: true,
devTools: true,
sandbox: false,
},
icon: iconPath,
})
mainWindow.once('ready-to-show', () => {
if (mainWindow) {
runningUiTestOnCi && mainWindow.setFullScreen(true)
runningUiTestOnCi() && mainWindow.setFullScreen(true)
mainWindow.show()
}
})
@@ -23,7 +23,7 @@ import {
createFakeMousePointer,
hideText,
showText,
sleep
sleep,
} from './util'
process.on('unhandledRejection', (error: Error) => {
@@ -55,8 +55,9 @@ async function doStuff() {
const browser = await webdriverio.remote(options)
await createFakeMousePointer(browser)
// Wait for Username input to be visible
await browser.$(`//label[contains(text(), "Username")]/..//input`)
await browser.$('//label[contains(text(), "Username")]/..//input')
const scenes = new SceneBuilder()
await scenes.record('connect', async () => {
await connectTo('127.0.0.1', browser)
@@ -64,10 +65,10 @@ async function doStuff() {
})
await scenes.record('topic_updates', async () => {
await showText('Topic overview', 2000, browser, 'top')
await sleep(2000)
await showText('Indicate topic updates', 2000, browser, 'bottom')
await sleep(3000)
await showText('Topic overview', 1000, browser, 'top')
await sleep(1000)
await showText('Indicate topic updates', 1000, browser, 'middle')
await sleep(1000)
})
await scenes.record('numeric_plots', async () => {
@@ -133,7 +134,7 @@ async function doStuff() {
await scenes.record('customize_subscriptions', async () => {
await sleep(2000)
await disconnect(browser)
await showText('Customize Subscriptions', 3000, browser, 'top')
await showText('Customize Subscriptions', 1000, browser, 'top')
await showAdvancedConnectionSettings(browser)
})
+122
View File
@@ -0,0 +1,122 @@
import * as fs from 'fs'
import * as os from 'os'
import * as webdriverio from 'webdriverio'
import mockMqtt, { stopUpdates as stopMqttUpdates } from './mock-mqtt'
import {
ClassNameMapping,
countInstancesOf,
createFakeMousePointer,
getHeapDump,
setFast,
sleep
} from './util'
import { clearSearch, searchTree } from './scenarios/searchTree'
import { connectTo } from './scenarios/connect'
import { reconnect } from './scenarios/reconnect'
process.on('unhandledRejection', (error: Error) => {
console.error('unhandledRejection', error.message, error.stack)
process.exit(1)
})
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
port: 9515, // "9515" is the port opened by chrome driver.
capabilities: {
browserName: 'electron',
chromeOptions: {
binary: `${__dirname}/../../../node_modules/.bin/electron`,
args: [`--app=${__dirname}/../../..`, '--force-device-scale-factor=1', '--no-sandbox', '--disable-dev-shm-usage', '--disable-extensions'].concat(runningUiTestOnCi),
},
windowTypes: ['app', 'webview'],
},
}
async function doStuff() {
console.log('Waiting for MQTT Broker on port 1880 (no auth)')
await mockMqtt()
console.log('start webdriver')
const browser = await webdriverio.remote(options)
setFast()
await createFakeMousePointer(browser)
// Wait for Username input to be visible
await browser.$('//label[contains(text(), "Username")]/..//input')
await connectTo('127.0.0.1', browser)
stopMqttUpdates()
await sleep(1000, true)
let heapDump = await getHeapDump(browser)
const initialTreeOccurrances = await countInstancesOf(heapDump, ClassNameMapping.Tree)
const initialNodeOccurrances = await countInstancesOf(heapDump, ClassNameMapping.TreeNode)
console.log(initialTreeOccurrances, initialNodeOccurrances)
await doX(3, async () => {
await reconnect(browser)
})
await sleep(1000, true)
await doX(15, async () => {
await searchTree('temp', browser)
await reconnect(browser)
})
await searchTree('ab', browser)
await clearSearch(browser)
await searchTree('temp', browser)
await clearSearch(browser)
await sleep(1000, true)
await waitForGarbageCollectorToDetermineLeak(browser, initialTreeOccurrances, initialNodeOccurrances)
}
async function waitForGarbageCollectorToDetermineLeak(browser: any, initialTreeOccurrances: number, initialNodeOccurrances: number) {
let delta = -1
let lastTreeOccurances = -1
let lastNodeOccurances = -1
let leak = false
while (delta < 0) {
if (lastTreeOccurances !== -1) {
await sleep(10000, true)
}
const heapDump = await getHeapDump(browser)
const currentTreeOccurrances = await countInstancesOf(heapDump, ClassNameMapping.Tree)
const currentNodeOccurrances = await countInstancesOf(heapDump, ClassNameMapping.TreeNode)
// Temporary "leaks" are expected due to React Fibers memoization
if (Math.abs(initialTreeOccurrances - currentTreeOccurrances) > 1 || Math.abs(currentNodeOccurrances - initialNodeOccurrances) > 8) {
console.error('Possible leak detected', initialTreeOccurrances, currentTreeOccurrances, initialNodeOccurrances, currentNodeOccurrances)
leak = true
} else {
leak = false
}
const treeDelta = lastTreeOccurances >= 0 ? currentTreeOccurrances - lastTreeOccurances : -1
const nodeDelta = lastTreeOccurances >= 0 ? currentNodeOccurrances - lastNodeOccurances : -1
delta = treeDelta + nodeDelta
lastTreeOccurances = currentTreeOccurrances
lastNodeOccurances = currentNodeOccurrances
}
if (leak) {
console.error('leak')
process.exit(100)
}
}
async function doX(x: number, action: () => Promise<any>) {
for (let i = 0; i < x; i += 1) {
await action()
await sleep(10, true)
}
}
doStuff()
+7 -2
View File
@@ -30,16 +30,21 @@ function temperature(base = 18, sineCoefficient = 2, offset = 0) {
return String(Math.round(temp * 100) / 100)
}
export function stop() {
export function stopUpdates() {
for (const interval of intervals) {
clearInterval(interval)
}
intervals = []
}
export function stop() {
stopUpdates()
try {
client && client.end()
} catch {}
}
const intervals: any = []
let intervals: any = []
function generateData(client: mqtt.MqttClient) {
client.publish('livingroom/lamp/state', 'on', { retain: true, qos: 0 })
+1 -1
View File
@@ -9,5 +9,5 @@ export async function connectTo(host: string, browser: Browser<void>) {
await browser.saveScreenshot('screen1.png')
const connectButton = await browser.$('//button/span[contains(text(),"Connect")]')
clickOn(connectButton, browser)
await clickOn(connectButton, browser)
}
+2 -2
View File
@@ -2,6 +2,6 @@ import { clickOn } from '../util'
import { Browser } from 'webdriverio'
export async function disconnect(browser: Browser<void>) {
const connectButton = await browser.$('//button/span[contains(text(),"Disconnect")]')
clickOn(connectButton, browser)
const disconnectButton = await browser.$('//button/span[contains(text(),"Disconnect")]')
await clickOn(disconnectButton, browser)
}
+9
View File
@@ -0,0 +1,9 @@
import { clickOn } from '../util'
import { Browser } from 'webdriverio'
export async function reconnect(browser: Browser<void>) {
const disconnectButton = await browser.$('//button/span[contains(text(),"Disconnect")]')
await clickOn(disconnectButton, browser)
const connectButton = await browser.$('//button/span[contains(text(),"Connect")]')
await clickOn(connectButton, browser)
}
+3 -1
View File
@@ -10,9 +10,11 @@ export async function showJsonFormatting(browser: Browser<void>) {
const payloadInput = await browser.$('//*[contains(@class, "ace_text-input")]')
await clickOn(editor, browser)
await browser.keys(['\uE009', 'A']) // Ctrl + A
await sleep(200)
await browser.keys(['\uE000']) // End keyboard modifier
await sleep(200)
await browser.keys(['\uE003']) // Backspace
await sleep(500)
await sleep(200)
await writeTextPayload(payloadInput, '{"action": "setState", "state": "on" }')
await sleep(300)
await clickOn(formatJsonButton, browser)
+9 -1
View File
@@ -1,4 +1,4 @@
import { clickOn, sleep, writeText, expandTopic, moveToCenterOfElement } from '../util'
import { clickOn, sleep, writeText, expandTopic, moveToCenterOfElement, showText } from '../util'
import { Browser } from 'webdriverio'
export async function showMenu(browser: Browser<void>) {
@@ -19,5 +19,13 @@ export async function showMenu(browser: Browser<void>) {
await clickOn(alphabetically, browser)
await sleep(2000)
await showText('Dark Mode', 1500, browser, 'top')
await sleep(1500)
const themeSwitch = await browser.$('//*[contains(text(), "Dark Mode")]/..//input')
await clickOn(themeSwitch, browser)
await sleep(3000)
await browser.saveScreenshot('screen_dark_mode.png')
await clickOn(themeSwitch, browser)
await clickOn(menuButton, browser)
}
+4 -4
View File
@@ -14,8 +14,8 @@ export async function showOffDiffCapability(browser: Browser<void>) {
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)
// // 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)
}
+41 -6
View File
@@ -1,17 +1,27 @@
import * as fs from 'fs'
import { Browser, Element } from 'webdriverio'
export { expandTopic } from './expandTopic'
let fast = false
export function setFast() {
fast = true
}
export function sleep(ms: number, required = false) {
return new Promise((resolve) => {
if (required) {
setTimeout(resolve, ms)
} else {
setTimeout(resolve, ms)
setTimeout(resolve, fast ? 0 : ms)
}
})
}
export async function writeText(text: string, browser: Browser<void>, delay = 0) {
if (fast) {
return browser.keys(text.split(''))
}
for (const c of text.split('')) {
await browser.keys([c])
await sleep(delay)
@@ -42,11 +52,12 @@ export async function moveToCenterOfElement(element: Element<void>, browser: Bro
const targetX = x + width / 2
const targetY = y + height / 2
const duration = 500
const duration = fast ? 1 : 500
const js = `window.demo.moveMouse(${targetX}, ${targetY}, ${duration});`
await browser.execute(js)
await sleep(duration + 500, true)
await sleep(duration)
await sleep(250, true)
await element.moveTo()
}
@@ -73,17 +84,41 @@ export async function createFakeMousePointer(browser: Browser<void>) {
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)
await browser.execute(js)
}
type HeapDump = any
export async function getHeapDump(browser: Browser<void>): Promise<HeapDump> {
const filename = 'heapdump.json'
const js = `window.demo.writeHeapdump('${filename}');`
await browser.execute(js)
const buffer = fs.readFileSync(filename)
fs.unlinkSync(filename)
return JSON.parse(buffer.toString())
}
export enum ClassNameMapping {
TreeNode = 'TreeNode_TreeNode',
TreeNodeComponent = 'TreeNode_TreeNodeComponent',
Tree = 'Tree_Tree',
}
export async function countInstancesOf(heapDump: HeapDump, className: ClassNameMapping): Promise<number> {
return heapDump.nodes
.map((idx: number) => heapDump.strings[idx])
.filter((s: string) => s === className).length
}
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)});`
browser.execute(js)
await browser.execute(js)
}
export async function hideText(browser: Browser<void>) {
const js = 'window.demo.hideMessage();'
browser.execute(js)
await browser.execute(js)
await sleep(600)
}
+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/demoVideo.ts", "src/spec/leakTest.ts", "scripts/*.ts"]
}
+11 -4
View File
@@ -1131,6 +1131,13 @@ dot-prop@^4.1.0:
dependencies:
is-obj "^1.0.0"
dot-prop@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.0.0.tgz#64b7968af349c3a9f966aa12658dbd5829f6b953"
integrity sha512-RTmaF2jx3nOBO2GvtFqjnDLycjFUMqt+2pwRx7JVYa81lDauoj9aNkyrJI2ikR58FbBIchiIlRiGG+muLJ4oHQ==
dependencies:
is-obj "^1.0.0"
dotenv-expand@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-4.2.0.tgz#def1f1ca5d6059d24a766e587942c21106ce1275"
@@ -1287,10 +1294,10 @@ electron-updater@^4.0.6:
semver "^5.6.0"
source-map-support "^0.5.9"
electron@4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/electron/-/electron-4.1.4.tgz#41ba9e041f38c25c62a7db806884410654df058f"
integrity sha512-MelOjntJvd33izEjR6H4N/Uii7y535z/b2BuYXJGLNSHL6o1IlyhUQmfiT87kWABayERgeuYERgvsyf956OOFw==
electron@5:
version "5.0.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-5.0.0.tgz#d8352e2c9625b3be0112ce0c1bf5c9b6f692557e"
integrity sha512-++emIe4vLihiYiAVL+E8DT5vSNVFEIuQCRxA+VfpDRVBcog85UB28vi4ogRmMOK3UffzKdWV6e1jqp3T0KpBoA==
dependencies:
"@types/node" "^10.12.18"
electron-download "^4.1.0"