mirror of
https://github.com/thomasnordquist/MQTT-Explorer.git
synced 2026-09-12 01:23:31 +00:00
Compare commits
19
Commits
v0.2.4
...
0.0.0-v0.2.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03e228e5a0 | ||
|
|
14f2f9ff1e | ||
|
|
86544c1334 | ||
|
|
9e71c3132e | ||
|
|
817202fc20 | ||
|
|
931ec0113c | ||
|
|
365ebc78ab | ||
|
|
a1d3f32f73 | ||
|
|
e9e6ea618d | ||
|
|
6021df7150 | ||
|
|
070b72b304 | ||
|
|
1f65a0f316 | ||
|
|
0f21f10c0d | ||
|
|
af2ff0149d | ||
|
|
8cd11cde3b | ||
|
|
fdbe6344d9 | ||
|
|
a56b41635c | ||
|
|
2c5c218fd1 | ||
|
|
749df70d5c |
+6
-3
@@ -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",
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)',
|
||||
// },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"postinstall": "yarn build"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"license": "CC-BY-ND-4.0",
|
||||
"nyc": {
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -78,7 +78,7 @@ export class ConnectionManager {
|
||||
}
|
||||
|
||||
class UpdateNotifier {
|
||||
public onCheckUpdateRequest = new EventDispatcher<void, UpdateNotifier>(this)
|
||||
public onCheckUpdateRequest = new EventDispatcher<void, UpdateNotifier>()
|
||||
constructor() {
|
||||
backendEvents.subscribe(checkForUpdates, () => {
|
||||
this.onCheckUpdateRequest.dispatch()
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -32,7 +32,6 @@
|
||||
"publish": [
|
||||
"github"
|
||||
],
|
||||
"provisioningProfile": "res/MQTT_Explorer_Store_Distribution_Profile.provisionprofile",
|
||||
"entitlements": "res/entitlements.mas.plist"
|
||||
},
|
||||
"linux": {
|
||||
@@ -70,7 +69,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 +90,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
@@ -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.
+1
-1
@@ -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
@@ -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))
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
+3
-3
@@ -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() {
|
||||
|
||||
+4
-1
@@ -14,6 +14,8 @@ if (!isDev() && !runningUiTestOnCi()) {
|
||||
const electronTelemetry = electronTelemetryFactory('9b0c8ca04a361eb8160d98c5', buildOptions)
|
||||
}
|
||||
|
||||
app.commandLine.appendSwitch('--no-sandbox')
|
||||
|
||||
autoUpdater.logger = log
|
||||
log.info('App starting...')
|
||||
|
||||
@@ -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) => {
|
||||
@@ -56,7 +56,12 @@ async function doStuff() {
|
||||
await createFakeMousePointer(browser)
|
||||
|
||||
// Wait for Username input to be visible
|
||||
await browser.$(`//label[contains(text(), "Username")]/..//input`)
|
||||
let inputField = undefined
|
||||
let start = Date.now()
|
||||
let maxWaitDuration = 30000
|
||||
while ((!inputField || !inputField.isExisting) && ((Date.now() - start) < maxWaitDuration)) {
|
||||
inputField = await browser.$(`//label[contains(text(), "Username")]/..//input`)
|
||||
}
|
||||
const scenes = new SceneBuilder()
|
||||
await scenes.record('connect', async () => {
|
||||
await connectTo('127.0.0.1', browser)
|
||||
@@ -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()
|
||||
@@ -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 })
|
||||
|
||||
@@ -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,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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
+41
-6
@@ -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
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user