Update code formatting
This commit is contained in:
8
.cspell.json
Normal file
8
.cspell.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"language": "en",
|
||||||
|
"words": [
|
||||||
|
"subheader",
|
||||||
|
"basepath",
|
||||||
|
"webdriverio"
|
||||||
|
]
|
||||||
|
}
|
||||||
2
.prettierignore
Normal file
2
.prettierignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
build
|
||||||
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"files.exclude": {
|
||||||
|
"**/node_modules": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -76,5 +76,8 @@
|
|||||||
"webpack-bundle-analyzer": "^3.0.3",
|
"webpack-bundle-analyzer": "^3.0.3",
|
||||||
"webpack-cli": "^3.1.2",
|
"webpack-cli": "^3.1.2",
|
||||||
"webpack-dev-server": "^3.1.14"
|
"webpack-dev-server": "^3.1.14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"electron": "^5.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,20 +9,18 @@ import { globalActions } from '.'
|
|||||||
import { resetStore as resetTreeStore, showTree } from './Tree'
|
import { resetStore as resetTreeStore, showTree } from './Tree'
|
||||||
import { showError } from './Global'
|
import { showError } from './Global'
|
||||||
import { TopicViewModel } from '../model/TopicViewModel'
|
import { TopicViewModel } from '../model/TopicViewModel'
|
||||||
import {
|
import { addMqttConnectionEvent, makeConnectionStateEvent, removeConnection, rendererEvents } from '../../../events'
|
||||||
addMqttConnectionEvent,
|
|
||||||
makeConnectionStateEvent,
|
|
||||||
removeConnection,
|
|
||||||
rendererEvents,
|
|
||||||
} from '../../../events'
|
|
||||||
|
|
||||||
export const connect = (options: MqttOptions, connectionId: string) => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
export const connect = (options: MqttOptions, connectionId: string) => (
|
||||||
|
dispatch: Dispatch<any>,
|
||||||
|
getState: () => AppState
|
||||||
|
) => {
|
||||||
dispatch(connecting(connectionId))
|
dispatch(connecting(connectionId))
|
||||||
rendererEvents.emit(addMqttConnectionEvent, { options, id: connectionId })
|
rendererEvents.emit(addMqttConnectionEvent, { options, id: connectionId })
|
||||||
const event = makeConnectionStateEvent(connectionId)
|
const event = makeConnectionStateEvent(connectionId)
|
||||||
const host = url.parse(options.url).hostname
|
const host = url.parse(options.url).hostname
|
||||||
|
|
||||||
rendererEvents.subscribe(event, (dataSourceState) => {
|
rendererEvents.subscribe(event, dataSourceState => {
|
||||||
console.log(dataSourceState)
|
console.log(dataSourceState)
|
||||||
if (dataSourceState.connected) {
|
if (dataSourceState.connected) {
|
||||||
const didReconnect = Boolean(getState().connection.tree)
|
const didReconnect = Boolean(getState().connection.tree)
|
||||||
@@ -59,7 +57,10 @@ const updateHealth = (dataSourceState: DataSourceState) => (dispatch: Dispatch<a
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const connected: (tree: q.Tree<TopicViewModel>, host: string) => Action = (tree: q.Tree<TopicViewModel>, host: string) => ({
|
export const connected: (tree: q.Tree<TopicViewModel>, host: string) => Action = (
|
||||||
|
tree: q.Tree<TopicViewModel>,
|
||||||
|
host: string
|
||||||
|
) => ({
|
||||||
tree,
|
tree,
|
||||||
host,
|
host,
|
||||||
type: ActionTypes.CONNECTION_SET_CONNECTED,
|
type: ActionTypes.CONNECTION_SET_CONNECTED,
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { AppState } from '../reducers'
|
import { AppState } from '../reducers'
|
||||||
import { clearLegacyConnectionOptions, loadLegacyConnectionOptions } from '../model/LegacyConnectionSettings'
|
import { clearLegacyConnectionOptions, loadLegacyConnectionOptions } from '../model/LegacyConnectionSettings'
|
||||||
import { ConnectionOptions, createEmptyConnection, makeDefaultConnections, CertificateParameters } from '../model/ConnectionOptions'
|
import {
|
||||||
|
ConnectionOptions,
|
||||||
|
createEmptyConnection,
|
||||||
|
makeDefaultConnections,
|
||||||
|
CertificateParameters,
|
||||||
|
} from '../model/ConnectionOptions'
|
||||||
import { default as persistentStorage, StorageIdentifier } from '../utils/PersistentStorage'
|
import { default as persistentStorage, StorageIdentifier } from '../utils/PersistentStorage'
|
||||||
import { Dispatch } from 'redux'
|
import { Dispatch } from 'redux'
|
||||||
import { showError } from './Global'
|
import { showError } from './Global'
|
||||||
@@ -8,12 +13,11 @@ import { remote } from 'electron'
|
|||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
|
||||||
import {
|
import { ActionTypes, Action } from '../reducers/ConnectionManager'
|
||||||
ActionTypes,
|
|
||||||
Action,
|
|
||||||
} from '../reducers/ConnectionManager'
|
|
||||||
|
|
||||||
const storedConnectionsIdentifier: StorageIdentifier<{[s: string]: ConnectionOptions}> = {
|
const storedConnectionsIdentifier: StorageIdentifier<{
|
||||||
|
[s: string]: ConnectionOptions
|
||||||
|
}> = {
|
||||||
id: 'ConnectionManager_connections',
|
id: 'ConnectionManager_connections',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,13 +41,18 @@ export const loadConnectionSettings = () => async (dispatch: Dispatch<any>, getS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const selectCertificate = (connectionId: string) => async (dispatch: Dispatch<any>, getState: () => AppState) => {
|
export const selectCertificate = (connectionId: string) => async (
|
||||||
|
dispatch: Dispatch<any>,
|
||||||
|
getState: () => AppState
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
const certificate = await openCertificate()
|
const certificate = await openCertificate()
|
||||||
console.log(certificate)
|
console.log(certificate)
|
||||||
dispatch(updateConnection(connectionId, {
|
dispatch(
|
||||||
selfSignedCertificate: certificate,
|
updateConnection(connectionId, {
|
||||||
}))
|
selfSignedCertificate: certificate,
|
||||||
|
})
|
||||||
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
dispatch(showError(error))
|
dispatch(showError(error))
|
||||||
@@ -57,30 +66,33 @@ async function openCertificate(): Promise<CertificateParameters> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
remote.dialog.showOpenDialog({ properties: ['openFile'], securityScopedBookmarks: true }, (filePaths?: Array<string>) => {
|
remote.dialog.showOpenDialog(
|
||||||
const selectedFile = filePaths && filePaths[0]
|
{ properties: ['openFile'], securityScopedBookmarks: true },
|
||||||
if (!selectedFile) {
|
(filePaths?: Array<string>) => {
|
||||||
reject(rejectReasons.noCertificateSelected)
|
const selectedFile = filePaths && filePaths[0]
|
||||||
return
|
if (!selectedFile) {
|
||||||
}
|
reject(rejectReasons.noCertificateSelected)
|
||||||
|
|
||||||
fs.readFile(selectedFile, (error, data) => {
|
|
||||||
if (error) {
|
|
||||||
reject(error)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.length > 16_384 || data.length < 128) {
|
fs.readFile(selectedFile, (error, data) => {
|
||||||
reject(rejectReasons.certificateSizeDoesNotMatch)
|
if (error) {
|
||||||
return
|
reject(error)
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
resolve({
|
if (data.length > 16_384 || data.length < 128) {
|
||||||
data: data.toString('base64'),
|
reject(rejectReasons.certificateSizeDoesNotMatch)
|
||||||
name: path.basename(selectedFile),
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
data: data.toString('base64'),
|
||||||
|
name: path.basename(selectedFile),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
})
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +129,7 @@ export const createConnection = () => (dispatch: Dispatch<any>) => {
|
|||||||
dispatch(selectConnection(newConnection.id))
|
dispatch(selectConnection(newConnection.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setConnections = (connections: {[s: string]: ConnectionOptions}): Action => ({
|
export const setConnections = (connections: { [s: string]: ConnectionOptions }): Action => ({
|
||||||
connections,
|
connections,
|
||||||
type: ActionTypes.CONNECTION_MANAGER_SET_CONNECTIONS,
|
type: ActionTypes.CONNECTION_MANAGER_SET_CONNECTIONS,
|
||||||
})
|
})
|
||||||
@@ -132,7 +144,7 @@ export const addConnection = (connection: ConnectionOptions): Action => ({
|
|||||||
type: ActionTypes.CONNECTION_MANAGER_ADD_CONNECTION,
|
type: ActionTypes.CONNECTION_MANAGER_ADD_CONNECTION,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const toggleAdvancedSettings = (): Action => ({
|
export const toggleAdvancedSettings = (): Action => ({
|
||||||
type: ActionTypes.CONNECTION_MANAGER_TOGGLE_ADVANCED_SETTINGS,
|
type: ActionTypes.CONNECTION_MANAGER_TOGGLE_ADVANCED_SETTINGS,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const setEditorMode = (editorMode: string): Action => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const publish = (connectionId: string) => (dispatch: Dispatch<Action>, getState: () => AppState) => {
|
export const publish = (connectionId: string) => (dispatch: Dispatch<Action>, getState: () => AppState) => {
|
||||||
const state = getState()
|
const state = getState()
|
||||||
const topic = state.publish.topic
|
const topic = state.publish.topic
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,7 @@ import { Dispatch } from 'redux'
|
|||||||
import { showError } from './Global'
|
import { showError } from './Global'
|
||||||
import { showTree } from './Tree'
|
import { showTree } from './Tree'
|
||||||
import { TopicViewModel } from '../model/TopicViewModel'
|
import { TopicViewModel } from '../model/TopicViewModel'
|
||||||
import {
|
import { ActionTypes, SettingsState, TopicOrder } from '../reducers/Settings'
|
||||||
ActionTypes,
|
|
||||||
SettingsState,
|
|
||||||
TopicOrder,
|
|
||||||
} from '../reducers/Settings'
|
|
||||||
import { Base64Message } from '../../../backend/src/Model/Base64Message'
|
import { Base64Message } from '../../../backend/src/Model/Base64Message'
|
||||||
import { globalActions } from '.'
|
import { globalActions } from '.'
|
||||||
|
|
||||||
@@ -21,7 +17,7 @@ const settingsIdentifier: StorageIdentifier<Partial<SettingsState>> = {
|
|||||||
|
|
||||||
export const loadSettings = () => async (dispatch: Dispatch<any>, getState: () => AppState) => {
|
export const loadSettings = () => async (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||||
try {
|
try {
|
||||||
const settings = await persistentStorage.load(settingsIdentifier) || {}
|
const settings = (await persistentStorage.load(settingsIdentifier)) || {}
|
||||||
dispatch({
|
dispatch({
|
||||||
settings: getState().settings.merge(settings),
|
settings: getState().settings.merge(settings),
|
||||||
type: ActionTypes.SETTINGS_DID_LOAD_SETTINGS,
|
type: ActionTypes.SETTINGS_DID_LOAD_SETTINGS,
|
||||||
@@ -102,26 +98,34 @@ export const filterTopics = (filterStr: string) => (dispatch: Dispatch<any>, get
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (!filterStr || !tree) {
|
if (!filterStr || !tree) {
|
||||||
dispatch(batchActions([setAutoExpandLimit(0), (showTree(tree) as any)]))
|
dispatch(batchActions([setAutoExpandLimit(0), showTree(tree) as any]))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const topicFilter = filterStr.toLowerCase()
|
const topicFilter = filterStr.toLowerCase()
|
||||||
|
|
||||||
const nodeFilter = (node: q.TreeNode<TopicViewModel>): boolean => {
|
const nodeFilter = (node: q.TreeNode<TopicViewModel>): boolean => {
|
||||||
const topicMatches = node.path().toLowerCase().indexOf(topicFilter) !== -1
|
const topicMatches =
|
||||||
|
node
|
||||||
|
.path()
|
||||||
|
.toLowerCase()
|
||||||
|
.indexOf(topicFilter) !== -1
|
||||||
if (topicMatches) {
|
if (topicMatches) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageMatches = node.message
|
const messageMatches =
|
||||||
&& node.message.value
|
node.message &&
|
||||||
&& Base64Message.toUnicodeString(node.message.value).toLowerCase().indexOf(filterStr) !== -1
|
node.message.value &&
|
||||||
|
Base64Message.toUnicodeString(node.message.value)
|
||||||
|
.toLowerCase()
|
||||||
|
.indexOf(filterStr) !== -1
|
||||||
|
|
||||||
return Boolean(messageMatches)
|
return Boolean(messageMatches)
|
||||||
}
|
}
|
||||||
|
|
||||||
const resultTree = tree.childTopics()
|
const resultTree = tree
|
||||||
|
.childTopics()
|
||||||
.filter(nodeFilter)
|
.filter(nodeFilter)
|
||||||
.map((node: q.TreeNode<TopicViewModel>) => {
|
.map((node: q.TreeNode<TopicViewModel>) => {
|
||||||
const clone = node.unconnectedClone()
|
const clone = node.unconnectedClone()
|
||||||
@@ -138,7 +142,7 @@ export const filterTopics = (filterStr: string) => (dispatch: Dispatch<any>, get
|
|||||||
nextTree.updateWithConnection(tree.updateSource, tree.connectionId, nodeFilter)
|
nextTree.updateWithConnection(tree.updateSource, tree.connectionId, nodeFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(batchActions([setAutoExpandLimit(autoExpandLimitForTree(nextTree)), (showTree(nextTree) as any)]))
|
dispatch(batchActions([setAutoExpandLimit(autoExpandLimitForTree(nextTree)), showTree(nextTree) as any]))
|
||||||
}
|
}
|
||||||
|
|
||||||
function autoExpandLimitForTree(tree: q.Tree<TopicViewModel>) {
|
function autoExpandLimitForTree(tree: q.Tree<TopicViewModel>) {
|
||||||
@@ -158,7 +162,10 @@ function autoExpandLimitForTree(tree: q.Tree<TopicViewModel>) {
|
|||||||
|
|
||||||
export const toggleTheme = () => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
export const toggleTheme = () => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: getState().settings.get('theme') === 'light' ? ActionTypes.SETTINGS_SET_THEME_DARK : ActionTypes.SETTINGS_SET_THEME_LIGHT,
|
type:
|
||||||
|
getState().settings.get('theme') === 'light'
|
||||||
|
? ActionTypes.SETTINGS_SET_THEME_DARK
|
||||||
|
: ActionTypes.SETTINGS_SET_THEME_LIGHT,
|
||||||
})
|
})
|
||||||
dispatch(storeSettings())
|
dispatch(storeSettings())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ export const setCompareMessage = (message?: q.Message) => (dispatch: Dispatch<an
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const clearTopic = (topic: q.TreeNode<any>, recursive: boolean, subtopicClearLimit = 50) => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
export const clearTopic = (topic: q.TreeNode<any>, recursive: boolean, subtopicClearLimit = 50) => (
|
||||||
|
dispatch: Dispatch<any>,
|
||||||
|
getState: () => AppState
|
||||||
|
) => {
|
||||||
const { connectionId } = getState().connection
|
const { connectionId } = getState().connection
|
||||||
if (!connectionId) {
|
if (!connectionId) {
|
||||||
return
|
return
|
||||||
@@ -36,10 +39,11 @@ export const clearTopic = (topic: q.TreeNode<any>, recursive: boolean, subtopicC
|
|||||||
rendererEvents.emit(publishEvent, mqttMessage)
|
rendererEvents.emit(publishEvent, mqttMessage)
|
||||||
|
|
||||||
if (recursive) {
|
if (recursive) {
|
||||||
topic.childTopics()
|
topic
|
||||||
|
.childTopics()
|
||||||
.filter(topic => Boolean(topic.message && topic.message.value))
|
.filter(topic => Boolean(topic.message && topic.message.value))
|
||||||
.slice(0, subtopicClearLimit)
|
.slice(0, subtopicClearLimit)
|
||||||
.forEach((topic) => {
|
.forEach(topic => {
|
||||||
console.log('deleting', topic.path())
|
console.log('deleting', topic.path())
|
||||||
const mqttMessage = {
|
const mqttMessage = {
|
||||||
topic: topic.path(),
|
topic: topic.path(),
|
||||||
|
|||||||
@@ -9,49 +9,55 @@ import { TopicViewModel } from '../model/TopicViewModel'
|
|||||||
import { globalActions } from '.'
|
import { globalActions } from '.'
|
||||||
const debounce = require('lodash.debounce')
|
const debounce = require('lodash.debounce')
|
||||||
|
|
||||||
export const selectTopic = (topic: q.TreeNode<TopicViewModel>) => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
export const selectTopic = (topic: q.TreeNode<TopicViewModel>) => (
|
||||||
|
dispatch: Dispatch<any>,
|
||||||
|
getState: () => AppState
|
||||||
|
) => {
|
||||||
debouncedSelectTopic(topic, dispatch, getState)
|
debouncedSelectTopic(topic, dispatch, getState)
|
||||||
}
|
}
|
||||||
|
|
||||||
const debouncedSelectTopic = debounce((topic: q.TreeNode<TopicViewModel>, dispatch: Dispatch<any>, getState: () => AppState) => {
|
const debouncedSelectTopic = debounce(
|
||||||
const previouslySelectedTopic = getState().tree.get('selectedTopic')
|
(topic: q.TreeNode<TopicViewModel>, dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||||
|
const previouslySelectedTopic = getState().tree.get('selectedTopic')
|
||||||
|
|
||||||
if (previouslySelectedTopic === topic) {
|
if (previouslySelectedTopic === topic) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update publish topic
|
// Update publish topic
|
||||||
let setTopicDispatch: any | undefined
|
let setTopicDispatch: any | undefined
|
||||||
if (!getState().publish.topic) {
|
if (!getState().publish.topic) {
|
||||||
setTopicDispatch = setTopic(topic.path())
|
setTopicDispatch = setTopic(topic.path())
|
||||||
} else if (previouslySelectedTopic && (previouslySelectedTopic.path() === getState().publish.topic)) {
|
} else if (previouslySelectedTopic && previouslySelectedTopic.path() === getState().publish.topic) {
|
||||||
setTopicDispatch = setTopic(topic.path())
|
setTopicDispatch = setTopic(topic.path())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (previouslySelectedTopic && previouslySelectedTopic.viewModel) {
|
if (previouslySelectedTopic && previouslySelectedTopic.viewModel) {
|
||||||
previouslySelectedTopic.viewModel.setSelected(false)
|
previouslySelectedTopic.viewModel.setSelected(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topic.viewModel) {
|
if (topic.viewModel) {
|
||||||
topic.viewModel.setSelected(true)
|
topic.viewModel.setSelected(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectTreeTopicDispatch = {
|
const selectTreeTopicDispatch = {
|
||||||
selectedTopic: topic,
|
selectedTopic: topic,
|
||||||
type: ActionTypes.TREE_SELECT_TOPIC,
|
type: ActionTypes.TREE_SELECT_TOPIC,
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SidebarActionTypes.SIDEBAR_SET_COMPARE_MESSAGE,
|
type: SidebarActionTypes.SIDEBAR_SET_COMPARE_MESSAGE,
|
||||||
message: undefined,
|
message: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (setTopicDispatch) {
|
if (setTopicDispatch) {
|
||||||
dispatch(batchActions([selectTreeTopicDispatch, setTopicDispatch]))
|
dispatch(batchActions([selectTreeTopicDispatch, setTopicDispatch]))
|
||||||
} else {
|
} else {
|
||||||
dispatch(selectTreeTopicDispatch)
|
dispatch(selectTreeTopicDispatch)
|
||||||
}
|
}
|
||||||
}, 70)
|
},
|
||||||
|
70
|
||||||
|
)
|
||||||
|
|
||||||
function destroyUnreferencedTree(state: AppState) {
|
function destroyUnreferencedTree(state: AppState) {
|
||||||
const visibleTree = state.tree.get('tree')
|
const visibleTree = state.tree.get('tree')
|
||||||
@@ -73,7 +79,10 @@ export const resetStore = () => (dispatch: Dispatch<any>, getState: () => AppSta
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const showTree = (tree: q.Tree<TopicViewModel> | undefined) => (dispatch: Dispatch<any>, getState: () => AppState): AnyAction => {
|
export const showTree = (tree: q.Tree<TopicViewModel> | undefined) => (
|
||||||
|
dispatch: Dispatch<any>,
|
||||||
|
getState: () => AppState
|
||||||
|
): AnyAction => {
|
||||||
destroyUnreferencedTree(getState())
|
destroyUnreferencedTree(getState())
|
||||||
|
|
||||||
return dispatch({
|
return dispatch({
|
||||||
|
|||||||
@@ -7,4 +7,13 @@ import * as sidebarActions from './Sidebar'
|
|||||||
import * as treeActions from './Tree'
|
import * as treeActions from './Tree'
|
||||||
import * as updateNotifierActions from './UpdateNotifier'
|
import * as updateNotifierActions from './UpdateNotifier'
|
||||||
|
|
||||||
export { settingsActions, treeActions, publishActions, updateNotifierActions, connectionActions, sidebarActions, connectionManagerActions, globalActions }
|
export {
|
||||||
|
settingsActions,
|
||||||
|
treeActions,
|
||||||
|
publishActions,
|
||||||
|
updateNotifierActions,
|
||||||
|
connectionActions,
|
||||||
|
sidebarActions,
|
||||||
|
connectionManagerActions,
|
||||||
|
globalActions,
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ interface Props {
|
|||||||
class App extends React.PureComponent<Props, {}> {
|
class App extends React.PureComponent<Props, {}> {
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = { }
|
this.state = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderNotification() {
|
private renderNotification() {
|
||||||
@@ -41,7 +41,9 @@ class App extends React.PureComponent<Props, {}> {
|
|||||||
<Notification
|
<Notification
|
||||||
message={str}
|
message={str}
|
||||||
type={isError ? 'error' : 'notification'}
|
type={isError ? 'error' : 'notification'}
|
||||||
onClose={() => { isError ? this.props.actions.showError(undefined) : this.props.actions.showNotification(undefined) }}
|
onClose={() => {
|
||||||
|
isError ? this.props.actions.showError(undefined) : this.props.actions.showNotification(undefined)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -65,7 +67,7 @@ class App extends React.PureComponent<Props, {}> {
|
|||||||
<div className={centerContent}>
|
<div className={centerContent}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
{this.renderNotification()}
|
{this.renderNotification()}
|
||||||
<React.Suspense fallback={<div></div>}>
|
<React.Suspense fallback={<div></div>}>
|
||||||
<Settings />
|
<Settings />
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
@@ -75,7 +77,11 @@ class App extends React.PureComponent<Props, {}> {
|
|||||||
</div>
|
</div>
|
||||||
<div className={settingsVisible ? contentShift : content}>
|
<div className={settingsVisible ? contentShift : content}>
|
||||||
<React.Suspense fallback={<div></div>}>
|
<React.Suspense fallback={<div></div>}>
|
||||||
<ContentView heightProperty={heightProperty} connectionId={this.props.connectionId} paneDefaults={paneDefaults} />
|
<ContentView
|
||||||
|
heightProperty={heightProperty}
|
||||||
|
connectionId={this.props.connectionId}
|
||||||
|
paneDefaults={paneDefaults}
|
||||||
|
/>
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -149,4 +155,9 @@ const mapStateToProps = (state: AppState) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(App))
|
export default withStyles(styles)(
|
||||||
|
connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(App)
|
||||||
|
)
|
||||||
|
|||||||
@@ -14,18 +14,9 @@ const styles = (theme: Theme) => ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const AddButton = withStyles(styles)((props: {
|
export const AddButton = withStyles(styles)((props: { classes: any; action: any }) => {
|
||||||
classes: any,
|
|
||||||
action: any,
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<Fab
|
<Fab size="small" color="secondary" aria-label="Add" className={props.classes.addButton} onClick={props.action}>
|
||||||
size="small"
|
|
||||||
color="secondary"
|
|
||||||
aria-label="Add"
|
|
||||||
className={props.classes.addButton}
|
|
||||||
onClick={props.action}
|
|
||||||
>
|
|
||||||
<Add className={props.classes.addIcon} />
|
<Add className={props.classes.addIcon} />
|
||||||
</Fab>
|
</Fab>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ class ConnectionSettings extends React.Component<Props, State> {
|
|||||||
<ClearAdornment action={this.clearCertificate} value={this.props.connection.selfSignedCertificate.name} />
|
<ClearAdornment action={this.clearCertificate} value={this.props.connection.selfSignedCertificate.name} />
|
||||||
{this.props.connection.selfSignedCertificate.name}
|
{this.props.connection.selfSignedCertificate.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,27 +89,26 @@ class ConnectionSettings extends React.Component<Props, State> {
|
|||||||
className={classes.fullWidth}
|
className={classes.fullWidth}
|
||||||
label="Subscription"
|
label="Subscription"
|
||||||
margin="normal"
|
margin="normal"
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => this.setState({ subscription: event.target.value })}
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
this.setState({ subscription: event.target.value })
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item={true} xs={2} className={classes.gridPadding}>
|
<Grid item={true} xs={2} className={classes.gridPadding}>
|
||||||
<Button
|
<Button
|
||||||
className={classes.button}
|
className={classes.button}
|
||||||
color="secondary"
|
color="secondary"
|
||||||
onClick={() => this.props.managerActions.addSubscription(this.state.subscription, this.props.connection.id)}
|
onClick={() =>
|
||||||
|
this.props.managerActions.addSubscription(this.state.subscription, this.props.connection.id)
|
||||||
|
}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
>
|
>
|
||||||
<Add /> Add
|
<Add /> Add
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item={true} xs={12} style={{ padding: 0 }}>
|
<Grid item={true} xs={12} style={{ padding: 0 }}>
|
||||||
<List
|
<List className={`${classes.topicList} advanced-connection-settings-topic-list`} component="nav">
|
||||||
className={`${classes.topicList} advanced-connection-settings-topic-list`}
|
<div className={classes.list}>{this.renderSubscriptions()}</div>
|
||||||
component="nav"
|
|
||||||
>
|
|
||||||
<div className={classes.list}>
|
|
||||||
{this.renderSubscriptions()}
|
|
||||||
</div>
|
|
||||||
</List>
|
</List>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item={true} xs={7} className={classes.gridPadding}>
|
<Grid item={true} xs={7} className={classes.gridPadding}>
|
||||||
@@ -151,17 +150,15 @@ class ConnectionSettings extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Subscription = (props: {
|
const Subscription = (props: { subscription: string; deleteAction: any }) => {
|
||||||
subscription: string,
|
|
||||||
deleteAction: any,
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<ListItem style={{ padding: '0 0 0 8px' }}>
|
<ListItem style={{ padding: '0 0 0 8px' }}>
|
||||||
<ListItemText>
|
<ListItemText>
|
||||||
<IconButton onClick={props.deleteAction} style={{ padding: '6px' }}>
|
<IconButton onClick={props.deleteAction} style={{ padding: '6px' }}>
|
||||||
<Delete />
|
<Delete />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{props.subscription}</ListItemText>
|
{props.subscription}
|
||||||
|
</ListItemText>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -199,4 +196,7 @@ const styles = (theme: Theme) => ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export default connect(undefined, mapDispatchToProps)(withStyles(styles)(ConnectionSettings))
|
export default connect(
|
||||||
|
undefined,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(withStyles(styles)(ConnectionSettings))
|
||||||
|
|||||||
@@ -29,17 +29,14 @@ import ConnectionHealthIndicator from '../helper/ConnectionHealthIndicator'
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
connection: ConnectionOptions
|
connection: ConnectionOptions
|
||||||
classes: {[s: string]: string}
|
classes: { [s: string]: string }
|
||||||
actions: typeof connectionActions,
|
actions: typeof connectionActions
|
||||||
managerActions: typeof connectionManagerActions
|
managerActions: typeof connectionManagerActions
|
||||||
connected: boolean
|
connected: boolean
|
||||||
connecting: boolean
|
connecting: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const protocols = [
|
const protocols = ['mqtt', 'ws']
|
||||||
'mqtt',
|
|
||||||
'ws',
|
|
||||||
]
|
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
showPassword: boolean
|
showPassword: boolean
|
||||||
@@ -127,20 +124,12 @@ class ConnectionSettings extends React.Component<Props, State> {
|
|||||||
const { classes, connection } = this.props
|
const { classes, connection } = this.props
|
||||||
|
|
||||||
const certSwitch = (
|
const certSwitch = (
|
||||||
<Switch
|
<Switch checked={connection.certValidation} onChange={this.toggleCertValidation} color="primary" />
|
||||||
checked={connection.certValidation}
|
|
||||||
onChange={this.toggleCertValidation}
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.switch}>
|
<div className={classes.switch}>
|
||||||
<FormControlLabel
|
<FormControlLabel control={certSwitch} label="Validate certificate" labelPlacement="bottom" />
|
||||||
control={certSwitch}
|
|
||||||
label="Validate certificate"
|
|
||||||
labelPlacement="bottom"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -154,21 +143,11 @@ class ConnectionSettings extends React.Component<Props, State> {
|
|||||||
private renderTlsSwitch() {
|
private renderTlsSwitch() {
|
||||||
const { classes, connection } = this.props
|
const { classes, connection } = this.props
|
||||||
|
|
||||||
const tlsSwitch = (
|
const tlsSwitch = <Switch checked={connection.encryption} onChange={this.toggleTls} color="primary" />
|
||||||
<Switch
|
|
||||||
checked={connection.encryption}
|
|
||||||
onChange={this.toggleTls}
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.switch}>
|
<div className={classes.switch}>
|
||||||
<FormControlLabel
|
<FormControlLabel control={tlsSwitch} label="Encryption (tls)" labelPlacement="bottom" />
|
||||||
control={tlsSwitch}
|
|
||||||
label="Encryption (tls)"
|
|
||||||
labelPlacement="bottom"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -185,12 +164,13 @@ class ConnectionSettings extends React.Component<Props, State> {
|
|||||||
if (this.props.connecting) {
|
if (this.props.connecting) {
|
||||||
return (
|
return (
|
||||||
<Button variant="contained" color="primary" className={classes.button} onClick={actions.disconnect}>
|
<Button variant="contained" color="primary" className={classes.button} onClick={actions.disconnect}>
|
||||||
<ConnectionHealthIndicator /> Abort
|
<ConnectionHealthIndicator />
|
||||||
|
Abort
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Button variant="contained" color="primary" className={classes.button} onClick={this.onClickConnect}>
|
<Button variant="contained" color="primary" className={classes.button} onClick={this.onClickConnect}>
|
||||||
<PowerSettingsNew /> Connect
|
<PowerSettingsNew /> Connect
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
@@ -212,10 +192,7 @@ class ConnectionSettings extends React.Component<Props, State> {
|
|||||||
|
|
||||||
const passwordVisibilityButton = (
|
const passwordVisibilityButton = (
|
||||||
<InputAdornment position="end">
|
<InputAdornment position="end">
|
||||||
<IconButton
|
<IconButton aria-label="Toggle password visibility" onClick={this.handleClickShowPassword}>
|
||||||
aria-label="Toggle password visibility"
|
|
||||||
onClick={this.handleClickShowPassword}
|
|
||||||
>
|
|
||||||
{this.state.showPassword ? <Visibility /> : <VisibilityOff />}
|
{this.state.showPassword ? <Visibility /> : <VisibilityOff />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
@@ -287,15 +264,28 @@ class ConnectionSettings extends React.Component<Props, State> {
|
|||||||
<br />
|
<br />
|
||||||
<div>
|
<div>
|
||||||
<div style={{ float: 'left' }}>
|
<div style={{ float: 'left' }}>
|
||||||
<Button variant="contained" className={classes.button} onClick={() => this.props.managerActions.deleteConnection(this.props.connection.id)}>
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
className={classes.button}
|
||||||
|
onClick={() => this.props.managerActions.deleteConnection(this.props.connection.id)}
|
||||||
|
>
|
||||||
Delete <Delete />
|
Delete <Delete />
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="contained" className={classes.button} onClick={this.props.managerActions.toggleAdvancedSettings}>
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
className={classes.button}
|
||||||
|
onClick={this.props.managerActions.toggleAdvancedSettings}
|
||||||
|
>
|
||||||
<Settings /> Advanced
|
<Settings /> Advanced
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ float : 'right' }}>
|
<div style={{ float: 'right' }}>
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={this.props.managerActions.saveConnectionSettings}>
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="secondary"
|
||||||
|
className={classes.button}
|
||||||
|
onClick={this.props.managerActions.saveConnectionSettings}
|
||||||
|
>
|
||||||
<Save /> Save
|
<Save /> Save
|
||||||
</Button>
|
</Button>
|
||||||
{this.renderConnectButton()}
|
{this.renderConnectButton()}
|
||||||
@@ -336,4 +326,7 @@ const styles = (theme: Theme) => ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(ConnectionSettings))
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(withStyles(styles)(ConnectionSettings))
|
||||||
|
|||||||
@@ -7,13 +7,7 @@ import { connect } from 'react-redux'
|
|||||||
import { connectionManagerActions } from '../../actions'
|
import { connectionManagerActions } from '../../actions'
|
||||||
import { ConnectionOptions, toMqttConnection } from '../../model/ConnectionOptions'
|
import { ConnectionOptions, toMqttConnection } from '../../model/ConnectionOptions'
|
||||||
import { Theme, withStyles } from '@material-ui/core/styles'
|
import { Theme, withStyles } from '@material-ui/core/styles'
|
||||||
import {
|
import { Modal, Paper, Toolbar, Typography, Collapse } from '@material-ui/core'
|
||||||
Modal,
|
|
||||||
Paper,
|
|
||||||
Toolbar,
|
|
||||||
Typography,
|
|
||||||
Collapse,
|
|
||||||
} from '@material-ui/core'
|
|
||||||
import AdvancedConnectionSettings from './AdvancedConnectionSettings'
|
import AdvancedConnectionSettings from './AdvancedConnectionSettings'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -37,8 +31,12 @@ class ConnectionSetup extends React.Component<Props, {}> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Collapse in={!showAdvancedSettings}><ConnectionSettings connection={connection} /></Collapse>
|
<Collapse in={!showAdvancedSettings}>
|
||||||
<Collapse in={showAdvancedSettings}><AdvancedConnectionSettings connection={connection} /></Collapse>
|
<ConnectionSettings connection={connection} />
|
||||||
|
</Collapse>
|
||||||
|
<Collapse in={showAdvancedSettings}>
|
||||||
|
<AdvancedConnectionSettings connection={connection} />
|
||||||
|
</Collapse>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -54,13 +52,15 @@ class ConnectionSetup extends React.Component<Props, {}> {
|
|||||||
<div>
|
<div>
|
||||||
<Modal open={visible} disableAutoFocus={true}>
|
<Modal open={visible} disableAutoFocus={true}>
|
||||||
<Paper className={classes.root}>
|
<Paper className={classes.root}>
|
||||||
<div className={classes.left}><ProfileList /></div>
|
<div className={classes.left}>
|
||||||
|
<ProfileList />
|
||||||
|
</div>
|
||||||
<div className={classes.right} key={connection && connection.id}>
|
<div className={classes.right} key={connection && connection.id}>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<Typography className={classes.title} variant="h6" color="inherit">MQTT Connection</Typography>
|
<Typography className={classes.title} variant="h6" color="inherit">
|
||||||
<Typography className={classes.connectionUri}>
|
MQTT Connection
|
||||||
{mqttConnection && mqttConnection.url}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<Typography className={classes.connectionUri}>{mqttConnection && mqttConnection.url}</Typography>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
{this.renderSettings()}
|
{this.renderSettings()}
|
||||||
</div>
|
</div>
|
||||||
@@ -115,7 +115,9 @@ const mapStateToProps = (state: AppState) => {
|
|||||||
return {
|
return {
|
||||||
visible: !state.connection.connected,
|
visible: !state.connection.connected,
|
||||||
showAdvancedSettings: state.connectionManager.showAdvancedSettings,
|
showAdvancedSettings: state.connectionManager.showAdvancedSettings,
|
||||||
connection: state.connectionManager.selected ? state.connectionManager.connections[state.connectionManager.selected] : undefined,
|
connection: state.connectionManager.selected
|
||||||
|
? state.connectionManager.connections[state.connectionManager.selected]
|
||||||
|
: undefined,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,4 +127,7 @@ const mapDispatchToProps = (dispatch: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(ConnectionSetup))
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(withStyles(styles)(ConnectionSetup))
|
||||||
|
|||||||
@@ -6,17 +6,12 @@ import { connect } from 'react-redux'
|
|||||||
import { connectionManagerActions } from '../../actions'
|
import { connectionManagerActions } from '../../actions'
|
||||||
import { ConnectionOptions, toMqttConnection } from '../../model/ConnectionOptions'
|
import { ConnectionOptions, toMqttConnection } from '../../model/ConnectionOptions'
|
||||||
import { Theme, withStyles } from '@material-ui/core/styles'
|
import { Theme, withStyles } from '@material-ui/core/styles'
|
||||||
import {
|
import { List, ListItem, ListSubheader, Typography } from '@material-ui/core'
|
||||||
List,
|
|
||||||
ListItem,
|
|
||||||
ListSubheader,
|
|
||||||
Typography,
|
|
||||||
} from '@material-ui/core'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
classes: any
|
classes: any
|
||||||
selected?: string
|
selected?: string
|
||||||
connections: {[s: string]: ConnectionOptions}
|
connections: { [s: string]: ConnectionOptions }
|
||||||
actions: any
|
actions: any
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +21,11 @@ class ProfileList extends React.Component<Props, {}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private addConnectionButton() {
|
private addConnectionButton() {
|
||||||
return <span id="addProfileButton" style={{ marginRight: '12px' }}><AddButton action={this.props.actions.createConnection} /></span>
|
return (
|
||||||
|
<span id="addProfileButton" style={{ marginRight: '12px' }}>
|
||||||
|
<AddButton action={this.props.actions.createConnection} />
|
||||||
|
</span>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
@@ -37,7 +36,13 @@ class ProfileList extends React.Component<Props, {}> {
|
|||||||
subheader={<ListSubheader component="div">{this.addConnectionButton()}Connections</ListSubheader>}
|
subheader={<ListSubheader component="div">{this.addConnectionButton()}Connections</ListSubheader>}
|
||||||
>
|
>
|
||||||
<div className={this.props.classes.list}>
|
<div className={this.props.classes.list}>
|
||||||
{Object.values(this.props.connections).map(connection => <ConnectionItem connection={connection} key={connection.id} selected={this.props.selected === connection.id} />)}
|
{Object.values(this.props.connections).map(connection => (
|
||||||
|
<ConnectionItem
|
||||||
|
connection={connection}
|
||||||
|
key={connection.id}
|
||||||
|
selected={this.props.selected === connection.id}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</List>
|
</List>
|
||||||
)
|
)
|
||||||
@@ -59,9 +64,9 @@ const mapDispatchToProps = (dispatch: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ConnectionItemProps {
|
interface ConnectionItemProps {
|
||||||
connection: ConnectionOptions,
|
connection: ConnectionOptions
|
||||||
actions: any,
|
actions: any
|
||||||
selected: boolean,
|
selected: boolean
|
||||||
classes: any
|
classes: any
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,17 +96,16 @@ const connectionItemRenderer = withStyles(connectionItemStyle)((props: Connectio
|
|||||||
style={{ display: 'block' }}
|
style={{ display: 'block' }}
|
||||||
onClick={() => props.actions.selectConnection(props.connection.id)}
|
onClick={() => props.actions.selectConnection(props.connection.id)}
|
||||||
>
|
>
|
||||||
<Typography className={props.classes.name}>
|
<Typography className={props.classes.name}>{props.connection.name || 'mqtt broker'}</Typography>
|
||||||
{props.connection.name || 'mqtt broker'}
|
<Typography className={props.classes.details}>{connection && connection.url}</Typography>
|
||||||
</Typography>
|
|
||||||
<Typography className={props.classes.details}>
|
|
||||||
{connection && connection.url}
|
|
||||||
</Typography>
|
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const ConnectionItem = connect(null, mapDispatchToProps)(connectionItemRenderer)
|
const ConnectionItem = connect(
|
||||||
|
null,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(connectionItemRenderer)
|
||||||
|
|
||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
return {
|
return {
|
||||||
@@ -110,4 +114,7 @@ const mapStateToProps = (state: AppState) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(ProfileList))
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(withStyles(styles)(ProfileList))
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ class Key extends React.Component<Props, {}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={this.props.classes.keyStyle}>
|
<div className={this.props.classes.keyStyle}>
|
||||||
<div className={this.props.classes.keyTextStyle}>{this.props.keyboardKey}</div>
|
<div className={this.props.classes.keyTextStyle}>{this.props.keyboardKey}</div>
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ const cursor = require('./cursor.png')
|
|||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
target: {x: number, y: number}
|
target: { x: number; y: number }
|
||||||
position: {x: number, y: number}
|
position: { x: number; y: number }
|
||||||
stepSizeX: number,
|
stepSizeX: number
|
||||||
stepSizeY: number,
|
stepSizeY: number
|
||||||
}
|
}
|
||||||
|
|
||||||
class Demo extends React.Component<{classes: any}, State> {
|
class Demo extends React.Component<{ classes: any }, State> {
|
||||||
private timer: any
|
private timer: any
|
||||||
private frameInterval = 20
|
private frameInterval = 20
|
||||||
|
|
||||||
@@ -32,8 +32,8 @@ class Demo extends React.Component<{classes: any}, State> {
|
|||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
position: {
|
position: {
|
||||||
x: this.state.position.x + (dirX * steSizeX),
|
x: this.state.position.x + dirX * steSizeX,
|
||||||
y: this.state.position.y + (dirY * steSizeY),
|
y: this.state.position.y + dirY * steSizeY,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -43,10 +43,10 @@ class Demo extends React.Component<{classes: any}, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
(window as any).demo.enableMouse = () => {
|
;(window as any).demo.enableMouse = () => {
|
||||||
this.setState({ enabled: true })
|
this.setState({ enabled: true })
|
||||||
}
|
}
|
||||||
(window as any).demo.moveMouse = (x: number, y: number, animationTime: number) => {
|
;(window as any).demo.moveMouse = (x: number, y: number, animationTime: number) => {
|
||||||
const stepSizeX = Math.abs(this.state.position.x - x) / (animationTime / this.frameInterval)
|
const stepSizeX = Math.abs(this.state.position.x - x) / (animationTime / this.frameInterval)
|
||||||
const stepSizeY = Math.abs(this.state.position.y - y) / (animationTime / this.frameInterval)
|
const stepSizeY = Math.abs(this.state.position.y - y) / (animationTime / this.frameInterval)
|
||||||
this.setState({ stepSizeX, stepSizeY, enabled: true, target: { x, y } })
|
this.setState({ stepSizeX, stepSizeY, enabled: true, target: { x, y } })
|
||||||
@@ -64,9 +64,7 @@ class Demo extends React.Component<{classes: any}, State> {
|
|||||||
top: this.state.position.y + 2,
|
top: this.state.position.y + 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <img src={cursor} style={cursorStyle} className={this.props.classes.cursor} />
|
||||||
<img src={cursor} style={cursorStyle} className={this.props.classes.cursor} />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ interface State {
|
|||||||
location: string
|
location: string
|
||||||
}
|
}
|
||||||
|
|
||||||
class Demo extends React.Component<{classes: any}, State> {
|
class Demo extends React.Component<{ classes: any }, State> {
|
||||||
private timer: any
|
private timer: any
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -20,20 +20,24 @@ class Demo extends React.Component<{classes: any}, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
(window as any).demo.showMessage = (message: string, location: string, duration: number, keys: Array<string> = []) => {
|
;(window as any).demo.showMessage = (
|
||||||
|
message: string,
|
||||||
|
location: string,
|
||||||
|
duration: number,
|
||||||
|
keys: Array<string> = []
|
||||||
|
) => {
|
||||||
this.clearTimer()
|
this.clearTimer()
|
||||||
this.setState({ message, location, keys })
|
this.setState({ message, location, keys })
|
||||||
this.timer = setTimeout(() => this.setState({ message: undefined }), duration)
|
this.timer = setTimeout(() => this.setState({ message: undefined }), duration)
|
||||||
}
|
}
|
||||||
|
;(window as any).demo.hideMessage = () => {
|
||||||
(window as any).demo.hideMessage = () => {
|
|
||||||
this.clearTimer()
|
this.clearTimer()
|
||||||
this.setState({ message: undefined })
|
this.setState({ message: undefined })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const positions: {[s: string]: number} = {
|
const positions: { [s: string]: number } = {
|
||||||
top: 0,
|
top: 0,
|
||||||
bottom: -65,
|
bottom: -65,
|
||||||
middle: -32,
|
middle: -32,
|
||||||
@@ -52,7 +56,6 @@ class Demo extends React.Component<{classes: any}, State> {
|
|||||||
color: 'white',
|
color: 'white',
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||||
borderRadius: '16px',
|
borderRadius: '16px',
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.state.message) {
|
if (!this.state.message) {
|
||||||
@@ -61,7 +64,8 @@ class Demo extends React.Component<{classes: any}, State> {
|
|||||||
|
|
||||||
let keys: Array<any> = []
|
let keys: Array<any> = []
|
||||||
if (this.state.keys.length > 0) {
|
if (this.state.keys.length > 0) {
|
||||||
keys = this.state.keys.map(key => [<Key key={key} keyboardKey={key} />])
|
keys = this.state.keys
|
||||||
|
.map(key => [<Key key={key} keyboardKey={key} />])
|
||||||
.reduce((prev, current) => {
|
.reduce((prev, current) => {
|
||||||
return [prev, '+' as any, current]
|
return [prev, '+' as any, current]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ function writeHeapdump(path?: string) {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
(window as any).demo = {
|
;(window as any).demo = {
|
||||||
writeHeapdump,
|
writeHeapdump,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,7 @@ import SentimentDissatisfied from '@material-ui/icons/SentimentDissatisfied'
|
|||||||
import Warning from '@material-ui/icons/Warning'
|
import Warning from '@material-ui/icons/Warning'
|
||||||
import { electronRendererTelementry } from 'electron-telemetry'
|
import { electronRendererTelementry } from 'electron-telemetry'
|
||||||
import { Theme, withStyles } from '@material-ui/core/styles'
|
import { Theme, withStyles } from '@material-ui/core/styles'
|
||||||
import {
|
import { Button, Modal, Paper, Toolbar, Typography } from '@material-ui/core'
|
||||||
Button,
|
|
||||||
Modal,
|
|
||||||
Paper,
|
|
||||||
Toolbar,
|
|
||||||
Typography,
|
|
||||||
} from '@material-ui/core'
|
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
error?: Error
|
error?: Error
|
||||||
@@ -21,7 +15,6 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ErrorBoundary extends React.Component<Props, State> {
|
class ErrorBoundary extends React.Component<Props, State> {
|
||||||
|
|
||||||
public static getDerivedStateFromError(error: Error) {
|
public static getDerivedStateFromError(error: Error) {
|
||||||
return { error }
|
return { error }
|
||||||
}
|
}
|
||||||
@@ -54,23 +47,36 @@ class ErrorBoundary extends React.Component<Props, State> {
|
|||||||
<Modal open={true} disableAutoFocus={true}>
|
<Modal open={true} disableAutoFocus={true}>
|
||||||
<Paper className={classes.root}>
|
<Paper className={classes.root}>
|
||||||
<Toolbar style={{ padding: '0' }}>
|
<Toolbar style={{ padding: '0' }}>
|
||||||
<Typography className={classes.title} variant="h6" color="inherit"><Warning /> Oooooops!</Typography>
|
<Typography className={classes.title} variant="h6" color="inherit">
|
||||||
|
<Warning /> Oooooops!
|
||||||
|
</Typography>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
<Typography className={classes.centered}>I hoped that you would never see this window, but MQTT-Explorer had an unexpected error.</Typography>
|
<Typography className={classes.centered}>
|
||||||
<Typography className={classes.centered}><SentimentDissatisfied /></Typography>
|
I hoped that you would never see this window, but MQTT-Explorer had an unexpected error.
|
||||||
|
</Typography>
|
||||||
|
<Typography className={classes.centered}>
|
||||||
|
<SentimentDissatisfied />
|
||||||
|
</Typography>
|
||||||
<pre className={classes.textColor} style={{ maxHeight: '35vh', overflow: 'scroll' }}>
|
<pre className={classes.textColor} style={{ maxHeight: '35vh', overflow: 'scroll' }}>
|
||||||
<code className={classes.textColor}>
|
<code className={classes.textColor}>{this.state.error.stack}</code>
|
||||||
{this.state.error.stack}
|
|
||||||
</code>
|
|
||||||
</pre>
|
</pre>
|
||||||
<Typography>
|
<Typography>
|
||||||
Please report this issue with a short description of what happened to
|
Please report this issue with a short description of what happened to
|
||||||
<span> <a className={classes.textColor} href="https://github.com/thomasnordquist/MQTT-Explorer/issues">https://github.com/thomasnordquist/MQTT-Explorer/issues</a></span>
|
<span>
|
||||||
|
{' '}
|
||||||
|
<a className={classes.textColor} href="https://github.com/thomasnordquist/MQTT-Explorer/issues">
|
||||||
|
https://github.com/thomasnordquist/MQTT-Explorer/issues
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
<div>
|
<div>
|
||||||
<div className={classes.buttonPositioning}>
|
<div className={classes.buttonPositioning}>
|
||||||
<Button className={classes.button} variant="contained" color="secondary" onClick={this.clearStorage}>Start Fresh</Button>
|
<Button className={classes.button} variant="contained" color="secondary" onClick={this.clearStorage}>
|
||||||
<Button className={classes.button} variant="contained" color="primary" onClick={this.restart}>Restart</Button>
|
Start Fresh
|
||||||
|
</Button>
|
||||||
|
<Button className={classes.button} variant="contained" color="primary" onClick={this.restart}>
|
||||||
|
Restart
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import ReactSplitPane from 'react-split-pane'
|
|||||||
import { Sidebar } from '../Sidebar'
|
import { Sidebar } from '../Sidebar'
|
||||||
import Tree from '../Tree/Tree'
|
import Tree from '../Tree/Tree'
|
||||||
|
|
||||||
export default function ContentView(props: {heightProperty: any, paneDefaults: any, connectionId: any}) {
|
export default function ContentView(props: { heightProperty: any; paneDefaults: any; connectionId: any }) {
|
||||||
return (
|
return (
|
||||||
<ReactSplitPane
|
<ReactSplitPane
|
||||||
step={20}
|
step={20}
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ const styles = (theme: Theme) => ({
|
|||||||
interface Props {
|
interface Props {
|
||||||
classes: any
|
classes: any
|
||||||
actions: {
|
actions: {
|
||||||
tree: typeof treeActions,
|
tree: typeof treeActions
|
||||||
}
|
}
|
||||||
paused: boolean
|
paused: boolean
|
||||||
tree?: q.Tree<any>
|
tree?: q.Tree<any>
|
||||||
}
|
}
|
||||||
|
|
||||||
class PauseButton extends React.Component<Props, {changes: number}> {
|
class PauseButton extends React.Component<Props, { changes: number }> {
|
||||||
private timer?: any
|
private timer?: any
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -42,7 +42,8 @@ class PauseButton extends React.Component<Props, {changes: number}> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={this.props.classes.bufferStats}>
|
<span className={this.props.classes.bufferStats}>
|
||||||
{this.state.changes} changes<br />
|
{this.state.changes} changes
|
||||||
|
<br />
|
||||||
buffer at {Math.round(this.props.tree.unmergedChanges().fillState() * 10000) / 100}%
|
buffer at {Math.round(this.props.tree.unmergedChanges().fillState() * 10000) / 100}%
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
@@ -66,12 +67,18 @@ class PauseButton extends React.Component<Props, {changes: number}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const message = this.props.paused ? 'Resumes updating the tree, after applying all recorded changes' : 'Stops all updates, records changes until the buffer is full.'
|
const message = this.props.paused
|
||||||
|
? 'Resumes updating the tree, after applying all recorded changes'
|
||||||
|
: 'Stops all updates, records changes until the buffer is full.'
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'inline-flex' }}>
|
<div style={{ display: 'inline-flex' }}>
|
||||||
<span>
|
<span>
|
||||||
<CustomIconButton onClick={this.props.actions.tree.togglePause} tooltip={message}>
|
<CustomIconButton onClick={this.props.actions.tree.togglePause} tooltip={message}>
|
||||||
{this.props.paused ? <Resume className={this.props.classes.icon} /> : <Pause className={this.props.classes.icon} />}
|
{this.props.paused ? (
|
||||||
|
<Resume className={this.props.classes.icon} />
|
||||||
|
) : (
|
||||||
|
<Pause className={this.props.classes.icon} />
|
||||||
|
)}
|
||||||
</CustomIconButton>
|
</CustomIconButton>
|
||||||
</span>
|
</span>
|
||||||
{this.props.paused ? this.renderBufferStats() : null}
|
{this.props.paused ? this.renderBufferStats() : null}
|
||||||
@@ -95,4 +102,7 @@ const mapDispatchToProps = (dispatch: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(PauseButton))
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(withStyles(styles)(PauseButton))
|
||||||
|
|||||||
@@ -11,14 +11,7 @@ import { connect } from 'react-redux'
|
|||||||
import { connectionActions, globalActions, settingsActions } from '../../actions'
|
import { connectionActions, globalActions, settingsActions } from '../../actions'
|
||||||
import { fade } from '@material-ui/core/styles/colorManipulator'
|
import { fade } from '@material-ui/core/styles/colorManipulator'
|
||||||
import { Theme, withStyles } from '@material-ui/core/styles'
|
import { Theme, withStyles } from '@material-ui/core/styles'
|
||||||
import {
|
import { AppBar, Button, IconButton, InputBase, Toolbar, Typography } from '@material-ui/core'
|
||||||
AppBar,
|
|
||||||
Button,
|
|
||||||
IconButton,
|
|
||||||
InputBase,
|
|
||||||
Toolbar,
|
|
||||||
Typography,
|
|
||||||
} from '@material-ui/core'
|
|
||||||
|
|
||||||
const styles = (theme: Theme) => ({
|
const styles = (theme: Theme) => ({
|
||||||
title: {
|
title: {
|
||||||
@@ -92,9 +85,9 @@ const styles = (theme: Theme) => ({
|
|||||||
interface Props {
|
interface Props {
|
||||||
classes: any
|
classes: any
|
||||||
actions: {
|
actions: {
|
||||||
settings: typeof settingsActions,
|
settings: typeof settingsActions
|
||||||
connection: typeof connectionActions,
|
connection: typeof connectionActions
|
||||||
global: typeof globalActions,
|
global: typeof globalActions
|
||||||
}
|
}
|
||||||
topicFilter?: string
|
topicFilter?: string
|
||||||
}
|
}
|
||||||
@@ -102,7 +95,7 @@ interface Props {
|
|||||||
class TitleBar extends React.Component<Props, {}> {
|
class TitleBar extends React.Component<Props, {}> {
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = { }
|
this.state = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderSearch() {
|
private renderSearch() {
|
||||||
@@ -117,7 +110,11 @@ class TitleBar extends React.Component<Props, {}> {
|
|||||||
value={topicFilter}
|
value={topicFilter}
|
||||||
onChange={this.onFilterChange}
|
onChange={this.onFilterChange}
|
||||||
placeholder="Search…"
|
placeholder="Search…"
|
||||||
endAdornment={<div style={{ width: '24px', paddingRight: '8px' }}><ClearAdornment variant="primary" action={this.clearFilter} value={topicFilter} /></div>}
|
endAdornment={
|
||||||
|
<div style={{ width: '24px', paddingRight: '8px' }}>
|
||||||
|
<ClearAdornment variant="primary" action={this.clearFilter} value={topicFilter} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
classes={{ root: classes.inputRoot, input: classes.inputInput }}
|
classes={{ root: classes.inputRoot, input: classes.inputInput }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -138,10 +135,17 @@ class TitleBar extends React.Component<Props, {}> {
|
|||||||
return (
|
return (
|
||||||
<AppBar position="static">
|
<AppBar position="static">
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<IconButton className={classes.menuButton} color="inherit" aria-label="Menu" onClick={actions.global.toggleSettingsVisibility}>
|
<IconButton
|
||||||
|
className={classes.menuButton}
|
||||||
|
color="inherit"
|
||||||
|
aria-label="Menu"
|
||||||
|
onClick={actions.global.toggleSettingsVisibility}
|
||||||
|
>
|
||||||
<Menu />
|
<Menu />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography className={classes.title} variant="h6" color="inherit">MQTT Explorer</Typography>
|
<Typography className={classes.title} variant="h6" color="inherit">
|
||||||
|
MQTT Explorer
|
||||||
|
</Typography>
|
||||||
{this.renderSearch()}
|
{this.renderSearch()}
|
||||||
<PauseButton />
|
<PauseButton />
|
||||||
<Button className={classes.disconnect} onClick={actions.connection.disconnect}>
|
<Button className={classes.disconnect} onClick={actions.connection.disconnect}>
|
||||||
@@ -170,4 +174,7 @@ const mapDispatchToProps = (dispatch: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(TitleBar))
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(withStyles(styles)(TitleBar))
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import {
|
import { InputLabel, Switch, Theme, Tooltip } from '@material-ui/core'
|
||||||
InputLabel,
|
|
||||||
Switch,
|
|
||||||
Theme,
|
|
||||||
Tooltip
|
|
||||||
} from '@material-ui/core'
|
|
||||||
import { withStyles } from '@material-ui/styles'
|
import { withStyles } from '@material-ui/styles'
|
||||||
const sha1 = require('sha1')
|
const sha1 = require('sha1')
|
||||||
|
|
||||||
function BooleanSwitch(props: {title: string, value: boolean, tooltip: string, action: () => void, classes: any}) {
|
function BooleanSwitch(props: { title: string; value: boolean; tooltip: string; action: () => void; classes: any }) {
|
||||||
const { tooltip, value, action, title, classes } = props
|
const { tooltip, value, action, title, classes } = props
|
||||||
|
|
||||||
const clickHandler = (e: React.MouseEvent) => {
|
const clickHandler = (e: React.MouseEvent) => {
|
||||||
@@ -20,21 +15,12 @@ function BooleanSwitch(props: {title: string, value: boolean, tooltip: string, a
|
|||||||
return (
|
return (
|
||||||
<div style={{ padding: '8px', display: 'flex' }}>
|
<div style={{ padding: '8px', display: 'flex' }}>
|
||||||
<Tooltip title={tooltip}>
|
<Tooltip title={tooltip}>
|
||||||
<InputLabel
|
<InputLabel htmlFor={`toggle-${sha1(title)}`} onClick={clickHandler} className={classes.label}>
|
||||||
htmlFor={`toggle-${sha1(title)}`}
|
|
||||||
onClick={clickHandler}
|
|
||||||
className={classes.label}
|
|
||||||
>
|
|
||||||
{title}
|
{title}
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title={tooltip}>
|
<Tooltip title={tooltip}>
|
||||||
<Switch
|
<Switch name={`toggle-${sha1(title)}`} checked={value} onChange={action} color="primary" />
|
||||||
name={`toggle-${sha1(title)}`}
|
|
||||||
checked={value}
|
|
||||||
onChange={action}
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -112,13 +112,17 @@ class BrokerStatistics extends React.Component<Props, {}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const str = node.message.value ? Base64Message.toUnicodeString(node.message.value) : ''
|
const str = node.message.value ? Base64Message.toUnicodeString(node.message.value) : ''
|
||||||
let value = (node.message && node.message.value) ? parseFloat(str) : NaN
|
let value = node.message && node.message.value ? parseFloat(str) : NaN
|
||||||
value = !isNaN(value) ? abbreviate(value) : str
|
value = !isNaN(value) ? abbreviate(value) : str
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={stat.title}>
|
<div key={stat.title}>
|
||||||
<Typography><b>{stat.title}</b></Typography>
|
<Typography>
|
||||||
<Typography style={{ paddingLeft: '8px' }}><i>{value}</i></Typography>
|
<b>{stat.title}</b>
|
||||||
|
</Typography>
|
||||||
|
<Typography style={{ paddingLeft: '8px' }}>
|
||||||
|
<i>{value}</i>
|
||||||
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,25 +23,32 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@material-ui/core'
|
} from '@material-ui/core'
|
||||||
|
|
||||||
export const autoExpandLimitSet = [{
|
export const autoExpandLimitSet = [
|
||||||
limit: 0,
|
{
|
||||||
name: 'Collapsed',
|
limit: 0,
|
||||||
}, {
|
name: 'Collapsed',
|
||||||
limit: 2,
|
},
|
||||||
name: 'Few',
|
{
|
||||||
}, {
|
limit: 2,
|
||||||
limit: 5,
|
name: 'Few',
|
||||||
name: 'Some',
|
},
|
||||||
}, {
|
{
|
||||||
limit: 15,
|
limit: 5,
|
||||||
name: 'Most',
|
name: 'Some',
|
||||||
}, {
|
},
|
||||||
limit: 30,
|
{
|
||||||
name: 'Most',
|
limit: 15,
|
||||||
}, {
|
name: 'Most',
|
||||||
limit: 1E6,
|
},
|
||||||
name: 'All',
|
{
|
||||||
}]
|
limit: 30,
|
||||||
|
name: 'Most',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
limit: 1e6,
|
||||||
|
name: 'All',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
const styles = (theme: Theme) => ({
|
const styles = (theme: Theme) => ({
|
||||||
drawer: {
|
drawer: {
|
||||||
@@ -70,8 +77,8 @@ const styles = (theme: Theme) => ({
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
actions: {
|
actions: {
|
||||||
settings: typeof settingsActions,
|
settings: typeof settingsActions
|
||||||
global: typeof globalActions,
|
global: typeof globalActions
|
||||||
}
|
}
|
||||||
autoExpandLimit: number
|
autoExpandLimit: number
|
||||||
classes: any
|
classes: any
|
||||||
@@ -96,29 +103,56 @@ class Settings extends React.Component<Props, {}> {
|
|||||||
private renderHighlightTopicUpdates() {
|
private renderHighlightTopicUpdates() {
|
||||||
const { highlightTopicUpdates, actions } = this.props
|
const { highlightTopicUpdates, actions } = this.props
|
||||||
|
|
||||||
return <BooleanSwitch title="Show Activity" tooltip="Topics blink when a new message arrives" value={highlightTopicUpdates} action={actions.settings.toggleHighlightTopicUpdates}/>
|
return (
|
||||||
|
<BooleanSwitch
|
||||||
|
title="Show Activity"
|
||||||
|
tooltip="Topics blink when a new message arrives"
|
||||||
|
value={highlightTopicUpdates}
|
||||||
|
action={actions.settings.toggleHighlightTopicUpdates}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private selectTopicsOnMouseOver() {
|
private selectTopicsOnMouseOver() {
|
||||||
const { actions, selectTopicWithMouseOver } = this.props
|
const { actions, selectTopicWithMouseOver } = this.props
|
||||||
const toggle = () => actions.settings.selectTopicWithMouseOver(!selectTopicWithMouseOver)
|
const toggle = () => actions.settings.selectTopicWithMouseOver(!selectTopicWithMouseOver)
|
||||||
|
|
||||||
return <BooleanSwitch title="Quick Preview" tooltip="Select topics on mouse over" value={selectTopicWithMouseOver} action={toggle} />
|
return (
|
||||||
|
<BooleanSwitch
|
||||||
|
title="Quick Preview"
|
||||||
|
tooltip="Select topics on mouse over"
|
||||||
|
value={selectTopicWithMouseOver}
|
||||||
|
action={toggle}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private toggleTheme() {
|
private toggleTheme() {
|
||||||
const { actions, theme } = this.props
|
const { actions, theme } = this.props
|
||||||
|
|
||||||
return <BooleanSwitch title="Dark Mode" tooltip="Enable dark theme" value={theme === 'dark'} action={actions.settings.toggleTheme} />
|
return (
|
||||||
|
<BooleanSwitch
|
||||||
|
title="Dark Mode"
|
||||||
|
tooltip="Enable dark theme"
|
||||||
|
value={theme === 'dark'}
|
||||||
|
action={actions.settings.toggleTheme}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderAutoExpand() {
|
private renderAutoExpand() {
|
||||||
const { classes, autoExpandLimit } = this.props
|
const { classes, autoExpandLimit } = this.props
|
||||||
|
|
||||||
const limits = autoExpandLimitSet.map(limit => <MenuItem key={limit.limit} value={limit.limit}>{limit.limit < 10000 && limit.limit > 0 ? `≤ ${limit.limit} topics` : limit.name}</MenuItem>)
|
const limits = autoExpandLimitSet.map(limit => (
|
||||||
|
<MenuItem key={limit.limit} value={limit.limit}>
|
||||||
|
{limit.limit < 10000 && limit.limit > 0 ? `≤ ${limit.limit} topics` : limit.name}
|
||||||
|
</MenuItem>
|
||||||
|
))
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '8px', display: 'flex' }}>
|
<div style={{ padding: '8px', display: 'flex' }}>
|
||||||
<InputLabel htmlFor="auto-expand" style={{ flex: '1', marginTop: '8px' }}>Auto Expand</InputLabel>
|
<InputLabel htmlFor="auto-expand" style={{ flex: '1', marginTop: '8px' }}>
|
||||||
|
Auto Expand
|
||||||
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={autoExpandLimit}
|
value={autoExpandLimit}
|
||||||
onChange={this.onChangeAutoExpand}
|
onChange={this.onChangeAutoExpand}
|
||||||
@@ -133,7 +167,7 @@ class Settings extends React.Component<Props, {}> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private onChangeAutoExpand = (e: React.ChangeEvent<{value: unknown}>) => {
|
private onChangeAutoExpand = (e: React.ChangeEvent<{ value: unknown }>) => {
|
||||||
this.props.actions.settings.setAutoExpandLimit(parseInt(String(e.target.value), 10))
|
this.props.actions.settings.setAutoExpandLimit(parseInt(String(e.target.value), 10))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +176,9 @@ class Settings extends React.Component<Props, {}> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '8px', display: 'flex' }}>
|
<div style={{ padding: '8px', display: 'flex' }}>
|
||||||
<InputLabel htmlFor="auto-expand" style={{ flex: '1', marginTop: '8px' }}>Topic Order</InputLabel>
|
<InputLabel htmlFor="auto-expand" style={{ flex: '1', marginTop: '8px' }}>
|
||||||
|
Topic Order
|
||||||
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={topicOrder}
|
value={topicOrder}
|
||||||
onChange={this.onChangeSorting}
|
onChange={this.onChangeSorting}
|
||||||
@@ -152,32 +188,26 @@ class Settings extends React.Component<Props, {}> {
|
|||||||
className={classes.input}
|
className={classes.input}
|
||||||
style={{ flex: '1' }}
|
style={{ flex: '1' }}
|
||||||
>
|
>
|
||||||
<MenuItem value={TopicOrder.none}><em>default</em></MenuItem>
|
<MenuItem value={TopicOrder.none}>
|
||||||
|
<em>default</em>
|
||||||
|
</MenuItem>
|
||||||
<MenuItem value={TopicOrder.abc}>a-z</MenuItem>
|
<MenuItem value={TopicOrder.abc}>a-z</MenuItem>
|
||||||
<MenuItem value={TopicOrder.messages}>{TopicOrder.messages}</MenuItem>
|
<MenuItem value={TopicOrder.messages}>{TopicOrder.messages}</MenuItem>
|
||||||
<MenuItem value={TopicOrder.topics}>{TopicOrder.topics}</MenuItem>
|
<MenuItem value={TopicOrder.topics}>{TopicOrder.topics}</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</div>)
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private onChangeSorting = (e: React.ChangeEvent<{value: unknown}>) => {
|
private onChangeSorting = (e: React.ChangeEvent<{ value: unknown }>) => {
|
||||||
this.props.actions.settings.setTopicOrder(e.target.value as TopicOrder)
|
this.props.actions.settings.setTopicOrder(e.target.value as TopicOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { classes, actions, visible } = this.props
|
const { classes, actions, visible } = this.props
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer anchor="left" className={classes.drawer} open={visible} variant="persistent">
|
||||||
anchor="left"
|
<div className={classes.paper} tabIndex={0} role="button">
|
||||||
className={classes.drawer}
|
|
||||||
open={visible}
|
|
||||||
variant="persistent"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={classes.paper}
|
|
||||||
tabIndex={0}
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
<Typography className={classes.title} variant="h6" color="inherit">
|
<Typography className={classes.title} variant="h6" color="inherit">
|
||||||
<IconButton onClick={actions.global.toggleSettingsVisibility}>
|
<IconButton onClick={actions.global.toggleSettingsVisibility}>
|
||||||
<ChevronRight />
|
<ChevronRight />
|
||||||
@@ -225,4 +255,9 @@ const mapDispatchToProps = (dispatch: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(Settings))
|
export default withStyles(styles)(
|
||||||
|
connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(Settings)
|
||||||
|
)
|
||||||
|
|||||||
@@ -3,14 +3,7 @@ import DateFormatter from '../helper/DateFormatter'
|
|||||||
import { AppState } from '../../reducers'
|
import { AppState } from '../../reducers'
|
||||||
import { bindActionCreators } from 'redux'
|
import { bindActionCreators } from 'redux'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import {
|
import { Input, InputLabel, MenuItem, Select, StyleRulesCallback, Theme } from '@material-ui/core'
|
||||||
Input,
|
|
||||||
InputLabel,
|
|
||||||
MenuItem,
|
|
||||||
Select,
|
|
||||||
StyleRulesCallback,
|
|
||||||
Theme
|
|
||||||
} from '@material-ui/core'
|
|
||||||
import { settingsActions } from '../../actions'
|
import { settingsActions } from '../../actions'
|
||||||
import { withStyles } from '@material-ui/styles'
|
import { withStyles } from '@material-ui/styles'
|
||||||
const moment = require('moment/min/moment-with-locales')
|
const moment = require('moment/min/moment-with-locales')
|
||||||
@@ -18,7 +11,7 @@ const moment = require('moment/min/moment-with-locales')
|
|||||||
interface Props {
|
interface Props {
|
||||||
actions: {
|
actions: {
|
||||||
settings: typeof settingsActions
|
settings: typeof settingsActions
|
||||||
},
|
}
|
||||||
timeLocale: string
|
timeLocale: string
|
||||||
classes: any
|
classes: any
|
||||||
}
|
}
|
||||||
@@ -29,26 +22,33 @@ function TimeLocaleSettings(props: Props) {
|
|||||||
const date = new Date()
|
const date = new Date()
|
||||||
const localeMenuItems = locales.map((l: string) => (
|
const localeMenuItems = locales.map((l: string) => (
|
||||||
<MenuItem key={l} value={l}>
|
<MenuItem key={l} value={l}>
|
||||||
<div>Locale: <b>{l}</b>, Format: <b><DateFormatter date={date} overrideLocale={l} /></b></div>
|
<div>
|
||||||
|
Locale: <b>{l}</b>, Format:{' '}
|
||||||
|
<b>
|
||||||
|
<DateFormatter date={date} overrideLocale={l} />
|
||||||
|
</b>
|
||||||
|
</div>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))
|
))
|
||||||
|
|
||||||
function updateLocale(e: React.ChangeEvent<{value: unknown}>) {
|
function updateLocale(e: React.ChangeEvent<{ value: unknown }>) {
|
||||||
const locale = e.target.value ? String(e.target.value) : ''
|
const locale = e.target.value ? String(e.target.value) : ''
|
||||||
actions.settings.setTimeLocale(locale)
|
actions.settings.setTimeLocale(locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '8px', display: 'flex' }}>
|
<div style={{ padding: '8px', display: 'flex' }}>
|
||||||
<InputLabel htmlFor="time-locale" style={{ flex: '1', marginTop: '8px' }}>Time Locale</InputLabel>
|
<InputLabel htmlFor="time-locale" style={{ flex: '1', marginTop: '8px' }}>
|
||||||
|
Time Locale
|
||||||
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={timeLocale}
|
value={timeLocale}
|
||||||
onChange={updateLocale}
|
onChange={updateLocale}
|
||||||
input={<Input name="time-locale" id="time-locale-label-placeholder" />}
|
input={<Input name="time-locale" id="time-locale-label-placeholder" />}
|
||||||
name="time-locale"
|
name="time-locale"
|
||||||
className={classes.input}
|
className={classes.input}
|
||||||
renderValue={v => <span>{String(v)}</span>}
|
renderValue={v => <span>{String(v)}</span>}
|
||||||
style={{ flex: '1' }}
|
style={{ flex: '1' }}
|
||||||
>
|
>
|
||||||
{localeMenuItems}
|
{localeMenuItems}
|
||||||
</Select>
|
</Select>
|
||||||
@@ -82,4 +82,9 @@ const styles = (theme: Theme) => ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(TimeLocaleSettings))
|
export default withStyles(styles)(
|
||||||
|
connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(TimeLocaleSettings)
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { Theme } from '@material-ui/core';
|
import { Theme } from '@material-ui/core'
|
||||||
import { withStyles } from '@material-ui/styles'
|
import { withStyles } from '@material-ui/styles'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
changes: Array<Diff.Change>
|
changes: Array<Diff.Change>
|
||||||
classes: {[s: string]: any}
|
classes: { [s: string]: any }
|
||||||
nameOfCompareMessage: string
|
nameOfCompareMessage: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeAmount(props: Props) {
|
function changeAmount(props: Props) {
|
||||||
const additions = props.changes.map(change => (change.added === true) ? (change.count || 0) : 0).reduce((a, b) => a + b)
|
const additions = props.changes.map(change => (change.added === true ? change.count || 0 : 0)).reduce((a, b) => a + b)
|
||||||
const deletions = props.changes.map(change => (change.removed === true) ? (change.count || 0) : 0).reduce((a, b) => a + b)
|
const deletions = props.changes
|
||||||
|
.map(change => (change.removed === true ? change.count || 0 : 0))
|
||||||
|
.reduce((a, b) => a + b)
|
||||||
if (additions === 0 && deletions === 0) {
|
if (additions === 0 && deletions === 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -19,9 +21,13 @@ function changeAmount(props: Props) {
|
|||||||
<span style={{ display: 'block', marginBottom: '8px', float: 'right' }}>
|
<span style={{ display: 'block', marginBottom: '8px', float: 'right' }}>
|
||||||
<span>
|
<span>
|
||||||
Comparing with <b>{props.nameOfCompareMessage}</b> message:
|
Comparing with <b>{props.nameOfCompareMessage}</b> message:
|
||||||
|
<span className={props.classes.additions}>
|
||||||
<span className={props.classes.additions}>+ {additions} line{additions === 1 ? '' : 's'}</span>
|
+ {additions} line{additions === 1 ? '' : 's'}
|
||||||
, <span className={props.classes.deletions}>- {deletions} line{deletions === 1 ? '' : 's'}</span>
|
</span>
|
||||||
|
,{' '}
|
||||||
|
<span className={props.classes.deletions}>
|
||||||
|
- {deletions} line{deletions === 1 ? '' : 's'}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,19 +5,13 @@ import Add from '@material-ui/icons/Add'
|
|||||||
import Remove from '@material-ui/icons/Remove'
|
import Remove from '@material-ui/icons/Remove'
|
||||||
import ShowChart from '@material-ui/icons/ShowChart'
|
import ShowChart from '@material-ui/icons/ShowChart'
|
||||||
import TopicPlot from '../TopicPlot'
|
import TopicPlot from '../TopicPlot'
|
||||||
import {
|
import { Fade, Paper, Popper, Theme, Tooltip } from '@material-ui/core'
|
||||||
Fade,
|
|
||||||
Paper,
|
|
||||||
Popper,
|
|
||||||
Theme,
|
|
||||||
Tooltip
|
|
||||||
} from '@material-ui/core'
|
|
||||||
import { JsonPropertyLocation } from '../../../../../backend/src/JsonAstParser'
|
import { JsonPropertyLocation } from '../../../../../backend/src/JsonAstParser'
|
||||||
import { lineChangeStyle, trimNewlineRight } from './util'
|
import { lineChangeStyle, trimNewlineRight } from './util'
|
||||||
import { withStyles } from '@material-ui/styles'
|
import { withStyles } from '@material-ui/styles'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
changes: Array<diff.Change>,
|
changes: Array<diff.Change>
|
||||||
literalPositions: Array<JsonPropertyLocation>
|
literalPositions: Array<JsonPropertyLocation>
|
||||||
classes: any
|
classes: any
|
||||||
className: string
|
className: string
|
||||||
@@ -58,32 +52,32 @@ const style = (theme: Theme) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChartIcon(props: { messageHistory: q.MessageHistory, classes: any, literal: JsonPropertyLocation }) {
|
function ChartIcon(props: { messageHistory: q.MessageHistory; classes: any; literal: JsonPropertyLocation }) {
|
||||||
const chartIconRef = React.useRef(null)
|
const chartIconRef = React.useRef(null)
|
||||||
const [open, setOpen] = React.useState(false)
|
const [open, setOpen] = React.useState(false)
|
||||||
|
|
||||||
const mouseOver = React.useCallback((event: React.MouseEvent<Element>) => {
|
const mouseOver = React.useCallback(
|
||||||
setOpen(true)
|
(event: React.MouseEvent<Element>) => {
|
||||||
}, [props.literal.path])
|
setOpen(true)
|
||||||
|
},
|
||||||
|
[props.literal.path]
|
||||||
|
)
|
||||||
|
|
||||||
const mouseOut = React.useCallback(() => {
|
const mouseOut = React.useCallback(() => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (<span>
|
return (
|
||||||
<ShowChart ref={chartIconRef} className={props.classes.icon} onMouseEnter={mouseOver} onMouseLeave={mouseOut} />
|
<span>
|
||||||
<Popper
|
<ShowChart ref={chartIconRef} className={props.classes.icon} onMouseEnter={mouseOver} onMouseLeave={mouseOut} />
|
||||||
open={open}
|
<Popper open={open} anchorEl={chartIconRef.current} placement="left-end">
|
||||||
anchorEl={chartIconRef.current}
|
<Fade in={open} timeout={300}>
|
||||||
placement="left-end"
|
<Paper style={{ width: '300px' }}>
|
||||||
>
|
{open ? <TopicPlot history={props.messageHistory} dotPath={props.literal.path} /> : <span />}
|
||||||
<Fade in={open} timeout={300}>
|
</Paper>
|
||||||
<Paper style={{ width: '300px' }}>
|
</Fade>
|
||||||
{open ? <TopicPlot history={props.messageHistory} dotPath={props.literal.path} /> : <span/>}
|
</Popper>
|
||||||
</Paper>
|
</span>
|
||||||
</Fade>
|
|
||||||
</Popper>
|
|
||||||
</span>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,9 +89,19 @@ function tokensForLine(change: diff.Change, line: number, props: Props) {
|
|||||||
let chartIcon = null
|
let chartIcon = null
|
||||||
if (literal) {
|
if (literal) {
|
||||||
if (hasEnoughDataToDisplayDiagrams) {
|
if (hasEnoughDataToDisplayDiagrams) {
|
||||||
chartIcon = <ChartIcon messageHistory={props.messageHistory} classes={{ icon: props.classes.iconButton }} literal={literal} />
|
chartIcon = (
|
||||||
|
<ChartIcon
|
||||||
|
messageHistory={props.messageHistory}
|
||||||
|
classes={{ icon: props.classes.iconButton }}
|
||||||
|
literal={literal}
|
||||||
|
/>
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
chartIcon = <Tooltip title="Not enough data"><ShowChart className={props.classes.iconDisabled} style={{ color: '#aaa' }} /></Tooltip>
|
chartIcon = (
|
||||||
|
<Tooltip title="Not enough data">
|
||||||
|
<ShowChart className={props.classes.iconDisabled} style={{ color: '#aaa' }} />
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,28 +110,39 @@ function tokensForLine(change: diff.Change, line: number, props: Props) {
|
|||||||
} else if (change.removed) {
|
} else if (change.removed) {
|
||||||
return [<Remove key="remove" className={classes.icon} />]
|
return [<Remove key="remove" className={classes.icon} />]
|
||||||
} else {
|
} else {
|
||||||
return [chartIcon, <div key="placeholder" style={{ width: '12px', display: 'inline-block' }} dangerouslySetInnerHTML={{ __html: ' ' }} />]
|
return [
|
||||||
|
chartIcon,
|
||||||
|
<div
|
||||||
|
key="placeholder"
|
||||||
|
style={{ width: '12px', display: 'inline-block' }}
|
||||||
|
dangerouslySetInnerHTML={{ __html: ' ' }}
|
||||||
|
/>,
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Gutters(props: Props) {
|
function Gutters(props: Props) {
|
||||||
let currentLine = -1
|
let currentLine = -1
|
||||||
const gutters = props.changes.map((change, key) => {
|
const gutters = props.changes
|
||||||
return trimNewlineRight(change.value)
|
.map((change, key) => {
|
||||||
.split('\n')
|
return trimNewlineRight(change.value)
|
||||||
.map((_, idx) => {
|
.split('\n')
|
||||||
currentLine = !change.removed ? currentLine + 1 : currentLine
|
.map((_, idx) => {
|
||||||
return (
|
currentLine = !change.removed ? currentLine + 1 : currentLine
|
||||||
<div key={`${key}-${idx}`} style={lineChangeStyle(change)} className={props.classes.gutterLine}>
|
return (
|
||||||
{tokensForLine(change, currentLine, props)}
|
<div key={`${key}-${idx}`} style={lineChangeStyle(change)} className={props.classes.gutterLine}>
|
||||||
</div>
|
{tokensForLine(change, currentLine, props)}
|
||||||
)
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}).reduce((a, b) => a.concat(b), [])
|
.reduce((a, b) => a.concat(b), [])
|
||||||
|
|
||||||
return <span>
|
return (
|
||||||
<pre className={props.className}>{gutters}</pre>
|
<span>
|
||||||
</span>
|
<pre className={props.className}>{gutters}</pre>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(style)(Gutters)
|
export default withStyles(style)(Gutters)
|
||||||
|
|||||||
@@ -40,35 +40,46 @@ class CodeDiff extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private plottableLiteralsIndexedWithLineNumbers() {
|
private plottableLiteralsIndexedWithLineNumbers() {
|
||||||
const allLiterals = this.isValidJson(this.props.current) ? (literalsMappedByLines(this.props.current) || []) : []
|
const allLiterals = this.isValidJson(this.props.current) ? literalsMappedByLines(this.props.current) || [] : []
|
||||||
|
|
||||||
return allLiterals
|
return allLiterals.map((l: JsonPropertyLocation) => (isPlottable(l.value) ? l : undefined)) as Array<
|
||||||
.map((l: JsonPropertyLocation) => isPlottable(l.value) ? l : undefined) as Array<JsonPropertyLocation>
|
JsonPropertyLocation
|
||||||
|
>
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStyledCodeLines(changes: Array<Diff.Change>) {
|
private renderStyledCodeLines(changes: Array<Diff.Change>) {
|
||||||
const styledLines = Prism.highlight(this.props.current, Prism.languages.json, 'json').split('\n')
|
const styledLines = Prism.highlight(this.props.current, Prism.languages.json, 'json').split('\n')
|
||||||
let lineNumber = 0
|
let lineNumber = 0
|
||||||
|
|
||||||
return changes.map((change, key) => {
|
return changes
|
||||||
const hasStyledCode = change.removed !== true
|
.map((change, key) => {
|
||||||
const changedLines = change.count || 0
|
const hasStyledCode = change.removed !== true
|
||||||
if (hasStyledCode && this.props.language === 'json') {
|
const changedLines = change.count || 0
|
||||||
const currentLines = styledLines.slice(lineNumber, lineNumber + changedLines)
|
if (hasStyledCode && this.props.language === 'json') {
|
||||||
const lines = currentLines.map((html: string, idx: number) => {
|
const currentLines = styledLines.slice(lineNumber, lineNumber + changedLines)
|
||||||
return <div key={`${key}-${idx}`} style={lineChangeStyle(change)} className={this.props.classes.line}><span dangerouslySetInnerHTML={{ __html: html }} /></div>
|
const lines = currentLines.map((html: string, idx: number) => {
|
||||||
})
|
return (
|
||||||
lineNumber += changedLines
|
<div key={`${key}-${idx}`} style={lineChangeStyle(change)} className={this.props.classes.line}>
|
||||||
|
<span dangerouslySetInnerHTML={{ __html: html }} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
lineNumber += changedLines
|
||||||
|
|
||||||
return [<div key={key}>{lines}</div>]
|
return [<div key={key}>{lines}</div>]
|
||||||
}
|
}
|
||||||
|
|
||||||
return trimNewlineRight(change.value)
|
return trimNewlineRight(change.value)
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map((line, idx) => {
|
.map((line, idx) => {
|
||||||
return <div key={`${key}-${idx}`} style={lineChangeStyle(change)} className={this.props.classes.line}><span>{line}</span></div>
|
return (
|
||||||
})
|
<div key={`${key}-${idx}`} style={lineChangeStyle(change)} className={this.props.classes.line}>
|
||||||
}).reduce((a, b) => a.concat(b), [])
|
<span>{line}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.reduce((a, b) => a.concat(b), [])
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
@@ -83,7 +94,8 @@ class CodeDiff extends React.Component<Props, State> {
|
|||||||
className={this.props.classes.gutters}
|
className={this.props.classes.gutters}
|
||||||
changes={changes}
|
changes={changes}
|
||||||
messageHistory={this.props.messageHistory}
|
messageHistory={this.props.messageHistory}
|
||||||
literalPositions={literalPositions} />
|
literalPositions={literalPositions}
|
||||||
|
/>
|
||||||
<pre className={this.props.classes.codeBlock}>{code}</pre>
|
<pre className={this.props.classes.codeBlock}>{code}</pre>
|
||||||
</div>
|
</div>
|
||||||
<DiffCount changes={changes} nameOfCompareMessage={this.props.nameOfCompareMessage} />
|
<DiffCount changes={changes} nameOfCompareMessage={this.props.nameOfCompareMessage} />
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ class HistoryDrawer extends React.Component<Props, State> {
|
|||||||
|
|
||||||
public renderHistory() {
|
public renderHistory() {
|
||||||
const style = (element: HistoryItem) => ({
|
const style = (element: HistoryItem) => ({
|
||||||
backgroundColor: element.selected ? this.props.theme.palette.action.selected : this.props.theme.palette.action.hover,
|
backgroundColor: element.selected
|
||||||
|
? this.props.theme.palette.action.selected
|
||||||
|
: this.props.theme.palette.action.hover,
|
||||||
margin: '8px',
|
margin: '8px',
|
||||||
padding: '8px 8px 0 8px',
|
padding: '8px 8px 0 8px',
|
||||||
cursor: this.props.onClick ? 'pointer' : 'inherit',
|
cursor: this.props.onClick ? 'pointer' : 'inherit',
|
||||||
@@ -50,11 +52,16 @@ class HistoryDrawer extends React.Component<Props, State> {
|
|||||||
key={element.key}
|
key={element.key}
|
||||||
style={style(element)}
|
style={style(element)}
|
||||||
onClick={(event: React.MouseEvent) => this.props.onClick && this.props.onClick(index, event.target)}
|
onClick={(event: React.MouseEvent) => this.props.onClick && this.props.onClick(index, event.target)}
|
||||||
tabIndex={0} onKeyDown={this.handleCtrlA}
|
tabIndex={0}
|
||||||
|
onKeyDown={this.handleCtrlA}
|
||||||
>
|
>
|
||||||
<div><i>{element.title}</i></div>
|
<div>
|
||||||
|
<i>{element.title}</i>
|
||||||
|
</div>
|
||||||
<div style={messageStyle}>
|
<div style={messageStyle}>
|
||||||
<span><pre>{element.value}</pre></span>
|
<span>
|
||||||
|
<pre>{element.value}</pre>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
@@ -63,11 +70,7 @@ class HistoryDrawer extends React.Component<Props, State> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={this.props.classes.main}>
|
<div className={this.props.classes.main}>
|
||||||
<Typography
|
<Typography component={'span'} onClick={this.toggle} style={{ cursor: 'pointer' }}>
|
||||||
component={'span'}
|
|
||||||
onClick={this.toggle}
|
|
||||||
style={{ cursor: 'pointer' }}
|
|
||||||
>
|
|
||||||
<Badge
|
<Badge
|
||||||
classes={{ badge: this.props.classes.badge }}
|
classes={{ badge: this.props.classes.badge }}
|
||||||
invisible={!visible}
|
invisible={!visible}
|
||||||
@@ -78,7 +81,7 @@ class HistoryDrawer extends React.Component<Props, State> {
|
|||||||
</Badge>
|
</Badge>
|
||||||
<div style={{ float: 'right' }}>{this.state.collapsed ? this.props.contentTypeIndicator : null}</div>
|
<div style={{ float: 'right' }}>{this.state.collapsed ? this.props.contentTypeIndicator : null}</div>
|
||||||
</Typography>
|
</Typography>
|
||||||
<div style={{ maxHeight: '230px', overflowY: 'scroll' }}>
|
<div style={{ maxHeight: '230px', overflowY: 'scroll' }}>
|
||||||
{this.state.collapsed ? null : this.props.children}
|
{this.state.collapsed ? null : this.props.children}
|
||||||
{this.state.collapsed ? null : elements}
|
{this.state.collapsed ? null : elements}
|
||||||
</div>
|
</div>
|
||||||
@@ -87,13 +90,7 @@ class HistoryDrawer extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return (
|
return <div style={{ display: 'block', width: '100%' }}>{this.renderHistory()}</div>
|
||||||
<div
|
|
||||||
style={{ display: 'block', width: '100%' }}
|
|
||||||
>
|
|
||||||
{this.renderHistory()}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const { XYPlot, LineMarkSeries, Hint, XAxis, YAxis, HorizontalGridLines } = requ
|
|||||||
const abbreviate = require('number-abbreviate')
|
const abbreviate = require('number-abbreviate')
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: Array<{x: number, y: number}>
|
data: Array<{ x: number; y: number }>
|
||||||
}
|
}
|
||||||
// const configuredCurve = d3Shape.curveBundle.beta(1)
|
// const configuredCurve = d3Shape.curveBundle.beta(1)
|
||||||
|
|
||||||
@@ -51,10 +51,7 @@ class PlotHistory extends React.Component<Props, Stats> {
|
|||||||
<XYPlot width={this.state.width} height={180}>
|
<XYPlot width={this.state.width} height={180}>
|
||||||
<HorizontalGridLines />
|
<HorizontalGridLines />
|
||||||
<XAxis />
|
<XAxis />
|
||||||
<YAxis
|
<YAxis width={45} tickFormat={(num: number) => abbreviate(num)} />
|
||||||
width={45}
|
|
||||||
tickFormat={(num: number) => abbreviate(num)}
|
|
||||||
/>
|
|
||||||
<LineMarkSeries
|
<LineMarkSeries
|
||||||
onValueMouseOver={this._rememberValue}
|
onValueMouseOver={this._rememberValue}
|
||||||
onValueMouseOut={this._forgetValue}
|
onValueMouseOut={this._forgetValue}
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Publish extends React.Component<Props, State> {
|
class Publish extends React.Component<Props, State> {
|
||||||
|
|
||||||
private editorOptions = {
|
private editorOptions = {
|
||||||
showLineNumbers: false,
|
showLineNumbers: false,
|
||||||
tabSize: 2,
|
tabSize: 2,
|
||||||
@@ -108,18 +107,18 @@ class Publish extends React.Component<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<FormControl style={{ width: '100%' }}>
|
<FormControl style={{ width: '100%' }}>
|
||||||
<InputLabel htmlFor="publish-topic">Topic</InputLabel>
|
<InputLabel htmlFor="publish-topic">Topic</InputLabel>
|
||||||
<Input
|
<Input
|
||||||
id="publish-topic"
|
id="publish-topic"
|
||||||
value={topicStr}
|
value={topicStr}
|
||||||
startAdornment={<span/>}
|
startAdornment={<span />}
|
||||||
endAdornment={<ClearAdornment action={this.clearTopic} value={topicStr} />}
|
endAdornment={<ClearAdornment action={this.clearTopic} value={topicStr} />}
|
||||||
onBlur={this.onTopicBlur}
|
onBlur={this.onTopicBlur}
|
||||||
onChange={this.updateTopic}
|
onChange={this.updateTopic}
|
||||||
multiline={true}
|
multiline={true}
|
||||||
placeholder="example/topic"
|
placeholder="example/topic"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -132,13 +131,7 @@ class Publish extends React.Component<Props, State> {
|
|||||||
|
|
||||||
private publishButton() {
|
private publishButton() {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button variant="contained" size="small" color="primary" onClick={this.publish} id="publish-button">
|
||||||
variant="contained"
|
|
||||||
size="small"
|
|
||||||
color="primary"
|
|
||||||
onClick={this.publish}
|
|
||||||
id="publish-button"
|
|
||||||
>
|
|
||||||
<Navigation style={{ marginRight: '8px' }} /> Publish
|
<Navigation style={{ marginRight: '8px' }} /> Publish
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
@@ -162,7 +155,11 @@ class Publish extends React.Component<Props, State> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title="Format JSON">
|
<Tooltip title="Format JSON">
|
||||||
<Fab style={{ width: '36px', height: '36px', marginLeft: '8px' }} onClick={this.formatJson} id="sidebar-publish-format-json">
|
<Fab
|
||||||
|
style={{ width: '36px', height: '36px', marginLeft: '8px' }}
|
||||||
|
onClick={this.formatJson}
|
||||||
|
id="sidebar-publish-format-json"
|
||||||
|
>
|
||||||
<FormatAlignLeft style={{ fontSize: '20px' }} />
|
<FormatAlignLeft style={{ fontSize: '20px' }} />
|
||||||
</Fab>
|
</Fab>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -175,9 +172,7 @@ class Publish extends React.Component<Props, State> {
|
|||||||
<div style={{ width: '100%', lineHeight: '64px' }}>
|
<div style={{ width: '100%', lineHeight: '64px' }}>
|
||||||
{this.renderEditorModeSelection()}
|
{this.renderEditorModeSelection()}
|
||||||
{this.renderFormatJson()}
|
{this.renderFormatJson()}
|
||||||
<div style={{ float: 'right', marginRight: '16px' }}>
|
<div style={{ float: 'right', marginRight: '16px' }}>{this.publishButton()}</div>
|
||||||
{this.publishButton()}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -239,30 +234,36 @@ class Publish extends React.Component<Props, State> {
|
|||||||
onChange={this.onChangeQoS}
|
onChange={this.onChangeQoS}
|
||||||
>
|
>
|
||||||
<MenuItem key={0} value={0} style={itemStyle}>
|
<MenuItem key={0} value={0} style={itemStyle}>
|
||||||
<Tooltip title="At most once"><div style={tooltipStyle}>0</div></Tooltip>
|
<Tooltip title="At most once">
|
||||||
|
<div style={tooltipStyle}>0</div>
|
||||||
|
</Tooltip>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem key={1} value={1} style={itemStyle}>
|
<MenuItem key={1} value={1} style={itemStyle}>
|
||||||
<Tooltip title="At least once"><div style={tooltipStyle}>1</div></Tooltip>
|
<Tooltip title="At least once">
|
||||||
|
<div style={tooltipStyle}>1</div>
|
||||||
|
</Tooltip>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem key={2} value={2} style={itemStyle}>
|
<MenuItem key={2} value={2} style={itemStyle}>
|
||||||
<Tooltip title="Exactly once"><div style={tooltipStyle}>2</div></Tooltip>
|
<Tooltip title="Exactly once">
|
||||||
|
<div style={tooltipStyle}>2</div>
|
||||||
|
</Tooltip>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</TextField>
|
</TextField>
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<div style={{ marginTop: '8px', clear: 'both' }}>
|
<div style={{ marginTop: '8px', clear: 'both' }}>
|
||||||
<div style={{ width: '100%', textAlign: 'right' }}>
|
<div style={{ width: '100%', textAlign: 'right' }}>
|
||||||
<FormControlLabel
|
<FormControlLabel style={labelStyle} control={qosSelect} label="QoS" labelPlacement="start" />
|
||||||
style={labelStyle}
|
<Tooltip
|
||||||
control={qosSelect}
|
title="Retained messages only appear to be retained, when client subscribes after the initial publish."
|
||||||
label="QoS"
|
placement="top"
|
||||||
labelPlacement="start"
|
>
|
||||||
/>
|
|
||||||
<Tooltip title="Retained messages only appear to be retained, when client subscribes after the initial publish." placement="top">
|
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value="retain"
|
value="retain"
|
||||||
style={labelStyle}
|
style={labelStyle}
|
||||||
control={<Checkbox color="primary" checked={this.props.retain} onChange={this.props.actions.toggleRetain} />}
|
control={
|
||||||
|
<Checkbox color="primary" checked={this.props.retain} onChange={this.props.actions.toggleRetain} />
|
||||||
|
}
|
||||||
label="retain"
|
label="retain"
|
||||||
labelPlacement="end"
|
labelPlacement="end"
|
||||||
/>
|
/>
|
||||||
@@ -343,4 +344,7 @@ const mapStateToProps = (state: AppState) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(Publish))
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(withTheme(Publish))
|
||||||
|
|||||||
@@ -14,13 +14,7 @@ import { settingsActions, sidebarActions } from '../../actions'
|
|||||||
import { Theme, withStyles } from '@material-ui/core/styles'
|
import { Theme, withStyles } from '@material-ui/core/styles'
|
||||||
import { TopicViewModel } from '../../model/TopicViewModel'
|
import { TopicViewModel } from '../../model/TopicViewModel'
|
||||||
|
|
||||||
import {
|
import { ExpansionPanel, ExpansionPanelDetails, ExpansionPanelSummary, Typography, Badge } from '@material-ui/core'
|
||||||
ExpansionPanel,
|
|
||||||
ExpansionPanelDetails,
|
|
||||||
ExpansionPanelSummary,
|
|
||||||
Typography,
|
|
||||||
Badge,
|
|
||||||
} from '@material-ui/core'
|
|
||||||
|
|
||||||
const throttle = require('lodash.throttle')
|
const throttle = require('lodash.throttle')
|
||||||
|
|
||||||
@@ -76,7 +70,7 @@ class Sidebar extends React.Component<Props, State> {
|
|||||||
private renderRecursiveTopicDeleteButton() {
|
private renderRecursiveTopicDeleteButton() {
|
||||||
const deleteLimit = 50
|
const deleteLimit = 50
|
||||||
const topicCount = this.props.node ? this.props.node.childTopicCount() : 0
|
const topicCount = this.props.node ? this.props.node.childTopicCount() : 0
|
||||||
if ((!this.props.node || topicCount === 0) || (this.props.node.message && topicCount === 1)) {
|
if (!this.props.node || topicCount === 0 || (this.props.node.message && topicCount === 1)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +80,10 @@ class Sidebar extends React.Component<Props, State> {
|
|||||||
badgeContent={<span style={{ whiteSpace: 'nowrap' }}>{topicCount >= deleteLimit ? '50+' : topicCount}</span>}
|
badgeContent={<span style={{ whiteSpace: 'nowrap' }}>{topicCount >= deleteLimit ? '50+' : topicCount}</span>}
|
||||||
color="secondary"
|
color="secondary"
|
||||||
>
|
>
|
||||||
<CustomIconButton onClick={() => this.deleteTopic(this.props.node, true, deleteLimit)} tooltip={`Deletes up to ${deleteLimit} sub-topics with a single click`}>
|
<CustomIconButton
|
||||||
|
onClick={() => this.deleteTopic(this.props.node, true, deleteLimit)}
|
||||||
|
tooltip={`Deletes up to ${deleteLimit} sub-topics with a single click`}
|
||||||
|
>
|
||||||
<Delete style={{ marginTop: '-3px' }} color="action" />
|
<Delete style={{ marginTop: '-3px' }} color="action" />
|
||||||
</CustomIconButton>
|
</CustomIconButton>
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -112,7 +109,9 @@ class Sidebar extends React.Component<Props, State> {
|
|||||||
<div>
|
<div>
|
||||||
<ExpansionPanel key="topic" defaultExpanded={true} disabled={!Boolean(this.props.node)}>
|
<ExpansionPanel key="topic" defaultExpanded={true} disabled={!Boolean(this.props.node)}>
|
||||||
<ExpansionPanelSummary expandIcon={<ExpandMore />} style={summaryStyle}>
|
<ExpansionPanelSummary expandIcon={<ExpandMore />} style={summaryStyle}>
|
||||||
<Typography className={classes.heading}>Topic {copyTopic} {deleteTopic} {deleteRecursiveTopic}</Typography>
|
<Typography className={classes.heading}>
|
||||||
|
Topic {copyTopic} {deleteTopic} {deleteRecursiveTopic}
|
||||||
|
</Typography>
|
||||||
</ExpansionPanelSummary>
|
</ExpansionPanelSummary>
|
||||||
<ExpansionPanelDetails style={this.detailsStyle}>
|
<ExpansionPanelDetails style={this.detailsStyle}>
|
||||||
<Topic node={this.props.node} didSelectNode={this.updateNode} />
|
<Topic node={this.props.node} didSelectNode={this.updateNode} />
|
||||||
@@ -204,4 +203,9 @@ const styles = (theme: Theme) => ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(Sidebar))
|
export default withStyles(styles)(
|
||||||
|
connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(Sidebar)
|
||||||
|
)
|
||||||
|
|||||||
@@ -32,30 +32,29 @@ class Topic extends React.Component<Props, {}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let key = 0
|
let key = 0
|
||||||
const breadCrumps = node.branch()
|
const breadCrumps = node
|
||||||
|
.branch()
|
||||||
.map(node => node.sourceEdge)
|
.map(node => node.sourceEdge)
|
||||||
.filter(edge => Boolean(edge))
|
.filter(edge => Boolean(edge))
|
||||||
.map(edge =>
|
.map(edge => [
|
||||||
[(
|
<Button
|
||||||
<Button
|
onClick={() => this.props.actions.selectTopic(edge!.target)}
|
||||||
onClick={() => this.props.actions.selectTopic(edge!.target)}
|
size="small"
|
||||||
size="small"
|
variant={theme.palette.type === 'light' ? 'contained' : undefined}
|
||||||
variant={theme.palette.type === 'light' ? 'contained' : undefined}
|
color={theme.palette.type === 'light' ? 'primary' : 'secondary'}
|
||||||
color={theme.palette.type === 'light' ? 'primary' : 'secondary'}
|
className={this.props.classes.button}
|
||||||
className={this.props.classes.button}
|
key={edge!.hash()}
|
||||||
key={edge!.hash()}
|
>
|
||||||
>
|
{edge!.name}
|
||||||
{edge!.name}
|
</Button>,
|
||||||
</Button>
|
])
|
||||||
)]
|
|
||||||
)
|
|
||||||
|
|
||||||
if (breadCrumps.length === 0) {
|
if (breadCrumps.length === 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const joinedBreadCrumps = breadCrumps.reduce((prev, current) =>
|
const joinedBreadCrumps = breadCrumps.reduce((prev, current) =>
|
||||||
prev.concat([<span key={key += 1}> / </span>]).concat(current)
|
prev.concat([<span key={(key += 1)}> / </span>]).concat(current)
|
||||||
)
|
)
|
||||||
|
|
||||||
return <span style={{ lineHeight: '2.2em' }}>{joinedBreadCrumps}</span>
|
return <span style={{ lineHeight: '2.2em' }}>{joinedBreadCrumps}</span>
|
||||||
@@ -68,4 +67,7 @@ const mapDispatchToProps = (dispatch: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(withStyles(styles, { withTheme: true })(Topic))
|
export default connect(
|
||||||
|
null,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(withStyles(styles, { withTheme: true })(Topic))
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ function nodeToHistory(history: q.MessageHistory) {
|
|||||||
.map((message: q.Message) => {
|
.map((message: q.Message) => {
|
||||||
const value = message.value ? toPlottableValue(Base64Message.toUnicodeString(message.value)) : NaN
|
const value = message.value ? toPlottableValue(Base64Message.toUnicodeString(message.value)) : NaN
|
||||||
return { x: message.received.getTime(), y: toPlottableValue(value) }
|
return { x: message.received.getTime(), y: toPlottableValue(value) }
|
||||||
}).filter(data => !isNaN(data.y as any)) as any
|
})
|
||||||
|
.filter(data => !isNaN(data.y as any)) as any
|
||||||
}
|
}
|
||||||
|
|
||||||
function nodeDotPathToHistory(history: q.MessageHistory, dotPath: string) {
|
function nodeDotPathToHistory(history: q.MessageHistory, dotPath: string) {
|
||||||
@@ -31,7 +32,8 @@ function nodeDotPathToHistory(history: q.MessageHistory, dotPath: string) {
|
|||||||
let value = dotProp.get(json, dotPath)
|
let value = dotProp.get(json, dotPath)
|
||||||
|
|
||||||
return { x: message.received.getTime(), y: toPlottableValue(value) }
|
return { x: message.received.getTime(), y: toPlottableValue(value) }
|
||||||
}).filter(data => !isNaN(data.y as any)) as any
|
})
|
||||||
|
.filter(data => !isNaN(data.y as any)) as any
|
||||||
}
|
}
|
||||||
|
|
||||||
function render(props: Props) {
|
function render(props: Props) {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
displayMessage?: q.Message,
|
displayMessage?: q.Message
|
||||||
anchorEl?: HTMLElement
|
anchorEl?: HTMLElement
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,18 +65,28 @@ class MessageHistory extends React.Component<Props, State> {
|
|||||||
const element = {
|
const element = {
|
||||||
value,
|
value,
|
||||||
key: `${message.messageNumber}-${message.received}`,
|
key: `${message.messageNumber}-${message.received}`,
|
||||||
title: (<span>
|
title: (
|
||||||
<DateFormatter date={message.received} />
|
<span>
|
||||||
{previousMessage ? <i>(-<DateFormatter date={message.received} intervalSince={previousMessage.received} />)</i> : null}
|
<DateFormatter date={message.received} />
|
||||||
<div style={{ float: 'right' }}><Copy value={value} /></div>
|
{previousMessage ? (
|
||||||
</span>),
|
<i>
|
||||||
|
(-
|
||||||
|
<DateFormatter date={message.received} intervalSince={previousMessage.received} />)
|
||||||
|
</i>
|
||||||
|
) : null}
|
||||||
|
<div style={{ float: 'right' }}>
|
||||||
|
<Copy value={value} />
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
selected: message && message === this.props.selected,
|
selected: message && message === this.props.selected,
|
||||||
}
|
}
|
||||||
previousMessage = message
|
previousMessage = message
|
||||||
return element
|
return element
|
||||||
})
|
})
|
||||||
|
|
||||||
const isMessagePlottable = node.message && node.message.value && isPlottable(Base64Message.toUnicodeString(node.message.value))
|
const isMessagePlottable =
|
||||||
|
node.message && node.message.value && isPlottable(Base64Message.toUnicodeString(node.message.value))
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<History
|
<History
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ interface State {}
|
|||||||
class ValuePanel extends React.Component<Props, State> {
|
class ValuePanel extends React.Component<Props, State> {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = { }
|
this.state = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderValue() {
|
private renderValue() {
|
||||||
@@ -84,9 +84,7 @@ class ValuePanel extends React.Component<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<div style={{ width: '100%', display: 'flex', paddingLeft: '8px' }}>
|
<div style={{ width: '100%', display: 'flex', paddingLeft: '8px' }}>
|
||||||
<span style={{ marginTop: '2px', flexGrow: 1 }}>{this.renderActionButtons()}</span>
|
<span style={{ marginTop: '2px', flexGrow: 1 }}>{this.renderActionButtons()}</span>
|
||||||
<div style={{ flex: 6, textAlign: 'right' }}>
|
<div style={{ flex: 6, textAlign: 'right' }}>{this.props.node.mqttMessage.retain ? retainedButton : null}</div>
|
||||||
{this.props.node.mqttMessage.retain ? retainedButton : null}
|
|
||||||
</div>
|
|
||||||
{this.messageMetaInfo()}
|
{this.messageMetaInfo()}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -100,7 +98,11 @@ class ValuePanel extends React.Component<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<span style={{ width: '100%', paddingLeft: '8px', flex: 6 }}>
|
<span style={{ width: '100%', paddingLeft: '8px', flex: 6 }}>
|
||||||
<Typography style={{ textAlign: 'right' }}>QoS: {this.props.node.mqttMessage.qos}</Typography>
|
<Typography style={{ textAlign: 'right' }}>QoS: {this.props.node.mqttMessage.qos}</Typography>
|
||||||
<Typography style={{ textAlign: 'right' }}><i><DateFormatter date={this.props.node.message.received} /></i></Typography>
|
<Typography style={{ textAlign: 'right' }}>
|
||||||
|
<i>
|
||||||
|
<DateFormatter date={this.props.node.message.received} />
|
||||||
|
</i>
|
||||||
|
</Typography>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -114,15 +116,24 @@ class ValuePanel extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToggleButtonGroup id="valueRendererDisplayMode" value={this.props.valueRendererDisplayMode} exclusive={true} onChange={handleValue}>
|
<ToggleButtonGroup
|
||||||
|
id="valueRendererDisplayMode"
|
||||||
|
value={this.props.valueRendererDisplayMode}
|
||||||
|
exclusive={true}
|
||||||
|
onChange={handleValue}
|
||||||
|
>
|
||||||
<ToggleButton className={this.props.classes.toggleButton} value="diff" id="valueRendererDisplayMode-diff">
|
<ToggleButton className={this.props.classes.toggleButton} value="diff" id="valueRendererDisplayMode-diff">
|
||||||
<Tooltip title="Show difference between the current and the last message">
|
<Tooltip title="Show difference between the current and the last message">
|
||||||
<span><Code className={this.props.classes.toggleButtonIcon} /></span>
|
<span>
|
||||||
|
<Code className={this.props.classes.toggleButtonIcon} />
|
||||||
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
<ToggleButton className={this.props.classes.toggleButton} value="raw" id="valueRendererDisplayMode-raw">
|
<ToggleButton className={this.props.classes.toggleButton} value="raw" id="valueRendererDisplayMode-raw">
|
||||||
<Tooltip title="Raw value">
|
<Tooltip title="Raw value">
|
||||||
<span><Reorder className={this.props.classes.toggleButtonIcon} /></span>
|
<span>
|
||||||
|
<Reorder className={this.props.classes.toggleButtonIcon} />
|
||||||
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
@@ -148,7 +159,10 @@ class ValuePanel extends React.Component<Props, State> {
|
|||||||
const { node, classes } = this.props
|
const { node, classes } = this.props
|
||||||
const { detailsStyle, summaryStyle } = this.panelStyle()
|
const { detailsStyle, summaryStyle } = this.panelStyle()
|
||||||
|
|
||||||
const copyValue = (node && node.message && node.message.value) ? <Copy value={Base64Message.toUnicodeString(node.message.value)} /> : null
|
const copyValue =
|
||||||
|
node && node.message && node.message.value ? (
|
||||||
|
<Copy value={Base64Message.toUnicodeString(node.message.value)} />
|
||||||
|
) : null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExpansionPanel key="value" defaultExpanded={true}>
|
<ExpansionPanel key="value" defaultExpanded={true}>
|
||||||
@@ -158,11 +172,15 @@ class ValuePanel extends React.Component<Props, State> {
|
|||||||
<ExpansionPanelDetails style={detailsStyle}>
|
<ExpansionPanelDetails style={detailsStyle}>
|
||||||
{this.renderViewOptions()}
|
{this.renderViewOptions()}
|
||||||
<div>
|
<div>
|
||||||
<React.Suspense fallback={<div>Loading...</div>}>
|
<React.Suspense fallback={<div>Loading...</div>}>{this.renderValue()}</React.Suspense>
|
||||||
{this.renderValue()}
|
</div>
|
||||||
</React.Suspense>
|
<div>
|
||||||
|
<MessageHistory
|
||||||
|
onSelect={this.handleMessageHistorySelect}
|
||||||
|
selected={this.props.compareMessage}
|
||||||
|
node={this.props.node}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div><MessageHistory onSelect={this.handleMessageHistorySelect} selected={this.props.compareMessage} node={this.props.node} /></div>
|
|
||||||
</ExpansionPanelDetails>
|
</ExpansionPanelDetails>
|
||||||
</ExpansionPanel>
|
</ExpansionPanel>
|
||||||
)
|
)
|
||||||
@@ -197,4 +215,7 @@ const styles = (theme: Theme) => ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(ValuePanel))
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(withStyles(styles)(ValuePanel))
|
||||||
|
|||||||
@@ -77,18 +77,24 @@ class TreeComponent extends React.PureComponent<Props, State> {
|
|||||||
const timeUntilNextUpdate = updateInterval - (performance.now() - this.renderTime)
|
const timeUntilNextUpdate = updateInterval - (performance.now() - this.renderTime)
|
||||||
|
|
||||||
this.updateTimer = setTimeout(() => {
|
this.updateTimer = setTimeout(() => {
|
||||||
window.requestIdleCallback(() => {
|
window.requestIdleCallback(
|
||||||
this.updateTimer && clearTimeout(this.updateTimer)
|
() => {
|
||||||
this.updateTimer = undefined
|
this.updateTimer && clearTimeout(this.updateTimer)
|
||||||
this.renderTime = performance.now()
|
this.updateTimer = undefined
|
||||||
|
this.renderTime = performance.now()
|
||||||
|
|
||||||
if (!this.props.paused) {
|
if (!this.props.paused) {
|
||||||
this.props.tree && this.props.tree.applyUnmergedChanges()
|
this.props.tree && this.props.tree.applyUnmergedChanges()
|
||||||
}
|
}
|
||||||
window.requestIdleCallback(() => {
|
window.requestIdleCallback(
|
||||||
this.setState({ lastUpdate: this.renderTime })
|
() => {
|
||||||
}, { timeout: 100 })
|
this.setState({ lastUpdate: this.renderTime })
|
||||||
}, { timeout: 500 })
|
},
|
||||||
|
{ timeout: 100 }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{ timeout: 500 }
|
||||||
|
)
|
||||||
}, Math.max(0, timeUntilNextUpdate))
|
}, Math.max(0, timeUntilNextUpdate))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,4 +143,7 @@ const mapDispatchToProps = (dispatch: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(TreeComponent)
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(TreeComponent)
|
||||||
|
|||||||
@@ -107,9 +107,11 @@ class TreeNodeComponent extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private stateHasChanged(newState: State) {
|
private stateHasChanged(newState: State) {
|
||||||
return this.state.collapsedOverride !== newState.collapsedOverride
|
return (
|
||||||
|| this.state.mouseOver !== newState.mouseOver
|
this.state.collapsedOverride !== newState.collapsedOverride ||
|
||||||
|| this.state.selected !== newState.selected
|
this.state.mouseOver !== newState.mouseOver ||
|
||||||
|
this.state.selected !== newState.selected
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private toggle() {
|
private toggle() {
|
||||||
@@ -137,7 +139,12 @@ class TreeNodeComponent extends React.Component<Props, State> {
|
|||||||
private mouseOver = (event: React.MouseEvent) => {
|
private mouseOver = (event: React.MouseEvent) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
this.setHover(true)
|
this.setHover(true)
|
||||||
if (this.props.settings.get('selectTopicWithMouseOver') && this.props.treeNode && this.props.treeNode.message && this.props.treeNode.message.value) {
|
if (
|
||||||
|
this.props.settings.get('selectTopicWithMouseOver') &&
|
||||||
|
this.props.treeNode &&
|
||||||
|
this.props.treeNode.message &&
|
||||||
|
this.props.treeNode.message.value
|
||||||
|
) {
|
||||||
this.props.didSelectTopic(this.props.treeNode)
|
this.props.didSelectTopic(this.props.treeNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,11 +188,13 @@ class TreeNodeComponent extends React.Component<Props, State> {
|
|||||||
|
|
||||||
public shouldComponentUpdate(nextProps: Props, nextState: State) {
|
public shouldComponentUpdate(nextProps: Props, nextState: State) {
|
||||||
const shouldRenderToRemoveCssAnimation = this.cssAnimationWasSetAt !== undefined
|
const shouldRenderToRemoveCssAnimation = this.cssAnimationWasSetAt !== undefined
|
||||||
return this.stateHasChanged(nextState)
|
return (
|
||||||
|| this.props.settings !== nextProps.settings
|
this.stateHasChanged(nextState) ||
|
||||||
|| (this.props.lastUpdate !== nextProps.lastUpdate)
|
this.props.settings !== nextProps.settings ||
|
||||||
|| this.animationDirty
|
this.props.lastUpdate !== nextProps.lastUpdate ||
|
||||||
|| shouldRenderToRemoveCssAnimation
|
this.animationDirty ||
|
||||||
|
shouldRenderToRemoveCssAnimation
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate() {
|
public componentDidUpdate() {
|
||||||
@@ -204,12 +213,19 @@ class TreeNodeComponent extends React.Component<Props, State> {
|
|||||||
public render() {
|
public render() {
|
||||||
const { classes } = this.props
|
const { classes } = this.props
|
||||||
|
|
||||||
const shouldStartAnimation = (!this.animationDirty) && !this.props.isRoot && this.props.settings.get('highlightTopicUpdates')
|
const shouldStartAnimation =
|
||||||
|
!this.animationDirty && !this.props.isRoot && this.props.settings.get('highlightTopicUpdates')
|
||||||
const animationName = this.props.theme.palette.type === 'light' ? 'updateLight' : 'updateDark'
|
const animationName = this.props.theme.palette.type === 'light' ? 'updateLight' : 'updateDark'
|
||||||
const animation = shouldStartAnimation ? { willChange: 'auto', translateZ: 0, animation: `${animationName} 0.5s` } : {}
|
const animation = shouldStartAnimation
|
||||||
|
? { willChange: 'auto', translateZ: 0, animation: `${animationName} 0.5s` }
|
||||||
|
: {}
|
||||||
this.animationDirty = shouldStartAnimation
|
this.animationDirty = shouldStartAnimation
|
||||||
|
|
||||||
const highlightClass = this.state.selected ? this.props.classes.selected : (this.state.mouseOver ? this.props.classes.hover : '')
|
const highlightClass = this.state.selected
|
||||||
|
? this.props.classes.selected
|
||||||
|
: this.state.mouseOver
|
||||||
|
? this.props.classes.hover
|
||||||
|
: ''
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -49,13 +49,16 @@ class TreeNodeSubnodes extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private renderMore() {
|
private renderMore() {
|
||||||
this.renderMoreAnimationFrame = (window as any).requestIdleCallback(() => {
|
this.renderMoreAnimationFrame = (window as any).requestIdleCallback(
|
||||||
this.setState({ ...this.state, alreadyAdded: this.state.alreadyAdded * 1.5 })
|
() => {
|
||||||
}, { timeout: 500 })
|
this.setState({ ...this.state, alreadyAdded: this.state.alreadyAdded * 1.5 })
|
||||||
|
},
|
||||||
|
{ timeout: 500 }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
(window as any).cancelIdleCallback(this.renderMoreAnimationFrame)
|
;(window as any).cancelIdleCallback(this.renderMoreAnimationFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
@@ -69,7 +72,7 @@ class TreeNodeSubnodes extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const nodes = this.sortedNodes().slice(0, this.state.alreadyAdded)
|
const nodes = this.sortedNodes().slice(0, this.state.alreadyAdded)
|
||||||
const listItems = nodes.map((node) => {
|
const listItems = nodes.map(node => {
|
||||||
return (
|
return (
|
||||||
<TreeNode
|
<TreeNode
|
||||||
key={`${node.hash()}-${this.props.filter}`}
|
key={`${node.hash()}-${this.props.filter}`}
|
||||||
@@ -82,11 +85,7 @@ class TreeNodeSubnodes extends React.Component<Props, State> {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return <span className={this.props.classes.list}>{listItems}</span>
|
||||||
<span className={this.props.classes.list}>
|
|
||||||
{listItems}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,17 +14,25 @@ export interface TreeNodeProps extends React.HTMLAttributes<HTMLElement> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class TreeNodeTitle extends React.Component<TreeNodeProps, {}> {
|
class TreeNodeTitle extends React.Component<TreeNodeProps, {}> {
|
||||||
|
|
||||||
private renderSourceEdge() {
|
private renderSourceEdge() {
|
||||||
const name = this.props.name || (this.props.treeNode.sourceEdge && this.props.treeNode.sourceEdge.name)
|
const name = this.props.name || (this.props.treeNode.sourceEdge && this.props.treeNode.sourceEdge.name)
|
||||||
|
|
||||||
return <span key="edge" className={this.props.classes.sourceEdge}>{name}</span>
|
return (
|
||||||
|
<span key="edge" className={this.props.classes.sourceEdge}>
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderValue() {
|
private renderValue() {
|
||||||
return this.props.treeNode.message && this.props.treeNode.message.value && this.props.treeNode.message.length > 0
|
return this.props.treeNode.message &&
|
||||||
? <span key="value" className={this.props.classes.value}> = {Base64Message.toUnicodeString(this.props.treeNode.message.value).slice(0, 120)}</span>
|
this.props.treeNode.message.value &&
|
||||||
: null
|
this.props.treeNode.message.length > 0 ? (
|
||||||
|
<span key="value" className={this.props.classes.value}>
|
||||||
|
{' '}
|
||||||
|
= {Base64Message.toUnicodeString(this.props.treeNode.message.value).slice(0, 120)}
|
||||||
|
</span>
|
||||||
|
) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderExpander() {
|
private renderExpander() {
|
||||||
@@ -32,7 +40,11 @@ class TreeNodeTitle extends React.Component<TreeNodeProps, {}> {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return <span key="expander" className={this.props.classes.expander} onClick={this.props.toggleCollapsed}>{this.props.collapsed ? '▶' : '▼'}</span>
|
return (
|
||||||
|
<span key="expander" className={this.props.classes.expander} onClick={this.props.toggleCollapsed}>
|
||||||
|
{this.props.collapsed ? '▶' : '▼'}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderMetadata() {
|
private renderMetadata() {
|
||||||
@@ -41,16 +53,16 @@ class TreeNodeTitle extends React.Component<TreeNodeProps, {}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const messages = this.props.treeNode.leafMessageCount()
|
const messages = this.props.treeNode.leafMessageCount()
|
||||||
return <span key="metadata" className={this.props.classes.collapsedSubnodes}>{`(${this.props.treeNode.childTopicCount()} topics, ${messages} messages)`}</span>
|
return (
|
||||||
|
<span
|
||||||
|
key="metadata"
|
||||||
|
className={this.props.classes.collapsedSubnodes}
|
||||||
|
>{`(${this.props.treeNode.childTopicCount()} topics, ${messages} messages)`}</span>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return ([
|
return [this.renderExpander(), this.renderSourceEdge(), this.renderMetadata(), this.renderValue()]
|
||||||
this.renderExpander(),
|
|
||||||
this.renderSourceEdge(),
|
|
||||||
this.renderMetadata(),
|
|
||||||
this.renderValue(),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,15 +12,7 @@ import { green } from '@material-ui/core/colors'
|
|||||||
import { Theme, withStyles } from '@material-ui/core/styles'
|
import { Theme, withStyles } from '@material-ui/core/styles'
|
||||||
import { updateNotifierActions } from '../actions'
|
import { updateNotifierActions } from '../actions'
|
||||||
|
|
||||||
import {
|
import { Button, IconButton, Modal, Paper, Snackbar, SnackbarContent, Typography } from '@material-ui/core'
|
||||||
Button,
|
|
||||||
IconButton,
|
|
||||||
Modal,
|
|
||||||
Paper,
|
|
||||||
Snackbar,
|
|
||||||
SnackbarContent,
|
|
||||||
Typography,
|
|
||||||
} from '@material-ui/core'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
showUpdateNotification: boolean
|
showUpdateNotification: boolean
|
||||||
@@ -30,7 +22,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface GithubRelease {
|
interface GithubRelease {
|
||||||
url: string,
|
url: string
|
||||||
assets?: Array<GithubAsset>
|
assets?: Array<GithubAsset>
|
||||||
published_at: string // "2019-01-25T20:14:39Z"
|
published_at: string // "2019-01-25T20:14:39Z"
|
||||||
body_html: string
|
body_html: string
|
||||||
@@ -59,7 +51,7 @@ class UpdateNotifier extends React.Component<Props, State> {
|
|||||||
this.state = { newerVersions: [] }
|
this.state = { newerVersions: [] }
|
||||||
|
|
||||||
const ownVersion = electron.remote.app.getVersion()
|
const ownVersion = electron.remote.app.getVersion()
|
||||||
this.fetchReleases().then((releases) => {
|
this.fetchReleases().then(releases => {
|
||||||
const newerVersions = releases
|
const newerVersions = releases
|
||||||
.filter(release => this.allowPrereleaseIfOwnVersionIsBeta(release, ownVersion))
|
.filter(release => this.allowPrereleaseIfOwnVersionIsBeta(release, ownVersion))
|
||||||
.filter(release => compareVersions(release.tag_name, ownVersion) > 0)
|
.filter(release => compareVersions(release.tag_name, ownVersion) > 0)
|
||||||
@@ -131,21 +123,19 @@ class UpdateNotifier extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private notificationActions() {
|
private notificationActions() {
|
||||||
return [(
|
return [
|
||||||
<Button key="undo" size="small" onClick={this.showDetails}>
|
<Button key="undo" size="small" onClick={this.showDetails}>
|
||||||
Details
|
Details
|
||||||
</Button>
|
</Button>,
|
||||||
), (
|
<IconButton
|
||||||
<IconButton
|
key="close"
|
||||||
key="close"
|
aria-label="Close"
|
||||||
aria-label="Close"
|
color="inherit"
|
||||||
color="inherit"
|
className={this.props.classes.close}
|
||||||
className={this.props.classes.close}
|
onClick={this.closeNotification}
|
||||||
onClick={this.closeNotification}
|
>
|
||||||
>
|
<Close />
|
||||||
<Close />
|
</IconButton>,
|
||||||
</IconButton>
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,23 +149,20 @@ class UpdateNotifier extends React.Component<Props, State> {
|
|||||||
.join('<hr />')
|
.join('<hr />')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal open={this.props.showUpdateDetails} disableAutoFocus={true} onClose={this.hideDetails}>
|
||||||
open={this.props.showUpdateDetails}
|
|
||||||
disableAutoFocus={true}
|
|
||||||
onClose={this.hideDetails}
|
|
||||||
>
|
|
||||||
<Paper className={this.props.classes.root}>
|
<Paper className={this.props.classes.root}>
|
||||||
<Typography variant="h6" className={this.props.classes.title}>Version {latestUpdate.tag_name}</Typography>
|
<Typography variant="h6" className={this.props.classes.title}>
|
||||||
|
Version {latestUpdate.tag_name}
|
||||||
|
</Typography>
|
||||||
<Typography className={this.props.classes.title}>Changelog</Typography>
|
<Typography className={this.props.classes.title}>Changelog</Typography>
|
||||||
<div className={this.props.classes.releaseNotes} dangerouslySetInnerHTML={{ __html: releaseNotes }} />
|
<div className={this.props.classes.releaseNotes} dangerouslySetInnerHTML={{ __html: releaseNotes }} />
|
||||||
{this.renderDownloads()}
|
{this.renderDownloads()}
|
||||||
<Button
|
<Button className={this.props.classes.download} onClick={this.openHomePage}>
|
||||||
className={this.props.classes.download}
|
|
||||||
onClick={this.openHomePage}
|
|
||||||
>
|
|
||||||
Github Page
|
Github Page
|
||||||
</Button>
|
</Button>
|
||||||
<Button className={this.props.classes.closeButton} color="secondary" onClick={this.hideDetails}>Close</Button>
|
<Button className={this.props.classes.closeButton} color="secondary" onClick={this.hideDetails}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
@@ -208,18 +195,14 @@ class UpdateNotifier extends React.Component<Props, State> {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return latestUpdate.assets
|
return latestUpdate.assets.filter(this.assetForCurrentPlatform).map(asset => (
|
||||||
.filter(this.assetForCurrentPlatform)
|
<div>
|
||||||
.map(asset => (
|
<Button className={this.props.classes.download} onClick={() => this.openUrl(asset.browser_download_url)}>
|
||||||
<div>
|
<CloudDownload />
|
||||||
<Button
|
{asset.name}
|
||||||
className={this.props.classes.download}
|
</Button>
|
||||||
onClick={() => this.openUrl(asset.browser_download_url)}
|
</div>
|
||||||
>
|
))
|
||||||
<CloudDownload /> {asset.name}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
@@ -283,4 +266,9 @@ const mapDispatchToProps = (dispatch: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(UpdateNotifier))
|
export default withStyles(styles)(
|
||||||
|
connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(UpdateNotifier)
|
||||||
|
)
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ const styles = (theme: Theme) => ({
|
|||||||
color: orange[600],
|
color: orange[600],
|
||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
boxShadow: theme.shadows[2].split('),').map(s => `inset ${s}`).join('),'),
|
boxShadow: theme.shadows[2]
|
||||||
|
.split('),')
|
||||||
|
.map(s => `inset ${s}`)
|
||||||
|
.join('),'),
|
||||||
padding: '6px',
|
padding: '6px',
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
backgroundColor: '#eee',
|
backgroundColor: '#eee',
|
||||||
|
|||||||
@@ -37,9 +37,11 @@ class Copy extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const icon = !this.state.didCopy
|
const icon = !this.state.didCopy ? (
|
||||||
? <FileCopy fontSize="inherit" />
|
<FileCopy fontSize="inherit" />
|
||||||
: <Check fontSize="inherit" style={{ cursor: 'default' }} />
|
) : (
|
||||||
|
<Check fontSize="inherit" style={{ cursor: 'default' }} />
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
@@ -61,4 +63,7 @@ const mapDispatchToProps = (dispatch: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(undefined, mapDispatchToProps)(Copy)
|
export default connect(
|
||||||
|
undefined,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(Copy)
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ class DateFormatter extends React.Component<Props, {}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private localizedDate(locale: string) {
|
private localizedDate(locale: string) {
|
||||||
return moment(this.props.date).locale(locale).format('L LTS')
|
return moment(this.props.date)
|
||||||
|
.locale(locale)
|
||||||
|
.format('L LTS')
|
||||||
}
|
}
|
||||||
|
|
||||||
private unitForInterval(milliseconds: number) {
|
private unitForInterval(milliseconds: number) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export function isElementInViewport(el: any) {
|
|||||||
return (
|
return (
|
||||||
rect.top >= 0 &&
|
rect.top >= 0 &&
|
||||||
rect.left >= 0 &&
|
rect.left >= 0 &&
|
||||||
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
|
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) /*or $(window).height() */ &&
|
||||||
rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
|
rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,17 +12,9 @@ import './utils/tracking'
|
|||||||
import { themes } from './theme'
|
import { themes } from './theme'
|
||||||
|
|
||||||
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
|
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
|
||||||
const store = createStore(
|
const store = createStore(reducers, composeEnhancers(applyMiddleware(reduxThunk, batchDispatchMiddleware)))
|
||||||
reducers,
|
|
||||||
composeEnhancers(
|
|
||||||
applyMiddleware(
|
|
||||||
reduxThunk,
|
|
||||||
batchDispatchMiddleware
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
function ApplicationRenderer(props: {theme: 'light' | 'dark'}) {
|
function ApplicationRenderer(props: { theme: 'light' | 'dark' }) {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={props.theme === 'light' ? themes.lightTheme : themes.darkTheme}>
|
<ThemeProvider theme={props.theme === 'light' ? themes.lightTheme : themes.darkTheme}>
|
||||||
<App />
|
<App />
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export function clearLegacyConnectionOptions() {
|
|||||||
window.localStorage.setItem('connectionSettings', '')
|
window.localStorage.setItem('connectionSettings', '')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadLegacyConnectionOptions(): {[s: string]: ConnectionOptions} | {} {
|
export function loadLegacyConnectionOptions(): { [s: string]: ConnectionOptions } | {} {
|
||||||
const legacySettingsString = window.localStorage.getItem('connectionSettings')
|
const legacySettingsString = window.localStorage.getItem('connectionSettings')
|
||||||
if (!legacySettingsString) {
|
if (!legacySettingsString) {
|
||||||
return {}
|
return {}
|
||||||
@@ -30,7 +30,7 @@ export function loadLegacyConnectionOptions(): {[s: string]: ConnectionOptions}
|
|||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const protocolMap: {[s: string]: string} = {
|
const protocolMap: { [s: string]: string } = {
|
||||||
'tcp://': 'mqtt',
|
'tcp://': 'mqtt',
|
||||||
'ws://': 'ws',
|
'ws://': 'ws',
|
||||||
'mqtt://': 'mqtt',
|
'mqtt://': 'mqtt',
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export enum ActionTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SetConnecting {
|
export interface SetConnecting {
|
||||||
type: ActionTypes.CONNECTION_SET_CONNECTING,
|
type: ActionTypes.CONNECTION_SET_CONNECTING
|
||||||
connectionId: string
|
connectionId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ConnectionOptions } from '../model/ConnectionOptions'
|
|||||||
import { createReducer } from './lib'
|
import { createReducer } from './lib'
|
||||||
|
|
||||||
export interface ConnectionManagerState {
|
export interface ConnectionManagerState {
|
||||||
connections: {[s: string]: ConnectionOptions},
|
connections: { [s: string]: ConnectionOptions }
|
||||||
selected?: string
|
selected?: string
|
||||||
showAdvancedSettings: boolean
|
showAdvancedSettings: boolean
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,15 @@ const initialState: ConnectionManagerState = {
|
|||||||
showAdvancedSettings: false,
|
showAdvancedSettings: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Action = SetConnections | SelectConnection | UpdateConnection | AddConnection | DeleteConnection | ToggleAdvancedSettings | DeleteSubscription | AddSubscription
|
export type Action =
|
||||||
|
| SetConnections
|
||||||
|
| SelectConnection
|
||||||
|
| UpdateConnection
|
||||||
|
| AddConnection
|
||||||
|
| DeleteConnection
|
||||||
|
| ToggleAdvancedSettings
|
||||||
|
| DeleteSubscription
|
||||||
|
| AddSubscription
|
||||||
|
|
||||||
export enum ActionTypes {
|
export enum ActionTypes {
|
||||||
CONNECTION_MANAGER_SET_CONNECTIONS = 'CONNECTION_MANAGER_SET_CONNECTIONS',
|
CONNECTION_MANAGER_SET_CONNECTIONS = 'CONNECTION_MANAGER_SET_CONNECTIONS',
|
||||||
@@ -29,7 +37,7 @@ export enum ActionTypes {
|
|||||||
|
|
||||||
export interface SetConnections {
|
export interface SetConnections {
|
||||||
type: ActionTypes.CONNECTION_MANAGER_SET_CONNECTIONS
|
type: ActionTypes.CONNECTION_MANAGER_SET_CONNECTIONS
|
||||||
connections: {[s: string]: ConnectionOptions}
|
connections: { [s: string]: ConnectionOptions }
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SelectConnection {
|
export interface SelectConnection {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export enum ActionTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface GlobalAction extends Action {
|
export interface GlobalAction extends Action {
|
||||||
type: ActionTypes,
|
type: ActionTypes
|
||||||
showUpdateNotification?: boolean
|
showUpdateNotification?: boolean
|
||||||
showUpdateDetails?: boolean
|
showUpdateDetails?: boolean
|
||||||
error?: string
|
error?: string
|
||||||
@@ -39,33 +39,36 @@ const initialStateFactory = Record<GlobalStateInterface>({
|
|||||||
settingsVisible: false,
|
settingsVisible: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const globalState: Reducer<Record<GlobalStateInterface>, GlobalAction> = (state = initialStateFactory(), action): GlobalState => {
|
export const globalState: Reducer<Record<GlobalStateInterface>, GlobalAction> = (
|
||||||
|
state = initialStateFactory(),
|
||||||
|
action
|
||||||
|
): GlobalState => {
|
||||||
trackEvent(action.type)
|
trackEvent(action.type)
|
||||||
console.log(action.type)
|
console.log(action.type)
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ActionTypes.showUpdateNotification:
|
case ActionTypes.showUpdateNotification:
|
||||||
return state.set('showUpdateNotification', action.showUpdateNotification)
|
return state.set('showUpdateNotification', action.showUpdateNotification)
|
||||||
|
|
||||||
case ActionTypes.toggleSettingsVisibility:
|
case ActionTypes.toggleSettingsVisibility:
|
||||||
return state.set('settingsVisible', !state.get('settingsVisible'))
|
return state.set('settingsVisible', !state.get('settingsVisible'))
|
||||||
|
|
||||||
case ActionTypes.showError:
|
case ActionTypes.showError:
|
||||||
return state.set('error', action.error)
|
return state.set('error', action.error)
|
||||||
|
|
||||||
case ActionTypes.showNotification:
|
case ActionTypes.showNotification:
|
||||||
return state.set('notification', action.notification)
|
return state.set('notification', action.notification)
|
||||||
|
|
||||||
case ActionTypes.didLaunch:
|
case ActionTypes.didLaunch:
|
||||||
return state.set('launching', false)
|
return state.set('launching', false)
|
||||||
|
|
||||||
case ActionTypes.showUpdateDetails:
|
case ActionTypes.showUpdateDetails:
|
||||||
if (action.showUpdateDetails === undefined) {
|
if (action.showUpdateDetails === undefined) {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
return state.set('showUpdateDetails', action.showUpdateDetails)
|
||||||
|
|
||||||
|
default:
|
||||||
return state
|
return state
|
||||||
}
|
|
||||||
return state.set('showUpdateDetails', action.showUpdateDetails)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,15 +22,15 @@ export interface SettingsState {
|
|||||||
theme: 'light' | 'dark'
|
theme: 'light' | 'dark'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Actions = SetAutoExpandLimitAction
|
export type Actions = SetAutoExpandLimitAction &
|
||||||
& DidLoadSettingsAction
|
DidLoadSettingsAction &
|
||||||
& SetTopicOrderAction
|
SetTopicOrderAction &
|
||||||
& FilterTopicsAction
|
FilterTopicsAction &
|
||||||
& ToggleHighlightTopicUpdatesAction
|
ToggleHighlightTopicUpdatesAction &
|
||||||
& SetValueRendererDisplayModeAction
|
SetValueRendererDisplayModeAction &
|
||||||
& SetTheme
|
SetTheme &
|
||||||
& SetSelectTopicWithMouseOverAction
|
SetSelectTopicWithMouseOverAction &
|
||||||
& SetTimeLocale
|
SetTimeLocale
|
||||||
|
|
||||||
export enum ActionTypes {
|
export enum ActionTypes {
|
||||||
SETTINGS_SET_AUTO_EXPAND_LIMIT = 'SETTINGS_SET_AUTO_EXPAND_LIMIT',
|
SETTINGS_SET_AUTO_EXPAND_LIMIT = 'SETTINGS_SET_AUTO_EXPAND_LIMIT',
|
||||||
@@ -60,7 +60,9 @@ const setTheme = (theme: 'light' | 'dark') => (state: Record<SettingsState>) =>
|
|||||||
return state.set('theme', theme)
|
return state.set('theme', theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
const reducerActions: {[s: string]: (state: Record<SettingsState>, action: Actions) => Record<SettingsState>} = {
|
const reducerActions: {
|
||||||
|
[s: string]: (state: Record<SettingsState>, action: Actions) => Record<SettingsState>
|
||||||
|
} = {
|
||||||
SETTINGS_SET_AUTO_EXPAND_LIMIT: setAutoExpandLimit,
|
SETTINGS_SET_AUTO_EXPAND_LIMIT: setAutoExpandLimit,
|
||||||
SETTINGS_SET_TOPIC_ORDER: setTopicOrder,
|
SETTINGS_SET_TOPIC_ORDER: setTopicOrder,
|
||||||
SETTINGS_FILTER_TOPICS: filterTopics,
|
SETTINGS_FILTER_TOPICS: filterTopics,
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ export enum ActionTypes {
|
|||||||
|
|
||||||
export type SidebarState = Record<SidebarModel>
|
export type SidebarState = Record<SidebarModel>
|
||||||
|
|
||||||
const actions: {[s: string]: (state: SidebarState, action: Action) => SidebarState} = {
|
const actions: {
|
||||||
|
[s: string]: (state: SidebarState, action: Action) => SidebarState
|
||||||
|
} = {
|
||||||
SIDEBAR_SET_COMPARE_MESSAGE: setCompareMessage,
|
SIDEBAR_SET_COMPARE_MESSAGE: setCompareMessage,
|
||||||
SIDEBAR_RESET_STORE: resetStore,
|
SIDEBAR_RESET_STORE: resetStore,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,9 @@ const setPaused = (pause: boolean) => (state: TreeState, action: ShowTree): Tree
|
|||||||
return state.set('paused', pause)
|
return state.set('paused', pause)
|
||||||
}
|
}
|
||||||
|
|
||||||
const actions: {[s: string]: (state: TreeState, action: Action) => TreeState} = {
|
const actions: {
|
||||||
|
[s: string]: (state: TreeState, action: Action) => TreeState
|
||||||
|
} = {
|
||||||
TREE_SHOW_TREE: showTree,
|
TREE_SHOW_TREE: showTree,
|
||||||
TREE_SELECT_TOPIC: selectTopic,
|
TREE_SELECT_TOPIC: selectTopic,
|
||||||
TREE_PAUSE_UPDATES: setPaused(true),
|
TREE_PAUSE_UPDATES: setPaused(true),
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import { treeReducer, TreeState } from './Tree'
|
|||||||
export interface AppState {
|
export interface AppState {
|
||||||
globalState: GlobalState
|
globalState: GlobalState
|
||||||
tree: TreeState
|
tree: TreeState
|
||||||
settings: Record<SettingsState>,
|
settings: Record<SettingsState>
|
||||||
publish: PublishState
|
publish: PublishState
|
||||||
sidebar: SidebarState,
|
sidebar: SidebarState
|
||||||
connection: ConnectionState
|
connection: ConnectionState
|
||||||
connectionManager: ConnectionManagerState
|
connectionManager: ConnectionManagerState
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,11 @@ class RemoteStorage implements PersistentStorage {
|
|||||||
public store<Model>(identifier: StorageIdentifier<Model>, data: Model): Promise<void> {
|
public store<Model>(identifier: StorageIdentifier<Model>, data: Model): Promise<void> {
|
||||||
const transactionId = v4()
|
const transactionId = v4()
|
||||||
const expectation = this.expectAck(transactionId)
|
const expectation = this.expectAck(transactionId)
|
||||||
rendererEvents.emit(storageStoreEvent, { data, transactionId, store: identifier.id })
|
rendererEvents.emit(storageStoreEvent, {
|
||||||
|
data,
|
||||||
|
transactionId,
|
||||||
|
store: identifier.id,
|
||||||
|
})
|
||||||
return expectation
|
return expectation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export const selectTextWithCtrlA = (options?: {targetSelector: string}) => (e: React.KeyboardEvent<HTMLDivElement>) => {
|
export const selectTextWithCtrlA = (options?: { targetSelector: string }) => (
|
||||||
|
e: React.KeyboardEvent<HTMLDivElement>
|
||||||
|
) => {
|
||||||
const isCtrlA = (e.metaKey || e.ctrlKey) && e.key === 'a'
|
const isCtrlA = (e.metaKey || e.ctrlKey) && e.key === 'a'
|
||||||
|
|
||||||
if (isCtrlA && window.getSelection) {
|
if (isCtrlA && window.getSelection) {
|
||||||
@@ -7,8 +9,8 @@ export const selectTextWithCtrlA = (options?: {targetSelector: string}) => (e: R
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
const selection = window.getSelection()
|
const selection = window.getSelection()
|
||||||
const range = document.createRange()
|
const range = document.createRange()
|
||||||
const eventTarget = (e.target as HTMLElement)
|
const eventTarget = e.target as HTMLElement
|
||||||
const target: HTMLElement | null = (options) ? eventTarget.querySelector(options.targetSelector) : eventTarget
|
const target: HTMLElement | null = options ? eventTarget.querySelector(options.targetSelector) : eventTarget
|
||||||
|
|
||||||
if (!target) {
|
if (!target) {
|
||||||
console.error('Could not find matching target for Ctrl+A Event')
|
console.error('Could not find matching target for Ctrl+A Event')
|
||||||
|
|||||||
@@ -4,9 +4,18 @@ import { electronRendererTelementry } from 'electron-telemetry'
|
|||||||
function trackProcessStatistics() {
|
function trackProcessStatistics() {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
try {
|
try {
|
||||||
electronRendererTelementry.trackCustomEvent({ name: 'heapStatistics', payload: process.getHeapStatistics() })
|
electronRendererTelementry.trackCustomEvent({
|
||||||
electronRendererTelementry.trackCustomEvent({ name: 'cpuUsage', payload: process.getCPUUsage() })
|
name: 'heapStatistics',
|
||||||
electronRendererTelementry.trackCustomEvent({ name: 'runningSince', payload: performance.now() })
|
payload: process.getHeapStatistics(),
|
||||||
|
})
|
||||||
|
electronRendererTelementry.trackCustomEvent({
|
||||||
|
name: 'cpuUsage',
|
||||||
|
payload: process.getCPUUsage(),
|
||||||
|
})
|
||||||
|
electronRendererTelementry.trackCustomEvent({
|
||||||
|
name: 'runningSince',
|
||||||
|
payload: performance.now(),
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,5 +34,11 @@
|
|||||||
],
|
],
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"instrument": true
|
"instrument": true
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"fs-extra": "^8.0.1",
|
||||||
|
"js-base64": "^2.5.1",
|
||||||
|
"lowdb": "^1.0.0",
|
||||||
|
"mqtt": "^3.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import {
|
|||||||
makeStorageResponseEvent,
|
makeStorageResponseEvent,
|
||||||
storageClearEvent,
|
storageClearEvent,
|
||||||
storageLoadEvent,
|
storageLoadEvent,
|
||||||
storageStoreEvent
|
storageStoreEvent,
|
||||||
} from '../../events/StorageEvents'
|
} from '../../events/StorageEvents'
|
||||||
|
|
||||||
export default class ConfigStorage {
|
export default class ConfigStorage {
|
||||||
private file: string
|
private file: string
|
||||||
@@ -32,31 +32,43 @@ export default class ConfigStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async init() {
|
public async init() {
|
||||||
backendEvents.subscribe(storageStoreEvent, async (event) => {
|
backendEvents.subscribe(storageStoreEvent, async event => {
|
||||||
const ack = makeStorageAcknowledgementEvent(event.transactionId)
|
const ack = makeStorageAcknowledgementEvent(event.transactionId)
|
||||||
try {
|
try {
|
||||||
const db = await this.getDb()
|
const db = await this.getDb()
|
||||||
await db.set(event.store, event.data).write()
|
await db.set(event.store, event.data).write()
|
||||||
backendEvents.emit(ack, undefined)
|
backendEvents.emit(ack, undefined)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
backendEvents.emit(ack, { error, transactionId: event.transactionId, store: event.store })
|
backendEvents.emit(ack, {
|
||||||
|
error,
|
||||||
|
transactionId: event.transactionId,
|
||||||
|
store: event.store,
|
||||||
|
})
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
backendEvents.subscribe(storageLoadEvent, async (event) => {
|
backendEvents.subscribe(storageLoadEvent, async event => {
|
||||||
const responseEvent = makeStorageResponseEvent(event.transactionId)
|
const responseEvent = makeStorageResponseEvent(event.transactionId)
|
||||||
try {
|
try {
|
||||||
const db = await this.getDb()
|
const db = await this.getDb()
|
||||||
const data = await db.get(event.store).value()
|
const data = await db.get(event.store).value()
|
||||||
backendEvents.emit(responseEvent, { data, transactionId: event.transactionId, store: event.store })
|
backendEvents.emit(responseEvent, {
|
||||||
|
data,
|
||||||
|
transactionId: event.transactionId,
|
||||||
|
store: event.store,
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
backendEvents.emit(responseEvent, { error, transactionId: event.transactionId, store: event.store })
|
backendEvents.emit(responseEvent, {
|
||||||
|
error,
|
||||||
|
transactionId: event.transactionId,
|
||||||
|
store: event.store,
|
||||||
|
})
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
backendEvents.subscribe(storageClearEvent, async (event) => {
|
backendEvents.subscribe(storageClearEvent, async event => {
|
||||||
try {
|
try {
|
||||||
const db = await this.getDb()
|
const db = await this.getDb()
|
||||||
const keys = await db.keys().value()
|
const keys = await db.keys().value()
|
||||||
@@ -65,7 +77,10 @@ export default class ConfigStorage {
|
|||||||
}
|
}
|
||||||
backendEvents.emit(makeStorageAcknowledgementEvent(event.transactionId), undefined)
|
backendEvents.emit(makeStorageAcknowledgementEvent(event.transactionId), undefined)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
backendEvents.emit(makeStorageAcknowledgementEvent(event.transactionId), { error, transactionId: event.transactionId })
|
backendEvents.emit(makeStorageAcknowledgementEvent(event.transactionId), {
|
||||||
|
error,
|
||||||
|
transactionId: event.transactionId,
|
||||||
|
})
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export class MqttSource implements DataSource<MqttOptions> {
|
|||||||
|
|
||||||
client.on('connect', () => {
|
client.on('connect', () => {
|
||||||
this.stateMachine.setConnected(true)
|
this.stateMachine.setConnected(true)
|
||||||
options.subscriptions.forEach((subscription) => {
|
options.subscriptions.forEach(subscription => {
|
||||||
client.subscribe(subscription, (err: Error) => {
|
client.subscribe(subscription, (err: Error) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.stateMachine.setError(err)
|
this.stateMachine.setError(err)
|
||||||
@@ -86,10 +86,10 @@ export class MqttSource implements DataSource<MqttOptions> {
|
|||||||
|
|
||||||
public publish(msg: MqttMessage) {
|
public publish(msg: MqttMessage) {
|
||||||
if (this.client) {
|
if (this.client) {
|
||||||
this.client.publish(
|
this.client.publish(msg.topic, msg.payload ? Base64Message.toUnicodeString(msg.payload) : '', {
|
||||||
msg.topic,
|
qos: msg.qos,
|
||||||
msg.payload ? Base64Message.toUnicodeString(msg.payload) : '',
|
retain: msg.retain,
|
||||||
{ qos: msg.qos, retain: msg.retain })
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,16 +59,20 @@ interface JsonAstArray {
|
|||||||
function jsonToPropertyPaths(ast: JsonAst, previousPath: Array<string> = []): Array<JsonPropertyLocation> {
|
function jsonToPropertyPaths(ast: JsonAst, previousPath: Array<string> = []): Array<JsonPropertyLocation> {
|
||||||
let children: Array<Array<JsonPropertyLocation>> = []
|
let children: Array<Array<JsonPropertyLocation>> = []
|
||||||
if (ast.type === 'Literal') {
|
if (ast.type === 'Literal') {
|
||||||
return [{
|
return [
|
||||||
value: ast.value,
|
{
|
||||||
path: previousPath.join('.'),
|
value: ast.value,
|
||||||
line: ast.loc.start.line,
|
path: previousPath.join('.'),
|
||||||
column: ast.loc.start.column,
|
line: ast.loc.start.line,
|
||||||
}]
|
column: ast.loc.start.column,
|
||||||
|
},
|
||||||
|
]
|
||||||
} else if (ast.type === 'Array') {
|
} else if (ast.type === 'Array') {
|
||||||
children = ast.children.map((value, idx) => jsonToPropertyPaths(value, previousPath.slice().concat([String(idx)])))
|
children = ast.children.map((value, idx) => jsonToPropertyPaths(value, previousPath.slice().concat([String(idx)])))
|
||||||
} else if (ast.type === 'Object') {
|
} else if (ast.type === 'Object') {
|
||||||
children = ast.children.map(property => jsonToPropertyPaths(property.value, previousPath.slice().concat([property.key.value])))
|
children = ast.children.map(property =>
|
||||||
|
jsonToPropertyPaths(property.value, previousPath.slice().concat([property.key.value]))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return children.reduce((a, b) => a.concat(b), [])
|
return children.reduce((a, b) => a.concat(b), [])
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export class RingBuffer<T extends Lengthwise> {
|
|||||||
if (remainingSize < 0) {
|
if (remainingSize < 0) {
|
||||||
this.freeSomeSpace(Math.abs(remainingSize))
|
this.freeSomeSpace(Math.abs(remainingSize))
|
||||||
}
|
}
|
||||||
while ((this.end - this.start) >= this.maxItems) {
|
while (this.end - this.start >= this.maxItems) {
|
||||||
this.dropFirst()
|
this.dropFirst()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
import { ChangeBuffer } from './ChangeBuffer'
|
import { ChangeBuffer } from './ChangeBuffer'
|
||||||
import { Destroyable } from './Destroyable'
|
import { Destroyable } from './Destroyable'
|
||||||
import {
|
import { EventBusInterface, EventDispatcher, makeConnectionMessageEvent, MqttMessage } from '../../../events'
|
||||||
EventBusInterface,
|
|
||||||
EventDispatcher,
|
|
||||||
makeConnectionMessageEvent,
|
|
||||||
MqttMessage
|
|
||||||
} from '../../../events'
|
|
||||||
import { TreeNode } from './'
|
import { TreeNode } from './'
|
||||||
import { TreeNodeFactory } from './TreeNodeFactory'
|
import { TreeNodeFactory } from './TreeNodeFactory'
|
||||||
|
|
||||||
@@ -35,7 +30,11 @@ export class Tree<ViewModel extends Destroyable> extends TreeNode<ViewModel> {
|
|||||||
this.didReceive.removeAllListeners()
|
this.didReceive.removeAllListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateWithConnection(emitter: EventBusInterface, connectionId: string, nodeFilter?: (node: TreeNode<ViewModel>) => boolean) {
|
public updateWithConnection(
|
||||||
|
emitter: EventBusInterface,
|
||||||
|
connectionId: string,
|
||||||
|
nodeFilter?: (node: TreeNode<ViewModel>) => boolean
|
||||||
|
) {
|
||||||
this.updateSource = emitter
|
this.updateSource = emitter
|
||||||
this.connectionId = connectionId
|
this.connectionId = connectionId
|
||||||
this.nodeFilter = nodeFilter
|
this.nodeFilter = nodeFilter
|
||||||
@@ -49,7 +48,7 @@ export class Tree<ViewModel extends Destroyable> extends TreeNode<ViewModel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public applyUnmergedChanges() {
|
public applyUnmergedChanges() {
|
||||||
this.unmergedMessages.popAll().forEach((msg) => {
|
this.unmergedMessages.popAll().forEach(msg => {
|
||||||
const edges = msg.topic.split('/')
|
const edges = msg.topic.split('/')
|
||||||
const node = TreeNodeFactory.fromEdgesAndValue<ViewModel>(edges, msg.payload)
|
const node = TreeNodeFactory.fromEdgesAndValue<ViewModel>(edges, msg.payload)
|
||||||
node.mqttMessage = msg
|
node.mqttMessage = msg
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export class TreeNode<ViewModel extends Destroyable> {
|
|||||||
public mqttMessage?: MqttMessage
|
public mqttMessage?: MqttMessage
|
||||||
public messageHistory: MessageHistory = new RingBuffer<Message>(20000, 100)
|
public messageHistory: MessageHistory = new RingBuffer<Message>(20000, 100)
|
||||||
public viewModel?: ViewModel
|
public viewModel?: ViewModel
|
||||||
public edges: {[s: string]: Edge<ViewModel>} = {}
|
public edges: { [s: string]: Edge<ViewModel> } = {}
|
||||||
public edgeArray: Array<Edge<ViewModel>> = []
|
public edgeArray: Array<Edge<ViewModel>> = []
|
||||||
public collapsed = false
|
public collapsed = false
|
||||||
public messages: number = 0
|
public messages: number = 0
|
||||||
@@ -52,7 +52,7 @@ export class TreeNode<ViewModel extends Destroyable> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isTopicEmptyLeaf() {
|
private isTopicEmptyLeaf() {
|
||||||
const hasNoMessage = (!this.message || !this.message.value || this.message.value.length === 0)
|
const hasNoMessage = !this.message || !this.message.value || this.message.value.length === 0
|
||||||
return hasNoMessage && this.isLeaf()
|
return hasNoMessage && this.isLeaf()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ export class TreeNode<ViewModel extends Destroyable> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public hash(): string {
|
public hash(): string {
|
||||||
return `N${(this.sourceEdge ? this.sourceEdge.hash() : '')}`
|
return `N${this.sourceEdge ? this.sourceEdge.hash() : ''}`
|
||||||
}
|
}
|
||||||
|
|
||||||
public firstNode(): TreeNode<ViewModel> {
|
public firstNode(): TreeNode<ViewModel> {
|
||||||
@@ -145,7 +145,7 @@ export class TreeNode<ViewModel extends Destroyable> {
|
|||||||
public path(): string {
|
public path(): string {
|
||||||
if (!this.cachedPath) {
|
if (!this.cachedPath) {
|
||||||
return this.branch()
|
return this.branch()
|
||||||
.map(node => (node.sourceEdge && node.sourceEdge.name))
|
.map(node => node.sourceEdge && node.sourceEdge.name)
|
||||||
.filter(name => name !== undefined)
|
.filter(name => name !== undefined)
|
||||||
.join('/')
|
.join('/')
|
||||||
}
|
}
|
||||||
@@ -199,9 +199,8 @@ export class TreeNode<ViewModel extends Destroyable> {
|
|||||||
|
|
||||||
public leafMessageCount(): number {
|
public leafMessageCount(): number {
|
||||||
if (this.cachedLeafMessageCount === undefined) {
|
if (this.cachedLeafMessageCount === undefined) {
|
||||||
this.cachedLeafMessageCount = this.edgeArray
|
this.cachedLeafMessageCount =
|
||||||
.map(edge => edge.target.leafMessageCount())
|
this.edgeArray.map(edge => edge.target.leafMessageCount()).reduce((a, b) => a + b, 0) + this.messages
|
||||||
.reduce((a, b) => a + b, 0) + this.messages
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.cachedLeafMessageCount as number
|
return this.cachedLeafMessageCount as number
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import { Edge, Tree, TreeNode } from './'
|
|||||||
|
|
||||||
export abstract class TreeNodeFactory {
|
export abstract class TreeNodeFactory {
|
||||||
private static messageCounter = 0
|
private static messageCounter = 0
|
||||||
public static insertNodeAtPosition<ViewModel extends Destroyable>(edgeNames: Array<string>, node: TreeNode<ViewModel>) {
|
public static insertNodeAtPosition<ViewModel extends Destroyable>(
|
||||||
|
edgeNames: Array<string>,
|
||||||
|
node: TreeNode<ViewModel>
|
||||||
|
) {
|
||||||
let currentNode: TreeNode<ViewModel> = new Tree()
|
let currentNode: TreeNode<ViewModel> = new Tree()
|
||||||
let edge
|
let edge
|
||||||
for (const edgeName of edgeNames) {
|
for (const edgeName of edgeNames) {
|
||||||
@@ -17,7 +20,10 @@ export abstract class TreeNodeFactory {
|
|||||||
node.sourceEdge!.target = node
|
node.sourceEdge!.target = node
|
||||||
}
|
}
|
||||||
|
|
||||||
public static fromEdgesAndValue<ViewModel extends Destroyable>(edgeNames: Array<string>, value?: Base64Message | null): TreeNode<ViewModel> {
|
public static fromEdgesAndValue<ViewModel extends Destroyable>(
|
||||||
|
edgeNames: Array<string>,
|
||||||
|
value?: Base64Message | null
|
||||||
|
): TreeNode<ViewModel> {
|
||||||
const node = new TreeNode<ViewModel>()
|
const node = new TreeNode<ViewModel>()
|
||||||
node.setMessage({
|
node.setMessage({
|
||||||
value: value || undefined,
|
value: value || undefined,
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ describe('Edge', () => {
|
|||||||
const topics2 = 'foo/foo/baz'.split('/')
|
const topics2 = 'foo/foo/baz'.split('/')
|
||||||
const bazEdge2 = TreeNodeFactory.fromEdgesAndValue(topics2, undefined).sourceEdge
|
const bazEdge2 = TreeNodeFactory.fromEdgesAndValue(topics2, undefined).sourceEdge
|
||||||
|
|
||||||
if (!bazEdge1 || !bazEdge2) {
|
if (!bazEdge1 || !bazEdge2) {
|
||||||
throw Error('should not happen')
|
throw Error('should not happen')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ describe('EventDispatcher', async () => {
|
|||||||
this.timeout(300)
|
this.timeout(300)
|
||||||
|
|
||||||
setTimeout(() => dispatcher.dispatch('hello'), 5)
|
setTimeout(() => dispatcher.dispatch('hello'), 5)
|
||||||
const response = await new Promise((resolve) => {
|
const response = await new Promise(resolve => {
|
||||||
dispatcher.subscribe((msg) => {
|
dispatcher.subscribe(msg => {
|
||||||
resolve(msg)
|
resolve(msg)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import 'mocha'
|
import 'mocha'
|
||||||
|
|
||||||
import { TreeNodeFactory } from '../'
|
import { TreeNodeFactory } from '../'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { Base64Message } from '../Base64Message';
|
import { Base64Message } from '../Base64Message'
|
||||||
|
|
||||||
describe('TreeNode', () => {
|
describe('TreeNode', () => {
|
||||||
const number3 = Base64Message.fromString("3")
|
const number3 = Base64Message.fromString('3')
|
||||||
const number5 = Base64Message.fromString("5")
|
const number5 = Base64Message.fromString('5')
|
||||||
it('firstNode should retrieve first node', () => {
|
it('firstNode should retrieve first node', () => {
|
||||||
const topics = 'foo/bar'.split('/')
|
const topics = 'foo/bar'.split('/')
|
||||||
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, undefined)
|
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, undefined)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'mocha'
|
|||||||
|
|
||||||
import { TreeNodeFactory } from '../'
|
import { TreeNodeFactory } from '../'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { Base64Message } from '../Base64Message';
|
import { Base64Message } from '../Base64Message'
|
||||||
|
|
||||||
describe('TreeNodeFactory', () => {
|
describe('TreeNodeFactory', () => {
|
||||||
it('root node must not have a sourceEdge', () => {
|
it('root node must not have a sourceEdge', () => {
|
||||||
@@ -45,7 +45,7 @@ describe('TreeNodeFactory', () => {
|
|||||||
expect(node.sourceEdge.name).to.eq('baz')
|
expect(node.sourceEdge.name).to.eq('baz')
|
||||||
|
|
||||||
const barNode = node.sourceEdge.source
|
const barNode = node.sourceEdge.source
|
||||||
if (!barNode || !barNode.sourceEdge) {
|
if (!barNode || !barNode.sourceEdge) {
|
||||||
expect.fail('should not fail')
|
expect.fail('should not fail')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
} from '../../events'
|
} from '../../events'
|
||||||
|
|
||||||
export class ConnectionManager {
|
export class ConnectionManager {
|
||||||
private connections: {[s: string]: DataSource<any>} = {}
|
private connections: { [s: string]: DataSource<any> } = {}
|
||||||
|
|
||||||
private handleConnectionRequest = (event: AddMqttConnection) => {
|
private handleConnectionRequest = (event: AddMqttConnection) => {
|
||||||
const connectionId = event.id
|
const connectionId = event.id
|
||||||
@@ -27,7 +27,7 @@ export class ConnectionManager {
|
|||||||
this.connections[connectionId] = connection
|
this.connections[connectionId] = connection
|
||||||
|
|
||||||
const connectionStateEvent = makeConnectionStateEvent(connectionId)
|
const connectionStateEvent = makeConnectionStateEvent(connectionId)
|
||||||
connection.stateMachine.onUpdate.subscribe((state) => {
|
connection.stateMachine.onUpdate.subscribe(state => {
|
||||||
backendEvents.emit(connectionStateEvent, state)
|
backendEvents.emit(connectionStateEvent, state)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -46,7 +46,12 @@ export class ConnectionManager {
|
|||||||
buffer = buffer.slice(0, 20000)
|
buffer = buffer.slice(0, 20000)
|
||||||
}
|
}
|
||||||
|
|
||||||
backendEvents.emit(messageEvent, { topic, payload: Base64Message.fromBuffer(buffer), qos: packet.qos, retain: packet.retain })
|
backendEvents.emit(messageEvent, {
|
||||||
|
topic,
|
||||||
|
payload: Base64Message.fromBuffer(buffer),
|
||||||
|
qos: packet.qos,
|
||||||
|
retain: packet.retain,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +73,6 @@ export class ConnectionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public closeAllConnections() {
|
public closeAllConnections() {
|
||||||
Object.keys(this.connections)
|
Object.keys(this.connections).forEach(conenctionId => this.removeConnection(conenctionId))
|
||||||
.forEach(conenctionId => this.removeConnection(conenctionId))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,16 +26,11 @@ describe('access JSON values via dot property paths', () => {
|
|||||||
expect(result[0].path).to.eq('foo.bar')
|
expect(result[0].path).to.eq('foo.bar')
|
||||||
expect(result[0].line).to.eq(3)
|
expect(result[0].line).to.eq(3)
|
||||||
expect(dotProp.get(data, result[0].path)).to.eq(4)
|
expect(dotProp.get(data, result[0].path)).to.eq(4)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('array path', () => {
|
it('array path', () => {
|
||||||
const data = {
|
const data = {
|
||||||
foo: [
|
foo: [1, 2, 3],
|
||||||
1,
|
|
||||||
2,
|
|
||||||
3,
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = parseJson(JSON.stringify(data, undefined, 2))
|
const result = parseJson(JSON.stringify(data, undefined, 2))
|
||||||
@@ -48,7 +43,7 @@ describe('access JSON values via dot property paths', () => {
|
|||||||
|
|
||||||
it('should fail parsing invalid json', () => {
|
it('should fail parsing invalid json', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
const result = parseJson("BLE2MQTT-8C48")
|
const result = parseJson('BLE2MQTT-8C48')
|
||||||
}).to.throw()
|
}).to.throw()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
538
backend/yarn.lock
Normal file
538
backend/yarn.lock
Normal file
@@ -0,0 +1,538 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
async-limiter@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
|
||||||
|
integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==
|
||||||
|
|
||||||
|
balanced-match@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||||
|
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
||||||
|
|
||||||
|
base64-js@^1.3.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3"
|
||||||
|
integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==
|
||||||
|
|
||||||
|
bl@^1.2.2:
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
|
||||||
|
integrity sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==
|
||||||
|
dependencies:
|
||||||
|
readable-stream "^2.3.5"
|
||||||
|
safe-buffer "^5.1.1"
|
||||||
|
|
||||||
|
brace-expansion@^1.1.7:
|
||||||
|
version "1.1.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||||
|
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
|
||||||
|
dependencies:
|
||||||
|
balanced-match "^1.0.0"
|
||||||
|
concat-map "0.0.1"
|
||||||
|
|
||||||
|
buffer-from@^1.0.0:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||||
|
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
||||||
|
|
||||||
|
callback-stream@^1.0.2:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/callback-stream/-/callback-stream-1.1.0.tgz#4701a51266f06e06eaa71fc17233822d875f4908"
|
||||||
|
integrity sha1-RwGlEmbwbgbqpx/BcjOCLYdfSQg=
|
||||||
|
dependencies:
|
||||||
|
inherits "^2.0.1"
|
||||||
|
readable-stream "> 1.0.0 < 3.0.0"
|
||||||
|
|
||||||
|
commist@^1.0.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/commist/-/commist-1.1.0.tgz#17811ec6978f6c15ee4de80c45c9beb77cee35d5"
|
||||||
|
integrity sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==
|
||||||
|
dependencies:
|
||||||
|
leven "^2.1.0"
|
||||||
|
minimist "^1.1.0"
|
||||||
|
|
||||||
|
concat-map@0.0.1:
|
||||||
|
version "0.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||||
|
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
||||||
|
|
||||||
|
concat-stream@^1.6.2:
|
||||||
|
version "1.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
|
||||||
|
integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
|
||||||
|
dependencies:
|
||||||
|
buffer-from "^1.0.0"
|
||||||
|
inherits "^2.0.3"
|
||||||
|
readable-stream "^2.2.2"
|
||||||
|
typedarray "^0.0.6"
|
||||||
|
|
||||||
|
core-util-is@~1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
|
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
||||||
|
|
||||||
|
d@1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
|
||||||
|
integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==
|
||||||
|
dependencies:
|
||||||
|
es5-ext "^0.10.50"
|
||||||
|
type "^1.0.1"
|
||||||
|
|
||||||
|
duplexify@^3.5.1, duplexify@^3.6.0:
|
||||||
|
version "3.7.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
|
||||||
|
integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==
|
||||||
|
dependencies:
|
||||||
|
end-of-stream "^1.0.0"
|
||||||
|
inherits "^2.0.1"
|
||||||
|
readable-stream "^2.0.0"
|
||||||
|
stream-shift "^1.0.0"
|
||||||
|
|
||||||
|
end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1:
|
||||||
|
version "1.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
|
||||||
|
integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==
|
||||||
|
dependencies:
|
||||||
|
once "^1.4.0"
|
||||||
|
|
||||||
|
es5-ext@^0.10.35, es5-ext@^0.10.50, es5-ext@~0.10.14:
|
||||||
|
version "0.10.50"
|
||||||
|
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.50.tgz#6d0e23a0abdb27018e5ac4fd09b412bc5517a778"
|
||||||
|
integrity sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==
|
||||||
|
dependencies:
|
||||||
|
es6-iterator "~2.0.3"
|
||||||
|
es6-symbol "~3.1.1"
|
||||||
|
next-tick "^1.0.0"
|
||||||
|
|
||||||
|
es6-iterator@~2.0.1, es6-iterator@~2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
|
||||||
|
integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c=
|
||||||
|
dependencies:
|
||||||
|
d "1"
|
||||||
|
es5-ext "^0.10.35"
|
||||||
|
es6-symbol "^3.1.1"
|
||||||
|
|
||||||
|
es6-map@^0.1.5:
|
||||||
|
version "0.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0"
|
||||||
|
integrity sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=
|
||||||
|
dependencies:
|
||||||
|
d "1"
|
||||||
|
es5-ext "~0.10.14"
|
||||||
|
es6-iterator "~2.0.1"
|
||||||
|
es6-set "~0.1.5"
|
||||||
|
es6-symbol "~3.1.1"
|
||||||
|
event-emitter "~0.3.5"
|
||||||
|
|
||||||
|
es6-set@~0.1.5:
|
||||||
|
version "0.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1"
|
||||||
|
integrity sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=
|
||||||
|
dependencies:
|
||||||
|
d "1"
|
||||||
|
es5-ext "~0.10.14"
|
||||||
|
es6-iterator "~2.0.1"
|
||||||
|
es6-symbol "3.1.1"
|
||||||
|
event-emitter "~0.3.5"
|
||||||
|
|
||||||
|
es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77"
|
||||||
|
integrity sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=
|
||||||
|
dependencies:
|
||||||
|
d "1"
|
||||||
|
es5-ext "~0.10.14"
|
||||||
|
|
||||||
|
event-emitter@~0.3.5:
|
||||||
|
version "0.3.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
|
||||||
|
integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=
|
||||||
|
dependencies:
|
||||||
|
d "1"
|
||||||
|
es5-ext "~0.10.14"
|
||||||
|
|
||||||
|
extend@^3.0.0:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||||
|
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
||||||
|
|
||||||
|
fs.realpath@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||||
|
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
||||||
|
|
||||||
|
glob-parent@^3.1.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
|
||||||
|
integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=
|
||||||
|
dependencies:
|
||||||
|
is-glob "^3.1.0"
|
||||||
|
path-dirname "^1.0.0"
|
||||||
|
|
||||||
|
glob-stream@^6.1.0:
|
||||||
|
version "6.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4"
|
||||||
|
integrity sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=
|
||||||
|
dependencies:
|
||||||
|
extend "^3.0.0"
|
||||||
|
glob "^7.1.1"
|
||||||
|
glob-parent "^3.1.0"
|
||||||
|
is-negated-glob "^1.0.0"
|
||||||
|
ordered-read-streams "^1.0.0"
|
||||||
|
pumpify "^1.3.5"
|
||||||
|
readable-stream "^2.1.5"
|
||||||
|
remove-trailing-separator "^1.0.1"
|
||||||
|
to-absolute-glob "^2.0.0"
|
||||||
|
unique-stream "^2.0.2"
|
||||||
|
|
||||||
|
glob@^7.1.1:
|
||||||
|
version "7.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
|
||||||
|
integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
|
||||||
|
dependencies:
|
||||||
|
fs.realpath "^1.0.0"
|
||||||
|
inflight "^1.0.4"
|
||||||
|
inherits "2"
|
||||||
|
minimatch "^3.0.4"
|
||||||
|
once "^1.3.0"
|
||||||
|
path-is-absolute "^1.0.0"
|
||||||
|
|
||||||
|
help-me@^1.0.1:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/help-me/-/help-me-1.1.0.tgz#8f2d508d0600b4a456da2f086556e7e5c056a3c6"
|
||||||
|
integrity sha1-jy1QjQYAtKRW2i8IZVbn5cBWo8Y=
|
||||||
|
dependencies:
|
||||||
|
callback-stream "^1.0.2"
|
||||||
|
glob-stream "^6.1.0"
|
||||||
|
through2 "^2.0.1"
|
||||||
|
xtend "^4.0.0"
|
||||||
|
|
||||||
|
inflight@^1.0.4:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||||
|
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
|
||||||
|
dependencies:
|
||||||
|
once "^1.3.0"
|
||||||
|
wrappy "1"
|
||||||
|
|
||||||
|
inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||||
|
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||||
|
|
||||||
|
is-absolute@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576"
|
||||||
|
integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==
|
||||||
|
dependencies:
|
||||||
|
is-relative "^1.0.0"
|
||||||
|
is-windows "^1.0.1"
|
||||||
|
|
||||||
|
is-extglob@^2.1.0:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||||
|
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
|
||||||
|
|
||||||
|
is-glob@^3.1.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
|
||||||
|
integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=
|
||||||
|
dependencies:
|
||||||
|
is-extglob "^2.1.0"
|
||||||
|
|
||||||
|
is-negated-glob@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2"
|
||||||
|
integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=
|
||||||
|
|
||||||
|
is-relative@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d"
|
||||||
|
integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==
|
||||||
|
dependencies:
|
||||||
|
is-unc-path "^1.0.0"
|
||||||
|
|
||||||
|
is-unc-path@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d"
|
||||||
|
integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==
|
||||||
|
dependencies:
|
||||||
|
unc-path-regex "^0.1.2"
|
||||||
|
|
||||||
|
is-windows@^1.0.1:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
|
||||||
|
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
|
||||||
|
|
||||||
|
isarray@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||||
|
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
||||||
|
|
||||||
|
json-stable-stringify-without-jsonify@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
||||||
|
integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
|
||||||
|
|
||||||
|
leven@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580"
|
||||||
|
integrity sha1-wuep93IJTe6dNCAq6KzORoeHVYA=
|
||||||
|
|
||||||
|
minimatch@^3.0.4:
|
||||||
|
version "3.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||||
|
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
|
||||||
|
dependencies:
|
||||||
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
|
minimist@^1.1.0, minimist@^1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||||
|
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
|
||||||
|
|
||||||
|
mqtt-packet@^6.0.0:
|
||||||
|
version "6.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/mqtt-packet/-/mqtt-packet-6.1.2.tgz#239493d185c9209011a5a22ebce30e098c731f81"
|
||||||
|
integrity sha512-yVG5PoS3wJ8TLzfS8pQMsDVLAf/EipnBAG5XQE9X/9L0EMxuduI9J2WnlRvJT497K1CUT4VJWjoP08+CKiKt1Q==
|
||||||
|
dependencies:
|
||||||
|
bl "^1.2.2"
|
||||||
|
inherits "^2.0.3"
|
||||||
|
process-nextick-args "^2.0.0"
|
||||||
|
safe-buffer "^5.1.2"
|
||||||
|
|
||||||
|
mqtt@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mqtt/-/mqtt-3.0.0.tgz#7961e5f61efba3eec37d5aa9c4cbcdeb6f841380"
|
||||||
|
integrity sha512-0nKV6MAc1ibKZwaZQUTb3iIdT4NVpj541BsYrqrGBcQdQ7Jd0MnZD1/6/nj1UFdGTboK9ZEUXvkCu2nPCugHFA==
|
||||||
|
dependencies:
|
||||||
|
base64-js "^1.3.0"
|
||||||
|
commist "^1.0.0"
|
||||||
|
concat-stream "^1.6.2"
|
||||||
|
end-of-stream "^1.4.1"
|
||||||
|
es6-map "^0.1.5"
|
||||||
|
help-me "^1.0.1"
|
||||||
|
inherits "^2.0.3"
|
||||||
|
minimist "^1.2.0"
|
||||||
|
mqtt-packet "^6.0.0"
|
||||||
|
pump "^3.0.0"
|
||||||
|
readable-stream "^2.3.6"
|
||||||
|
reinterval "^1.1.0"
|
||||||
|
split2 "^3.1.0"
|
||||||
|
websocket-stream "^5.1.2"
|
||||||
|
xtend "^4.0.1"
|
||||||
|
|
||||||
|
next-tick@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
|
||||||
|
integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
|
||||||
|
|
||||||
|
once@^1.3.0, once@^1.3.1, once@^1.4.0:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||||
|
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
||||||
|
dependencies:
|
||||||
|
wrappy "1"
|
||||||
|
|
||||||
|
ordered-read-streams@^1.0.0:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e"
|
||||||
|
integrity sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=
|
||||||
|
dependencies:
|
||||||
|
readable-stream "^2.0.1"
|
||||||
|
|
||||||
|
path-dirname@^1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
|
||||||
|
integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=
|
||||||
|
|
||||||
|
path-is-absolute@^1.0.0:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||||
|
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
||||||
|
|
||||||
|
process-nextick-args@^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"
|
||||||
|
integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
|
||||||
|
|
||||||
|
pump@^2.0.0:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
|
||||||
|
integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==
|
||||||
|
dependencies:
|
||||||
|
end-of-stream "^1.1.0"
|
||||||
|
once "^1.3.1"
|
||||||
|
|
||||||
|
pump@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
|
||||||
|
integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
|
||||||
|
dependencies:
|
||||||
|
end-of-stream "^1.1.0"
|
||||||
|
once "^1.3.1"
|
||||||
|
|
||||||
|
pumpify@^1.3.5:
|
||||||
|
version "1.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce"
|
||||||
|
integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==
|
||||||
|
dependencies:
|
||||||
|
duplexify "^3.6.0"
|
||||||
|
inherits "^2.0.3"
|
||||||
|
pump "^2.0.0"
|
||||||
|
|
||||||
|
"readable-stream@> 1.0.0 < 3.0.0", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
|
||||||
|
version "2.3.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||||
|
integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
|
||||||
|
dependencies:
|
||||||
|
core-util-is "~1.0.0"
|
||||||
|
inherits "~2.0.3"
|
||||||
|
isarray "~1.0.0"
|
||||||
|
process-nextick-args "~2.0.0"
|
||||||
|
safe-buffer "~5.1.1"
|
||||||
|
string_decoder "~1.1.1"
|
||||||
|
util-deprecate "~1.0.1"
|
||||||
|
|
||||||
|
readable-stream@^3.0.0:
|
||||||
|
version "3.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc"
|
||||||
|
integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==
|
||||||
|
dependencies:
|
||||||
|
inherits "^2.0.3"
|
||||||
|
string_decoder "^1.1.1"
|
||||||
|
util-deprecate "^1.0.1"
|
||||||
|
|
||||||
|
reinterval@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/reinterval/-/reinterval-1.1.0.tgz#3361ecfa3ca6c18283380dd0bb9546f390f5ece7"
|
||||||
|
integrity sha1-M2Hs+jymwYKDOA3Qu5VG85D17Oc=
|
||||||
|
|
||||||
|
remove-trailing-separator@^1.0.1:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
||||||
|
integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8=
|
||||||
|
|
||||||
|
safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||||
|
version "5.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
|
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||||
|
|
||||||
|
split2@^3.1.0:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/split2/-/split2-3.1.1.tgz#c51f18f3e06a8c4469aaab487687d8d956160bb6"
|
||||||
|
integrity sha512-emNzr1s7ruq4N+1993yht631/JH+jaj0NYBosuKmLcq+JkGQ9MmTw1RB1fGaTCzUuseRIClrlSLHRNYGwWQ58Q==
|
||||||
|
dependencies:
|
||||||
|
readable-stream "^3.0.0"
|
||||||
|
|
||||||
|
stream-shift@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
|
||||||
|
integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=
|
||||||
|
|
||||||
|
string_decoder@^1.1.1:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d"
|
||||||
|
integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "~5.1.0"
|
||||||
|
|
||||||
|
string_decoder@~1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||||
|
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "~5.1.0"
|
||||||
|
|
||||||
|
through2-filter@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254"
|
||||||
|
integrity sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==
|
||||||
|
dependencies:
|
||||||
|
through2 "~2.0.0"
|
||||||
|
xtend "~4.0.0"
|
||||||
|
|
||||||
|
through2@^2.0.1, through2@~2.0.0:
|
||||||
|
version "2.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
|
||||||
|
integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
|
||||||
|
dependencies:
|
||||||
|
readable-stream "~2.3.6"
|
||||||
|
xtend "~4.0.1"
|
||||||
|
|
||||||
|
to-absolute-glob@^2.0.0:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b"
|
||||||
|
integrity sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=
|
||||||
|
dependencies:
|
||||||
|
is-absolute "^1.0.0"
|
||||||
|
is-negated-glob "^1.0.0"
|
||||||
|
|
||||||
|
type@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/type/-/type-1.0.1.tgz#084c9a17fcc9151a2cdb1459905c2e45e4bb7d61"
|
||||||
|
integrity sha512-MAM5dBMJCJNKs9E7JXo4CXRAansRfG0nlJxW7Wf6GZzSOvH31zClSaHdIMWLehe/EGMBkqeC55rrkaOr5Oo7Nw==
|
||||||
|
|
||||||
|
typedarray@^0.0.6:
|
||||||
|
version "0.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||||
|
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||||
|
|
||||||
|
ultron@~1.1.0:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
|
||||||
|
integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==
|
||||||
|
|
||||||
|
unc-path-regex@^0.1.2:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
|
||||||
|
integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo=
|
||||||
|
|
||||||
|
unique-stream@^2.0.2:
|
||||||
|
version "2.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.3.1.tgz#c65d110e9a4adf9a6c5948b28053d9a8d04cbeac"
|
||||||
|
integrity sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==
|
||||||
|
dependencies:
|
||||||
|
json-stable-stringify-without-jsonify "^1.0.1"
|
||||||
|
through2-filter "^3.0.0"
|
||||||
|
|
||||||
|
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||||
|
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||||
|
|
||||||
|
websocket-stream@^5.1.2:
|
||||||
|
version "5.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/websocket-stream/-/websocket-stream-5.5.0.tgz#9827f2846fc0d2b4dca7aab8f92980b2548b868e"
|
||||||
|
integrity sha512-EXy/zXb9kNHI07TIMz1oIUIrPZxQRA8aeJ5XYg5ihV8K4kD1DuA+FY6R96HfdIHzlSzS8HiISAfrm+vVQkZBug==
|
||||||
|
dependencies:
|
||||||
|
duplexify "^3.5.1"
|
||||||
|
inherits "^2.0.1"
|
||||||
|
readable-stream "^2.3.3"
|
||||||
|
safe-buffer "^5.1.2"
|
||||||
|
ws "^3.2.0"
|
||||||
|
xtend "^4.0.0"
|
||||||
|
|
||||||
|
wrappy@1:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||||
|
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||||
|
|
||||||
|
ws@^3.2.0:
|
||||||
|
version "3.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"
|
||||||
|
integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==
|
||||||
|
dependencies:
|
||||||
|
async-limiter "~1.0.0"
|
||||||
|
safe-buffer "~5.1.0"
|
||||||
|
ultron "~1.1.0"
|
||||||
|
|
||||||
|
xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1:
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||||
|
integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { IpcMain, IpcRenderer, ipcMain, ipcRenderer } from 'electron'
|
import { IpcMain, ipcMain, ipcRenderer } from 'electron'
|
||||||
|
|
||||||
import { Event } from './Events'
|
import { Event } from './Events'
|
||||||
|
import { IpcRendererEventBus } from './IpcRendererEventBus'
|
||||||
|
|
||||||
export interface EventBusInterface {
|
export interface EventBusInterface {
|
||||||
subscribe<MessageType>(event: Event<MessageType>, callback: (msg: MessageType) => void): void
|
subscribe<MessageType>(event: Event<MessageType>, callback: (msg: MessageType) => void): void
|
||||||
@@ -9,7 +10,7 @@ export interface EventBusInterface {
|
|||||||
unsubscribe<MessageType>(event: Event<MessageType>, callback: any): void
|
unsubscribe<MessageType>(event: Event<MessageType>, callback: any): void
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CallbackStore {
|
export interface CallbackStore {
|
||||||
wrappedCallback: any
|
wrappedCallback: any
|
||||||
callback: any
|
callback: any
|
||||||
}
|
}
|
||||||
@@ -45,45 +46,5 @@ class IpcMainEventBus implements EventBusInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IpcRendererEventBus implements EventBusInterface {
|
|
||||||
private ipc: IpcRenderer
|
|
||||||
private callbacks: Array<CallbackStore> = []
|
|
||||||
|
|
||||||
constructor(ipc: IpcRenderer) {
|
|
||||||
this.ipc = ipc
|
|
||||||
}
|
|
||||||
|
|
||||||
public subscribe<MessageType>(event: Event<MessageType>, callback: (msg: MessageType) => void) {
|
|
||||||
const wrappedCallback = (_event: any, arg: any) => {
|
|
||||||
callback(arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ipc.on(event.topic, wrappedCallback)
|
|
||||||
|
|
||||||
this.callbacks.push({
|
|
||||||
callback,
|
|
||||||
wrappedCallback,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsubscribeAll<MessageType>(event: Event<MessageType>) {
|
|
||||||
this.ipc.removeAllListeners(event.topic)
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsubscribe<MessageType>(event: Event<MessageType>, callback: any) {
|
|
||||||
const item = this.callbacks.find(store => store.callback === callback)
|
|
||||||
if (!item) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ipc.removeListener(event.topic, item.wrappedCallback)
|
|
||||||
this.callbacks = this.callbacks.filter(a => a !== item)
|
|
||||||
}
|
|
||||||
|
|
||||||
public emit<MessageType>(event: Event<MessageType>, msg: MessageType) {
|
|
||||||
this.ipc.send(event.topic, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const rendererEvents = new IpcRendererEventBus(ipcRenderer)
|
export const rendererEvents = new IpcRendererEventBus(ipcRenderer)
|
||||||
export const backendEvents = new IpcMainEventBus(ipcMain)
|
export const backendEvents = new IpcMainEventBus(ipcMain)
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import { DataSourceState, MqttOptions } from '../backend/src/DataSource'
|
|
||||||
|
|
||||||
import { UpdateInfo } from 'builder-util-runtime'
|
|
||||||
import { Base64Message } from '../backend/src/Model/Base64Message'
|
import { Base64Message } from '../backend/src/Model/Base64Message'
|
||||||
|
import { DataSourceState, MqttOptions } from '../backend/src/DataSource'
|
||||||
export { UpdateInfo } from 'builder-util-runtime'
|
import { UpdateInfo } from 'builder-util-runtime'
|
||||||
|
|
||||||
export interface Event<MessageType> {
|
export interface Event<MessageType> {
|
||||||
topic: string
|
topic: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AddMqttConnection {
|
export interface AddMqttConnection {
|
||||||
id: string,
|
id: string
|
||||||
options: MqttOptions
|
options: MqttOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,9 +30,9 @@ export const updateAvailable: Event<UpdateInfo> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MqttMessage {
|
export interface MqttMessage {
|
||||||
topic: string,
|
topic: string
|
||||||
payload: Base64Message | null,
|
payload: Base64Message | null
|
||||||
qos: 0 | 1 | 2,
|
qos: 0 | 1 | 2
|
||||||
retain: boolean
|
retain: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
35
events/IpcRendererEventBus.ts
Normal file
35
events/IpcRendererEventBus.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { CallbackStore, EventBusInterface } from './EventBus'
|
||||||
|
import { Event } from './Events'
|
||||||
|
import { IpcRenderer } from 'electron'
|
||||||
|
|
||||||
|
export class IpcRendererEventBus implements EventBusInterface {
|
||||||
|
private ipc: IpcRenderer
|
||||||
|
private callbacks: Array<CallbackStore> = []
|
||||||
|
constructor(ipc: IpcRenderer) {
|
||||||
|
this.ipc = ipc
|
||||||
|
}
|
||||||
|
public subscribe<MessageType>(event: Event<MessageType>, callback: (msg: MessageType) => void) {
|
||||||
|
const wrappedCallback = (_: any, arg: any) => {
|
||||||
|
callback(arg)
|
||||||
|
}
|
||||||
|
this.ipc.on(event.topic, wrappedCallback)
|
||||||
|
this.callbacks.push({
|
||||||
|
callback,
|
||||||
|
wrappedCallback,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
public unsubscribeAll<MessageType>(event: Event<MessageType>) {
|
||||||
|
this.ipc.removeAllListeners(event.topic)
|
||||||
|
}
|
||||||
|
public unsubscribe<MessageType>(event: Event<MessageType>, callback: any) {
|
||||||
|
const item = this.callbacks.find(store => store.callback === callback)
|
||||||
|
if (!item) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.ipc.removeListener(event.topic, item.wrappedCallback)
|
||||||
|
this.callbacks = this.callbacks.filter(a => a !== item)
|
||||||
|
}
|
||||||
|
public emit<MessageType>(event: Event<MessageType>, msg: MessageType) {
|
||||||
|
this.ipc.send(event.topic, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,13 +5,13 @@ interface StorageEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface StoreCommand extends StorageEvent {
|
export interface StoreCommand extends StorageEvent {
|
||||||
store?: string,
|
store?: string
|
||||||
data?: any
|
data?: any
|
||||||
error?: any
|
error?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadCommand extends StorageEvent {
|
export interface LoadCommand extends StorageEvent {
|
||||||
store: string,
|
store: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const storageStoreEvent: Event<StoreCommand> = {
|
export const storageStoreEvent: Event<StoreCommand> = {
|
||||||
|
|||||||
11
package.json
11
package.json
@@ -10,6 +10,9 @@
|
|||||||
"dev": "npm-run-all --parallel dev:*",
|
"dev": "npm-run-all --parallel dev:*",
|
||||||
"dev:app": "cd app && npm run dev",
|
"dev:app": "cd app && npm run dev",
|
||||||
"dev:electron": "tsc && electron . --development",
|
"dev:electron": "tsc && electron . --development",
|
||||||
|
"lint": "yarn run lint:prettier; yarn run lint:tslint",
|
||||||
|
"lint:prettier": "prettier --check \"**/*.ts{x,}\"",
|
||||||
|
"lint:tslint": "node_modules/.bin/tslint -p ./",
|
||||||
"build": "tsc && cd app && yarn run build && cd ..",
|
"build": "tsc && cd app && yarn run build && cd ..",
|
||||||
"test-backend": "cd backend && yarn run test && cd ..",
|
"test-backend": "cd backend && yarn run test && cd ..",
|
||||||
"prepare-release": "ts-node scripts/prepare-release.ts",
|
"prepare-release": "ts-node scripts/prepare-release.ts",
|
||||||
@@ -68,7 +71,9 @@
|
|||||||
"@types/semver": "^6.0.0",
|
"@types/semver": "^6.0.0",
|
||||||
"@types/sha1": "^1.1.1",
|
"@types/sha1": "^1.1.1",
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
|
"builder-util-runtime": "^8.2.5",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
|
"cspell": "^4.0.23",
|
||||||
"electron": "5",
|
"electron": "5",
|
||||||
"electron-builder": "^20.38.5",
|
"electron-builder": "^20.38.5",
|
||||||
"fs-extra": "^8.0.1",
|
"fs-extra": "^8.0.1",
|
||||||
@@ -76,6 +81,7 @@
|
|||||||
"mocha": "^6.1.4",
|
"mocha": "^6.1.4",
|
||||||
"mustache": "^3.0.1",
|
"mustache": "^3.0.1",
|
||||||
"nyc": "^14.1.1",
|
"nyc": "^14.1.1",
|
||||||
|
"prettier": "1.18.2",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"source-map-support": "^0.5.9",
|
"source-map-support": "^0.5.9",
|
||||||
"spectron": "^5.0.0",
|
"spectron": "^5.0.0",
|
||||||
@@ -86,7 +92,7 @@
|
|||||||
"tslint-react-recommended": "^1.0.15",
|
"tslint-react-recommended": "^1.0.15",
|
||||||
"tslint-strict-null-checks": "^1.0.1",
|
"tslint-strict-null-checks": "^1.0.1",
|
||||||
"typescript": "^3.2.2",
|
"typescript": "^3.2.2",
|
||||||
"webdriverio": "~5.9.6"
|
"webdriverio": "5.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"about-window": "^1.12.1",
|
"about-window": "^1.12.1",
|
||||||
@@ -100,5 +106,8 @@
|
|||||||
"mqtt": "^3.0.0",
|
"mqtt": "^3.0.0",
|
||||||
"sha1": "^1.1.1",
|
"sha1": "^1.1.1",
|
||||||
"yarn-run-all": "^3.1.1"
|
"yarn-run-all": "^3.1.1"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"ffmpeg-concat": "^1.0.13"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
43
package.ts
43
package.ts
@@ -59,24 +59,24 @@ const mac: builder.CliOptions = {
|
|||||||
|
|
||||||
async function executeBuild() {
|
async function executeBuild() {
|
||||||
switch (process.argv[2]) {
|
switch (process.argv[2]) {
|
||||||
case 'win':
|
case 'win':
|
||||||
await buildWithOptions(winPortable, { platform: 'win', package: 'portable' })
|
await buildWithOptions(winPortable, { platform: 'win', package: 'portable' })
|
||||||
await buildWithOptions(winNsis, { platform: 'win', package: 'nsis' })
|
await buildWithOptions(winNsis, { platform: 'win', package: 'nsis' })
|
||||||
break
|
break
|
||||||
case 'appx':
|
case 'appx':
|
||||||
await buildWithOptions(winAppx, { platform: 'win', package: 'appx' })
|
await buildWithOptions(winAppx, { platform: 'win', package: 'appx' })
|
||||||
break
|
break
|
||||||
case 'linux':
|
case 'linux':
|
||||||
await buildWithOptions(linuxAppImage, { platform: 'linux', package: 'AppImage' })
|
await buildWithOptions(linuxAppImage, { platform: 'linux', package: 'AppImage' })
|
||||||
await buildWithOptions(linuxSnap, { platform: 'linux', package: 'snap' })
|
await buildWithOptions(linuxSnap, { platform: 'linux', package: 'snap' })
|
||||||
break
|
break
|
||||||
case 'mac':
|
case 'mac':
|
||||||
await buildWithOptions(mac, { platform: 'mac', package: 'mas' })
|
await buildWithOptions(mac, { platform: 'mac', package: 'mas' })
|
||||||
await buildWithOptions(mac, { platform: 'mac', package: 'dmg' })
|
await buildWithOptions(mac, { platform: 'mac', package: 'dmg' })
|
||||||
await buildWithOptions(mac, { platform: 'mac', package: 'zip' })
|
await buildWithOptions(mac, { platform: 'mac', package: 'zip' })
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
await buildWithOptions({ ...mac, projectDir: '' }, { platform: 'mac', package: 'mas-dev' })
|
await buildWithOptions({ ...mac, projectDir: '' }, { platform: 'mac', package: 'mas-dev' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ type Packages = 'portable' | 'nsis' | 'appx' | 'AppImage' | 'snap' | 'dmg' | 'zi
|
|||||||
async function buildWithOptions(options: builder.CliOptions, buildInfo: BuildInfo) {
|
async function buildWithOptions(options: builder.CliOptions, buildInfo: BuildInfo) {
|
||||||
fs.writeFileSync(path.join(options.projectDir!, 'buildOptions.json'), JSON.stringify(buildInfo))
|
fs.writeFileSync(path.join(options.projectDir!, 'buildOptions.json'), JSON.stringify(buildInfo))
|
||||||
|
|
||||||
const jsonLocation = path.join((options.projectDir as string), 'package.json')
|
const jsonLocation = path.join(options.projectDir as string, 'package.json')
|
||||||
const packageJsonStr = fs.readFileSync(jsonLocation).toString()
|
const packageJsonStr = fs.readFileSync(jsonLocation).toString()
|
||||||
|
|
||||||
const packageJson = JSON.parse(fs.readFileSync(jsonLocation).toString())
|
const packageJson = JSON.parse(fs.readFileSync(jsonLocation).toString())
|
||||||
@@ -102,7 +102,10 @@ async function buildWithOptions(options: builder.CliOptions, buildInfo: BuildInf
|
|||||||
|
|
||||||
if (buildInfo.platform === 'mac') {
|
if (buildInfo.platform === 'mac') {
|
||||||
console.log(buildInfo.package)
|
console.log(buildInfo.package)
|
||||||
const provisioningProfile = (buildInfo.package === 'mas') ? 'res/MQTT_Explorer_Store_Distribution_Profile.provisionprofile' : 'res/MQTTExplorerdmg.provisionprofile'
|
const provisioningProfile =
|
||||||
|
buildInfo.package === 'mas'
|
||||||
|
? 'res/MQTT_Explorer_Store_Distribution_Profile.provisionprofile'
|
||||||
|
: 'res/MQTTExplorerdmg.provisionprofile'
|
||||||
dotProp.set(packageJson, 'build.mac.provisioningProfile', provisioningProfile)
|
dotProp.set(packageJson, 'build.mac.provisioningProfile', provisioningProfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7
prettier.config.js
Normal file
7
prettier.config.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
trailingComma: 'es5',
|
||||||
|
tabWidth: 2,
|
||||||
|
semi: false,
|
||||||
|
singleQuote: true,
|
||||||
|
printWidth: 120,
|
||||||
|
};
|
||||||
@@ -3,7 +3,6 @@ import * as path from 'path'
|
|||||||
import { chdir } from 'process'
|
import { chdir } from 'process'
|
||||||
import { exec } from './util'
|
import { exec } from './util'
|
||||||
|
|
||||||
|
|
||||||
interface Target {
|
interface Target {
|
||||||
name: 'appImage' | string
|
name: 'appImage' | string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ async function cutScenes(scenes: Array<Scene>) {
|
|||||||
fs.unlinkSync(outputFile)
|
fs.unlinkSync(outputFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
await exec('ffmpeg', `-i app2.mp4 -ss ${scene.start / 1000} -t ${scene.duration / 1000} ${scene.name}.mp4`.split(' '))
|
await exec(
|
||||||
|
'ffmpeg',
|
||||||
|
`-i app2.mp4 -ss ${scene.start / 1000} -t ${scene.duration / 1000} ${scene.name}.mp4`.split(' ')
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +40,7 @@ class TransitionBuilder {
|
|||||||
return {
|
return {
|
||||||
output: outputFile,
|
output: outputFile,
|
||||||
videos: this.scenes.map(s => `${s}.mp4`),
|
videos: this.scenes.map(s => `${s}.mp4`),
|
||||||
transistions: this.transitions.map(name => ({
|
transitions: this.transitions.map(name => ({
|
||||||
name: name !== 'none' ? name : 'fade',
|
name: name !== 'none' ? name : 'fade',
|
||||||
duration: name !== 'none' ? 1000 : 10,
|
duration: name !== 'none' ? 1000 : 10,
|
||||||
})),
|
})),
|
||||||
@@ -47,7 +50,7 @@ class TransitionBuilder {
|
|||||||
|
|
||||||
// const scenes: Array<Scene> = JSON.parse(fs.readFileSync('./scenes.json').toString())
|
// const scenes: Array<Scene> = JSON.parse(fs.readFileSync('./scenes.json').toString())
|
||||||
// cutScenes(scenes)
|
// cutScenes(scenes)
|
||||||
let builder = new TransitionBuilder()
|
const builder = new TransitionBuilder()
|
||||||
.startWith('connect')
|
.startWith('connect')
|
||||||
.transitionTo('numeric_plots', 'cube')
|
.transitionTo('numeric_plots', 'cube')
|
||||||
.transitionTo('diffs', 'pixelize')
|
.transitionTo('diffs', 'pixelize')
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import * as mime from 'mime'
|
|||||||
const githubToken = process.env.GH_TOKEN
|
const githubToken = process.env.GH_TOKEN
|
||||||
|
|
||||||
async function tagUrl(tag: string): Promise<string | undefined> {
|
async function tagUrl(tag: string): Promise<string | undefined> {
|
||||||
const response = await axios.get(`https://api.github.com/repos/thomasnordquist/mqtt-explorer/releases?access_token=${githubToken}`)
|
const response = await axios.get(
|
||||||
|
`https://api.github.com/repos/thomasnordquist/mqtt-explorer/releases?access_token=${githubToken}`
|
||||||
|
)
|
||||||
const tagRelease = response.data.find((release: any) => release.tag_name === tag)
|
const tagRelease = response.data.find((release: any) => release.tag_name === tag)
|
||||||
|
|
||||||
return tagRelease ? cleanUploadUrl(tagRelease.upload_url) : undefined
|
return tagRelease ? cleanUploadUrl(tagRelease.upload_url) : undefined
|
||||||
@@ -29,8 +31,9 @@ async function createDraft(tag: string) {
|
|||||||
return cleanUploadUrl(response.data.upload_url)
|
return cleanUploadUrl(response.data.upload_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanUploadUrl(url: string) {
|
function cleanUploadUrl(url: string): string | undefined {
|
||||||
return url.match(/(.*){/)![1]
|
const match = url.match(/(.*){/)
|
||||||
|
return match ? match[1] : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uploadAsset() {
|
async function uploadAsset() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { spawn, ChildProcess } from 'child_process'
|
import { spawn, ChildProcess } from 'child_process'
|
||||||
|
|
||||||
export async function exec(cmd: string, args: string[] = []) {
|
export async function exec(cmd: string, args: Array<string> = []) {
|
||||||
const child = spawn(cmd, args, { shell: true })
|
const child = spawn(cmd, args, { shell: true })
|
||||||
redirectOutputFor(child)
|
redirectOutputFor(child)
|
||||||
await waitFor(child)
|
await waitFor(child)
|
||||||
@@ -23,7 +23,7 @@ function redirectOutputFor(child: ChildProcess) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function waitFor(child: ChildProcess) {
|
async function waitFor(child: ChildProcess) {
|
||||||
return new Promise((resolve) => {
|
return new Promise(resolve => {
|
||||||
child.once('close', () => resolve())
|
child.once('close', () => resolve())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,10 +112,6 @@ const viewMenu: MenuItemConstructorOptions = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
const template: any = [
|
const template: any = [applicationMenu, editMenu, viewMenu]
|
||||||
applicationMenu,
|
|
||||||
editMenu,
|
|
||||||
viewMenu,
|
|
||||||
]
|
|
||||||
|
|
||||||
export const menuTemplate = Menu.buildFromTemplate(template)
|
export const menuTemplate = Menu.buildFromTemplate(template)
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
import { autoUpdater } from 'electron-updater'
|
import { autoUpdater, UpdateInfo } from 'electron-updater'
|
||||||
import { BuildInfo } from 'electron-telemetry/build/Model'
|
import { BuildInfo } from 'electron-telemetry/build/Model'
|
||||||
import { UpdateInfo } from '../events'
|
|
||||||
|
|
||||||
export function shouldAutoUpdate(build: BuildInfo) {
|
export function shouldAutoUpdate(build: BuildInfo) {
|
||||||
return build.package !== 'portable' && build.package !== 'appx' && build.package !== 'snap' && build.package !== 'mas' && build.platform !== 'mac'
|
return (
|
||||||
|
build.package !== 'portable' &&
|
||||||
|
build.package !== 'appx' &&
|
||||||
|
build.package !== 'snap' &&
|
||||||
|
build.package !== 'mas' &&
|
||||||
|
build.platform !== 'mac'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleAutoUpdate() {
|
export function handleAutoUpdate() {
|
||||||
@@ -11,7 +16,7 @@ export function handleAutoUpdate() {
|
|||||||
console.log('There is an update available')
|
console.log('There is an update available')
|
||||||
})
|
})
|
||||||
|
|
||||||
autoUpdater.on('error', (error) => {
|
autoUpdater.on('error', error => {
|
||||||
console.error('could not update due to error', error)
|
console.error('could not update due to error', error)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,17 @@ import * as fs from 'fs'
|
|||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import { BuildInfo } from 'electron-telemetry/build/Model'
|
import { BuildInfo } from 'electron-telemetry/build/Model'
|
||||||
|
|
||||||
let buildOptions: BuildInfo = ({ platform: process.platform, package: 'unpacked' } as any)
|
let buildOptions: BuildInfo = {
|
||||||
|
platform: process.platform,
|
||||||
|
package: 'unpacked',
|
||||||
|
} as any
|
||||||
try {
|
try {
|
||||||
const options = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'buildOptions.json')).toString())
|
const options = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'buildOptions.json')).toString())
|
||||||
if (typeof options.platform === 'string' && typeof options.package === 'string') {
|
if (typeof options.platform === 'string' && typeof options.package === 'string') {
|
||||||
buildOptions = options
|
buildOptions = options
|
||||||
}
|
}
|
||||||
} catch (loadingBuildOptionsMayFail) {}
|
} catch (loadingBuildOptionsMayFail) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
export default buildOptions
|
export default buildOptions
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { electronTelemetryFactory } from 'electron-telemetry'
|
|||||||
import { menuTemplate } from './MenuTemplate'
|
import { menuTemplate } from './MenuTemplate'
|
||||||
import buildOptions from './buildOptions'
|
import buildOptions from './buildOptions'
|
||||||
import { waitForDevServer, isDev, runningUiTestOnCi, loadDevTools } from './development'
|
import { waitForDevServer, isDev, runningUiTestOnCi, loadDevTools } from './development'
|
||||||
import { shouldAutoUpdate as shouldAutoUpdate, handleAutoUpdate } from './autoUpdater'
|
import { shouldAutoUpdate, handleAutoUpdate } from './autoUpdater'
|
||||||
|
|
||||||
if (!isDev() && !runningUiTestOnCi()) {
|
if (!isDev() && !runningUiTestOnCi()) {
|
||||||
const electronTelemetry = electronTelemetryFactory('9b0c8ca04a361eb8160d98c5', buildOptions)
|
const electronTelemetry = electronTelemetryFactory('9b0c8ca04a361eb8160d98c5', buildOptions)
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
export interface Scene {
|
export interface Scene {
|
||||||
name: SceneNames,
|
name: SceneNames
|
||||||
start: number
|
start: number
|
||||||
stop: number
|
stop: number
|
||||||
duration: number
|
duration: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SceneNames = 'connect'
|
export type SceneNames =
|
||||||
|
| 'connect'
|
||||||
| 'topic_updates'
|
| 'topic_updates'
|
||||||
| 'numeric_plots'
|
| 'numeric_plots'
|
||||||
| 'json-formatting'
|
| 'json-formatting'
|
||||||
|
|||||||
@@ -1,30 +1,23 @@
|
|||||||
|
import * as fs from 'fs'
|
||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
import * as webdriverio from 'webdriverio'
|
import * as webdriverio from 'webdriverio'
|
||||||
import mockMqtt, { stop as stopMqtt } from './mock-mqtt'
|
import mockMqtt, { stop as stopMqtt } from './mock-mqtt'
|
||||||
import { clearOldTopics } from './scenarios/clearOldTopics'
|
import { clearOldTopics } from './scenarios/clearOldTopics'
|
||||||
import { clearSearch, searchTree } from './scenarios/searchTree'
|
import { clearSearch, searchTree } from './scenarios/searchTree'
|
||||||
|
import { clickOnHistory, createFakeMousePointer, hideText, showText, sleep } from './util'
|
||||||
import { connectTo } from './scenarios/connect'
|
import { connectTo } from './scenarios/connect'
|
||||||
import { copyTopicToClipboard } from './scenarios/copyTopicToClipboard'
|
import { copyTopicToClipboard } from './scenarios/copyTopicToClipboard'
|
||||||
import { copyValueToClipboard } from './scenarios/copyValueToClipboard'
|
import { copyValueToClipboard } from './scenarios/copyValueToClipboard'
|
||||||
import { disconnect } from './scenarios/disconnect'
|
import { disconnect } from './scenarios/disconnect'
|
||||||
import { publishTopic } from './scenarios/publishTopic'
|
import { publishTopic } from './scenarios/publishTopic'
|
||||||
|
import { SceneBuilder } from './SceneBuilder'
|
||||||
|
import { showAdvancedConnectionSettings } from './scenarios/showAdvancedConnectionSettings'
|
||||||
import { showJsonFormatting } from './scenarios/showJsonFormatting'
|
import { showJsonFormatting } from './scenarios/showJsonFormatting'
|
||||||
import { showJsonPreview } from './scenarios/showJsonPreview'
|
import { showJsonPreview } from './scenarios/showJsonPreview'
|
||||||
import { showMenu } from './scenarios/showMenu'
|
import { showMenu } from './scenarios/showMenu'
|
||||||
import { showNumericPlot } from './scenarios/showNumericPlot'
|
import { showNumericPlot } from './scenarios/showNumericPlot'
|
||||||
import { showOffDiffCapability } from './scenarios/showOffDiffCapability'
|
import { showOffDiffCapability } from './scenarios/showOffDiffCapability'
|
||||||
import { showZoomLevel } from './scenarios/showZoomLevel'
|
import { showZoomLevel } from './scenarios/showZoomLevel'
|
||||||
import { showAdvancedConnectionSettings } from './scenarios/showAdvancedConnectionSettings'
|
|
||||||
import { SceneBuilder } from './SceneBuilder'
|
|
||||||
import * as fs from 'fs'
|
|
||||||
|
|
||||||
import {
|
|
||||||
clickOnHistory,
|
|
||||||
createFakeMousePointer,
|
|
||||||
hideText,
|
|
||||||
showText,
|
|
||||||
sleep,
|
|
||||||
} from './util'
|
|
||||||
|
|
||||||
process.on('unhandledRejection', (error: Error | any) => {
|
process.on('unhandledRejection', (error: Error | any) => {
|
||||||
console.error('unhandledRejection', error.message, error.stack)
|
console.error('unhandledRejection', error.message, error.stack)
|
||||||
@@ -41,7 +34,13 @@ const options = {
|
|||||||
browserName: 'electron',
|
browserName: 'electron',
|
||||||
chromeOptions: {
|
chromeOptions: {
|
||||||
binary: `${__dirname}/../../../node_modules/.bin/electron`,
|
binary: `${__dirname}/../../../node_modules/.bin/electron`,
|
||||||
args: [`--app=${__dirname}/../../..`, '--force-device-scale-factor=1', '--no-sandbox', '--disable-dev-shm-usage', '--disable-extensions'].concat(runningUiTestOnCi),
|
args: [
|
||||||
|
`--app=${__dirname}/../../..`,
|
||||||
|
'--force-device-scale-factor=1',
|
||||||
|
'--no-sandbox',
|
||||||
|
'--disable-dev-shm-usage',
|
||||||
|
'--disable-extensions',
|
||||||
|
].concat(runningUiTestOnCi),
|
||||||
},
|
},
|
||||||
windowTypes: ['app', 'webview'],
|
windowTypes: ['app', 'webview'],
|
||||||
},
|
},
|
||||||
@@ -55,7 +54,6 @@ async function doStuff() {
|
|||||||
const browser = await webdriverio.remote(options)
|
const browser = await webdriverio.remote(options)
|
||||||
await createFakeMousePointer(browser)
|
await createFakeMousePointer(browser)
|
||||||
|
|
||||||
|
|
||||||
// Wait for Username input to be visible
|
// Wait for Username input to be visible
|
||||||
await browser.$('//label[contains(text(), "Username")]/..//input')
|
await browser.$('//label[contains(text(), "Username")]/..//input')
|
||||||
const scenes = new SceneBuilder()
|
const scenes = new SceneBuilder()
|
||||||
|
|||||||
@@ -1,15 +1,6 @@
|
|||||||
import * as fs from 'fs'
|
|
||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
import * as webdriverio from 'webdriverio'
|
|
||||||
import mockMqtt, { stopUpdates as stopMqttUpdates } from './mock-mqtt'
|
import mockMqtt, { stopUpdates as stopMqttUpdates } from './mock-mqtt'
|
||||||
import {
|
import { ClassNameMapping, countInstancesOf, createFakeMousePointer, getHeapDump, setFast, sleep } from './util'
|
||||||
ClassNameMapping,
|
|
||||||
countInstancesOf,
|
|
||||||
createFakeMousePointer,
|
|
||||||
getHeapDump,
|
|
||||||
setFast,
|
|
||||||
sleep
|
|
||||||
} from './util'
|
|
||||||
import { clearSearch, searchTree } from './scenarios/searchTree'
|
import { clearSearch, searchTree } from './scenarios/searchTree'
|
||||||
import { connectTo } from './scenarios/connect'
|
import { connectTo } from './scenarios/connect'
|
||||||
import { reconnect } from './scenarios/reconnect'
|
import { reconnect } from './scenarios/reconnect'
|
||||||
@@ -29,7 +20,13 @@ const options = {
|
|||||||
browserName: 'electron',
|
browserName: 'electron',
|
||||||
chromeOptions: {
|
chromeOptions: {
|
||||||
binary: `${__dirname}/../../../node_modules/.bin/electron`,
|
binary: `${__dirname}/../../../node_modules/.bin/electron`,
|
||||||
args: [`--app=${__dirname}/../../..`, '--force-device-scale-factor=1', '--no-sandbox', '--disable-dev-shm-usage', '--disable-extensions'].concat(runningUiTestOnCi),
|
args: [
|
||||||
|
`--app=${__dirname}/../../..`,
|
||||||
|
'--force-device-scale-factor=1',
|
||||||
|
'--no-sandbox',
|
||||||
|
'--disable-dev-shm-usage',
|
||||||
|
'--disable-extensions',
|
||||||
|
].concat(runningUiTestOnCi),
|
||||||
},
|
},
|
||||||
windowTypes: ['app', 'webview'],
|
windowTypes: ['app', 'webview'],
|
||||||
},
|
},
|
||||||
@@ -40,7 +37,7 @@ async function doStuff() {
|
|||||||
await mockMqtt()
|
await mockMqtt()
|
||||||
console.log('start webdriver')
|
console.log('start webdriver')
|
||||||
|
|
||||||
const browser = await webdriverio.remote(options)
|
const browser = await WebdriverIO.remote(options)
|
||||||
setFast()
|
setFast()
|
||||||
await createFakeMousePointer(browser)
|
await createFakeMousePointer(browser)
|
||||||
|
|
||||||
@@ -50,10 +47,10 @@ async function doStuff() {
|
|||||||
stopMqttUpdates()
|
stopMqttUpdates()
|
||||||
await sleep(1000, true)
|
await sleep(1000, true)
|
||||||
|
|
||||||
let heapDump = await getHeapDump(browser)
|
const heapDump = await getHeapDump(browser)
|
||||||
const initialTreeOccurrances = await countInstancesOf(heapDump, ClassNameMapping.Tree)
|
const initialTreeOccurrences = await countInstancesOf(heapDump, ClassNameMapping.Tree)
|
||||||
const initialNodeOccurrances = await countInstancesOf(heapDump, ClassNameMapping.TreeNode)
|
const initialNodeOccurrences = await countInstancesOf(heapDump, ClassNameMapping.TreeNode)
|
||||||
console.log(initialTreeOccurrances, initialNodeOccurrances)
|
console.log(initialTreeOccurrences, initialNodeOccurrences)
|
||||||
|
|
||||||
await doX(3, async () => {
|
await doX(3, async () => {
|
||||||
await reconnect(browser)
|
await reconnect(browser)
|
||||||
@@ -74,36 +71,49 @@ async function doStuff() {
|
|||||||
|
|
||||||
await sleep(1000, true)
|
await sleep(1000, true)
|
||||||
|
|
||||||
await waitForGarbageCollectorToDetermineLeak(browser, initialTreeOccurrances, initialNodeOccurrances)
|
await waitForGarbageCollectorToDetermineLeak(browser, initialTreeOccurrences, initialNodeOccurrences)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function waitForGarbageCollectorToDetermineLeak(browser: any, initialTreeOccurrances: number, initialNodeOccurrances: number) {
|
async function waitForGarbageCollectorToDetermineLeak(
|
||||||
|
browser: any,
|
||||||
|
initialTreeOccurrences: number,
|
||||||
|
initialNodeOccurrences: number
|
||||||
|
) {
|
||||||
let delta = -1
|
let delta = -1
|
||||||
let lastTreeOccurances = -1
|
let lastTreeOccurrences = -1
|
||||||
let lastNodeOccurances = -1
|
let lastNodeOccurrences = -1
|
||||||
let leak = false
|
let leak = false
|
||||||
while (delta < 0) {
|
while (delta < 0) {
|
||||||
if (lastTreeOccurances !== -1) {
|
if (lastTreeOccurrences !== -1) {
|
||||||
await sleep(10000, true)
|
await sleep(10000, true)
|
||||||
}
|
}
|
||||||
const heapDump = await getHeapDump(browser)
|
const heapDump = await getHeapDump(browser)
|
||||||
const currentTreeOccurrances = await countInstancesOf(heapDump, ClassNameMapping.Tree)
|
const currentTreeOccurrences = await countInstancesOf(heapDump, ClassNameMapping.Tree)
|
||||||
const currentNodeOccurrances = await countInstancesOf(heapDump, ClassNameMapping.TreeNode)
|
const currentNodeOccurrences = await countInstancesOf(heapDump, ClassNameMapping.TreeNode)
|
||||||
|
|
||||||
// Temporary "leaks" are expected due to React Fibers memoization
|
// Temporary "leaks" are expected due to React Fibers memoization
|
||||||
if (Math.abs(initialTreeOccurrances - currentTreeOccurrances) > 1 || Math.abs(currentNodeOccurrances - initialNodeOccurrances) > 8) {
|
if (
|
||||||
console.error('Possible leak detected', initialTreeOccurrances, currentTreeOccurrances, initialNodeOccurrances, currentNodeOccurrances)
|
Math.abs(initialTreeOccurrences - currentTreeOccurrences) > 1 ||
|
||||||
|
Math.abs(currentNodeOccurrences - initialNodeOccurrences) > 8
|
||||||
|
) {
|
||||||
|
console.error(
|
||||||
|
'Possible leak detected',
|
||||||
|
initialTreeOccurrences,
|
||||||
|
currentTreeOccurrences,
|
||||||
|
initialNodeOccurrences,
|
||||||
|
currentNodeOccurrences
|
||||||
|
)
|
||||||
leak = true
|
leak = true
|
||||||
} else {
|
} else {
|
||||||
leak = false
|
leak = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const treeDelta = lastTreeOccurances >= 0 ? currentTreeOccurrances - lastTreeOccurances : -1
|
const treeDelta = lastTreeOccurrences >= 0 ? currentTreeOccurrences - lastTreeOccurrences : -1
|
||||||
const nodeDelta = lastTreeOccurances >= 0 ? currentNodeOccurrances - lastNodeOccurances : -1
|
const nodeDelta = lastTreeOccurrences >= 0 ? currentNodeOccurrences - lastNodeOccurrences : -1
|
||||||
delta = treeDelta + nodeDelta
|
delta = treeDelta + nodeDelta
|
||||||
|
|
||||||
lastTreeOccurances = currentTreeOccurrances
|
lastTreeOccurrences = currentTreeOccurrences
|
||||||
lastNodeOccurances = currentNodeOccurrances
|
lastNodeOccurrences = currentNodeOccurrences
|
||||||
}
|
}
|
||||||
|
|
||||||
if (leak) {
|
if (leak) {
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
import * as mqtt from 'mqtt'
|
import * as mqtt from 'mqtt'
|
||||||
|
|
||||||
const settings = {
|
let mqttClient: mqtt.MqttClient
|
||||||
port: 1883,
|
|
||||||
}
|
|
||||||
|
|
||||||
let client: mqtt.MqttClient
|
|
||||||
function startServer(): Promise<mqtt.MqttClient> {
|
function startServer(): Promise<mqtt.MqttClient> {
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async resolve => {
|
||||||
// const server = new mosca.Server(settings)
|
mqttClient = await connectMqtt()
|
||||||
// await new Promise(resolve => server.once('ready', resolve))
|
generateData(mqttClient)
|
||||||
client = await connectMqtt()
|
resolve(mqttClient)
|
||||||
generateData(client)
|
|
||||||
resolve(client)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectMqtt(): Promise<mqtt.MqttClient> {
|
function connectMqtt(): Promise<mqtt.MqttClient> {
|
||||||
return new Promise((resolve) => {
|
return new Promise(resolve => {
|
||||||
const client = mqtt.connect('mqtt://127.0.0.1:1883', { username: 'thomas', password: 'bierbier' })
|
const client = mqtt.connect('mqtt://127.0.0.1:1883', {
|
||||||
|
username: 'thomas',
|
||||||
|
password: 'bierbier',
|
||||||
|
})
|
||||||
client.once('connect', () => {
|
client.once('connect', () => {
|
||||||
resolve(client)
|
resolve(client)
|
||||||
})
|
})
|
||||||
@@ -40,8 +37,10 @@ export function stopUpdates() {
|
|||||||
export function stop() {
|
export function stop() {
|
||||||
stopUpdates()
|
stopUpdates()
|
||||||
try {
|
try {
|
||||||
client && client.end()
|
mqttClient && mqttClient.end()
|
||||||
} catch {}
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let intervals: any = []
|
let intervals: any = []
|
||||||
@@ -49,20 +48,36 @@ let intervals: any = []
|
|||||||
function generateData(client: mqtt.MqttClient) {
|
function generateData(client: mqtt.MqttClient) {
|
||||||
client.publish('livingroom/lamp/state', 'on', { retain: true, qos: 0 })
|
client.publish('livingroom/lamp/state', 'on', { retain: true, qos: 0 })
|
||||||
client.publish('livingroom/lamp/brightness', '128', { retain: true, qos: 0 })
|
client.publish('livingroom/lamp/brightness', '128', { retain: true, qos: 0 })
|
||||||
client.publish('livingroom/thermostat/targetTemperature', '20°C', { retain: true, qos: 0 })
|
client.publish('livingroom/thermostat/targetTemperature', '20°C', {
|
||||||
|
retain: true,
|
||||||
|
qos: 0,
|
||||||
|
})
|
||||||
intervals.push(setInterval(() => client.publish('livingroom/temperature', temperature()), 1000))
|
intervals.push(setInterval(() => client.publish('livingroom/temperature', temperature()), 1000))
|
||||||
intervals.push(setInterval(() => client.publish('livingroom/humidity', temperature(60, -2, 0)), 1000))
|
intervals.push(setInterval(() => client.publish('livingroom/humidity', temperature(60, -2, 0)), 1000))
|
||||||
|
|
||||||
client.publish('livingroom/lamp-1/state', 'on', { retain: true, qos: 0 })
|
client.publish('livingroom/lamp-1/state', 'on', { retain: true, qos: 0 })
|
||||||
client.publish('livingroom/lamp-1/brightness', '48', { retain: true, qos: 0 })
|
client.publish('livingroom/lamp-1/brightness', '48', {
|
||||||
|
retain: true,
|
||||||
|
qos: 0,
|
||||||
|
})
|
||||||
client.publish('livingroom/lamp-2/state', 'off', { retain: true, qos: 0 })
|
client.publish('livingroom/lamp-2/state', 'off', { retain: true, qos: 0 })
|
||||||
client.publish('livingroom/lamp-2/brightness', '48', { retain: true, qos: 0 })
|
client.publish('livingroom/lamp-2/brightness', '48', {
|
||||||
|
retain: true,
|
||||||
|
qos: 0,
|
||||||
|
})
|
||||||
|
|
||||||
client.publish('kitchen/lamp/state', 'off', { retain: true, qos: 0 })
|
client.publish('kitchen/lamp/state', 'off', { retain: true, qos: 0 })
|
||||||
client.subscribe('kitchen/lamp/set')
|
client.subscribe('kitchen/lamp/set')
|
||||||
client.on('message', (topic, payload) => {
|
client.on('message', (topic, payload) => {
|
||||||
if (topic === 'kitchen/lamp/set') {
|
if (topic === 'kitchen/lamp/set') {
|
||||||
setTimeout(() => client.publish('kitchen/lamp/state', payload, { retain: true, qos: 0 }), 500)
|
setTimeout(
|
||||||
|
() =>
|
||||||
|
client.publish('kitchen/lamp/state', payload, {
|
||||||
|
retain: true,
|
||||||
|
qos: 0,
|
||||||
|
}),
|
||||||
|
500
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -74,7 +89,10 @@ function generateData(client: mqtt.MqttClient) {
|
|||||||
client.publish('garden/lamps/state', 'off', { retain: true, qos: 0 })
|
client.publish('garden/lamps/state', 'off', { retain: true, qos: 0 })
|
||||||
client.publish('garden/lamps/state', 'off', { retain: true, qos: 0 })
|
client.publish('garden/lamps/state', 'off', { retain: true, qos: 0 })
|
||||||
|
|
||||||
client.publish('zigbee2mqtt/bridge/state', 'online', { retain: true, qos: 0 })
|
client.publish('zigbee2mqtt/bridge/state', 'online', {
|
||||||
|
retain: true,
|
||||||
|
qos: 0,
|
||||||
|
})
|
||||||
client.publish('ble2mqtt/bridge/state', 'online', { retain: true, qos: 0 })
|
client.publish('ble2mqtt/bridge/state', 'online', { retain: true, qos: 0 })
|
||||||
|
|
||||||
// Used for demonstrating "clean up"
|
// Used for demonstrating "clean up"
|
||||||
@@ -84,30 +102,33 @@ function generateData(client: mqtt.MqttClient) {
|
|||||||
// Just stuff
|
// Just stuff
|
||||||
client.publish('01-80-C2-00-00-0F/LWT', 'offline', { retain: true, qos: 0 })
|
client.publish('01-80-C2-00-00-0F/LWT', 'offline', { retain: true, qos: 0 })
|
||||||
|
|
||||||
intervals.push(setInterval(() => {
|
intervals.push(
|
||||||
client.publish('3d-printer/OctoPrint/temperature/bed', '{"_timestamp":1548589083,"actual":25.9,"target":0}')
|
setInterval(() => {
|
||||||
client.publish('3d-printer/OctoPrint/temperature/tool0', '{"_timestamp":1548589093,"actual":26.4,"target":0}')
|
client.publish('3d-printer/OctoPrint/temperature/bed', '{"_timestamp":1548589083,"actual":25.9,"target":0}')
|
||||||
}, 3333))
|
client.publish('3d-printer/OctoPrint/temperature/tool0', '{"_timestamp":1548589093,"actual":26.4,"target":0}')
|
||||||
|
}, 3333)
|
||||||
|
)
|
||||||
|
|
||||||
let state = true
|
let state = true
|
||||||
intervals.push(setInterval(() => {
|
intervals.push(
|
||||||
state = !state
|
setInterval(() => {
|
||||||
const js = {
|
state = !state
|
||||||
tags: {
|
const js = {
|
||||||
entityId: 33512,
|
tags: {
|
||||||
entityType: 'person',
|
entityId: 33512,
|
||||||
host: 'd44ad81e10f9',
|
entityType: 'person',
|
||||||
server: 'http://localhost/dataActuality',
|
host: 'd44ad81e10f9',
|
||||||
status: state ? 'live' : 'inactive',
|
server: 'http://localhost/dataActuality',
|
||||||
},
|
status: state ? 'live' : 'inactive',
|
||||||
timestamp: Date.now(),
|
},
|
||||||
}
|
timestamp: Date.now(),
|
||||||
client.publish(
|
}
|
||||||
'actuality/showcase',
|
client.publish('actuality/showcase', JSON.stringify(js), {
|
||||||
JSON.stringify(js),
|
retain: true,
|
||||||
{ retain: true, qos: 0 }
|
qos: 0,
|
||||||
)
|
})
|
||||||
}, 2102))
|
}, 2102)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default startServer
|
export default startServer
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { clickOn, sleep, writeText, expandTopic, moveToCenterOfElement } from '../util'
|
import { Browser, Element } from 'webdriverio'
|
||||||
import { Browser } from 'webdriverio'
|
import { clickOn, expandTopic, moveToCenterOfElement, sleep, writeText } from '../util'
|
||||||
|
|
||||||
export async function clearOldTopics(browser: Browser) {
|
export async function clearOldTopics(browser: Browser) {
|
||||||
const topics = ['hello', 'test 123']
|
const topics = ['hello', 'test 123']
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { Browser, Element } from 'webdriverio'
|
||||||
import { clickOn, writeTextToInput } from '../util'
|
import { clickOn, writeTextToInput } from '../util'
|
||||||
import { Browser } from 'webdriverio'
|
|
||||||
|
|
||||||
export async function connectTo(host: string, browser: Browser) {
|
export async function connectTo(host: string, browser: Browser) {
|
||||||
await writeTextToInput('Host', host, browser)
|
await writeTextToInput('Host', host, browser)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { Browser, Element } from 'webdriverio'
|
||||||
import { clickOn } from '../util'
|
import { clickOn } from '../util'
|
||||||
import { Browser } from 'webdriverio'
|
|
||||||
|
|
||||||
export async function copyTopicToClipboard(browser: Browser) {
|
export async function copyTopicToClipboard(browser: Browser) {
|
||||||
const copyButton = await browser.$('//p[contains(text(), "Topic")]/span')
|
const copyButton = await browser.$('//p[contains(text(), "Topic")]/span')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { clickOn, sleep, writeText, expandTopic } from '../util'
|
import { Browser, Element } from 'webdriverio'
|
||||||
import { Browser } from 'webdriverio'
|
import { clickOn, expandTopic, sleep, writeText } from '../util'
|
||||||
|
|
||||||
export async function copyValueToClipboard(browser: Browser) {
|
export async function copyValueToClipboard(browser: Browser) {
|
||||||
const copyButton = await browser.$('//p[contains(text(), "Value")]/span')
|
const copyButton = await browser.$('//p[contains(text(), "Value")]/span')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { Browser, Element } from 'webdriverio'
|
||||||
import { clickOn } from '../util'
|
import { clickOn } from '../util'
|
||||||
import { Browser } from 'webdriverio'
|
|
||||||
|
|
||||||
export async function disconnect(browser: Browser) {
|
export async function disconnect(browser: Browser) {
|
||||||
const disconnectButton = await browser.$('//button/span[contains(text(),"Disconnect")]')
|
const disconnectButton = await browser.$('//button/span[contains(text(),"Disconnect")]')
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user