mirror of
https://github.com/thomasnordquist/MQTT-Explorer.git
synced 2026-09-11 17:13:53 +00:00
Compare commits
57
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ead83463c | ||
|
|
2d8253ef31 | ||
|
|
d4a709f0d5 | ||
|
|
d090197fe5 | ||
|
|
63cdc53075 | ||
|
|
dca35c1ed7 | ||
|
|
63530a41ac | ||
|
|
d3bf5e87e1 | ||
|
|
10c6c3cecd | ||
|
|
4d8fa72491 | ||
|
|
b9c0a66948 | ||
|
|
6b859d31e8 | ||
|
|
4fd328e716 | ||
|
|
b606f14836 | ||
|
|
ad49c5c819 | ||
|
|
749a591465 | ||
|
|
a1b1c92648 | ||
|
|
85c6c4ebbc | ||
|
|
de7c9479c9 | ||
|
|
138f51eb39 | ||
|
|
107ca83882 | ||
|
|
839ad531ba | ||
|
|
174eb0c767 | ||
|
|
953422dcd4 | ||
|
|
4c58d4af69 | ||
|
|
766a283b01 | ||
|
|
870043bde6 | ||
|
|
d2fea71bd5 | ||
|
|
208c379898 | ||
|
|
55e3c827c4 | ||
|
|
a4015141c3 | ||
|
|
8ac285846a | ||
|
|
2cb4084ed6 | ||
|
|
0bcae7912c | ||
|
|
1868a08237 | ||
|
|
ba919c19fa | ||
|
|
701477f32e | ||
|
|
fc59c46121 | ||
|
|
eb67c69fad | ||
|
|
ca3bd0b22b | ||
|
|
797d11d58d | ||
|
|
8281a852ab | ||
|
|
e5c3f207d9 | ||
|
|
2efc8f4dda | ||
|
|
60a06d8c82 | ||
|
|
857500f9c9 | ||
|
|
396e6985ce | ||
|
|
6d75f29f03 | ||
|
|
f8cdf0837e | ||
|
|
5bb96233d1 | ||
|
|
f3ff6cde7b | ||
|
|
ec88f9822d | ||
|
|
235e81826b | ||
|
|
615ec17b96 | ||
|
|
ddc801fe93 | ||
|
|
24c6b7e7b3 | ||
|
|
55ea381b3b |
@@ -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
|
||||
@@ -1,42 +1,46 @@
|
||||
# [MQTT Explorer](https://thomasnordquist.github.io/MQTT-Explorer/)
|
||||
[](https://travis-ci.org/thomasnordquist/MQTT-Explorer/releases)
|
||||
[](https://travis-ci.org/thomasnordquist/MQTT-Explorer/releases)
|
||||
[](https://travis-ci.org/thomasnordquist/MQTT-Explorer)
|
||||
[](https://travis-ci.org/thomasnordquist/MQTT-Explorer)
|
||||
|
||||
### 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 | [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 | [AppImage](https://github.com/thomasnordquist/MQTT-Explorer/releases/download/v0.2.0/MQTT-Explorer-0.2.0-x86_64.AppImage)<br>*Run AppImage:<br>Make it executable and double-click it.* |
|
||||
|  | Ubuntu | [](https://snapcraft.io/mqtt-explorer)<br>[Ubuntu Store](snap://mqtt-explorer) |
|
||||
|  | Mac | [dmg](https://github.com/thomasnordquist/MQTT-Explorer/releases/download/v0.2.0/MQTT-Explorer-0.2.0.dmg) |
|
||||
|
||||
[More Downloads](https://github.com/thomasnordquist/MQTT-Explorer/releases)
|
||||
|
||||
## Screenshots
|
||||

|
||||
|
||||
## Download
|
||||
|
||||
| Platform | | Downloads |
|
||||
|:----------:|:-------------:|:------:|
|
||||
|  | 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)<br>*Run AppImage:<br>Make it executable and double-click it.* |
|
||||
|  | Mac | [dmg](https://github.com/thomasnordquist/MQTT-Explorer/releases/download/v0.1.3/MQTT-Explorer-0.1.3.dmg) |
|
||||
|
||||
[More Downloads](https://github.com/thomasnordquist/MQTT-Explorer/releases)
|
||||
|
||||
## Demo
|
||||

|
||||

|
||||
|
||||
## 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
@@ -1,43 +1,47 @@
|
||||
# [MQTT Explorer](https://thomasnordquist.github.io/MQTT-Explorer/)
|
||||
[](https://travis-ci.org/thomasnordquist/MQTT-Explorer/releases)
|
||||
[](https://travis-ci.org/thomasnordquist/MQTT-Explorer/releases)
|
||||
[](https://travis-ci.org/thomasnordquist/MQTT-Explorer)
|
||||

|
||||
[](https://travis-ci.org/thomasnordquist/MQTT-Explorer)
|
||||
[](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 | {{windowsTargets}} |
|
||||
|  | Linux | {{linuxTargets}}<br>*Run AppImage:<br>Make it executable and double-click it.* |
|
||||
|  | Ubuntu | [](https://snapcraft.io/mqtt-explorer)<br>[Ubuntu Store](snap://mqtt-explorer) |
|
||||
|  | Mac | {{macTargets}} |
|
||||
|
||||
[More Downloads](https://github.com/thomasnordquist/MQTT-Explorer/releases)
|
||||
|
||||
## Screenshots
|
||||

|
||||
|
||||
## Download
|
||||
|
||||
| Platform | | Downloads |
|
||||
|:----------:|:-------------:|:------:|
|
||||
|  | Windows | {{windowsTargets}} |
|
||||
|  | Linux | {{linuxTargets}}<br>*Run AppImage:<br>Make it executable and double-click it.* |
|
||||
|  | Mac | {{macTargets}} |
|
||||
|
||||
[More Downloads](https://github.com/thomasnordquist/MQTT-Explorer/releases)
|
||||
|
||||
## Demo
|
||||

|
||||

|
||||
|
||||
## 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
|
||||
|
||||
+4
-1
@@ -12,7 +12,9 @@
|
||||
"@material-ui/core": "^4.0.0-alpha.0",
|
||||
"@material-ui/icons": "^3.0.1",
|
||||
"@material-ui/styles": "^3.0.0-alpha.8",
|
||||
"@types/diff": "^4.0.1",
|
||||
"@types/node": "^10.12.18",
|
||||
"@types/prismjs": "^1.9.1",
|
||||
"@types/react": "^16.7.18",
|
||||
"@types/react-dom": "^16.0.11",
|
||||
"@types/react-redux": "^6.0.12",
|
||||
@@ -26,6 +28,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,10 +37,10 @@
|
||||
"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",
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -165,14 +165,10 @@ class Sidebar extends React.Component<Props, State> {
|
||||
{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} />
|
||||
<ValueRenderer node={this.props.node} compareWith={this.state.compareMessage} />
|
||||
</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,18 +191,6 @@ class Sidebar extends React.Component<Props, State> {
|
||||
)
|
||||
}
|
||||
|
||||
private valueRenderWidthChange = (width: number) => {
|
||||
this.setState({ valueRenderWidth: width })
|
||||
}
|
||||
|
||||
private showValueComparison = (a: any) => (
|
||||
<Fade {...a.TransitionProps} timeout={350}>
|
||||
<Paper style={{ maxWidth: this.state.valueRenderWidth }}>
|
||||
<ValueRenderer message={this.state.compareMessage} />
|
||||
</Paper>
|
||||
</Fade>
|
||||
)
|
||||
|
||||
private messageMetaInfo() {
|
||||
if (!this.props.node || !this.props.node.message || !this.props.node.mqttMessage) {
|
||||
return null
|
||||
|
||||
@@ -1,65 +1,102 @@
|
||||
import * as React from 'react'
|
||||
import * as q from '../../../../backend/src/Model'
|
||||
|
||||
import * as React from 'react'
|
||||
import CodeDiff from '../CodeDiff'
|
||||
import { default as ReactResizeDetector } from 'react-resize-detector'
|
||||
import { Theme, withTheme } from '@material-ui/core/styles'
|
||||
|
||||
import { default as ReactJson } from 'react-json-view'
|
||||
|
||||
interface Props {
|
||||
message?: q.Message
|
||||
node?: q.TreeNode<any>,
|
||||
compareWith?: q.Message
|
||||
theme: Theme
|
||||
}
|
||||
|
||||
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>
|
||||
if (!this.props.node) {
|
||||
return null
|
||||
}
|
||||
|
||||
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 { node } = this.props
|
||||
if (!node || !node.message) {
|
||||
return <span key="empty" />
|
||||
}
|
||||
|
||||
const message = node.message
|
||||
const previousMessages = node.messageHistory.toArray()
|
||||
const previousMessage = previousMessages[previousMessages.length - 2]
|
||||
const compareMessage = this.props.compareWith || previousMessage || message
|
||||
|
||||
let json
|
||||
try {
|
||||
json = JSON.parse(message.value)
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
+54
-63
@@ -111,6 +111,11 @@
|
||||
prop-types "^15.6.0"
|
||||
react-is "^16.8.0"
|
||||
|
||||
"@types/diff@^4.0.1":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/diff/-/diff-4.0.1.tgz#471950ff97e2205ab02404bd7ac960fac074d886"
|
||||
integrity sha512-BFob98KGKIqjcJjbmO/g+daJinDOtSUs27e0NTirODSUHYhUUAVXmq/nKIrmRFsOHrOWj5mncvByrzyw1X1PNQ==
|
||||
|
||||
"@types/jss@^9.5.6":
|
||||
version "9.5.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/jss/-/jss-9.5.7.tgz#fa57a6d0b38a3abef8a425e3eb6a53495cb9d5a0"
|
||||
@@ -124,6 +129,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67"
|
||||
integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==
|
||||
|
||||
"@types/prismjs@^1.9.1":
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.9.1.tgz#e63b1ed46f184046615dd87b978f902c9f328649"
|
||||
integrity sha512-6TjOUrpDM2yjyPE/bdenDqxfoR4E8ve7coEdtiKg5IMBsjqTGsIN7HF5dpaNg/Ab8VumNeRXI8EjQsyBYWPeNA==
|
||||
|
||||
"@types/prop-types@*":
|
||||
version "15.5.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.8.tgz#8ae4e0ea205fe95c3901a5a1df7f66495e3a56ce"
|
||||
@@ -612,11 +622,6 @@ balanced-match@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
||||
|
||||
base16@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70"
|
||||
integrity sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=
|
||||
|
||||
base64-arraybuffer@0.1.5:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
|
||||
@@ -1023,6 +1028,15 @@ clean-css@4.2.x:
|
||||
dependencies:
|
||||
source-map "~0.6.0"
|
||||
|
||||
clipboard@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.4.tgz#836dafd66cf0fea5d71ce5d5b0bf6e958009112d"
|
||||
integrity sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==
|
||||
dependencies:
|
||||
good-listener "^1.2.2"
|
||||
select "^1.1.2"
|
||||
tiny-emitter "^2.0.0"
|
||||
|
||||
cliui@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49"
|
||||
@@ -1602,6 +1616,11 @@ delayed-stream@~1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
|
||||
|
||||
delegate@^3.1.2:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
|
||||
integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==
|
||||
|
||||
delegates@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||
@@ -1645,6 +1664,11 @@ diff-match-patch@^1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.4.tgz#6ac4b55237463761c4daf0dc603eb869124744b1"
|
||||
integrity sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg==
|
||||
|
||||
diff@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
|
||||
integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==
|
||||
|
||||
diffie-hellman@^5.0.0:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
|
||||
@@ -2164,14 +2188,7 @@ faye-websocket@~0.11.1:
|
||||
dependencies:
|
||||
websocket-driver ">=0.5.1"
|
||||
|
||||
fbemitter@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-2.1.1.tgz#523e14fdaf5248805bb02f62efc33be703f51865"
|
||||
integrity sha1-Uj4U/a9SSIBbsC9i78M75wP1GGU=
|
||||
dependencies:
|
||||
fbjs "^0.8.4"
|
||||
|
||||
fbjs@^0.8.0, fbjs@^0.8.1, fbjs@^0.8.4:
|
||||
fbjs@^0.8.1:
|
||||
version "0.8.17"
|
||||
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
|
||||
integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=
|
||||
@@ -2258,14 +2275,6 @@ flush-write-stream@^1.0.0:
|
||||
inherits "^2.0.1"
|
||||
readable-stream "^2.0.4"
|
||||
|
||||
flux@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/flux/-/flux-3.1.3.tgz#d23bed515a79a22d933ab53ab4ada19d05b2f08a"
|
||||
integrity sha1-0jvtUVp5oi2TOrU6tK2hnQWy8Io=
|
||||
dependencies:
|
||||
fbemitter "^2.0.0"
|
||||
fbjs "^0.8.0"
|
||||
|
||||
follow-redirects@^1.0.0, follow-redirects@^1.3.0:
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.6.1.tgz#514973c44b5757368bad8bddfe52f81f015c94cb"
|
||||
@@ -2459,6 +2468,13 @@ globby@^6.1.0:
|
||||
pify "^2.0.0"
|
||||
pinkie-promise "^2.0.0"
|
||||
|
||||
good-listener@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
|
||||
integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=
|
||||
dependencies:
|
||||
delegate "^3.1.2"
|
||||
|
||||
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2:
|
||||
version "4.1.15"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
||||
@@ -3341,21 +3357,11 @@ lodash-es@^4.17.11:
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.11.tgz#145ab4a7ac5c5e52a3531fb4f310255a152b4be0"
|
||||
integrity sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q==
|
||||
|
||||
lodash.curry@^4.0.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170"
|
||||
integrity sha1-JI42By7ekGUB11lmIAqG2riyMXA=
|
||||
|
||||
lodash.debounce@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
|
||||
|
||||
lodash.flow@^3.3.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a"
|
||||
integrity sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=
|
||||
|
||||
lodash.get@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||
@@ -4270,6 +4276,13 @@ pretty-error@^2.1.1:
|
||||
renderkid "^2.0.1"
|
||||
utila "~0.4"
|
||||
|
||||
prismjs@^1.15.0:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.15.0.tgz#8801d332e472091ba8def94976c8877ad60398d9"
|
||||
integrity sha512-Lf2JrFYx8FanHrjoV5oL8YHCclLQgbJcVZR+gikGGMqz6ub5QVWDTM6YIwm3BuPxM/LOV+rKns3LssXNLIf+DA==
|
||||
optionalDependencies:
|
||||
clipboard "^2.0.0"
|
||||
|
||||
process-nextick-args@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
||||
@@ -4375,11 +4388,6 @@ punycode@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
pure-color@^1.2.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e"
|
||||
integrity sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=
|
||||
|
||||
qs@6.5.2, qs@~6.5.2:
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
@@ -4471,16 +4479,6 @@ react-ace@^6.3.2:
|
||||
lodash.isequal "^4.5.0"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
react-base16-styling@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c"
|
||||
integrity sha1-7yFW1mz0E5aVyKFniGy2nqZgeSw=
|
||||
dependencies:
|
||||
base16 "^1.0.0"
|
||||
lodash.curry "^4.0.1"
|
||||
lodash.flow "^3.3.0"
|
||||
pure-color "^1.2.0"
|
||||
|
||||
react-dom@^16.6.3, react-dom@^16.7.0:
|
||||
version "16.7.0"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.7.0.tgz#a17b2a7ca89ee7390bc1ed5eb81783c7461748b8"
|
||||
@@ -4510,16 +4508,6 @@ react-is@^16.8.0:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.2.tgz#09891d324cad1cb0c1f2d91f70a71a4bee34df0f"
|
||||
integrity sha512-D+NxhSR2HUCjYky1q1DwpNUD44cDpUXzSmmFyC3ug1bClcU/iDNy0YNn1iwme28fn+NFhpA13IndOd42CrFb+Q==
|
||||
|
||||
react-json-view@^1.19.1:
|
||||
version "1.19.1"
|
||||
resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.19.1.tgz#95d8e59e024f08a25e5dc8f076ae304eed97cf5c"
|
||||
integrity sha512-u5e0XDLIs9Rj43vWkKvwL8G3JzvXSl6etuS5G42a8klMohZuYFQzSN6ri+/GiBptDqlrXPTdExJVU7x9rrlXhg==
|
||||
dependencies:
|
||||
flux "^3.1.3"
|
||||
react-base16-styling "^0.6.0"
|
||||
react-lifecycles-compat "^3.0.4"
|
||||
react-textarea-autosize "^6.1.0"
|
||||
|
||||
react-lifecycles-compat@^3.0.2, react-lifecycles-compat@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||
@@ -4574,13 +4562,6 @@ react-style-proptype@^3.0.0:
|
||||
dependencies:
|
||||
prop-types "^15.5.4"
|
||||
|
||||
react-textarea-autosize@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz#df91387f8a8f22020b77e3833c09829d706a09a5"
|
||||
integrity sha512-F6bI1dgib6fSvG8so1HuArPUv+iVEfPliuLWusLF+gAKz0FbB4jLrWUrTAeq1afnPT2c9toEZYUdz/y1uKMy4A==
|
||||
dependencies:
|
||||
prop-types "^15.6.0"
|
||||
|
||||
react-transition-group@^2.2.1:
|
||||
version "2.5.3"
|
||||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.5.3.tgz#26de363cab19e5c88ae5dbae105c706cf953bb92"
|
||||
@@ -4934,6 +4915,11 @@ select-hose@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
|
||||
integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=
|
||||
|
||||
select@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
|
||||
integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=
|
||||
|
||||
selfsigned@^1.9.1:
|
||||
version "1.10.4"
|
||||
resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.4.tgz#cdd7eccfca4ed7635d47a08bf2d5d3074092e2cd"
|
||||
@@ -5442,6 +5428,11 @@ timers-browserify@^2.0.4:
|
||||
dependencies:
|
||||
setimmediate "^1.0.4"
|
||||
|
||||
tiny-emitter@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
|
||||
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
|
||||
|
||||
tiny-lr@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-1.1.1.tgz#9fa547412f238fedb068ee295af8b682c98b2aab"
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
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
|
||||
|
||||
test_script:
|
||||
- yarn test
|
||||
|
||||
before_deploy:
|
||||
- yarn prepare-release
|
||||
- yarn package appx
|
||||
|
||||
deploy:
|
||||
- provider: Environment
|
||||
name: production
|
||||
on:
|
||||
branch: master # only this will work
|
||||
APPVEYOR_REPO_TAG: true # condition will never be evaluated
|
||||
|
||||
artifacts:
|
||||
- path: 'build\clean\build\MQTT-Explorer*.appx'
|
||||
|
||||
@@ -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
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-4
@@ -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,12 @@
|
||||
"category": "Development",
|
||||
"maintainer": "Thomas Nordquist"
|
||||
},
|
||||
"appx": {
|
||||
"applicationId": "mqttexplorer",
|
||||
"identityName": "51031thomas.nordquist.MQTT-Explorer",
|
||||
"publisherDisplayName": "Thomas Nordquist",
|
||||
"publisher": "CN=0A6DE643-4AA2-4FF2-9729-6935C9ED8C13"
|
||||
},
|
||||
"directories": {
|
||||
"app": "./",
|
||||
"buildResources": "res",
|
||||
@@ -44,6 +50,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",
|
||||
@@ -54,7 +61,8 @@
|
||||
"axios": "^0.18.0",
|
||||
"chai": "^4.2.0",
|
||||
"electron": "^4.0.2",
|
||||
"electron-builder": "^20.38.4",
|
||||
"electron-builder": "^20.38.5",
|
||||
"fs-extra": "^7.0.1",
|
||||
"mime": "^2.4.0",
|
||||
"mocha": "^5.2.0",
|
||||
"mustache": "^3.0.1",
|
||||
|
||||
+13
-1
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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()
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/bin/bash
|
||||
ffmpeg -s:v 1024x700 -r 20 -f rawvideo -pix_fmt yuv420p -i qrawvideorgb24.yuv app2.mp4
|
||||
DIMENSIONS="1280x720"
|
||||
|
||||
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 +11,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=1280:-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=1280:-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
@@ -9,7 +9,7 @@ function finish {
|
||||
|
||||
trap finish EXIT
|
||||
|
||||
DIMENSIONS="1024x700"
|
||||
DIMENSIONS="1280x720"
|
||||
SCR=99
|
||||
# Start new window manager
|
||||
Xvfb :$SCR -screen 0 "$DIMENSIONS"x24 -ac &
|
||||
|
||||
+41
-1
@@ -8,7 +8,6 @@ const applicationMenu = {
|
||||
{
|
||||
label: 'About Application',
|
||||
click: () => {
|
||||
console.log(path.join(__dirname, 'icon.png'))
|
||||
openAboutWindow({
|
||||
icon_path: path.join(__dirname, '..', '..', 'icon.png'),
|
||||
license: 'AGPL-3.0',
|
||||
@@ -75,9 +74,50 @@ const editMenu = {
|
||||
],
|
||||
}
|
||||
|
||||
const viewMenu = {
|
||||
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 = [
|
||||
applicationMenu,
|
||||
editMenu,
|
||||
viewMenu,
|
||||
]
|
||||
|
||||
export const menuTemplate = Menu.buildFromTemplate(template)
|
||||
|
||||
+4
-4
@@ -15,7 +15,7 @@ if (!isDev) {
|
||||
}
|
||||
|
||||
const isDebugEnabled = Boolean(process.argv.find(arg => arg === 'debug'))
|
||||
const isFullscreen = Boolean(process.argv.find(arg => arg === '--fullscreen'))
|
||||
const runningUiTestOnCi = Boolean(process.argv.find(arg => arg === '--runningUiTestOnCi'))
|
||||
|
||||
require('electron-debug')({ enabled: isDebugEnabled })
|
||||
|
||||
@@ -36,8 +36,8 @@ function createWindow() {
|
||||
const iconPath = path.join(__dirname, 'icon.png')
|
||||
// Create the browser window.
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1024,
|
||||
height: 700,
|
||||
width: runningUiTestOnCi ? 1280 : 1024,
|
||||
height: runningUiTestOnCi ? 720 : 700,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
@@ -48,7 +48,7 @@ function createWindow() {
|
||||
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
if (mainWindow) {
|
||||
isFullscreen && mainWindow.setFullScreen(true)
|
||||
runningUiTestOnCi && mainWindow.setFullScreen(true)
|
||||
mainWindow.show()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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}`
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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==
|
||||
@@ -1460,7 +1467,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==
|
||||
|
||||
Reference in New Issue
Block a user