Merge pull request #795 from thomasnordquist/tnordquist/decode-data-in-frontend
decode data in frontend
This commit is contained in:
@@ -2,7 +2,7 @@ import { Action, ActionTypes } from '../reducers/Publish'
|
|||||||
import { AppState } from '../reducers'
|
import { AppState } from '../reducers'
|
||||||
import { Base64Message } from '../../../backend/src/Model/Base64Message'
|
import { Base64Message } from '../../../backend/src/Model/Base64Message'
|
||||||
import { Dispatch } from 'redux'
|
import { Dispatch } from 'redux'
|
||||||
import { makePublishEvent, rendererEvents } from '../../../events'
|
import { MqttMessage, makePublishEvent, rendererEvents } from '../../../events'
|
||||||
|
|
||||||
export const setTopic = (topic?: string): Action => {
|
export const setTopic = (topic?: string): Action => {
|
||||||
return {
|
return {
|
||||||
@@ -41,7 +41,7 @@ export const publish = (connectionId: string) => (dispatch: Dispatch<Action>, ge
|
|||||||
}
|
}
|
||||||
|
|
||||||
const publishEvent = makePublishEvent(connectionId)
|
const publishEvent = makePublishEvent(connectionId)
|
||||||
const mqttMessage = {
|
const mqttMessage: Partial<MqttMessage> = {
|
||||||
topic,
|
topic,
|
||||||
payload: state.publish.payload ? Base64Message.fromString(state.publish.payload) : null,
|
payload: state.publish.payload ? Base64Message.fromString(state.publish.payload) : null,
|
||||||
retain: state.publish.retain,
|
retain: state.publish.retain,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as q from '../../../backend/src/Model'
|
import * as q from '../../../backend/src/Model'
|
||||||
import { ActionTypes, SettingsStateModel, TopicOrder } from '../reducers/Settings'
|
import { ActionTypes, SettingsStateModel, TopicOrder, ValueRendererDisplayMode } from '../reducers/Settings'
|
||||||
import { AppState } from '../reducers'
|
import { AppState } from '../reducers'
|
||||||
import { autoExpandLimitSet } from '../components/SettingsDrawer/Settings'
|
import { autoExpandLimitSet } from '../components/SettingsDrawer/Settings'
|
||||||
import { Base64Message } from '../../../backend/src/Model/Base64Message'
|
import { Base64Message } from '../../../backend/src/Model/Base64Message'
|
||||||
@@ -68,13 +68,14 @@ export const selectTopicWithMouseOver = (doSelect: boolean) => (dispatch: Dispat
|
|||||||
dispatch(storeSettings())
|
dispatch(storeSettings())
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setValueDisplayMode = (valueRendererDisplayMode: 'diff' | 'raw') => (dispatch: Dispatch<any>) => {
|
export const setValueDisplayMode =
|
||||||
dispatch({
|
(valueRendererDisplayMode: ValueRendererDisplayMode) => (dispatch: Dispatch<any>) => {
|
||||||
valueRendererDisplayMode,
|
dispatch({
|
||||||
type: ActionTypes.SETTINGS_SET_VALUE_RENDERER_DISPLAY_MODE,
|
valueRendererDisplayMode,
|
||||||
})
|
type: ActionTypes.SETTINGS_SET_VALUE_RENDERER_DISPLAY_MODE,
|
||||||
dispatch(storeSettings())
|
})
|
||||||
}
|
dispatch(storeSettings())
|
||||||
|
}
|
||||||
|
|
||||||
export const toggleHighlightTopicUpdates = () => (dispatch: Dispatch<any>) => {
|
export const toggleHighlightTopicUpdates = () => (dispatch: Dispatch<any>) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
@@ -117,7 +118,7 @@ export const filterTopics = (filterStr: string) => (dispatch: Dispatch<any>, get
|
|||||||
const messageMatches =
|
const messageMatches =
|
||||||
node.message &&
|
node.message &&
|
||||||
node.message.payload &&
|
node.message.payload &&
|
||||||
Base64Message.toUnicodeString(node.message.payload).toLowerCase().indexOf(filterStr) !== -1
|
node.message.payload.toUnicodeString().toLowerCase().indexOf(filterStr) !== -1
|
||||||
|
|
||||||
return Boolean(messageMatches)
|
return Boolean(messageMatches)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,13 +33,8 @@ const debouncedSelectTopic = debounce(
|
|||||||
setTopicDispatch = setTopic(topic.path())
|
setTopicDispatch = setTopic(topic.path())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (previouslySelectedTopic && previouslySelectedTopic.viewModel) {
|
previouslySelectedTopic?.viewModel?.setSelected(false)
|
||||||
previouslySelectedTopic.viewModel.setSelected(false)
|
topic.viewModel?.setSelected(true)
|
||||||
}
|
|
||||||
|
|
||||||
if (topic.viewModel) {
|
|
||||||
topic.viewModel.setSelected(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectTreeTopicDispatch = {
|
const selectTreeTopicDispatch = {
|
||||||
selectedTopic: topic,
|
selectedTopic: topic,
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ function TopicChart(props: Props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<TopicPlot
|
<TopicPlot
|
||||||
|
node={props.treeNode ? props.treeNode : undefined}
|
||||||
color={props.parameters.color}
|
color={props.parameters.color}
|
||||||
interpolation={props.parameters.interpolation}
|
interpolation={props.parameters.interpolation}
|
||||||
timeInterval={props.parameters.timeRange ? props.parameters.timeRange.until : undefined}
|
timeInterval={props.parameters.timeRange ? props.parameters.timeRange.until : undefined}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { connect } from 'react-redux'
|
|||||||
import { connectionManagerActions } from '../../../actions'
|
import { connectionManagerActions } from '../../../actions'
|
||||||
import { ConnectionOptions } from '../../../model/ConnectionOptions'
|
import { ConnectionOptions } from '../../../model/ConnectionOptions'
|
||||||
import { KeyCodes } from '../../../utils/KeyCodes'
|
import { KeyCodes } from '../../../utils/KeyCodes'
|
||||||
import { List, ListSubheader } from '@material-ui/core'
|
import { List } from '@material-ui/core'
|
||||||
import { Theme, withStyles } from '@material-ui/core/styles'
|
import { Theme, withStyles } from '@material-ui/core/styles'
|
||||||
import { useGlobalKeyEventHandler } from '../../../effects/useGlobalKeyEventHandler'
|
import { useGlobalKeyEventHandler } from '../../../effects/useGlobalKeyEventHandler'
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ function renderStat(tree: q.Tree<TopicViewModel>, stat: Stats) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const str = node.message.payload ? Base64Message.toUnicodeString(node.message.payload) : ''
|
const str = node.message.payload ? node.message.payload.toUnicodeString() : ''
|
||||||
let value = node.message && node.message.payload ? parseFloat(str) : NaN
|
let value = node.message && node.message.payload ? parseFloat(str) : NaN
|
||||||
value = !isNaN(value) ? abbreviate(value) : str
|
value = !isNaN(value) ? abbreviate(value) : str
|
||||||
|
|
||||||
|
|||||||
@@ -52,16 +52,16 @@ function ChartPreview(props: Props) {
|
|||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<Tooltip title="Add to chart panel, not enough data for preview">
|
<Tooltip title="Add to chart panel, not enough data for preview">
|
||||||
<ShowChart
|
<ShowChart
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={props.classes.icon}
|
className={props.classes.icon}
|
||||||
style={{ color: '#aaa' }}
|
style={{ color: '#aaa' }}
|
||||||
data-test-type="ShowChart"
|
data-test-type="ShowChart"
|
||||||
data-test={props.literal.path}
|
data-test={props.literal.path}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
@@ -69,7 +69,7 @@ function ChartPreview(props: Props) {
|
|||||||
<Popper open={open} anchorEl={chartIconRef.current} placement="left-end">
|
<Popper open={open} anchorEl={chartIconRef.current} placement="left-end">
|
||||||
<Fade in={open} timeout={300}>
|
<Fade in={open} timeout={300}>
|
||||||
<Paper style={{ width: '300px' }}>
|
<Paper style={{ width: '300px' }}>
|
||||||
{open ? <TopicPlot history={props.treeNode.messageHistory} dotPath={props.literal.path} /> : <span />}
|
{open ? <TopicPlot node={props.treeNode} history={props.treeNode.messageHistory} dotPath={props.literal.path} /> : <span />}
|
||||||
</Paper>
|
</Paper>
|
||||||
</Fade>
|
</Fade>
|
||||||
</Popper>
|
</Popper>
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import * as q from '../../../../backend/src/Model'
|
import * as q from '../../../../backend/src/Model'
|
||||||
import React, { useState, useEffect, useCallback } from 'react'
|
import React, { useState, useEffect, useCallback } from 'react'
|
||||||
import ExpandMore from '@material-ui/icons/ExpandMore'
|
|
||||||
import NodeStats from './NodeStats'
|
import NodeStats from './NodeStats'
|
||||||
import ValuePanel from './ValueRenderer/ValuePanel'
|
import ValuePanel from './ValueRenderer/ValuePanel'
|
||||||
import { AppState } from '../../reducers'
|
import { AppState } from '../../reducers'
|
||||||
import { Badge, ExpansionPanel, ExpansionPanelDetails, ExpansionPanelSummary, Typography } from '@material-ui/core'
|
import { ExpansionPanelDetails } from '@material-ui/core'
|
||||||
import { bindActionCreators } from 'redux'
|
import { bindActionCreators } from 'redux'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { settingsActions, sidebarActions } from '../../actions'
|
import { settingsActions, sidebarActions } from '../../actions'
|
||||||
@@ -28,7 +27,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function useUpdateNodeWhenNodeReceivesUpdates(node?: q.TreeNode<any>) {
|
function useUpdateNodeWhenNodeReceivesUpdates(node?: q.TreeNode<any>) {
|
||||||
const [lastUpdate, setLastUpdate] = useState(0)
|
const [, setLastUpdate] = useState(0)
|
||||||
const updateNode = useCallback(
|
const updateNode = useCallback(
|
||||||
throttle(() => {
|
throttle(() => {
|
||||||
setLastUpdate(node ? node.lastUpdate : 0)
|
setLastUpdate(node ? node.lastUpdate : 0)
|
||||||
@@ -52,7 +51,6 @@ function Sidebar(props: Props) {
|
|||||||
const { classes, tree, nodePath } = props
|
const { classes, tree, nodePath } = props
|
||||||
const node = usePollingToFetchTreeNode(tree, nodePath || '')
|
const node = usePollingToFetchTreeNode(tree, nodePath || '')
|
||||||
useUpdateNodeWhenNodeReceivesUpdates(node)
|
useUpdateNodeWhenNodeReceivesUpdates(node)
|
||||||
// console.log(node && node.path(), tree, nodePath)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="Sidebar" className={classes.drawer}>
|
<div id="Sidebar" className={classes.drawer}>
|
||||||
|
|||||||
@@ -6,19 +6,19 @@ import Topic from './Topic'
|
|||||||
import { bindActionCreators } from 'redux'
|
import { bindActionCreators } from 'redux'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { RecursiveTopicDeleteButton } from './RecursiveTopicDeleteButton'
|
import { RecursiveTopicDeleteButton } from './RecursiveTopicDeleteButton'
|
||||||
import { sidebarActions } from '../../../actions'
|
|
||||||
import { TopicDeleteButton } from './TopicDeleteButton'
|
import { TopicDeleteButton } from './TopicDeleteButton'
|
||||||
|
import { TopicTypeButton } from './TopicTypeButton'
|
||||||
|
import { sidebarActions } from '../../../actions'
|
||||||
|
|
||||||
const TopicPanel = (props: { node?: q.TreeNode<any>; actions: typeof sidebarActions }) => {
|
const TopicPanel = (props: { node?: q.TreeNode<any>; actions: typeof sidebarActions }) => {
|
||||||
const { node } = props
|
const { node } = props
|
||||||
console.log(node && node.path())
|
|
||||||
const copyTopic = node ? <Copy value={node.path()} /> : null
|
const copyTopic = node ? <Copy value={node.path()} /> : null
|
||||||
|
|
||||||
const deleteTopic = useCallback((topic?: q.TreeNode<any>, recursive: boolean = false) => {
|
const deleteTopic = useCallback((topic?: q.TreeNode<any>, recursive: boolean = false) => {
|
||||||
if (!topic) {
|
if (!topic) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
props.actions.clearTopic(topic, recursive)
|
props.actions.clearTopic(topic, recursive)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -29,11 +29,12 @@ const TopicPanel = (props: { node?: q.TreeNode<any>; actions: typeof sidebarActi
|
|||||||
Topic {copyTopic}
|
Topic {copyTopic}
|
||||||
<TopicDeleteButton node={node} deleteTopicAction={deleteTopic} />
|
<TopicDeleteButton node={node} deleteTopicAction={deleteTopic} />
|
||||||
<RecursiveTopicDeleteButton node={node} deleteTopicAction={deleteTopic} />
|
<RecursiveTopicDeleteButton node={node} deleteTopicAction={deleteTopic} />
|
||||||
|
<TopicTypeButton node={node} />
|
||||||
</span>
|
</span>
|
||||||
<Topic node={node} />
|
<Topic node={node} />
|
||||||
</Panel>
|
</Panel>
|
||||||
),
|
),
|
||||||
[node, node && node.childTopicCount()]
|
[node, node?.childTopicCount()]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
103
app/src/components/Sidebar/TopicPanel/TopicTypeButton.tsx
Normal file
103
app/src/components/Sidebar/TopicPanel/TopicTypeButton.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import React, { useCallback, useMemo } from 'react'
|
||||||
|
import * as q from '../../../../../backend/src/Model'
|
||||||
|
import ClickAwayListener from '@material-ui/core/ClickAwayListener'
|
||||||
|
import Grow from '@material-ui/core/Grow'
|
||||||
|
import Button from '@material-ui/core/Button'
|
||||||
|
import Paper from '@material-ui/core/Paper'
|
||||||
|
import Popper from '@material-ui/core/Popper'
|
||||||
|
import MenuItem from '@material-ui/core/MenuItem'
|
||||||
|
import MenuList from '@material-ui/core/MenuList'
|
||||||
|
import WarningRounded from '@material-ui/icons/WarningRounded'
|
||||||
|
import { MessageDecoder, decoders } from '../../../decoders'
|
||||||
|
import { Tooltip } from '@material-ui/core'
|
||||||
|
|
||||||
|
export const TopicTypeButton = (props: { node?: q.TreeNode<any> }) => {
|
||||||
|
const { node } = props
|
||||||
|
if (!node || !node.message || !node.message.payload) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = decoders.flatMap(decoder => decoder.formats.map(format => [decoder, format] as const))
|
||||||
|
|
||||||
|
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null)
|
||||||
|
const [open, setOpen] = React.useState(false)
|
||||||
|
|
||||||
|
const selectOption = useCallback(
|
||||||
|
(decoder: MessageDecoder, format: string) => {
|
||||||
|
if (!node) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
node.viewModel.decoder = { decoder, format }
|
||||||
|
setOpen(false)
|
||||||
|
},
|
||||||
|
[node]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleToggle = useCallback(
|
||||||
|
(event: React.MouseEvent<HTMLElement>) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
if (open === true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setAnchorEl(event.currentTarget)
|
||||||
|
setOpen(prevOpen => !prevOpen)
|
||||||
|
},
|
||||||
|
[open]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleClose = useCallback((event: React.MouseEvent<Document, MouseEvent>) => {
|
||||||
|
if (anchorEl && anchorEl.contains(event.target as HTMLElement)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setOpen(false)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button onClick={handleToggle}>
|
||||||
|
{props.node?.viewModel.decoder?.format ?? props.node?.type}
|
||||||
|
<Popper open={open} anchorEl={anchorEl} role={undefined} transition>
|
||||||
|
{({ TransitionProps, placement }) => (
|
||||||
|
<Grow
|
||||||
|
{...TransitionProps}
|
||||||
|
style={{
|
||||||
|
transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Paper>
|
||||||
|
<ClickAwayListener onClickAway={handleClose}>
|
||||||
|
<MenuList id="topicTypeMode">
|
||||||
|
{options.map(([decoder, format], index) => (
|
||||||
|
<MenuItem
|
||||||
|
key={format}
|
||||||
|
selected={node && format === node.type}
|
||||||
|
onClick={() => selectOption(decoder, format)}
|
||||||
|
>
|
||||||
|
<DecoderStatus decoder={decoder} format={format} node={node} />
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</MenuList>
|
||||||
|
</ClickAwayListener>
|
||||||
|
</Paper>
|
||||||
|
</Grow>
|
||||||
|
)}
|
||||||
|
</Popper>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DecoderStatus({ node, decoder, format }: { node: q.TreeNode<any>; decoder: MessageDecoder; format: string }) {
|
||||||
|
const decoded = useMemo(() => {
|
||||||
|
return node.message?.payload && decoder.decode(node.message?.payload, format)
|
||||||
|
}, [node.message, decoder, format])
|
||||||
|
|
||||||
|
return decoded?.error ? (
|
||||||
|
<Tooltip title={decoded.error}>
|
||||||
|
<div>
|
||||||
|
{format} <WarningRounded />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<>{format}</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import Copy from '../../helper/Copy'
|
|||||||
import DateFormatter from '../../helper/DateFormatter'
|
import DateFormatter from '../../helper/DateFormatter'
|
||||||
import History from '../HistoryDrawer'
|
import History from '../HistoryDrawer'
|
||||||
import TopicPlot from '../../TopicPlot'
|
import TopicPlot from '../../TopicPlot'
|
||||||
import { Base64Message } from '../../../../../backend/src/Model/Base64Message'
|
|
||||||
import { isPlottable } from '../CodeDiff/util'
|
import { isPlottable } from '../CodeDiff/util'
|
||||||
import { TopicViewModel } from '../../../model/TopicViewModel'
|
import { TopicViewModel } from '../../../model/TopicViewModel'
|
||||||
import { bindActionCreators } from 'redux'
|
import { bindActionCreators } from 'redux'
|
||||||
@@ -13,6 +12,8 @@ import { chartActions } from '../../../actions'
|
|||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import CustomIconButton from '../../helper/CustomIconButton'
|
import CustomIconButton from '../../helper/CustomIconButton'
|
||||||
import { MessageId } from '../MessageId'
|
import { MessageId } from '../MessageId'
|
||||||
|
import { useSubscription } from '../../hooks/useSubscription'
|
||||||
|
import { useDecoder } from '../../hooks/useDecoder'
|
||||||
|
|
||||||
const throttle = require('lodash.throttle')
|
const throttle = require('lodash.throttle')
|
||||||
|
|
||||||
@@ -25,117 +26,100 @@ interface Props {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
export const MessageHistory: React.FC<Props> = props => {
|
||||||
displayMessage?: q.Message
|
const [, setLastUpdate] = React.useState(Date.now())
|
||||||
anchorEl?: HTMLElement
|
const updateNodeThrottled = React.useCallback(
|
||||||
lastUpdate: number
|
throttle(() => {
|
||||||
}
|
setLastUpdate
|
||||||
|
}, 300),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
class MessageHistory extends React.PureComponent<Props, State> {
|
useSubscription(props.node?.onMessage, updateNodeThrottled)
|
||||||
private updateNode = throttle(() => {
|
const decodeMessage = useDecoder(props.node)
|
||||||
this.setState({ lastUpdate: Date.now() })
|
|
||||||
}, 300)
|
|
||||||
|
|
||||||
constructor(props: any) {
|
function addNodeToCharts(event: React.MouseEvent) {
|
||||||
super(props)
|
|
||||||
this.state = { lastUpdate: 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
private addNodeToCharts = (event: React.MouseEvent) => {
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
const { node } = this.props
|
const { node } = props
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.actions.charts.addChart({ topic: node.path() })
|
props.actions.charts.addChart({ topic: node.path() })
|
||||||
}
|
}
|
||||||
|
|
||||||
private displayMessage = (index: number, eventTarget: EventTarget) => {
|
function displayMessage(index: number, eventTarget: EventTarget) {
|
||||||
const message = this.props.node && this.props.node.messageHistory.toArray().reverse()[index]
|
const message = props.node && props.node.messageHistory.toArray().reverse()[index]
|
||||||
if (message) {
|
if (message) {
|
||||||
this.props.onSelect(message)
|
props.onSelect(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillReceiveProps(nextProps: Props) {
|
const { node } = props
|
||||||
this.props.node && this.props.node.onMessage.unsubscribe(this.updateNode)
|
if (!node) {
|
||||||
nextProps.node && nextProps.node.onMessage.subscribe(this.updateNode)
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount() {
|
const history = node.messageHistory.toArray()
|
||||||
this.props.node && this.props.node.onMessage.subscribe(this.updateNode)
|
let previousMessage: q.Message | undefined = node.message
|
||||||
}
|
const historyElements = [...history].reverse().map((message, idx) => {
|
||||||
|
const value = node.message ? decodeMessage(message)?.message?.format()[0] ?? null : null
|
||||||
|
|
||||||
public componentWillUnMount() {
|
const element = {
|
||||||
this.props.node && this.props.node.onMessage.unsubscribe(this.updateNode)
|
value: value ?? '',
|
||||||
}
|
key: `${message.messageNumber}-${message.received}`,
|
||||||
|
title: (
|
||||||
public render() {
|
<span>
|
||||||
const { node } = this.props
|
<div style={{ float: 'left' }}>
|
||||||
if (!node) {
|
<DateFormatter date={message.received} />
|
||||||
return null
|
{previousMessage && previousMessage !== message ? (
|
||||||
}
|
<i>
|
||||||
|
(-
|
||||||
const history = node.messageHistory.toArray()
|
<DateFormatter date={message.received} intervalSince={previousMessage.received} />)
|
||||||
let previousMessage: q.Message | undefined = node.message
|
</i>
|
||||||
const historyElements = [...history].reverse().map((message, idx) => {
|
) : null}
|
||||||
const value = message.payload ? Base64Message.toUnicodeString(message.payload) : ''
|
</div>
|
||||||
const element = {
|
|
||||||
value,
|
|
||||||
key: `${message.messageNumber}-${message.received}`,
|
|
||||||
title: (
|
|
||||||
<span>
|
<span>
|
||||||
<div style={{ float: 'left' }}>
|
|
||||||
<DateFormatter date={message.received} />
|
<MessageId message={message} />
|
||||||
{previousMessage && previousMessage !== message ? (
|
|
||||||
<i>
|
|
||||||
(-
|
|
||||||
<DateFormatter date={message.received} intervalSince={previousMessage.received} />)
|
|
||||||
</i>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
<span>
|
|
||||||
|
|
||||||
<MessageId message={message} />
|
|
||||||
</span>
|
|
||||||
<div style={{ float: 'right' }}>
|
|
||||||
<Copy value={value} />
|
|
||||||
</div>
|
|
||||||
</span>
|
</span>
|
||||||
),
|
<div style={{ float: 'right' }}>
|
||||||
selected: message && message === this.props.selected,
|
<Copy value={value ?? ''} />
|
||||||
}
|
</div>
|
||||||
previousMessage = message
|
</span>
|
||||||
return element
|
),
|
||||||
})
|
selected: message && message === props.selected,
|
||||||
|
}
|
||||||
|
previousMessage = message
|
||||||
|
return element
|
||||||
|
})
|
||||||
|
|
||||||
const isMessagePlottable =
|
const value = node.message ? decodeMessage(node.message)?.message?.format()[0] ?? null : null
|
||||||
node.message && node.message.payload && isPlottable(Base64Message.toUnicodeString(node.message.payload))
|
|
||||||
return (
|
const isMessagePlottable = isPlottable(value)
|
||||||
<div>
|
return (
|
||||||
<History
|
<div>
|
||||||
items={historyElements}
|
<History
|
||||||
contentTypeIndicator={
|
items={historyElements}
|
||||||
isMessagePlottable ? (
|
contentTypeIndicator={
|
||||||
<CustomIconButton
|
isMessagePlottable ? (
|
||||||
style={{ height: '22px', width: '22px' }}
|
<CustomIconButton
|
||||||
onClick={this.addNodeToCharts}
|
style={{ height: '22px', width: '22px' }}
|
||||||
tooltip="Add to chart panel"
|
onClick={addNodeToCharts}
|
||||||
>
|
tooltip="Add to chart panel"
|
||||||
<ShowChart style={{ marginTop: '-5px' }} />
|
>
|
||||||
</CustomIconButton>
|
<ShowChart style={{ marginTop: '-5px' }} />
|
||||||
) : undefined
|
</CustomIconButton>
|
||||||
}
|
) : undefined
|
||||||
onClick={this.displayMessage}
|
}
|
||||||
>
|
onClick={displayMessage}
|
||||||
{isMessagePlottable ? <TopicPlot history={node.messageHistory} /> : null}
|
>
|
||||||
</History>
|
{isMessagePlottable ? <TopicPlot node={node} history={node.messageHistory} /> : null}
|
||||||
</div>
|
</History>
|
||||||
)
|
</div>
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: any) => {
|
const mapDispatchToProps = (dispatch: any) => {
|
||||||
@@ -144,4 +128,4 @@ const mapDispatchToProps = (dispatch: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(MessageHistory)
|
export default connect(null, mapDispatchToProps)(React.memo(MessageHistory))
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import Panel from '../Panel'
|
|||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import ValueRenderer from './ValueRenderer'
|
import ValueRenderer from './ValueRenderer'
|
||||||
import { AppState } from '../../../reducers'
|
import { AppState } from '../../../reducers'
|
||||||
import { Base64Message } from '../../../../../backend/src/Model/Base64Message'
|
|
||||||
import { bindActionCreators } from 'redux'
|
import { bindActionCreators } from 'redux'
|
||||||
import { Theme, Typography, withStyles } from '@material-ui/core'
|
import { Theme, Typography, withStyles } from '@material-ui/core'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { sidebarActions } from '../../../actions'
|
import { sidebarActions } from '../../../actions'
|
||||||
import DeleteSelectedTopicButton from './DeleteSelectedTopicButton'
|
import DeleteSelectedTopicButton from './DeleteSelectedTopicButton'
|
||||||
import { MessageId } from '../MessageId'
|
import { MessageId } from '../MessageId'
|
||||||
|
import { useDecoder } from '../../hooks/useDecoder'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
node?: q.TreeNode<any>
|
node?: q.TreeNode<any>
|
||||||
@@ -35,6 +35,7 @@ function RenderedValue(props: { node?: q.TreeNode<any>; compareMessage?: q.Messa
|
|||||||
|
|
||||||
function ValuePanel(props: Props) {
|
function ValuePanel(props: Props) {
|
||||||
const { node, compareMessage } = props
|
const { node, compareMessage } = props
|
||||||
|
const decodeMessage = useDecoder(node)
|
||||||
|
|
||||||
function renderViewOptions() {
|
function renderViewOptions() {
|
||||||
if (!props.node || !props.node.message) {
|
if (!props.node || !props.node.message) {
|
||||||
@@ -54,6 +55,10 @@ function ValuePanel(props: Props) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getDecodedValue = useCallback(() => {
|
||||||
|
return node?.message && decodeMessage(node.message)?.message?.toUnicodeString()
|
||||||
|
}, [node, decodeMessage])
|
||||||
|
|
||||||
function messageMetaInfo() {
|
function messageMetaInfo() {
|
||||||
if (!props.node || !props.node.message) {
|
if (!props.node || !props.node.message) {
|
||||||
return null
|
return null
|
||||||
@@ -85,10 +90,9 @@ function ValuePanel(props: Props) {
|
|||||||
[compareMessage]
|
[compareMessage]
|
||||||
)
|
)
|
||||||
|
|
||||||
const copyValue =
|
const [value] =
|
||||||
node && node.message && node.message.payload ? (
|
node && node.message && node.message.payload ? node.message.payload?.format(node.type) : [null, undefined]
|
||||||
<Copy value={Base64Message.toUnicodeString(node.message.payload)} />
|
const copyValue = value ? <Copy getValue={getDecodedValue} /> : null
|
||||||
) : null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel>
|
<Panel>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import * as q from '../../../../../backend/src/Model'
|
import * as q from '../../../../../backend/src/Model'
|
||||||
import * as React from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import CodeDiff from '../CodeDiff'
|
import CodeDiff from '../CodeDiff'
|
||||||
import { AppState } from '../../../reducers'
|
import { AppState } from '../../../reducers'
|
||||||
import { Base64Message } from '../../../../../backend/src/Model/Base64Message'
|
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { ValueRendererDisplayMode } from '../../../reducers/Settings'
|
import { ValueRendererDisplayMode } from '../../../reducers/Settings'
|
||||||
import { Fade } from '@material-ui/core'
|
import { Fade } from '@material-ui/core'
|
||||||
import { Decoder } from '../../../../../backend/src/Model/Decoder'
|
import { Decoder } from '../../../../../backend/src/Model/Decoder'
|
||||||
|
import { useDecoder } from '../../hooks/useDecoder'
|
||||||
|
import { TopicViewModel } from '../../../model/TopicViewModel'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
message: q.Message
|
message: q.Message
|
||||||
@@ -15,103 +16,114 @@ interface Props {
|
|||||||
renderMode: ValueRendererDisplayMode
|
renderMode: ValueRendererDisplayMode
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
type Language = 'json'
|
||||||
width: number
|
|
||||||
|
function renderDiff(
|
||||||
|
treeNode: q.TreeNode<TopicViewModel>,
|
||||||
|
compareWithPreviousMessage: boolean,
|
||||||
|
current: string = '',
|
||||||
|
previous: string = '',
|
||||||
|
title?: string,
|
||||||
|
language?: Language
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<CodeDiff
|
||||||
|
treeNode={treeNode}
|
||||||
|
previous={previous}
|
||||||
|
current={current}
|
||||||
|
title={title}
|
||||||
|
language={language}
|
||||||
|
nameOfCompareMessage={compareWithPreviousMessage ? 'selected' : 'previous'}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ValueRenderer extends React.Component<Props, State> {
|
function renderDiffMode(
|
||||||
constructor(props: Props) {
|
treeNode: q.TreeNode<TopicViewModel>,
|
||||||
super(props)
|
currentStr: string | undefined,
|
||||||
this.state = { width: 0 }
|
compareStr: string | undefined,
|
||||||
}
|
currentType: Language | undefined,
|
||||||
|
compareType: Language | undefined,
|
||||||
|
compareWithPreviousMessage: boolean
|
||||||
|
) {
|
||||||
|
const language = currentType === compareType && compareType === 'json' ? 'json' : undefined
|
||||||
|
|
||||||
private renderDiff(current: string = '', previous: string = '', title?: string, language?: 'json') {
|
return <div>{renderDiff(treeNode, compareWithPreviousMessage, currentStr, compareStr, undefined, language)}</div>
|
||||||
return (
|
}
|
||||||
<CodeDiff
|
|
||||||
treeNode={this.props.treeNode}
|
|
||||||
previous={previous}
|
|
||||||
current={current}
|
|
||||||
title={title}
|
|
||||||
language={language}
|
|
||||||
nameOfCompareMessage={this.props.compareWith ? 'selected' : 'previous'}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private convertMessage(msg?: Base64Message): [string | undefined, 'json' | undefined] {
|
function renderRawMode(
|
||||||
if (!msg) {
|
treeNode: q.TreeNode<TopicViewModel>,
|
||||||
return [undefined, undefined]
|
currentStr: string | undefined,
|
||||||
}
|
compareStr: string | undefined,
|
||||||
|
currentType: Language | undefined,
|
||||||
|
compareType: Language | undefined,
|
||||||
|
compareWithPreviousMessage: boolean
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{renderDiff(treeNode, compareWithPreviousMessage, currentStr, currentStr, undefined, currentType)}
|
||||||
|
<Fade in={Boolean(compareStr)} timeout={400}>
|
||||||
|
<div>
|
||||||
|
{Boolean(compareStr)
|
||||||
|
? renderDiff(treeNode, compareWithPreviousMessage, compareStr, compareStr, 'selected', compareType)
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
</Fade>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const str = Base64Message.toUnicodeString(msg)
|
export const ValueRenderer: React.FC<Props> = ({ treeNode, compareWith: compare, message, renderMode }) => {
|
||||||
try {
|
const decodeMessage = useDecoder(treeNode)
|
||||||
JSON.parse(str)
|
const decodedMessage = useMemo(() => decodeMessage(message), [decodeMessage, message])
|
||||||
} catch (error) {
|
|
||||||
return [str, undefined]
|
|
||||||
}
|
|
||||||
|
|
||||||
return [this.messageToPrettyJson(str), 'json']
|
const previousMessages = treeNode.messageHistory.toArray()
|
||||||
}
|
const previousMessage = previousMessages[previousMessages.length - 2]
|
||||||
|
const compareMessage = compare || previousMessage || message
|
||||||
|
const compareWithPreviousMessage = !!compare
|
||||||
|
|
||||||
private messageToPrettyJson(str: string): string | undefined {
|
const [currentStr, currentType] = useMemo(
|
||||||
try {
|
() => decodedMessage?.message?.format(treeNode.type) ?? [],
|
||||||
const json = JSON.parse(str)
|
[decodedMessage, treeNode.type]
|
||||||
return JSON.stringify(json, undefined, ' ')
|
)
|
||||||
} catch {
|
const [compareStr, compareType] = useMemo(
|
||||||
return undefined
|
() => decodeMessage(compareMessage)?.message?.format(treeNode.type) ?? [],
|
||||||
}
|
[compareMessage, decodeMessage, treeNode.type]
|
||||||
}
|
)
|
||||||
|
|
||||||
private renderRawMode(message: q.Message, compare?: q.Message) {
|
function renderValue(
|
||||||
if (!message.payload) {
|
treeNode: q.TreeNode<TopicViewModel>,
|
||||||
return
|
currentStr: string | undefined,
|
||||||
}
|
compareStr: string | undefined,
|
||||||
const [value, valueLanguage] = this.convertMessage(message.payload)
|
currentType: Language | undefined,
|
||||||
const [compareStr, compareStrLanguage] =
|
compareType: Language | undefined,
|
||||||
compare && compare.payload ? this.convertMessage(compare.payload) : [undefined, undefined]
|
renderMode: string,
|
||||||
|
compareWithPreviousMessage: boolean
|
||||||
return (
|
) {
|
||||||
<div>
|
if (!decodedMessage) {
|
||||||
{this.renderDiff(value, value, undefined, valueLanguage)}
|
|
||||||
<Fade in={Boolean(compareStr)} timeout={400}>
|
|
||||||
<div>
|
|
||||||
{Boolean(compareStr) ? this.renderDiff(compareStr, compareStr, 'selected', compareStrLanguage) : null}
|
|
||||||
</div>
|
|
||||||
</Fade>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
public render() {
|
|
||||||
return (
|
|
||||||
<div style={{ padding: '0px 0px 8px 0px', width: '100%' }}>
|
|
||||||
{this.props.message?.payload?.decoder === Decoder.SPARKPLUG && 'Decoded SparkplugB'}
|
|
||||||
{this.renderValue()}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderValue() {
|
|
||||||
const { message, treeNode, compareWith, renderMode } = this.props
|
|
||||||
const previousMessages = treeNode.messageHistory.toArray()
|
|
||||||
const previousMessage = previousMessages[previousMessages.length - 2]
|
|
||||||
const compareMessage = compareWith || previousMessage || message
|
|
||||||
|
|
||||||
if (renderMode === 'raw') {
|
|
||||||
return this.renderRawMode(message, compareWith)
|
|
||||||
}
|
|
||||||
if (!message.payload) {
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const compareValue = compareMessage.payload || message.payload
|
switch (renderMode) {
|
||||||
const [current, currentLanguage] = this.convertMessage(message.payload)
|
case 'diff':
|
||||||
const [compare, compareLanguage] = this.convertMessage(compareValue)
|
return renderDiffMode(treeNode, currentStr, compareStr, currentType, compareType, compareWithPreviousMessage)
|
||||||
|
default:
|
||||||
const language = currentLanguage === compareLanguage && compareLanguage === 'json' ? 'json' : undefined
|
return renderRawMode(treeNode, currentStr, compareStr, currentType, compareType, compareWithPreviousMessage)
|
||||||
|
}
|
||||||
return this.renderDiff(current, compare, undefined, language)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderedValue = useMemo(
|
||||||
|
() =>
|
||||||
|
renderValue(treeNode, currentStr, compareStr, currentType, compareType, renderMode, compareWithPreviousMessage),
|
||||||
|
[treeNode, currentStr, compareStr, currentType, compareType, renderMode, compareWithPreviousMessage]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ padding: '0px 0px 8px 0px', width: '100%' }}>
|
||||||
|
{decodedMessage?.decoder === Decoder.SPARKPLUG && 'Decoded SparkplugB'}
|
||||||
|
{renderedValue}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ import * as dotProp from 'dot-prop'
|
|||||||
import * as q from '../../../backend/src/Model'
|
import * as q from '../../../backend/src/Model'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import PlotHistory from './Chart/Chart'
|
import PlotHistory from './Chart/Chart'
|
||||||
import { Base64Message } from '../../../backend/src/Model/Base64Message'
|
|
||||||
import { toPlottableValue } from './Sidebar/CodeDiff/util'
|
import { toPlottableValue } from './Sidebar/CodeDiff/util'
|
||||||
import { PlotCurveTypes } from '../reducers/Charts'
|
import { PlotCurveTypes } from '../reducers/Charts'
|
||||||
|
import { DecoderFunction, useDecoder } from './hooks/useDecoder'
|
||||||
|
|
||||||
const parseDuration = require('parse-duration')
|
const parseDuration = require('parse-duration')
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
node?: q.TreeNode<any>
|
||||||
history: q.MessageHistory
|
history: q.MessageHistory
|
||||||
dotPath?: string
|
dotPath?: string
|
||||||
timeInterval?: string
|
timeInterval?: string
|
||||||
@@ -25,21 +27,27 @@ function filterUsingTimeRange(startTime: number | undefined, data: Array<q.Messa
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
function nodeToHistory(startTime: number | undefined, history: q.MessageHistory) {
|
function nodeToHistory(decodeMessage: DecoderFunction, startTime: number | undefined, history: q.MessageHistory) {
|
||||||
return filterUsingTimeRange(startTime, history.toArray())
|
return filterUsingTimeRange(startTime, history.toArray())
|
||||||
.map((message: q.Message) => {
|
.map((message: q.Message) => {
|
||||||
const value = message.payload ? toPlottableValue(Base64Message.toUnicodeString(message.payload)) : NaN
|
const decoded = decodeMessage(message)?.message?.toUnicodeString()
|
||||||
return { x: message.received.getTime(), y: toPlottableValue(value) }
|
return { x: message.received.getTime(), y: toPlottableValue(decoded) }
|
||||||
})
|
})
|
||||||
.filter(data => !isNaN(data.y as any)) as any
|
.filter(data => !isNaN(data.y as any)) as any
|
||||||
}
|
}
|
||||||
|
|
||||||
function nodeDotPathToHistory(startTime: number | undefined, history: q.MessageHistory, dotPath: string) {
|
function nodeDotPathToHistory(
|
||||||
|
decodeMessage: DecoderFunction,
|
||||||
|
startTime: number | undefined,
|
||||||
|
history: q.MessageHistory,
|
||||||
|
dotPath: string
|
||||||
|
) {
|
||||||
return filterUsingTimeRange(startTime, history.toArray())
|
return filterUsingTimeRange(startTime, history.toArray())
|
||||||
.map((message: q.Message) => {
|
.map((message: q.Message) => {
|
||||||
let json: any = {}
|
let json: any = {}
|
||||||
try {
|
try {
|
||||||
json = message.payload ? JSON.parse(Base64Message.toUnicodeString(message.payload)) : {}
|
const decoded = decodeMessage(message)?.message
|
||||||
|
json = decoded ? JSON.parse(decoded.toUnicodeString()) : {}
|
||||||
} catch (ignore) {}
|
} catch (ignore) {}
|
||||||
|
|
||||||
const value = dotProp.get(json, dotPath)
|
const value = dotProp.get(json, dotPath)
|
||||||
@@ -50,14 +58,17 @@ function nodeDotPathToHistory(startTime: number | undefined, history: q.MessageH
|
|||||||
}
|
}
|
||||||
|
|
||||||
function TopicPlot(props: Props) {
|
function TopicPlot(props: Props) {
|
||||||
|
const decodeMessage = useDecoder(props.node)
|
||||||
const startOffset = props.timeInterval ? parseDuration(props.timeInterval) : undefined
|
const startOffset = props.timeInterval ? parseDuration(props.timeInterval) : undefined
|
||||||
const data = React.useMemo(
|
const data = React.useMemo(() => {
|
||||||
() =>
|
if (!props.node) {
|
||||||
props.dotPath
|
return []
|
||||||
? nodeDotPathToHistory(startOffset, props.history, props.dotPath)
|
}
|
||||||
: nodeToHistory(startOffset, props.history),
|
|
||||||
[props.history.last(), startOffset, props.dotPath]
|
return props.dotPath
|
||||||
)
|
? nodeDotPathToHistory(decodeMessage, startOffset, props.history, props.dotPath)
|
||||||
|
: nodeToHistory(decodeMessage, startOffset, props.history)
|
||||||
|
}, [props.history.last(), startOffset, props.dotPath])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlotHistory
|
<PlotHistory
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as q from '../../../../../backend/src/Model'
|
import * as q from '../../../../../backend/src/Model'
|
||||||
import React, { memo } from 'react'
|
import React, { memo } from 'react'
|
||||||
import { Base64Message } from '../../../../../backend/src/Model/Base64Message'
|
|
||||||
import { Theme, withStyles } from '@material-ui/core'
|
import { Theme, withStyles } from '@material-ui/core'
|
||||||
import { TopicViewModel } from '../../../model/TopicViewModel'
|
import { TopicViewModel } from '../../../model/TopicViewModel'
|
||||||
|
import { useDecoder } from '../../hooks/useDecoder'
|
||||||
|
|
||||||
export interface TreeNodeProps extends React.HTMLAttributes<HTMLElement> {
|
export interface TreeNodeProps extends React.HTMLAttributes<HTMLElement> {
|
||||||
treeNode: q.TreeNode<TopicViewModel>
|
treeNode: q.TreeNode<TopicViewModel>
|
||||||
@@ -14,67 +14,72 @@ export interface TreeNodeProps extends React.HTMLAttributes<HTMLElement> {
|
|||||||
classes: any
|
classes: any
|
||||||
}
|
}
|
||||||
|
|
||||||
class TreeNodeTitle extends React.PureComponent<TreeNodeProps, {}> {
|
export const TreeNodeTitle = (props: TreeNodeProps) => {
|
||||||
private renderSourceEdge() {
|
const decodeMessage = useDecoder(props.treeNode)
|
||||||
const name = this.props.name || (this.props.treeNode.sourceEdge && this.props.treeNode.sourceEdge.name)
|
|
||||||
|
function renderSourceEdge() {
|
||||||
|
const name = props.name || (props.treeNode.sourceEdge && props.treeNode.sourceEdge.name)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span key="edge" className={this.props.classes.sourceEdge} data-test-topic={name}>
|
<span key="edge" className={props.classes.sourceEdge} data-test-topic={name}>
|
||||||
{name}
|
{name}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private truncatedMessage() {
|
function truncatedMessage() {
|
||||||
const limit = 400
|
const limit = 400
|
||||||
if (!this.props.treeNode.message || !this.props.treeNode.message.payload) {
|
if (!props.treeNode.message || !props.treeNode.message.payload) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
const [value = ''] = decodeMessage(props.treeNode.message)?.message?.format(props.treeNode.type) ?? []
|
||||||
|
|
||||||
const str = Base64Message.toUnicodeString(this.props.treeNode.message.payload)
|
return value.length > limit ? `${value.slice(0, limit)}…` : value
|
||||||
return str.length > limit ? `${str.slice(0, limit)}…` : str
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderValue() {
|
function renderValue() {
|
||||||
return this.props.treeNode.message &&
|
return props.treeNode.message && props.treeNode.message.payload && props.treeNode.message.length > 0 ? (
|
||||||
this.props.treeNode.message.payload &&
|
<span key="value" className={props.classes.value}>
|
||||||
this.props.treeNode.message.length > 0 ? (
|
|
||||||
<span key="value" className={this.props.classes.value}>
|
|
||||||
{' '}
|
{' '}
|
||||||
= {this.truncatedMessage()}
|
= {truncatedMessage()}
|
||||||
</span>
|
</span>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderExpander() {
|
function renderExpander() {
|
||||||
if (this.props.treeNode.edgeCount() === 0) {
|
if (props.treeNode.edgeCount() === 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span key="expander" className={this.props.classes.expander} onClick={this.props.toggleCollapsed}>
|
<span key="expander" className={props.classes.expander} onClick={props.toggleCollapsed}>
|
||||||
{this.props.collapsed ? '▶' : '▼'}
|
{props.collapsed ? '▶' : '▼'}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderMetadata() {
|
function renderMetadata() {
|
||||||
if (this.props.treeNode.edgeCount() === 0 || !this.props.collapsed) {
|
if (props.treeNode.edgeCount() === 0 || !props.collapsed) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = this.props.treeNode.leafMessageCount()
|
const messages = props.treeNode.leafMessageCount()
|
||||||
const topicCount = this.props.treeNode.childTopicCount()
|
const topicCount = props.treeNode.childTopicCount()
|
||||||
return (
|
return (
|
||||||
<span key="metadata" className={this.props.classes.collapsedSubnodes}>{` (${topicCount} ${
|
<span key="metadata" className={props.classes.collapsedSubnodes}>{` (${topicCount} ${
|
||||||
topicCount === 1 ? 'topic' : 'topics'
|
topicCount === 1 ? 'topic' : 'topics'
|
||||||
}, ${messages} ${messages === 1 ? 'message' : 'messages'})`}</span>
|
}, ${messages} ${messages === 1 ? 'message' : 'messages'})`}</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
return (
|
||||||
return [this.renderExpander(), this.renderSourceEdge(), this.renderMetadata(), this.renderValue()]
|
<>
|
||||||
}
|
{renderExpander()}
|
||||||
|
{renderSourceEdge()}
|
||||||
|
{renderMetadata()}
|
||||||
|
{renderValue()}
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = (theme: Theme) => ({
|
const styles = (theme: Theme) => ({
|
||||||
|
|||||||
18
app/src/components/Tree/TreeNode/effects/useViewModel.tsx
Normal file
18
app/src/components/Tree/TreeNode/effects/useViewModel.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import * as q from '../../../../../../backend/src/Model'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { TopicViewModel } from '../../../../model/TopicViewModel'
|
||||||
|
|
||||||
|
export function useViewModel(treeNode: q.TreeNode<TopicViewModel> | undefined) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (treeNode && !treeNode?.viewModel) {
|
||||||
|
treeNode.viewModel = new TopicViewModel(treeNode)
|
||||||
|
}
|
||||||
|
treeNode?.viewModel?.retain()
|
||||||
|
|
||||||
|
return function cleanup() {
|
||||||
|
treeNode?.viewModel?.release()
|
||||||
|
}
|
||||||
|
}, [treeNode])
|
||||||
|
|
||||||
|
return treeNode?.viewModel
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import * as q from '../../../../../../backend/src/Model'
|
import * as q from '../../../../../../backend/src/Model'
|
||||||
import React, { useEffect } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { TopicViewModel } from '../../../../model/TopicViewModel'
|
import { TopicViewModel } from '../../../../model/TopicViewModel'
|
||||||
|
import { useSubscription } from '../../../hooks/useSubscription'
|
||||||
|
import { useViewModel } from './useViewModel'
|
||||||
|
|
||||||
export function useViewModelSubscriptions(
|
export function useViewModelSubscriptions(
|
||||||
treeNode: q.TreeNode<TopicViewModel>,
|
treeNode: q.TreeNode<TopicViewModel>,
|
||||||
@@ -8,37 +10,21 @@ export function useViewModelSubscriptions(
|
|||||||
setSelected: (value: boolean) => void,
|
setSelected: (value: boolean) => void,
|
||||||
setCollapsedOverride: (value: boolean) => void
|
setCollapsedOverride: (value: boolean) => void
|
||||||
) {
|
) {
|
||||||
useEffect(() => {
|
const viewModel = useViewModel(treeNode)
|
||||||
const selectionDidChange = () => {
|
|
||||||
const selected = treeNode.viewModel && treeNode.viewModel.isSelected()
|
|
||||||
treeNode.viewModel && setSelected(Boolean(selected))
|
|
||||||
|
|
||||||
if (selected && nodeRef && nodeRef.current) {
|
const selectionDidChange = useCallback(() => {
|
||||||
nodeRef.current.focus({ preventScroll: false })
|
const selected = viewModel && viewModel.isSelected()
|
||||||
}
|
viewModel && setSelected(Boolean(selected))
|
||||||
}
|
|
||||||
|
|
||||||
const expandedDidChange = () => {
|
if (selected && nodeRef && nodeRef.current) {
|
||||||
treeNode.viewModel && setCollapsedOverride(!treeNode.viewModel.isExpanded())
|
nodeRef.current.focus({ preventScroll: false })
|
||||||
}
|
}
|
||||||
|
}, [viewModel])
|
||||||
|
|
||||||
function addSubscriber() {
|
const expandedDidChange = useCallback(() => {
|
||||||
treeNode.viewModel = new TopicViewModel()
|
viewModel && setCollapsedOverride(!viewModel.isExpanded())
|
||||||
treeNode.viewModel.selectionChange.subscribe(selectionDidChange)
|
}, [viewModel])
|
||||||
treeNode.viewModel.expandedChange.subscribe(expandedDidChange)
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeSubscriber() {
|
useSubscription(viewModel?.selectionChange, selectionDidChange)
|
||||||
if (treeNode.viewModel) {
|
useSubscription(viewModel?.expandedChange, expandedDidChange)
|
||||||
treeNode.viewModel.selectionChange.unsubscribe(selectionDidChange)
|
|
||||||
treeNode.viewModel.expandedChange.unsubscribe(expandedDidChange)
|
|
||||||
treeNode.viewModel = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addSubscriber()
|
|
||||||
return function cleanup() {
|
|
||||||
removeSubscriber()
|
|
||||||
}
|
|
||||||
}, [treeNode])
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as compareVersions from 'compare-versions'
|
import compareVersions from 'compare-versions'
|
||||||
import * as electron from 'electron'
|
import electron from 'electron'
|
||||||
import * as os from 'os'
|
import os from 'os'
|
||||||
import * as React from 'react'
|
import React from 'react'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import CloudDownload from '@material-ui/icons/CloudDownload'
|
import CloudDownload from '@material-ui/icons/CloudDownload'
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import { globalActions } from '../../actions'
|
|||||||
const copy = require('copy-text-to-clipboard')
|
const copy = require('copy-text-to-clipboard')
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value: string
|
value?: string
|
||||||
|
getValue?: () => string | undefined
|
||||||
actions: {
|
actions: {
|
||||||
global: typeof globalActions
|
global: typeof globalActions
|
||||||
}
|
}
|
||||||
@@ -28,7 +29,7 @@ class Copy extends React.PureComponent<Props, State> {
|
|||||||
private handleClick = (event: React.MouseEvent) => {
|
private handleClick = (event: React.MouseEvent) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
copy(this.props.value)
|
copy(this.props.value ?? this.props.getValue?.())
|
||||||
this.props.actions.global.showNotification('Copied to clipboard')
|
this.props.actions.global.showNotification('Copied to clipboard')
|
||||||
this.setState({ didCopy: true })
|
this.setState({ didCopy: true })
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as moment from 'moment'
|
import moment from 'moment'
|
||||||
import * as React from 'react'
|
import React from 'react'
|
||||||
import { AppState } from '../../reducers'
|
import { AppState } from '../../reducers'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
|
||||||
@@ -12,6 +12,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const unitMapping = {
|
const unitMapping = {
|
||||||
|
ms: 'milliseconds',
|
||||||
s: 'seconds',
|
s: 'seconds',
|
||||||
m: 'minutes',
|
m: 'minutes',
|
||||||
h: 'hours',
|
h: 'hours',
|
||||||
@@ -21,7 +22,7 @@ class DateFormatter extends React.PureComponent<Props, {}> {
|
|||||||
private intervalSince(intervalSince: Date) {
|
private intervalSince(intervalSince: Date) {
|
||||||
const interval = intervalSince.getTime() - this.props.date.getTime()
|
const interval = intervalSince.getTime() - this.props.date.getTime()
|
||||||
const unit = this.unitForInterval(interval)
|
const unit = this.unitForInterval(interval)
|
||||||
return `${Math.round(moment.duration(interval).as(unit) * 100) / 100} ${unitMapping[unit]}`
|
return `${moment.duration(interval).as(unit).toFixed(3)} ${unitMapping[unit]}`
|
||||||
}
|
}
|
||||||
|
|
||||||
private legacyDate() {
|
private legacyDate() {
|
||||||
@@ -31,10 +32,11 @@ class DateFormatter extends React.PureComponent<Props, {}> {
|
|||||||
private localizedDate(locale: string) {
|
private localizedDate(locale: string) {
|
||||||
return moment(this.props.date)
|
return moment(this.props.date)
|
||||||
.locale(locale)
|
.locale(locale)
|
||||||
.format(this.props.timeFirst ? 'LTS L' : 'L LTS')
|
.format(this.props.timeFirst ? 'LTS.SSS L' : 'L LTS.SSS')
|
||||||
}
|
}
|
||||||
|
|
||||||
private unitForInterval(milliseconds: number) {
|
private unitForInterval(milliseconds: number) {
|
||||||
|
const oneSecond = 1000 * 1
|
||||||
const oneMinute = 1000 * 60
|
const oneMinute = 1000 * 60
|
||||||
const oneHour = oneMinute * 60
|
const oneHour = oneMinute * 60
|
||||||
|
|
||||||
@@ -46,7 +48,11 @@ class DateFormatter extends React.PureComponent<Props, {}> {
|
|||||||
return 'm'
|
return 'm'
|
||||||
}
|
}
|
||||||
|
|
||||||
return 's'
|
if (milliseconds > oneSecond * 0.5) {
|
||||||
|
return 's'
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'ms'
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
|
|||||||
31
app/src/components/hooks/useDecoder.ts
Normal file
31
app/src/components/hooks/useDecoder.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import * as q from '../../../../backend/src/Model'
|
||||||
|
import { useCallback, useState } from 'react'
|
||||||
|
import { TopicViewModel } from '../../model/TopicViewModel'
|
||||||
|
import { useSubscription } from './useSubscription'
|
||||||
|
import { useViewModel } from '../Tree/TreeNode/effects/useViewModel'
|
||||||
|
import { DecoderEnvelope } from '../../decoders/DecoderEnvelope'
|
||||||
|
import { Decoder } from '../../../../backend/src/Model/Decoder'
|
||||||
|
|
||||||
|
export type DecoderFunction = (message: q.Message) => DecoderEnvelope | undefined
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the latest decoder for a topic
|
||||||
|
*
|
||||||
|
* @param treeNode
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function useDecoder(treeNode: q.TreeNode<TopicViewModel> | undefined): DecoderFunction {
|
||||||
|
const viewModel = useViewModel(treeNode)
|
||||||
|
const [decoder, setDecoder] = useState(viewModel?.decoder)
|
||||||
|
|
||||||
|
useSubscription(viewModel?.onDecoderChange, setDecoder)
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
message => {
|
||||||
|
return decoder && message.payload
|
||||||
|
? decoder.decoder.decode(message.payload, decoder.format)
|
||||||
|
: { message: message.payload ?? undefined, decoder: Decoder.NONE }
|
||||||
|
},
|
||||||
|
[decoder]
|
||||||
|
)
|
||||||
|
}
|
||||||
10
app/src/components/hooks/useSubscription.ts
Normal file
10
app/src/components/hooks/useSubscription.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
import { EventDispatcher } from '../../../../events'
|
||||||
|
|
||||||
|
export function useSubscription<T>(dispatcher: EventDispatcher<T> | undefined, callback: (value: T) => void) {
|
||||||
|
useEffect(() => {
|
||||||
|
dispatcher?.subscribe(callback)
|
||||||
|
|
||||||
|
return () => dispatcher?.unsubscribe(callback)
|
||||||
|
}, [dispatcher, callback])
|
||||||
|
}
|
||||||
56
app/src/decoders/BinaryDecoder.ts
Normal file
56
app/src/decoders/BinaryDecoder.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { Base64Message } from '../../../backend/src/Model/Base64Message'
|
||||||
|
import { Decoder } from '../../../backend/src/Model/Decoder'
|
||||||
|
import { DecoderEnvelope } from './DecoderEnvelope'
|
||||||
|
import { MessageDecoder } from './MessageDecoder'
|
||||||
|
|
||||||
|
type BinaryFormats =
|
||||||
|
| 'int8'
|
||||||
|
| 'int16'
|
||||||
|
| 'int32'
|
||||||
|
| 'int64'
|
||||||
|
| 'uint8'
|
||||||
|
| 'uint16'
|
||||||
|
| 'uint32'
|
||||||
|
| 'uint64'
|
||||||
|
| 'float'
|
||||||
|
| 'double'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binary decode primitive binary data type and arrays of these
|
||||||
|
*/
|
||||||
|
export const BinaryDecoder: MessageDecoder<BinaryFormats> = {
|
||||||
|
formats: ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64', 'float', 'double'],
|
||||||
|
decode(input: Base64Message, format: BinaryFormats): DecoderEnvelope {
|
||||||
|
const decodingOption = {
|
||||||
|
int8: [Buffer.prototype.readInt8, 1],
|
||||||
|
int16: [Buffer.prototype.readInt16LE, 2],
|
||||||
|
int32: [Buffer.prototype.readInt32LE, 4],
|
||||||
|
int64: [Buffer.prototype.readBigInt64LE, 8],
|
||||||
|
uint8: [Buffer.prototype.readUint8, 1],
|
||||||
|
uint16: [Buffer.prototype.readUint16LE, 2],
|
||||||
|
uint32: [Buffer.prototype.readUint32LE, 4],
|
||||||
|
uint64: [Buffer.prototype.readBigUint64LE, 8],
|
||||||
|
float: [Buffer.prototype.readFloatLE, 4],
|
||||||
|
double: [Buffer.prototype.readDoubleLE, 8],
|
||||||
|
} as const
|
||||||
|
|
||||||
|
const [readNumber, bytesToRead] = decodingOption[format]
|
||||||
|
|
||||||
|
const buf = input.toBuffer()
|
||||||
|
let str: String[] = []
|
||||||
|
if (buf.length % bytesToRead !== 0) {
|
||||||
|
return {
|
||||||
|
error: 'Data type does not align with message',
|
||||||
|
decoder: Decoder.NONE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let index = 0; index < buf.length; index += bytesToRead) {
|
||||||
|
str.push((readNumber as any).apply(buf, [index]).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: Base64Message.fromString(JSON.stringify(str.length === 1 ? str[0] : str)),
|
||||||
|
decoder: Decoder.NONE,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
8
app/src/decoders/DecoderEnvelope.ts
Normal file
8
app/src/decoders/DecoderEnvelope.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { Base64Message } from '../../../backend/src/Model/Base64Message'
|
||||||
|
import { Decoder } from '../../../backend/src/Model/Decoder'
|
||||||
|
|
||||||
|
export interface DecoderEnvelope {
|
||||||
|
message?: Base64Message
|
||||||
|
error?: string
|
||||||
|
decoder: Decoder
|
||||||
|
}
|
||||||
13
app/src/decoders/MessageDecoder.ts
Normal file
13
app/src/decoders/MessageDecoder.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { Base64Message } from '../../../backend/src/Model/Base64Message'
|
||||||
|
import { DecoderEnvelope } from './DecoderEnvelope'
|
||||||
|
|
||||||
|
export interface MessageDecoder<T = string> {
|
||||||
|
/**
|
||||||
|
* Can be used to
|
||||||
|
* @param topic
|
||||||
|
*/
|
||||||
|
formats: T[]
|
||||||
|
canDecodeTopic?(topic: string): boolean
|
||||||
|
canDecodeData?(data: Base64Message): boolean
|
||||||
|
decode(input: Base64Message, format: T | string | undefined): DecoderEnvelope
|
||||||
|
}
|
||||||
28
app/src/decoders/SparkplugBDecoder.ts
Normal file
28
app/src/decoders/SparkplugBDecoder.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { Base64Message } from '../../../backend/src/Model/Base64Message'
|
||||||
|
import { Decoder } from '../../../backend/src/Model/Decoder'
|
||||||
|
import { get } from 'sparkplug-payload'
|
||||||
|
import { MessageDecoder } from './MessageDecoder'
|
||||||
|
var sparkplug = get('spBv1.0')
|
||||||
|
|
||||||
|
export const SparkplugDecoder: MessageDecoder = {
|
||||||
|
formats: ['Sparkplug'],
|
||||||
|
canDecodeTopic(topic: string) {
|
||||||
|
return !!topic.match(/^spBv1\.0\/[^/]+\/[ND](DATA|CMD|DEATH|BIRTH)\/[^/]+(\/[^/]+)?$/u)
|
||||||
|
},
|
||||||
|
decode(input) {
|
||||||
|
try {
|
||||||
|
const message = Base64Message.fromString(
|
||||||
|
JSON.stringify(
|
||||||
|
// @ts-ignore
|
||||||
|
sparkplug.decodePayload(new Uint8Array(input.toBuffer()))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return { message, decoder: Decoder.SPARKPLUG }
|
||||||
|
} catch {
|
||||||
|
return {
|
||||||
|
error: 'Failed to decode sparkplugb payload',
|
||||||
|
decoder: Decoder.NONE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
10
app/src/decoders/StringDecoder.ts
Normal file
10
app/src/decoders/StringDecoder.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Base64Message } from '../../../backend/src/Model/Base64Message'
|
||||||
|
import { Decoder } from '../../../backend/src/Model/Decoder'
|
||||||
|
import { MessageDecoder } from './MessageDecoder'
|
||||||
|
|
||||||
|
export const StringDecoder: MessageDecoder = {
|
||||||
|
formats: ['string'],
|
||||||
|
decode(input: Base64Message) {
|
||||||
|
return { message: input, decoder: Decoder.NONE }
|
||||||
|
},
|
||||||
|
}
|
||||||
6
app/src/decoders/index.ts
Normal file
6
app/src/decoders/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { StringDecoder } from './StringDecoder'
|
||||||
|
import { BinaryDecoder } from './BinaryDecoder'
|
||||||
|
import { SparkplugDecoder } from './SparkplugBDecoder'
|
||||||
|
export * from './MessageDecoder'
|
||||||
|
|
||||||
|
export const decoders = [SparkplugDecoder, BinaryDecoder, StringDecoder] as const
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ConnectionOptions, createEmptyConnection } from './ConnectionOptions'
|
import { ConnectionOptions, createEmptyConnection } from './ConnectionOptions'
|
||||||
import { v4 } from 'uuid'
|
|
||||||
|
|
||||||
interface LegacyConnectionSettings {
|
interface LegacyConnectionSettings {
|
||||||
host: string
|
host: string
|
||||||
|
|||||||
@@ -1,19 +1,77 @@
|
|||||||
|
import * as q from '../../../backend/src/Model'
|
||||||
import { Destroyable } from '../../../backend/src/Model/Destroyable'
|
import { Destroyable } from '../../../backend/src/Model/Destroyable'
|
||||||
|
import { MessageDecoder, decoders } from '../decoders'
|
||||||
import { EventDispatcher } from '../../../events'
|
import { EventDispatcher } from '../../../events'
|
||||||
|
|
||||||
|
function findDecoder<T extends Destroyable>(node: q.TreeNode<T>): TopicDecoder | undefined {
|
||||||
|
const decoder = decoders.find(
|
||||||
|
decoder =>
|
||||||
|
decoder.canDecodeTopic?.(node.path()) || (node.message?.payload && decoder.canDecodeData?.(node.message?.payload))
|
||||||
|
)
|
||||||
|
|
||||||
|
return decoder
|
||||||
|
? {
|
||||||
|
decoder,
|
||||||
|
format: undefined,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
type TopicDecoder = { decoder: MessageDecoder; format: string | undefined }
|
||||||
|
|
||||||
export class TopicViewModel implements Destroyable {
|
export class TopicViewModel implements Destroyable {
|
||||||
private selected: boolean
|
private selected: boolean
|
||||||
private expanded: boolean
|
private expanded: boolean
|
||||||
|
private owner: q.TreeNode<TopicViewModel> | undefined
|
||||||
|
private _decoder?: TopicDecoder
|
||||||
|
/**
|
||||||
|
* Reference counter for useViewModel hook
|
||||||
|
*/
|
||||||
|
private referenceCounter = 0
|
||||||
public selectionChange = new EventDispatcher<void>()
|
public selectionChange = new EventDispatcher<void>()
|
||||||
public expandedChange = new EventDispatcher<void>()
|
public expandedChange = new EventDispatcher<void>()
|
||||||
|
public onDecoderChange = new EventDispatcher<TopicDecoder | undefined>()
|
||||||
|
|
||||||
public constructor() {
|
get decoder(): TopicDecoder | undefined {
|
||||||
|
if (!this._decoder) {
|
||||||
|
this._decoder = this.owner && findDecoder(this.owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
set decoder(override: TopicDecoder | undefined) {
|
||||||
|
this._decoder = override
|
||||||
|
|
||||||
|
this.onDecoderChange.dispatch(override)
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(treeNode: q.TreeNode<TopicViewModel>) {
|
||||||
|
this.owner = treeNode
|
||||||
this.selected = false
|
this.selected = false
|
||||||
this.expanded = false
|
this.expanded = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public retain() {
|
||||||
|
this.referenceCounter += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
public release() {
|
||||||
|
this.referenceCounter -= 1
|
||||||
|
if (this.referenceCounter <= 0) {
|
||||||
|
this.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public destroy() {
|
public destroy() {
|
||||||
|
console.log('destroy', this.referenceCounter)
|
||||||
|
if (this.owner) {
|
||||||
|
this.owner.viewModel = undefined
|
||||||
|
this.owner = undefined
|
||||||
|
}
|
||||||
this.selectionChange.removeAllListeners()
|
this.selectionChange.removeAllListeners()
|
||||||
|
this.onDecoderChange.removeAllListeners()
|
||||||
|
this.expandedChange.removeAllListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
public isSelected() {
|
public isSelected() {
|
||||||
|
|||||||
@@ -4,38 +4,26 @@
|
|||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"lib": [
|
"lib": ["es2019", "dom"],
|
||||||
"es2017",
|
|
||||||
"dom"
|
|
||||||
],
|
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"outDir": "./build/",
|
"outDir": "./build/",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"target": "es2017",
|
"target": "ES2017",
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"paths": {
|
"paths": {
|
||||||
"react": [
|
"react": ["./node_modules/@types/react"]
|
||||||
"./node_modules/@types/react"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"types": [
|
"types": ["react"],
|
||||||
"react"
|
|
||||||
],
|
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["./src/**/*"],
|
||||||
"./src/**/*"
|
"exclude": ["**/*.d.ts", ".src/**/*.png", "./node_modules"],
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"**/*.d.ts",
|
|
||||||
".src/**/*.png",
|
|
||||||
"./node_modules"
|
|
||||||
],
|
|
||||||
"awesomeTypescriptLoaderOptions": {
|
"awesomeTypescriptLoaderOptions": {
|
||||||
"useCache": true,
|
"useCache": true,
|
||||||
"transpileModule": true,
|
"transpileModule": true,
|
||||||
"errorsAsWarnings": true
|
"errorsAsWarnings": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,15 @@ module.exports = {
|
|||||||
// All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
|
// All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
|
||||||
{
|
{
|
||||||
test: /\.tsx?$/,
|
test: /\.tsx?$/,
|
||||||
loader: 'ts-loader',
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'ts-loader',
|
||||||
|
// options: {
|
||||||
|
// configFile: './tsconfig.json',
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
exclude: /node_modules/,
|
||||||
},
|
},
|
||||||
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
|
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
|
||||||
{ enforce: 'pre', test: /\.js$/, loader: 'source-map-loader' },
|
{ enforce: 'pre', test: /\.js$/, loader: 'source-map-loader' },
|
||||||
@@ -96,4 +104,7 @@ module.exports = {
|
|||||||
// "react": "React",
|
// "react": "React",
|
||||||
// "react-dom": "ReactDOM"
|
// "react-dom": "ReactDOM"
|
||||||
},
|
},
|
||||||
|
cache: {
|
||||||
|
type: 'filesystem',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
671
app/yarn.lock
671
app/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
import * as FileAsync from 'lowdb/adapters/FileAsync'
|
import FileAsync from 'lowdb/adapters/FileAsync'
|
||||||
import * as fs from 'fs-extra'
|
import fs from 'fs-extra'
|
||||||
import * as lowdb from 'lowdb'
|
import lowdb from 'lowdb'
|
||||||
import * as path from 'path'
|
import path from 'path'
|
||||||
import { backendRpc } from '../../events'
|
import { backendRpc } from '../../events'
|
||||||
import { storageClearEvent, storageLoadEvent, storageStoreEvent } from '../../events/StorageEvents'
|
import { storageClearEvent, storageLoadEvent, storageStoreEvent } from '../../events/StorageEvents'
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export class MqttSource implements DataSource<MqttOptions> {
|
|||||||
|
|
||||||
public publish(msg: MqttMessage) {
|
public publish(msg: MqttMessage) {
|
||||||
if (this.client) {
|
if (this.client) {
|
||||||
this.client.publish(msg.topic, msg.payload ? Base64Message.toUnicodeString(msg.payload) : '', {
|
this.client.publish(msg.topic, (msg.payload && new Base64Message(msg.payload))?.toBuffer() ?? '', {
|
||||||
qos: msg.qos,
|
qos: msg.qos,
|
||||||
retain: msg.retain,
|
retain: msg.retain,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,31 +1,93 @@
|
|||||||
import { Base64 } from 'js-base64'
|
import { Base64 } from 'js-base64'
|
||||||
import { Decoder } from './Decoder'
|
import { TopicDataType } from './TreeNode'
|
||||||
|
|
||||||
|
export type Base64MessageDTO = Pick<Base64Message, 'base64Message'>
|
||||||
|
|
||||||
export class Base64Message {
|
export class Base64Message {
|
||||||
private base64Message: string
|
public base64Message: string
|
||||||
private unicodeValue: string
|
private _unicodeValue: string | undefined
|
||||||
public decoder: Decoder
|
|
||||||
public length: number
|
|
||||||
|
|
||||||
private constructor(base64Str: string) {
|
// Todo: Rename to `encodedLength`
|
||||||
this.base64Message = base64Str
|
public get length(): number {
|
||||||
this.unicodeValue = Base64.decode(base64Str)
|
return this.base64Message.length
|
||||||
this.length = base64Str.length
|
|
||||||
this.decoder = Decoder.NONE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static toUnicodeString(message: Base64Message) {
|
private get unicodeValue(): string {
|
||||||
return message.unicodeValue || ''
|
if (!this._unicodeValue) {
|
||||||
|
this._unicodeValue = Base64.decode(this.base64Message ?? '')
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._unicodeValue
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(base64Str?: string | Base64MessageDTO, error?: string) {
|
||||||
|
if (typeof base64Str === 'string' || typeof base64Str === 'undefined') {
|
||||||
|
this.base64Message = base64Str ?? ''
|
||||||
|
} else {
|
||||||
|
if (typeof base64Str.base64Message !== 'string') {
|
||||||
|
throw new Error('Received unexpected type in copy constructor')
|
||||||
|
}
|
||||||
|
this.base64Message = base64Str.base64Message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override default JSON serialization behavior to only return the DTO
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public toJSON(): Base64MessageDTO {
|
||||||
|
return { base64Message: this.base64Message }
|
||||||
|
}
|
||||||
|
|
||||||
|
public toUnicodeString() {
|
||||||
|
return this.unicodeValue || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
public static fromBuffer(buffer: Buffer) {
|
public static fromBuffer(buffer: Buffer) {
|
||||||
return new Base64Message(buffer.toString('base64'))
|
return new Base64Message(buffer.toString('base64'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public toBuffer(): Buffer {
|
||||||
|
return Buffer.from(this.base64Message, 'base64')
|
||||||
|
}
|
||||||
|
|
||||||
public static fromString(str: string) {
|
public static fromString(str: string) {
|
||||||
return new Base64Message(Base64.encode(str))
|
return new Base64Message(Base64.encode(str))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public format(type: TopicDataType = 'string'): [string, 'json' | undefined] {
|
||||||
|
try {
|
||||||
|
switch (type) {
|
||||||
|
case 'json': {
|
||||||
|
const json = JSON.parse(this.toUnicodeString())
|
||||||
|
return [JSON.stringify(json, undefined, ' '), 'json']
|
||||||
|
}
|
||||||
|
case 'hex': {
|
||||||
|
const hex = Base64Message.toHex(this)
|
||||||
|
return [hex, undefined]
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
const str = this.toUnicodeString()
|
||||||
|
return [str, undefined]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const str = this.toUnicodeString()
|
||||||
|
return [str, undefined]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static toHex(message: Base64Message) {
|
||||||
|
const buf = Buffer.from(message.base64Message, 'base64')
|
||||||
|
|
||||||
|
let str: string = ''
|
||||||
|
buf.forEach(element => {
|
||||||
|
let hex = element.toString(16).toUpperCase()
|
||||||
|
str += `0x${hex.length < 2 ? '0' + hex : hex} `
|
||||||
|
})
|
||||||
|
return str.trimRight()
|
||||||
|
}
|
||||||
|
|
||||||
public static toDataUri(message: Base64Message, mimeType: string) {
|
public static toDataUri(message: Base64Message, mimeType: string) {
|
||||||
return `data:${mimeType};base64,${message.base64Message}`
|
return `data:${mimeType};base64,${message.base64Message}`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class ChangeBuffer {
|
|||||||
public push(val: MqttMessage) {
|
public push(val: MqttMessage) {
|
||||||
if (!this.isFull()) {
|
if (!this.isFull()) {
|
||||||
this.buffer.push({ message: val, received: new Date() })
|
this.buffer.push({ message: val, received: new Date() })
|
||||||
this.size += this.estimatedMessageOverhead + (val.payload ? val.payload.length : 0)
|
this.size += this.estimatedMessageOverhead + (val.payload?.base64Message.length ?? 0)
|
||||||
this.length += 1
|
this.length += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Base64Message } from './Base64Message'
|
import { Base64Message } from './Base64Message'
|
||||||
import { QoS } from '../DataSource/MqttSource'
|
import { QoS } from '../DataSource/MqttSource'
|
||||||
|
import { MemoryConsumptionExpressedByLength } from './RingBuffer'
|
||||||
|
|
||||||
export interface Message {
|
export interface Message extends MemoryConsumptionExpressedByLength {
|
||||||
// mqtt based info
|
// mqtt based info
|
||||||
payload: Base64Message | null
|
payload: Base64Message | null
|
||||||
messageId?: number
|
messageId?: number
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { Destroyable } from './Destroyable'
|
|||||||
import { Edge, Message, RingBuffer, MessageHistory } from './'
|
import { Edge, Message, RingBuffer, MessageHistory } from './'
|
||||||
import { EventDispatcher } from '../../../events'
|
import { EventDispatcher } from '../../../events'
|
||||||
|
|
||||||
|
export type TopicDataType = 'string' | 'json' | 'hex'
|
||||||
|
|
||||||
export class TreeNode<ViewModel extends Destroyable> {
|
export class TreeNode<ViewModel extends Destroyable> {
|
||||||
public sourceEdge?: Edge<ViewModel>
|
public sourceEdge?: Edge<ViewModel>
|
||||||
public message?: Message
|
public message?: Message
|
||||||
@@ -17,6 +19,7 @@ export class TreeNode<ViewModel extends Destroyable> {
|
|||||||
public onMessage = new EventDispatcher<Message>()
|
public onMessage = new EventDispatcher<Message>()
|
||||||
public onDestroy = new EventDispatcher<TreeNode<ViewModel>>()
|
public onDestroy = new EventDispatcher<TreeNode<ViewModel>>()
|
||||||
public isTree = false
|
public isTree = false
|
||||||
|
public type: TopicDataType = 'json'
|
||||||
|
|
||||||
private cachedPath?: string
|
private cachedPath?: string
|
||||||
private cachedChildTopics?: Array<TreeNode<ViewModel>>
|
private cachedChildTopics?: Array<TreeNode<ViewModel>>
|
||||||
@@ -153,7 +156,7 @@ export class TreeNode<ViewModel extends Destroyable> {
|
|||||||
|
|
||||||
public path(): string {
|
public path(): string {
|
||||||
if (!this.cachedPath) {
|
if (!this.cachedPath) {
|
||||||
return this.branch()
|
this.cachedPath = 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('/')
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Destroyable } from './Destroyable'
|
import { Destroyable } from './Destroyable'
|
||||||
import { Edge, Tree, TreeNode } from './'
|
import { Edge, Tree, TreeNode } from './'
|
||||||
import { MqttMessage } from '../../../events'
|
import { MqttMessage } from '../../../events'
|
||||||
|
import { Base64Message } from './Base64Message'
|
||||||
|
|
||||||
export abstract class TreeNodeFactory {
|
export abstract class TreeNodeFactory {
|
||||||
private static messageCounter = 0
|
private static messageCounter = 0
|
||||||
@@ -30,7 +31,8 @@ export abstract class TreeNodeFactory {
|
|||||||
mqttMessage.retain
|
mqttMessage.retain
|
||||||
node.setMessage({
|
node.setMessage({
|
||||||
...mqttMessage,
|
...mqttMessage,
|
||||||
length: mqttMessage.payload?.length ?? 0,
|
payload: mqttMessage.payload && new Base64Message(mqttMessage.payload?.base64Message),
|
||||||
|
length: mqttMessage.payload?.base64Message.length ?? 0,
|
||||||
received: receiveDate,
|
received: receiveDate,
|
||||||
messageNumber: this.messageCounter,
|
messageNumber: this.messageCounter,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export { Edge } from './Edge'
|
export { Edge } from './Edge'
|
||||||
export { TreeNode } from './TreeNode'
|
export { TreeNode, TopicDataType } from './TreeNode'
|
||||||
export { Message } from './Message'
|
export { Message } from './Message'
|
||||||
export { TreeNodeFactory } from './TreeNodeFactory'
|
export { TreeNodeFactory } from './TreeNodeFactory'
|
||||||
export { Tree } from './Tree'
|
export { Tree } from './Tree'
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
import { Base64Message } from './Base64Message'
|
|
||||||
import { Decoder } from './Decoder'
|
|
||||||
import { get } from 'sparkplug-payload'
|
|
||||||
var sparkplug = get("spBv1.0")
|
|
||||||
|
|
||||||
export const SparkplugDecoder = {
|
|
||||||
decode(input: Buffer): Base64Message {
|
|
||||||
try {
|
|
||||||
const message = Base64Message.fromString(JSON.stringify(
|
|
||||||
// @ts-ignore
|
|
||||||
sparkplug.decodePayload(new Uint8Array(input)))
|
|
||||||
)
|
|
||||||
message.decoder = Decoder.SPARKPLUG
|
|
||||||
return message
|
|
||||||
} catch {
|
|
||||||
const message = Base64Message.fromString("Failed to decode sparkplugb payload")
|
|
||||||
message.decoder = Decoder.NONE
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'mocha'
|
import 'mocha'
|
||||||
|
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { Base64Message } from '../Base64Message'
|
|
||||||
import { makeTreeNode } from './makeTreeNode'
|
import { makeTreeNode } from './makeTreeNode'
|
||||||
|
|
||||||
describe('TreeNode', () => {
|
describe('TreeNode', () => {
|
||||||
@@ -14,7 +13,7 @@ describe('TreeNode', () => {
|
|||||||
it('updateWithNode should update value', () => {
|
it('updateWithNode should update value', () => {
|
||||||
const topics = 'foo/bar'.split('/')
|
const topics = 'foo/bar'.split('/')
|
||||||
const leaf = makeTreeNode('foo/bar', '3')
|
const leaf = makeTreeNode('foo/bar', '3')
|
||||||
expect(Base64Message.toUnicodeString(leaf.message!.payload!)).to.eq('3')
|
expect(leaf.message!.payload!.toUnicodeString()).to.eq('3')
|
||||||
|
|
||||||
const updateLeave = makeTreeNode('foo/bar', '5')
|
const updateLeave = makeTreeNode('foo/bar', '5')
|
||||||
|
|
||||||
@@ -22,13 +21,13 @@ describe('TreeNode', () => {
|
|||||||
root.updateWithNode(updateLeave.firstNode())
|
root.updateWithNode(updateLeave.firstNode())
|
||||||
|
|
||||||
expect(root.sourceEdge).to.eq(undefined)
|
expect(root.sourceEdge).to.eq(undefined)
|
||||||
expect(Base64Message.toUnicodeString(leaf.message!.payload!)).to.eq('5')
|
expect(leaf.message!.payload!.toUnicodeString()).to.eq('5')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('updateWithNode should update intermediate nodes', () => {
|
it('updateWithNode should update intermediate nodes', () => {
|
||||||
const topics1 = 'foo/bar/baz'.split('/')
|
const topics1 = 'foo/bar/baz'.split('/')
|
||||||
const leaf = makeTreeNode('foo/bar/baz', '3')
|
const leaf = makeTreeNode('foo/bar/baz', '3')
|
||||||
expect(Base64Message.toUnicodeString(leaf.message!.payload!)).to.eq('3')
|
expect(leaf.message!.payload!.toUnicodeString()).to.eq('3')
|
||||||
|
|
||||||
const topics2 = 'foo/bar'.split('/')
|
const topics2 = 'foo/bar'.split('/')
|
||||||
const updateLeave = makeTreeNode('foo/bar', '5')
|
const updateLeave = makeTreeNode('foo/bar', '5')
|
||||||
@@ -37,10 +36,10 @@ describe('TreeNode', () => {
|
|||||||
|
|
||||||
const barNode = leaf.firstNode().findNode('foo/bar')
|
const barNode = leaf.firstNode().findNode('foo/bar')
|
||||||
expect(barNode && barNode.sourceEdge && barNode.sourceEdge.name).to.eq('bar')
|
expect(barNode && barNode.sourceEdge && barNode.sourceEdge.name).to.eq('bar')
|
||||||
expect(Base64Message.toUnicodeString(barNode!.message!.payload!)).to.eq('5')
|
expect(barNode!.message!.payload!.toUnicodeString()).to.eq('5')
|
||||||
|
|
||||||
expect(leaf.sourceEdge && leaf.sourceEdge.name).to.eq('baz')
|
expect(leaf.sourceEdge && leaf.sourceEdge.name).to.eq('baz')
|
||||||
expect(Base64Message.toUnicodeString(leaf.message!.payload!)).to.eq('3')
|
expect(leaf.message!.payload!.toUnicodeString()).to.eq('3')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('updateWithNode should add nodes to the tree', () => {
|
it('updateWithNode should add nodes to the tree', () => {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'mocha'
|
import 'mocha'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { Base64Message } from '../Base64Message'
|
|
||||||
import { makeTreeNode } from './makeTreeNode'
|
import { makeTreeNode } from './makeTreeNode'
|
||||||
|
|
||||||
describe('TreeNodeFactory', () => {
|
describe('TreeNodeFactory', () => {
|
||||||
@@ -20,7 +19,7 @@ describe('TreeNodeFactory', () => {
|
|||||||
|
|
||||||
expect(node).to.not.eq(undefined)
|
expect(node).to.not.eq(undefined)
|
||||||
expect(node.sourceEdge.name).to.eq('bar')
|
expect(node.sourceEdge.name).to.eq('bar')
|
||||||
expect(Base64Message.toUnicodeString(node.message.payload!)).to.eq('5')
|
expect(node.message.payload!.toUnicodeString()).to.eq('5')
|
||||||
|
|
||||||
const foo = node.firstNode().findNode('foo')
|
const foo = node.firstNode().findNode('foo')
|
||||||
expect(foo && foo.sourceEdge && foo.sourceEdge.name).to.eq('foo')
|
expect(foo && foo.sourceEdge && foo.sourceEdge.name).to.eq('foo')
|
||||||
@@ -34,7 +33,7 @@ describe('TreeNodeFactory', () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(Base64Message.toUnicodeString(node.message.payload!)).to.eq('5')
|
expect(node.message.payload!.toUnicodeString()).to.eq('5')
|
||||||
expect(node.sourceEdge.name).to.eq('baz')
|
expect(node.sourceEdge.name).to.eq('baz')
|
||||||
|
|
||||||
const barNode = node.sourceEdge.source
|
const barNode = node.sourceEdge.source
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
makePublishEvent,
|
makePublishEvent,
|
||||||
removeConnection,
|
removeConnection,
|
||||||
} from '../../events'
|
} from '../../events'
|
||||||
import { SparkplugDecoder } from './Model/sparkplugb'
|
|
||||||
|
|
||||||
export class ConnectionManager {
|
export class ConnectionManager {
|
||||||
private connections: { [s: string]: DataSource<any> } = {}
|
private connections: { [s: string]: DataSource<any> } = {}
|
||||||
@@ -48,12 +47,7 @@ export class ConnectionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let decoded_payload = null
|
let decoded_payload = null
|
||||||
// spell-checker: disable-next-line
|
decoded_payload = Base64Message.fromBuffer(buffer)
|
||||||
if (topic.match(/^spBv1\.0\/[^/]+\/[ND](DATA|CMD|DEATH|BIRTH)\/[^/]+(\/[^/]+)?$/u)) {
|
|
||||||
decoded_payload = SparkplugDecoder.decode(buffer)
|
|
||||||
} else {
|
|
||||||
decoded_payload = Base64Message.fromBuffer(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
backendEvents.emit(messageEvent, {
|
backendEvents.emit(messageEvent, {
|
||||||
topic,
|
topic,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Base64Message } from '../backend/src/Model/Base64Message'
|
import { Base64MessageDTO } from '../backend/src/Model/Base64Message'
|
||||||
import { DataSourceState, MqttOptions } from '../backend/src/DataSource'
|
import { DataSourceState, MqttOptions } from '../backend/src/DataSource'
|
||||||
import { UpdateInfo } from 'builder-util-runtime'
|
import { UpdateInfo } from 'builder-util-runtime'
|
||||||
import { RpcEvent } from './EventSystem/Rpc'
|
import { RpcEvent } from './EventSystem/Rpc'
|
||||||
@@ -32,7 +32,7 @@ export const updateAvailable: Event<UpdateInfo> = {
|
|||||||
|
|
||||||
export interface MqttMessage {
|
export interface MqttMessage {
|
||||||
topic: string
|
topic: string
|
||||||
payload: Base64Message | null
|
payload: Base64MessageDTO | null
|
||||||
qos: 0 | 1 | 2
|
qos: 0 | 1 | 2
|
||||||
retain: boolean
|
retain: boolean
|
||||||
// Set if QoS is > 0 on received messages
|
// Set if QoS is > 0 on received messages
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ registerCrashReporter()
|
|||||||
// const electronTelemetry = electronTelemetryFactory('9b0c8ca04a361eb8160d98c5', buildOptions)
|
// const electronTelemetry = electronTelemetryFactory('9b0c8ca04a361eb8160d98c5', buildOptions)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
app.commandLine.appendSwitch('--no-sandbox')
|
// disable-dev-shm-usage is required to run the debug console
|
||||||
|
app.commandLine.appendSwitch('--no-sandbox --disable-dev-shm-usage')
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
backendRpc.on(makeOpenDialogRpc(), async request => {
|
backendRpc.on(makeOpenDialogRpc(), async request => {
|
||||||
return dialog.showOpenDialog(BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0], request)
|
return dialog.showOpenDialog(BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0], request)
|
||||||
@@ -69,8 +70,6 @@ async function createWindow() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('icon path', iconPath)
|
|
||||||
|
|
||||||
// Load the index.html of the app.
|
// Load the index.html of the app.
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
mainWindow.loadURL('http://localhost:8080')
|
mainWindow.loadURL('http://localhost:8080')
|
||||||
|
|||||||
@@ -9,15 +9,11 @@
|
|||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"sourceRoot": "src/",
|
"sourceRoot": "src/",
|
||||||
"target": "ES2017",
|
"target": "ES2017",
|
||||||
"lib": [
|
"lib": ["ES2017", "dom"],
|
||||||
"es2017",
|
|
||||||
"dom"
|
|
||||||
],
|
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"types": [
|
"types": ["node"],
|
||||||
"node"
|
"skipLibCheck": true,
|
||||||
],
|
"esModuleInterop": true
|
||||||
"skipLibCheck": true
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/electron.ts",
|
"src/electron.ts",
|
||||||
@@ -26,7 +22,5 @@
|
|||||||
"src/spec/leakTest.ts",
|
"src/spec/leakTest.ts",
|
||||||
"scripts/*.ts"
|
"scripts/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": ["node_modules"]
|
||||||
"node_modules"
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|||||||
260
yarn.lock
260
yarn.lock
@@ -659,6 +659,38 @@
|
|||||||
minimatch "^3.0.4"
|
minimatch "^3.0.4"
|
||||||
plist "^3.0.4"
|
plist "^3.0.4"
|
||||||
|
|
||||||
|
"@eslint-community/eslint-utils@^4.2.0":
|
||||||
|
version "4.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
||||||
|
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
|
||||||
|
dependencies:
|
||||||
|
eslint-visitor-keys "^3.3.0"
|
||||||
|
|
||||||
|
"@eslint-community/regexpp@^4.6.1":
|
||||||
|
version "4.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63"
|
||||||
|
integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==
|
||||||
|
|
||||||
|
"@eslint/eslintrc@^3.1.0":
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.1.0.tgz#dbd3482bfd91efa663cbe7aa1f506839868207b6"
|
||||||
|
integrity sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==
|
||||||
|
dependencies:
|
||||||
|
ajv "^6.12.4"
|
||||||
|
debug "^4.3.2"
|
||||||
|
espree "^10.0.1"
|
||||||
|
globals "^14.0.0"
|
||||||
|
ignore "^5.2.0"
|
||||||
|
import-fresh "^3.2.1"
|
||||||
|
js-yaml "^4.1.0"
|
||||||
|
minimatch "^3.1.2"
|
||||||
|
strip-json-comments "^3.1.1"
|
||||||
|
|
||||||
|
"@eslint/js@9.3.0":
|
||||||
|
version "9.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.3.0.tgz#2e8f65c9c55227abc4845b1513c69c32c679d8fe"
|
||||||
|
integrity sha512-niBqk8iwv96+yuTwjM6bWg8ovzAPF9qkICsGtcoa5/dmqcEMfdwNAX7+/OHcJHc7wj7XqPxH98oAHytFYlw6Sw==
|
||||||
|
|
||||||
"@fastify/busboy@^2.0.0":
|
"@fastify/busboy@^2.0.0":
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d"
|
resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d"
|
||||||
@@ -683,6 +715,30 @@
|
|||||||
reflect-metadata "^0.1.12"
|
reflect-metadata "^0.1.12"
|
||||||
tslib "^1.8.1"
|
tslib "^1.8.1"
|
||||||
|
|
||||||
|
"@humanwhocodes/config-array@^0.13.0":
|
||||||
|
version "0.13.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748"
|
||||||
|
integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==
|
||||||
|
dependencies:
|
||||||
|
"@humanwhocodes/object-schema" "^2.0.3"
|
||||||
|
debug "^4.3.1"
|
||||||
|
minimatch "^3.0.5"
|
||||||
|
|
||||||
|
"@humanwhocodes/module-importer@^1.0.1":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
|
||||||
|
integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
|
||||||
|
|
||||||
|
"@humanwhocodes/object-schema@^2.0.3":
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
|
||||||
|
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
|
||||||
|
|
||||||
|
"@humanwhocodes/retry@^0.3.0":
|
||||||
|
version "0.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.0.tgz#6d86b8cb322660f03d3f0aa94b99bdd8e172d570"
|
||||||
|
integrity sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==
|
||||||
|
|
||||||
"@isaacs/cliui@^8.0.2":
|
"@isaacs/cliui@^8.0.2":
|
||||||
version "8.0.2"
|
version "8.0.2"
|
||||||
resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz"
|
resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz"
|
||||||
@@ -786,7 +842,7 @@
|
|||||||
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
|
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
|
||||||
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
||||||
|
|
||||||
"@nodelib/fs.walk@^1.2.3":
|
"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8":
|
||||||
version "1.2.8"
|
version "1.2.8"
|
||||||
resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz"
|
resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz"
|
||||||
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
|
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
|
||||||
@@ -1514,12 +1570,17 @@ about-window@^1.12.1:
|
|||||||
resolved "https://registry.npmjs.org/about-window/-/about-window-1.15.2.tgz"
|
resolved "https://registry.npmjs.org/about-window/-/about-window-1.15.2.tgz"
|
||||||
integrity sha512-31mDAnLUfKm4uShfMzeEoS6a3nEto2tUt4zZn7qyAKedaTV4p0dGiW1n+YG8vtRh78mZiewghWJmoxDY+lHyYg==
|
integrity sha512-31mDAnLUfKm4uShfMzeEoS6a3nEto2tUt4zZn7qyAKedaTV4p0dGiW1n+YG8vtRh78mZiewghWJmoxDY+lHyYg==
|
||||||
|
|
||||||
|
acorn-jsx@^5.3.2:
|
||||||
|
version "5.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
||||||
|
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
||||||
|
|
||||||
acorn-walk@^8.1.1:
|
acorn-walk@^8.1.1:
|
||||||
version "8.3.2"
|
version "8.3.2"
|
||||||
resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz"
|
resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz"
|
||||||
integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==
|
integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==
|
||||||
|
|
||||||
acorn@^8.4.1:
|
acorn@^8.11.3, acorn@^8.4.1:
|
||||||
version "8.11.3"
|
version "8.11.3"
|
||||||
resolved "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz"
|
resolved "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz"
|
||||||
integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==
|
integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==
|
||||||
@@ -1559,7 +1620,7 @@ ajv-keywords@^3.4.1:
|
|||||||
resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz"
|
resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz"
|
||||||
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
|
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
|
||||||
|
|
||||||
ajv@^6.10.0, ajv@^6.12.0:
|
ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.4:
|
||||||
version "6.12.6"
|
version "6.12.6"
|
||||||
resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"
|
resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"
|
||||||
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
||||||
@@ -2475,7 +2536,7 @@ cross-spawn@^6.0.5:
|
|||||||
shebang-command "^1.2.0"
|
shebang-command "^1.2.0"
|
||||||
which "^1.2.9"
|
which "^1.2.9"
|
||||||
|
|
||||||
cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.3:
|
cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||||
version "7.0.3"
|
version "7.0.3"
|
||||||
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz"
|
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz"
|
||||||
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
||||||
@@ -2674,6 +2735,11 @@ deep-extend@^0.6.0:
|
|||||||
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
||||||
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
|
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
|
||||||
|
|
||||||
|
deep-is@^0.1.3:
|
||||||
|
version "0.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
||||||
|
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
|
||||||
|
|
||||||
default-require-extensions@^3.0.0:
|
default-require-extensions@^3.0.0:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz"
|
resolved "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz"
|
||||||
@@ -3069,16 +3135,112 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
|
|||||||
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
|
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
|
||||||
integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
|
integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
|
||||||
|
|
||||||
|
eslint-plugin-react-hooks@^4.6.2:
|
||||||
|
version "4.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz#c829eb06c0e6f484b3fbb85a97e57784f328c596"
|
||||||
|
integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==
|
||||||
|
|
||||||
|
eslint-scope@^8.0.1:
|
||||||
|
version "8.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.0.1.tgz#a9601e4b81a0b9171657c343fb13111688963cfc"
|
||||||
|
integrity sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==
|
||||||
|
dependencies:
|
||||||
|
esrecurse "^4.3.0"
|
||||||
|
estraverse "^5.2.0"
|
||||||
|
|
||||||
|
eslint-visitor-keys@^3.3.0:
|
||||||
|
version "3.4.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
|
||||||
|
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
|
||||||
|
|
||||||
|
eslint-visitor-keys@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb"
|
||||||
|
integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==
|
||||||
|
|
||||||
|
eslint@^9.3.0:
|
||||||
|
version "9.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.3.0.tgz#36a96db84592618d6ed9074d677e92f4e58c08b9"
|
||||||
|
integrity sha512-5Iv4CsZW030lpUqHBapdPo3MJetAPtejVW8B84GIcIIv8+ohFaddXsrn1Gn8uD9ijDb+kcYKFUVmC8qG8B2ORQ==
|
||||||
|
dependencies:
|
||||||
|
"@eslint-community/eslint-utils" "^4.2.0"
|
||||||
|
"@eslint-community/regexpp" "^4.6.1"
|
||||||
|
"@eslint/eslintrc" "^3.1.0"
|
||||||
|
"@eslint/js" "9.3.0"
|
||||||
|
"@humanwhocodes/config-array" "^0.13.0"
|
||||||
|
"@humanwhocodes/module-importer" "^1.0.1"
|
||||||
|
"@humanwhocodes/retry" "^0.3.0"
|
||||||
|
"@nodelib/fs.walk" "^1.2.8"
|
||||||
|
ajv "^6.12.4"
|
||||||
|
chalk "^4.0.0"
|
||||||
|
cross-spawn "^7.0.2"
|
||||||
|
debug "^4.3.2"
|
||||||
|
escape-string-regexp "^4.0.0"
|
||||||
|
eslint-scope "^8.0.1"
|
||||||
|
eslint-visitor-keys "^4.0.0"
|
||||||
|
espree "^10.0.1"
|
||||||
|
esquery "^1.4.2"
|
||||||
|
esutils "^2.0.2"
|
||||||
|
fast-deep-equal "^3.1.3"
|
||||||
|
file-entry-cache "^8.0.0"
|
||||||
|
find-up "^5.0.0"
|
||||||
|
glob-parent "^6.0.2"
|
||||||
|
ignore "^5.2.0"
|
||||||
|
imurmurhash "^0.1.4"
|
||||||
|
is-glob "^4.0.0"
|
||||||
|
is-path-inside "^3.0.3"
|
||||||
|
json-stable-stringify-without-jsonify "^1.0.1"
|
||||||
|
levn "^0.4.1"
|
||||||
|
lodash.merge "^4.6.2"
|
||||||
|
minimatch "^3.1.2"
|
||||||
|
natural-compare "^1.4.0"
|
||||||
|
optionator "^0.9.3"
|
||||||
|
strip-ansi "^6.0.1"
|
||||||
|
text-table "^0.2.0"
|
||||||
|
|
||||||
|
espree@^10.0.1:
|
||||||
|
version "10.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/espree/-/espree-10.0.1.tgz#600e60404157412751ba4a6f3a2ee1a42433139f"
|
||||||
|
integrity sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==
|
||||||
|
dependencies:
|
||||||
|
acorn "^8.11.3"
|
||||||
|
acorn-jsx "^5.3.2"
|
||||||
|
eslint-visitor-keys "^4.0.0"
|
||||||
|
|
||||||
esprima@^4.0.0, esprima@^4.0.1:
|
esprima@^4.0.0, esprima@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz"
|
resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz"
|
||||||
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
||||||
|
|
||||||
|
esquery@^1.4.2:
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b"
|
||||||
|
integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==
|
||||||
|
dependencies:
|
||||||
|
estraverse "^5.1.0"
|
||||||
|
|
||||||
|
esrecurse@^4.3.0:
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
|
||||||
|
integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
|
||||||
|
dependencies:
|
||||||
|
estraverse "^5.2.0"
|
||||||
|
|
||||||
|
estraverse@^5.1.0, estraverse@^5.2.0:
|
||||||
|
version "5.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
|
||||||
|
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
|
||||||
|
|
||||||
esutils@^1.1.6:
|
esutils@^1.1.6:
|
||||||
version "1.1.6"
|
version "1.1.6"
|
||||||
resolved "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz"
|
resolved "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz"
|
||||||
integrity sha512-RG1ZkUT7iFJG9LSHr7KDuuMSlujfeTtMNIcInURxKAxhMtwQhI3NrQhz26gZQYlsYZQKzsnwtpKrFKj9K9Qu1A==
|
integrity sha512-RG1ZkUT7iFJG9LSHr7KDuuMSlujfeTtMNIcInURxKAxhMtwQhI3NrQhz26gZQYlsYZQKzsnwtpKrFKj9K9Qu1A==
|
||||||
|
|
||||||
|
esutils@^2.0.2:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||||
|
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
||||||
|
|
||||||
event-stream@=3.3.4:
|
event-stream@=3.3.4:
|
||||||
version "3.3.4"
|
version "3.3.4"
|
||||||
resolved "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz"
|
resolved "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz"
|
||||||
@@ -3161,7 +3323,7 @@ extsprintf@^1.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07"
|
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07"
|
||||||
integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==
|
integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==
|
||||||
|
|
||||||
fast-deep-equal@^3.1.1:
|
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
|
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
|
||||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||||
@@ -3187,6 +3349,11 @@ fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0:
|
|||||||
resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz"
|
resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz"
|
||||||
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
|
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
|
||||||
|
|
||||||
|
fast-levenshtein@^2.0.6:
|
||||||
|
version "2.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||||
|
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
|
||||||
|
|
||||||
fastest-levenshtein@^1.0.16:
|
fastest-levenshtein@^1.0.16:
|
||||||
version "1.0.16"
|
version "1.0.16"
|
||||||
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5"
|
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5"
|
||||||
@@ -3255,7 +3422,7 @@ find-up-simple@^1.0.0:
|
|||||||
resolved "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz"
|
resolved "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz"
|
||||||
integrity sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==
|
integrity sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==
|
||||||
|
|
||||||
find-up@5.0.0:
|
find-up@5.0.0, find-up@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz"
|
resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz"
|
||||||
integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
|
integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
|
||||||
@@ -3557,6 +3724,13 @@ glob-parent@^5.1.2, glob-parent@~5.1.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-glob "^4.0.1"
|
is-glob "^4.0.1"
|
||||||
|
|
||||||
|
glob-parent@^6.0.2:
|
||||||
|
version "6.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
|
||||||
|
integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
|
||||||
|
dependencies:
|
||||||
|
is-glob "^4.0.3"
|
||||||
|
|
||||||
glob@8.1.0:
|
glob@8.1.0:
|
||||||
version "8.1.0"
|
version "8.1.0"
|
||||||
resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz"
|
resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz"
|
||||||
@@ -3626,6 +3800,11 @@ globals@^11.1.0:
|
|||||||
resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz"
|
resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz"
|
||||||
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
|
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
|
||||||
|
|
||||||
|
globals@^14.0.0:
|
||||||
|
version "14.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e"
|
||||||
|
integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==
|
||||||
|
|
||||||
globalthis@^1.0.1, globalthis@^1.0.3:
|
globalthis@^1.0.1, globalthis@^1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz"
|
resolved "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz"
|
||||||
@@ -3897,12 +4076,12 @@ ignore-walk@^6.0.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
minimatch "^9.0.0"
|
minimatch "^9.0.0"
|
||||||
|
|
||||||
ignore@^5.2.4:
|
ignore@^5.2.0, ignore@^5.2.4:
|
||||||
version "5.3.1"
|
version "5.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
|
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
|
||||||
integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
|
integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
|
||||||
|
|
||||||
import-fresh@^3.3.0:
|
import-fresh@^3.2.1, import-fresh@^3.3.0:
|
||||||
version "3.3.0"
|
version "3.3.0"
|
||||||
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz"
|
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz"
|
||||||
integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
|
integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
|
||||||
@@ -4104,7 +4283,7 @@ is-fullwidth-code-point@^3.0.0:
|
|||||||
resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz"
|
resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz"
|
||||||
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
||||||
|
|
||||||
is-glob@^4.0.1, is-glob@~4.0.1:
|
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz"
|
resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz"
|
||||||
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
||||||
@@ -4138,6 +4317,11 @@ is-obj@^2.0.0:
|
|||||||
resolved "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz"
|
resolved "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz"
|
||||||
integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==
|
integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==
|
||||||
|
|
||||||
|
is-path-inside@^3.0.3:
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
|
||||||
|
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
|
||||||
|
|
||||||
is-plain-obj@^2.1.0:
|
is-plain-obj@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz"
|
resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz"
|
||||||
@@ -4438,6 +4622,11 @@ json-schema-traverse@^0.4.1:
|
|||||||
resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz"
|
resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz"
|
||||||
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
||||||
|
|
||||||
|
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 sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
|
||||||
|
|
||||||
json-stringify-nice@^1.1.4:
|
json-stringify-nice@^1.1.4:
|
||||||
version "1.1.4"
|
version "1.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz#2c937962b80181d3f317dd39aa323e14f5a60a67"
|
resolved "https://registry.yarnpkg.com/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz#2c937962b80181d3f317dd39aa323e14f5a60a67"
|
||||||
@@ -4509,6 +4698,14 @@ leven@^2.1.0:
|
|||||||
resolved "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz"
|
resolved "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz"
|
||||||
integrity sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==
|
integrity sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==
|
||||||
|
|
||||||
|
levn@^0.4.1:
|
||||||
|
version "0.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
|
||||||
|
integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
|
||||||
|
dependencies:
|
||||||
|
prelude-ls "^1.2.1"
|
||||||
|
type-check "~0.4.0"
|
||||||
|
|
||||||
libnpmaccess@^8.0.1:
|
libnpmaccess@^8.0.1:
|
||||||
version "8.0.5"
|
version "8.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-8.0.5.tgz#ef14fecab8385669e91d6be27971ae448064211f"
|
resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-8.0.5.tgz#ef14fecab8385669e91d6be27971ae448064211f"
|
||||||
@@ -4703,6 +4900,11 @@ lodash.isstring@^4.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||||
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
|
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
|
||||||
|
|
||||||
|
lodash.merge@^4.6.2:
|
||||||
|
version "4.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||||
|
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||||
|
|
||||||
lodash.uniqby@^4.7.0:
|
lodash.uniqby@^4.7.0:
|
||||||
version "4.7.0"
|
version "4.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302"
|
resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302"
|
||||||
@@ -4924,7 +5126,7 @@ minimatch@5.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^2.0.1"
|
brace-expansion "^2.0.1"
|
||||||
|
|
||||||
minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2:
|
minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
|
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
|
||||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||||
@@ -5126,6 +5328,11 @@ mz@^2.4.0:
|
|||||||
object-assign "^4.0.1"
|
object-assign "^4.0.1"
|
||||||
thenify-all "^1.0.0"
|
thenify-all "^1.0.0"
|
||||||
|
|
||||||
|
natural-compare@^1.4.0:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||||
|
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
||||||
|
|
||||||
negotiator@^0.6.3:
|
negotiator@^0.6.3:
|
||||||
version "0.6.3"
|
version "0.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
|
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
|
||||||
@@ -5499,6 +5706,18 @@ onetime@^6.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mimic-fn "^4.0.0"
|
mimic-fn "^4.0.0"
|
||||||
|
|
||||||
|
optionator@^0.9.3:
|
||||||
|
version "0.9.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
|
||||||
|
integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==
|
||||||
|
dependencies:
|
||||||
|
deep-is "^0.1.3"
|
||||||
|
fast-levenshtein "^2.0.6"
|
||||||
|
levn "^0.4.1"
|
||||||
|
prelude-ls "^1.2.1"
|
||||||
|
type-check "^0.4.0"
|
||||||
|
word-wrap "^1.2.5"
|
||||||
|
|
||||||
p-cancelable@^2.0.0:
|
p-cancelable@^2.0.0:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz"
|
resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz"
|
||||||
@@ -5908,6 +6127,11 @@ postcss-selector-parser@^6.0.10:
|
|||||||
cssesc "^3.0.0"
|
cssesc "^3.0.0"
|
||||||
util-deprecate "^1.0.2"
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
|
prelude-ls@^1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||||
|
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||||
|
|
||||||
prettier@^3.2.5:
|
prettier@^3.2.5:
|
||||||
version "3.2.5"
|
version "3.2.5"
|
||||||
resolved "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz"
|
resolved "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz"
|
||||||
@@ -6892,7 +7116,7 @@ strip-final-newline@^4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-4.0.0.tgz#35a369ec2ac43df356e3edd5dcebb6429aa1fa5c"
|
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-4.0.0.tgz#35a369ec2ac43df356e3edd5dcebb6429aa1fa5c"
|
||||||
integrity sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==
|
integrity sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==
|
||||||
|
|
||||||
strip-json-comments@3.1.1:
|
strip-json-comments@3.1.1, strip-json-comments@^3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
|
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
|
||||||
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
||||||
@@ -7010,7 +7234,7 @@ text-extensions@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-2.4.0.tgz#a1cfcc50cf34da41bfd047cc744f804d1680ea34"
|
resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-2.4.0.tgz#a1cfcc50cf34da41bfd047cc744f804d1680ea34"
|
||||||
integrity sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==
|
integrity sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==
|
||||||
|
|
||||||
text-table@~0.2.0:
|
text-table@^0.2.0, text-table@~0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||||
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
|
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
|
||||||
@@ -7228,6 +7452,13 @@ tunnel@^0.0.6:
|
|||||||
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
|
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
|
||||||
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
|
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
|
||||||
|
|
||||||
|
type-check@^0.4.0, type-check@~0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
||||||
|
integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
|
||||||
|
dependencies:
|
||||||
|
prelude-ls "^1.2.1"
|
||||||
|
|
||||||
type-detect@^4.0.0, type-detect@^4.0.8:
|
type-detect@^4.0.0, type-detect@^4.0.8:
|
||||||
version "4.0.8"
|
version "4.0.8"
|
||||||
resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz"
|
resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz"
|
||||||
@@ -7534,6 +7765,11 @@ which@^4.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
isexe "^3.1.1"
|
isexe "^3.1.1"
|
||||||
|
|
||||||
|
word-wrap@^1.2.5:
|
||||||
|
version "1.2.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
|
||||||
|
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
|
||||||
|
|
||||||
wordwrap@^1.0.0:
|
wordwrap@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
|
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
|
||||||
|
|||||||
Reference in New Issue
Block a user