Add topic filter
This commit is contained in:
@@ -42,6 +42,10 @@ class UpdateNotifier extends React.Component<Props, {}> {
|
|||||||
rendererEvents.subscribe(updateAvailable, this.handleUpdate)
|
rendererEvents.subscribe(updateAvailable, this.handleUpdate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public componentWillUnmount() {
|
||||||
|
rendererEvents.unsubscribeAll(updateAvailable)
|
||||||
|
}
|
||||||
|
|
||||||
private fixUrl(url: string, version: string) {
|
private fixUrl(url: string, version: string) {
|
||||||
if (!/^http/.test(url)) {
|
if (!/^http/.test(url)) {
|
||||||
return `https://github.com/thomasnordquist/MQTT-Explorer/releases/download/v${version}/${url}`
|
return `https://github.com/thomasnordquist/MQTT-Explorer/releases/download/v${version}/${url}`
|
||||||
@@ -49,9 +53,6 @@ class UpdateNotifier extends React.Component<Props, {}> {
|
|||||||
|
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
public componentWillUnmount() {
|
|
||||||
rendererEvents.unsubscribeAll(updateAvailable)
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleUpdate = (updateInfo: UpdateInfo) => {
|
private handleUpdate = (updateInfo: UpdateInfo) => {
|
||||||
this.updateInfo = updateInfo
|
this.updateInfo = updateInfo
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Dispatch } from 'redux'
|
|||||||
import { rendererEvents, addMqttConnectionEvent, makeConnectionStateEvent, removeConnection } from '../../../events'
|
import { rendererEvents, addMqttConnectionEvent, makeConnectionStateEvent, removeConnection } from '../../../events'
|
||||||
import { AppState } from '../reducers'
|
import { AppState } from '../reducers'
|
||||||
import * as q from '../../../backend/src/Model'
|
import * as q from '../../../backend/src/Model'
|
||||||
|
import { showTree } from './Tree'
|
||||||
|
|
||||||
export const connect = (options: MqttOptions, connectionId: string) => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
export const connect = (options: MqttOptions, connectionId: string) => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||||
dispatch(connecting(connectionId))
|
dispatch(connecting(connectionId))
|
||||||
@@ -14,6 +15,7 @@ export const connect = (options: MqttOptions, connectionId: string) => (dispatch
|
|||||||
const tree = new q.Tree()
|
const tree = new q.Tree()
|
||||||
tree.updateWithConnection(rendererEvents, connectionId)
|
tree.updateWithConnection(rendererEvents, connectionId)
|
||||||
dispatch(connected(tree))
|
dispatch(connected(tree))
|
||||||
|
dispatch(showTree(tree))
|
||||||
} else if (dataSourceState.error) {
|
} else if (dataSourceState.error) {
|
||||||
dispatch(showError(dataSourceState.error))
|
dispatch(showError(dataSourceState.error))
|
||||||
dispatch(disconnect())
|
dispatch(disconnect())
|
||||||
@@ -36,11 +38,12 @@ export const showError = (error?: string) => ({
|
|||||||
type: ActionTypes.CONNECTION_SET_SHOW_ERROR,
|
type: ActionTypes.CONNECTION_SET_SHOW_ERROR,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const disconnect = () => (dispatch: Dispatch<Action>, getState: () => AppState) => {
|
export const disconnect = () => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||||
const { connectionId, tree } = getState().connection
|
const { connectionId, tree } = getState().connection
|
||||||
rendererEvents.emit(removeConnection, connectionId)
|
rendererEvents.emit(removeConnection, connectionId)
|
||||||
tree && tree.stopUpdating()
|
tree && tree.stopUpdating()
|
||||||
|
|
||||||
|
dispatch(showTree(undefined))
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ActionTypes.CONNECTION_SET_DISCONNECTED,
|
type: ActionTypes.CONNECTION_SET_DISCONNECTED,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { Action, ActionTypes, TopicOrder } from '../reducers/Settings'
|
import { Action, ActionTypes, TopicOrder } from '../reducers/Settings'
|
||||||
|
import { ActionTypes as TreeActionTypes, Action as TreeAction } from '../reducers/Tree'
|
||||||
|
import { Dispatch } from 'redux'
|
||||||
|
import { AppState } from '../reducers'
|
||||||
|
import * as q from '../../../backend/src/Model'
|
||||||
|
|
||||||
export const setAutoExpandLimit = (autoExpandLimit: number = 0): Action => {
|
export const setAutoExpandLimit = (autoExpandLimit: number = 0): Action => {
|
||||||
return {
|
return {
|
||||||
@@ -20,9 +24,44 @@ export const setTopicOrder = (topicOrder: TopicOrder = TopicOrder.none): Action
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const filterTopics = (topicFilter: string): Action => {
|
export const filterTopics = (filterStr: string) => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||||
return {
|
const topicFilter = filterStr.toLowerCase()
|
||||||
|
|
||||||
|
dispatch({
|
||||||
topicFilter,
|
topicFilter,
|
||||||
type: ActionTypes.SETTINGS_FILTER_TOPICS,
|
type: ActionTypes.SETTINGS_FILTER_TOPICS,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { tree } = getState().connection
|
||||||
|
if (!tree) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!topicFilter) {
|
||||||
|
dispatch({
|
||||||
|
tree,
|
||||||
|
filter: '',
|
||||||
|
type: TreeActionTypes.TREE_SHOW_TREE,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultTree = tree.leafes()
|
||||||
|
.filter(leaf => leaf.path().toLowerCase().indexOf(topicFilter) !== -1)
|
||||||
|
.map((node) => {
|
||||||
|
const clone = node.unconnectedClone()
|
||||||
|
q.TreeNodeFactory.insertNodeAtPosition(node.path().split('/'), clone)
|
||||||
|
return clone.firstNode()
|
||||||
|
})
|
||||||
|
.reduce((a: q.TreeNode, b: q.TreeNode) => {
|
||||||
|
a.updateWithNode(b)
|
||||||
|
return a
|
||||||
|
}, new q.Tree())
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
tree: resultTree,
|
||||||
|
filter: topicFilter,
|
||||||
|
type: TreeActionTypes.TREE_SHOW_TREE,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { AppState } from '../reducers'
|
|||||||
import { makePublishEvent, rendererEvents } from '../../../events'
|
import { makePublishEvent, rendererEvents } from '../../../events'
|
||||||
|
|
||||||
export const clearRetainedTopic = () => (dispatch: Dispatch<Action>, getState: () => AppState) => {
|
export const clearRetainedTopic = () => (dispatch: Dispatch<Action>, getState: () => AppState) => {
|
||||||
const { selectedTopic } = getState().tooBigReducer
|
const { selectedTopic } = getState().tree
|
||||||
const { connectionId } = getState().connection
|
const { connectionId } = getState().connection
|
||||||
|
|
||||||
if (!selectedTopic || !connectionId) {
|
if (!selectedTopic || !connectionId) {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { ActionTypes, CustomAction, AppState } from '../reducers'
|
import { AppState } from '../reducers'
|
||||||
|
import { ActionTypes } from '../reducers/Tree'
|
||||||
import * as q from '../../../backend/src/Model'
|
import * as q from '../../../backend/src/Model'
|
||||||
import { Dispatch } from 'redux'
|
import { Dispatch } from 'redux'
|
||||||
import { setTopic } from './Publish'
|
import { setTopic } from './Publish'
|
||||||
|
|
||||||
export const selectTopic = (topic: q.TreeNode) => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
export const selectTopic = (topic: q.TreeNode) => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||||
const { selectedTopic } = getState().tooBigReducer
|
const { selectedTopic } = getState().tree
|
||||||
|
|
||||||
// Update publish topic
|
// Update publish topic
|
||||||
if (selectedTopic && (selectedTopic.path() === getState().publish.topic || !getState().publish.topic)) {
|
if (selectedTopic && (selectedTopic.path() === getState().publish.topic || !getState().publish.topic)) {
|
||||||
@@ -13,6 +14,13 @@ export const selectTopic = (topic: q.TreeNode) => (dispatch: Dispatch<any>, getS
|
|||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
selectedTopic: topic,
|
selectedTopic: topic,
|
||||||
type: ActionTypes.selectTopic,
|
type: ActionTypes.TREE_SELECT_TOPIC,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const showTree = (tree?: q.Tree) => {
|
||||||
|
return {
|
||||||
|
tree,
|
||||||
|
type: ActionTypes.TREE_SHOW_TREE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import * as q from '../../../backend/src/Model'
|
|
||||||
|
|
||||||
import { AppState } from '../reducers'
|
import { AppState } from '../reducers'
|
||||||
import {
|
import {
|
||||||
@@ -19,6 +18,7 @@ import { bindActionCreators } from 'redux'
|
|||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { settingsActions } from '../actions'
|
import { settingsActions } from '../actions'
|
||||||
import { TopicOrder } from '../reducers/Settings'
|
import { TopicOrder } from '../reducers/Settings'
|
||||||
|
import Topic from './Sidebar/Topic';
|
||||||
|
|
||||||
const styles: StyleRulesCallback = theme => ({
|
const styles: StyleRulesCallback = theme => ({
|
||||||
drawer: {
|
drawer: {
|
||||||
@@ -40,7 +40,7 @@ const styles: StyleRulesCallback = theme => ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
actions?: any
|
actions: typeof settingsActions
|
||||||
autoExpandLimit: number
|
autoExpandLimit: number
|
||||||
visible: boolean
|
visible: boolean
|
||||||
store?: any
|
store?: any
|
||||||
@@ -107,7 +107,7 @@ class Settings extends React.Component<Props, {}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onChangeAutoExpand = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
private onChangeAutoExpand = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
this.props.actions.setAutoExpandLimit(e.target.value)
|
this.props.actions.setAutoExpandLimit(parseInt(e.target.value, 10))
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderNodeOrder() {
|
private renderNodeOrder() {
|
||||||
@@ -133,7 +133,7 @@ class Settings extends React.Component<Props, {}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onChangeSorting = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
private onChangeSorting = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
this.props.actions.setNodeOrder(e.target.value)
|
this.props.actions.setTopicOrder(e.target.value as TopicOrder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ class Sidebar extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public componentWillUnmount() {
|
||||||
|
this.props.node && this.removeUpdateListener(this.props.node)
|
||||||
|
}
|
||||||
|
|
||||||
private registerUpdateListener(node: q.TreeNode) {
|
private registerUpdateListener(node: q.TreeNode) {
|
||||||
node.onMerge.subscribe(this.updateNode)
|
node.onMerge.subscribe(this.updateNode)
|
||||||
node.onMessage.subscribe(this.updateNode)
|
node.onMessage.subscribe(this.updateNode)
|
||||||
@@ -215,7 +219,7 @@ class Sidebar extends React.Component<Props, State> {
|
|||||||
|
|
||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
return {
|
return {
|
||||||
node: state.tooBigReducer.selectedTopic,
|
node: state.tree.selectedTopic,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ interface Props {
|
|||||||
didSelectNode?: (node: q.TreeNode) => void
|
didSelectNode?: (node: q.TreeNode) => void
|
||||||
connectionId?: string
|
connectionId?: string
|
||||||
tree?: q.Tree
|
tree?: q.Tree
|
||||||
|
filter: string
|
||||||
}
|
}
|
||||||
|
|
||||||
class Tree extends React.Component<Props, {}> {
|
class Tree extends React.Component<Props, {}> {
|
||||||
@@ -46,9 +47,14 @@ class Tree extends React.Component<Props, {}> {
|
|||||||
if (nextProps.tree) {
|
if (nextProps.tree) {
|
||||||
nextProps.tree.onMerge.subscribe(this.throttledTreeUpdate)
|
nextProps.tree.onMerge.subscribe(this.throttledTreeUpdate)
|
||||||
}
|
}
|
||||||
|
this.setState(this.state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public componentWillUnmount() {
|
||||||
|
this.props.tree && this.props.tree.onMerge.unsubscribe(this.throttledTreeUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
public throttledTreeUpdate = () => {
|
public throttledTreeUpdate = () => {
|
||||||
if (this.updateTimer) {
|
if (this.updateTimer) {
|
||||||
return
|
return
|
||||||
@@ -68,15 +74,9 @@ class Tree extends React.Component<Props, {}> {
|
|||||||
}, Math.max(0, timeUntilNextUpdate))
|
}, Math.max(0, timeUntilNextUpdate))
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount() {
|
|
||||||
if (this.props.connectionId) {
|
|
||||||
const event = makeConnectionMessageEvent(this.props.connectionId)
|
|
||||||
rendererEvents.unsubscribeAll(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
if (!this.props.tree) {
|
const { tree, filter } = this.props
|
||||||
|
if (!tree) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,17 +84,17 @@ class Tree extends React.Component<Props, {}> {
|
|||||||
lineHeight: '1.1',
|
lineHeight: '1.1',
|
||||||
cursor: 'default',
|
cursor: 'default',
|
||||||
}
|
}
|
||||||
|
const key = `rootNode-${filter}`
|
||||||
return (
|
return (
|
||||||
<div style={style}>
|
<div style={style}>
|
||||||
<TreeNode
|
<TreeNode
|
||||||
|
key={key}
|
||||||
animateChages={true}
|
animateChages={true}
|
||||||
isRoot={true}
|
isRoot={true}
|
||||||
treeNode={this.props.tree}
|
treeNode={tree}
|
||||||
name="/"
|
name="/"
|
||||||
|
lastUpdate={tree.lastUpdate}
|
||||||
collapsed={false}
|
collapsed={false}
|
||||||
key="rootNode"
|
|
||||||
lastUpdate={this.props.tree.lastUpdate}
|
|
||||||
performanceCallback={this.performanceCallback}
|
performanceCallback={this.performanceCallback}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -109,7 +109,8 @@ class Tree extends React.Component<Props, {}> {
|
|||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
return {
|
return {
|
||||||
autoExpandLimit: state.settings.autoExpandLimit,
|
autoExpandLimit: state.settings.autoExpandLimit,
|
||||||
tree: state.connection.tree,
|
tree: state.tree.tree,
|
||||||
|
filter: state.tree.filter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,17 @@ export interface Props {
|
|||||||
theme: Theme
|
theme: Theme
|
||||||
}
|
}
|
||||||
|
|
||||||
class TreeNodeSubnodes extends React.Component<Props, {}> {
|
interface State {
|
||||||
|
alreadyAdded: number
|
||||||
|
}
|
||||||
|
|
||||||
|
class TreeNodeSubnodes extends React.Component<Props, State> {
|
||||||
|
private renderMoreAnimationFrame?: any
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props)
|
||||||
|
this.state = { alreadyAdded: 10 }
|
||||||
|
}
|
||||||
|
|
||||||
private sortedNodes(): q.TreeNode[] {
|
private sortedNodes(): q.TreeNode[] {
|
||||||
const { topicOrder, treeNode } = this.props
|
const { topicOrder, treeNode } = this.props
|
||||||
|
|
||||||
@@ -39,17 +49,32 @@ class TreeNodeSubnodes extends React.Component<Props, {}> {
|
|||||||
return nodes
|
return nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderMore() {
|
||||||
|
this.renderMoreAnimationFrame = window.requestAnimationFrame(() => {
|
||||||
|
this.setState({ ...this.state, alreadyAdded: this.state.alreadyAdded * 1.5 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentWillUnmount() {
|
||||||
|
window.cancelAnimationFrame(this.renderMoreAnimationFrame)
|
||||||
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const edges = Object.values(this.props.treeNode.edges)
|
const edges = Object.values(this.props.treeNode.edges)
|
||||||
if (edges.length === 0 || this.props.collapsed) {
|
if (edges.length === 0 || this.props.collapsed) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.alreadyAdded < edges.length) {
|
||||||
|
const delta = Math.min(this.state.alreadyAdded, edges.length - this.state.alreadyAdded)
|
||||||
|
this.renderMore()
|
||||||
|
}
|
||||||
|
|
||||||
const listItemStyle = {
|
const listItemStyle = {
|
||||||
padding: '3px 0px 0px 8px',
|
padding: '3px 0px 0px 8px',
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodes = this.sortedNodes()
|
const nodes = this.sortedNodes().slice(0, this.state.alreadyAdded)
|
||||||
const listItems = nodes.map(node => (
|
const listItems = nodes.map(node => (
|
||||||
<div key={node.hash()}>
|
<div key={node.hash()}>
|
||||||
<TreeNode
|
<TreeNode
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import App from './App'
|
|||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
import { createStore, applyMiddleware, compose } from 'redux'
|
import { createStore, applyMiddleware, compose } from 'redux'
|
||||||
|
|
||||||
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
|
const composeEnhancers = /*(window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || */ compose
|
||||||
const store = createStore(
|
const store = createStore(
|
||||||
reducers,
|
reducers,
|
||||||
composeEnhancers(
|
composeEnhancers(
|
||||||
|
|||||||
84
app/src/reducers/Settings.ts
Normal file
84
app/src/reducers/Settings.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { Action } from 'redux'
|
||||||
|
import { createReducer } from './lib'
|
||||||
|
|
||||||
|
export enum TopicOrder {
|
||||||
|
none = 'none',
|
||||||
|
messages = '#messages',
|
||||||
|
abc = 'abc',
|
||||||
|
topics = '#topics',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SettingsState {
|
||||||
|
autoExpandLimit: number
|
||||||
|
visible: boolean
|
||||||
|
topicOrder: TopicOrder
|
||||||
|
topicFilter?: string
|
||||||
|
}
|
||||||
|
export type Action = SetAutoExpandLimit | ToggleVisibility | SetTopicOrder | FilterTopics
|
||||||
|
|
||||||
|
export enum ActionTypes {
|
||||||
|
SETTINGS_SET_AUTO_EXPAND_LIMIT = 'SETTINGS_SET_AUTO_EXPAND_LIMIT',
|
||||||
|
SETTINGS_TOGGLE_VISIBILITY = 'SETTINGS_TOGGLE_VISIBILITY',
|
||||||
|
SETTINGS_SET_TOPIC_ORDER = 'SETTINGS_SET_TOPIC_ORDER',
|
||||||
|
SETTINGS_FILTER_TOPICS = 'SETTINGS_FILTER_TOPICS',
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: SettingsState = {
|
||||||
|
autoExpandLimit: 0,
|
||||||
|
topicOrder: TopicOrder.none,
|
||||||
|
visible: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const settingsReducer = createReducer(initialState, {
|
||||||
|
SETTINGS_SET_AUTO_EXPAND_LIMIT: setAutoExpandLimit,
|
||||||
|
SETTINGS_TOGGLE_VISIBILITY: toggleVisibility,
|
||||||
|
SETTINGS_SET_TOPIC_ORDER: setTopicOrder,
|
||||||
|
SETTINGS_FILTER_TOPICS: filterTopics,
|
||||||
|
})
|
||||||
|
|
||||||
|
function setAutoExpandLimit(state: SettingsState, action: SetAutoExpandLimit) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
autoExpandLimit: action.autoExpandLimit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetAutoExpandLimit {
|
||||||
|
type: ActionTypes.SETTINGS_SET_AUTO_EXPAND_LIMIT
|
||||||
|
autoExpandLimit: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleVisibility(state: SettingsState, action: ToggleVisibility) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
visible: !state.visible,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToggleVisibility {
|
||||||
|
type: ActionTypes.SETTINGS_TOGGLE_VISIBILITY
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTopicOrder(state: SettingsState, action: SetTopicOrder) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
topicOrder: action.topicOrder,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetTopicOrder {
|
||||||
|
type: ActionTypes.SETTINGS_SET_TOPIC_ORDER
|
||||||
|
topicOrder: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterTopics(state: SettingsState, action: FilterTopics) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
topicFilter: action.topicFilter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FilterTopics {
|
||||||
|
type: ActionTypes.SETTINGS_FILTER_TOPICS
|
||||||
|
topicFilter: string
|
||||||
|
}
|
||||||
49
app/src/reducers/Tree.ts
Normal file
49
app/src/reducers/Tree.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import * as q from '../../../backend/src/Model'
|
||||||
|
import { Action } from 'redux'
|
||||||
|
import { createReducer } from './lib'
|
||||||
|
|
||||||
|
export interface TreeState {
|
||||||
|
tree?: q.Tree
|
||||||
|
selectedTopic?: q.TreeNode
|
||||||
|
filter?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Action = ShowTree | SelectTopic
|
||||||
|
|
||||||
|
export enum ActionTypes {
|
||||||
|
TREE_SHOW_TREE = 'TREE_SHOW_TREE',
|
||||||
|
TREE_SELECT_TOPIC = 'TREE_SELECT_TOPIC',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShowTree {
|
||||||
|
type: ActionTypes.TREE_SHOW_TREE
|
||||||
|
tree?: q.Tree
|
||||||
|
filter?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectTopic {
|
||||||
|
type: ActionTypes.TREE_SELECT_TOPIC
|
||||||
|
selectedTopic?: q.TreeNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: TreeState = { }
|
||||||
|
|
||||||
|
export const treeReducer = createReducer(initialState, {
|
||||||
|
TREE_SHOW_TREE: showTree,
|
||||||
|
TREE_SELECT_TOPIC: selectTopic,
|
||||||
|
})
|
||||||
|
|
||||||
|
function showTree(state: TreeState, action: ShowTree) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
tree: action.tree,
|
||||||
|
filter: action.filter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectTopic(state: TreeState, action: SelectTopic) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
selectedTopic: action.selectedTopic,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,35 +6,33 @@ import { trackEvent } from '../tracking'
|
|||||||
import { PublishState, publishReducer } from './Publish'
|
import { PublishState, publishReducer } from './Publish'
|
||||||
import { ConnectionState, connectionReducer } from './Connection'
|
import { ConnectionState, connectionReducer } from './Connection'
|
||||||
import { SettingsState, settingsReducer } from './Settings'
|
import { SettingsState, settingsReducer } from './Settings'
|
||||||
|
import { TreeState, treeReducer } from './Tree'
|
||||||
|
|
||||||
export enum ActionTypes {
|
export enum ActionTypes {
|
||||||
selectTopic = 'SELECT_TOPIC',
|
|
||||||
showUpdateNotification = 'SHOW_UPDATE_NOTIFICATION',
|
showUpdateNotification = 'SHOW_UPDATE_NOTIFICATION',
|
||||||
showUpdateDetails = 'SHOW_UPDATE_DETAILS',
|
showUpdateDetails = 'SHOW_UPDATE_DETAILS',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomAction extends Action {
|
export interface CustomAction extends Action {
|
||||||
type: ActionTypes,
|
type: ActionTypes,
|
||||||
selectedTopic?: q.TreeNode
|
|
||||||
showUpdateNotification?: boolean
|
showUpdateNotification?: boolean
|
||||||
showUpdateDetails?: boolean
|
showUpdateDetails?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
tooBigReducer: TooBigOfState
|
tooBigReducer: TooBigOfState
|
||||||
|
tree: TreeState
|
||||||
settings: SettingsState,
|
settings: SettingsState,
|
||||||
publish: PublishState
|
publish: PublishState
|
||||||
connection: ConnectionState
|
connection: ConnectionState
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TooBigOfState {
|
export interface TooBigOfState {
|
||||||
selectedTopic?: q.TreeNode
|
|
||||||
showUpdateNotification?: boolean
|
showUpdateNotification?: boolean
|
||||||
showUpdateDetails: boolean
|
showUpdateDetails: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialBigState: TooBigOfState = {
|
const initialBigState: TooBigOfState = {
|
||||||
selectedTopic: undefined,
|
|
||||||
showUpdateDetails: false,
|
showUpdateDetails: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,17 +41,8 @@ const tooBigReducer: Reducer<TooBigOfState | undefined, CustomAction> = (state =
|
|||||||
throw Error('No initial state')
|
throw Error('No initial state')
|
||||||
}
|
}
|
||||||
trackEvent(action.type)
|
trackEvent(action.type)
|
||||||
console.log(action, state)
|
|
||||||
switch (action.type) {
|
|
||||||
case ActionTypes.selectTopic:
|
|
||||||
if (!action.selectedTopic) {
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
selectedTopic: action.selectedTopic,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
case ActionTypes.showUpdateNotification:
|
case ActionTypes.showUpdateNotification:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@@ -79,6 +68,7 @@ const reducer = combineReducers({
|
|||||||
publish: publishReducer,
|
publish: publishReducer,
|
||||||
connection: connectionReducer,
|
connection: connectionReducer,
|
||||||
settings: settingsReducer,
|
settings: settingsReducer,
|
||||||
|
tree: treeReducer,
|
||||||
})
|
})
|
||||||
|
|
||||||
export default reducer
|
export default reducer
|
||||||
|
|||||||
@@ -10,9 +10,19 @@ export class RingBuffer<T extends Lengthwise> {
|
|||||||
private start: number = 0
|
private start: number = 0
|
||||||
private end: number = 0
|
private end: number = 0
|
||||||
|
|
||||||
constructor(capacity: number, maxItems = Infinity) {
|
constructor(capacity: number, maxItems = Infinity, ringBuffer?: RingBuffer<T>) {
|
||||||
this.capacity = capacity
|
this.capacity = capacity
|
||||||
this.maxItems = maxItems
|
this.maxItems = maxItems
|
||||||
|
|
||||||
|
if (ringBuffer) {
|
||||||
|
this.items = ringBuffer.toArray()
|
||||||
|
this.end = this.items.length
|
||||||
|
this.usage = this.items.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public clone(): RingBuffer<T> {
|
||||||
|
return new RingBuffer(this.capacity, this.maxItems, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
public toArray() {
|
public toArray() {
|
||||||
|
|||||||
@@ -16,6 +16,17 @@ export class TreeNode {
|
|||||||
private cachedLeafes?: TreeNode[]
|
private cachedLeafes?: TreeNode[]
|
||||||
private cachedLeafMessageCount?: number
|
private cachedLeafMessageCount?: number
|
||||||
|
|
||||||
|
public unconnectedClone() {
|
||||||
|
const node = new TreeNode()
|
||||||
|
node.message = this.message
|
||||||
|
node.mqttMessage = this.mqttMessage
|
||||||
|
node.messageHistory = this.messageHistory.clone()
|
||||||
|
node.messages = this.messages
|
||||||
|
node.lastUpdate = this.lastUpdate
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
constructor(sourceEdge?: Edge, message?: Message) {
|
constructor(sourceEdge?: Edge, message?: Message) {
|
||||||
if (sourceEdge) {
|
if (sourceEdge) {
|
||||||
this.sourceEdge = sourceEdge
|
this.sourceEdge = sourceEdge
|
||||||
|
|||||||
@@ -5,21 +5,29 @@ interface HasLength {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export abstract class TreeNodeFactory {
|
export abstract class TreeNodeFactory {
|
||||||
public static fromEdgesAndValue<T extends HasLength>(edgeNames: string[], value?: T): TreeNode {
|
public static insertNodeAtPosition(edgeNames: string[], node: TreeNode) {
|
||||||
let currentNode: TreeNode = new Tree()
|
let currentNode: TreeNode = new Tree()
|
||||||
|
let edge
|
||||||
for (const edgeName of edgeNames) {
|
for (const edgeName of edgeNames) {
|
||||||
const edge = new Edge(edgeName)
|
edge = new Edge(edgeName)
|
||||||
const newNode = new TreeNode(edge)
|
|
||||||
edge.target = newNode
|
|
||||||
currentNode.addEdge(edge)
|
currentNode.addEdge(edge)
|
||||||
currentNode = newNode
|
currentNode = new TreeNode(edge)
|
||||||
|
edge.target = currentNode
|
||||||
}
|
}
|
||||||
|
node.sourceEdge = edge
|
||||||
|
node.sourceEdge!.target = node
|
||||||
|
}
|
||||||
|
|
||||||
currentNode.setMessage({
|
public static fromEdgesAndValue<T extends HasLength>(edgeNames: string[], value?: T): TreeNode {
|
||||||
|
const node = new TreeNode()
|
||||||
|
node.setMessage({
|
||||||
value,
|
value,
|
||||||
length: value ? value.length : 0,
|
length: value ? value.length : 0,
|
||||||
received: new Date(),
|
received: new Date(),
|
||||||
})
|
})
|
||||||
return currentNode
|
|
||||||
|
this.insertNodeAtPosition(edgeNames, node)
|
||||||
|
|
||||||
|
return node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user