Adapt redux

Add hover-effect on nodes
Add Setting drawer
Ass auto expansion setting
This commit is contained in:
Thomas Nordquist
2019-01-07 22:47:22 +01:00
parent e945721221
commit e72696dc57
21 changed files with 2667 additions and 139 deletions

57
app/package-lock.json generated
View File

@@ -87,6 +87,19 @@
} }
} }
}, },
"@material-ui/lab": {
"version": "3.0.0-alpha.27",
"resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-3.0.0-alpha.27.tgz",
"integrity": "sha512-VwUCXEJEo1QkSTDoAg5g8K51f6Re7Fh5d7m5IO9W95B9iEYVJhSGy+WY1/ZQdpJl1F1gPzL9u2asCg6LIazBLA==",
"dev": true,
"requires": {
"@babel/runtime": "7.2.0",
"@material-ui/utils": "^3.0.0-alpha.2",
"classnames": "^2.2.5",
"keycode": "^2.1.9",
"prop-types": "^15.6.0"
}
},
"@material-ui/utils": { "@material-ui/utils": {
"version": "3.0.0-alpha.2", "version": "3.0.0-alpha.2",
"resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-3.0.0-alpha.2.tgz", "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-3.0.0-alpha.2.tgz",
@@ -129,10 +142,21 @@
"version": "16.0.11", "version": "16.0.11",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.0.11.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.0.11.tgz",
"integrity": "sha512-x6zUx9/42B5Kl2Vl9HlopV8JF64wLpX3c+Pst9kc1HgzrsH+mkehe/zmHMQTplIrR48H2gpU7ZqurQolYu8XBA==", "integrity": "sha512-x6zUx9/42B5Kl2Vl9HlopV8JF64wLpX3c+Pst9kc1HgzrsH+mkehe/zmHMQTplIrR48H2gpU7ZqurQolYu8XBA==",
"dev": true,
"requires": { "requires": {
"@types/react": "*" "@types/react": "*"
} }
}, },
"@types/react-redux": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-6.0.12.tgz",
"integrity": "sha512-fvcpm7cfW/JMflRdZgegCVbSGYt/hyEWQKriesaLZDRDjBGKQsAiui08VCQg5lBpocPmulVGKFhICtOAcMUPOQ==",
"dev": true,
"requires": {
"@types/react": "*",
"redux": "^4.0.0"
}
},
"@types/react-transition-group": { "@types/react-transition-group": {
"version": "2.0.15", "version": "2.0.15",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.0.15.tgz", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.0.15.tgz",
@@ -2892,6 +2916,15 @@
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",
"integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==" "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw=="
}, },
"invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"dev": true,
"requires": {
"loose-envify": "^1.0.0"
}
},
"invert-kv": { "invert-kv": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
@@ -4202,6 +4235,20 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
}, },
"react-redux": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-6.0.0.tgz",
"integrity": "sha512-EmbC3uLl60pw2VqSSkj6HpZ6jTk12RMrwXMBdYtM6niq0MdEaRq9KYCwpJflkOZj349BLGQm1MI/JO1W96kLWQ==",
"dev": true,
"requires": {
"@babel/runtime": "^7.2.0",
"hoist-non-react-statics": "^3.2.1",
"invariant": "^2.2.4",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2",
"react-is": "^16.6.3"
}
},
"react-textarea-autosize": { "react-textarea-autosize": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "http://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz", "resolved": "http://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz",
@@ -4265,6 +4312,16 @@
} }
} }
}, },
"redux": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz",
"integrity": "sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==",
"dev": true,
"requires": {
"loose-envify": "^1.4.0",
"symbol-observable": "^1.2.0"
}
},
"regenerator-runtime": { "regenerator-runtime": {
"version": "0.12.1", "version": "0.12.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",

View File

@@ -11,13 +11,18 @@
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@material-ui/icons": "^3.0.1", "@material-ui/icons": "^3.0.1",
"@material-ui/lab": "^3.0.0-alpha.27",
"@types/react": "^16.7.18",
"@types/react-dom": "^16.0.11",
"@types/react-redux": "^6.0.12",
"react-redux": "^6.0.0",
"react-transition-group": "^2.5.2",
"redux": "^4.0.1",
"webpack-livereload-plugin": "^2.2.0" "webpack-livereload-plugin": "^2.2.0"
}, },
"dependencies": { "dependencies": {
"@material-ui/core": "^3.7.1", "@material-ui/core": "^3.7.1",
"@types/node": "^10.12.18", "@types/node": "^10.12.18",
"@types/react": "^16.7.18",
"@types/react-dom": "^16.0.11",
"@types/sha1": "^1.1.1", "@types/sha1": "^1.1.1",
"@types/socket.io-client": "^1.4.32", "@types/socket.io-client": "^1.4.32",
"@types/vis": "^4.21.9", "@types/vis": "^4.21.9",

View File

@@ -1,13 +1,17 @@
import * as React from 'react' import * as React from 'react'
import { connect } from 'react-redux'
import { AppState } from './reducers'
import { withStyles, Theme } from '@material-ui/core/styles'
import CssBaseline from '@material-ui/core/CssBaseline'
import * as q from '../../backend/src/Model' import * as q from '../../backend/src/Model'
import { Tree } from './components/Tree/Tree' import Tree from './components/Tree/Tree'
import TitleBar from './components/TitleBar' import TitleBar from './components/TitleBar'
import Sidebar from './components/Sidebar/Sidebar' import Sidebar from './components/Sidebar/Sidebar'
import Connection from './components/ConnectionSetup/Connection' import Connection from './components/ConnectionSetup/Connection'
// import { default as EventBus } from '../../events' import Settings from './components/Settings'
import { withTheme, Theme } from '@material-ui/core/styles'
interface State { interface State {
selectedNode?: q.TreeNode, selectedNode?: q.TreeNode,
@@ -17,6 +21,7 @@ interface State {
interface Props { interface Props {
name: string name: string
theme: Theme theme: Theme
settingsVisible: boolean
} }
class App extends React.Component<Props, State> { class App extends React.Component<Props, State> {
@@ -29,40 +34,68 @@ class App extends React.Component<Props, State> {
private getStyles(): {[s: string]: React.CSSProperties} { private getStyles(): {[s: string]: React.CSSProperties} {
const { theme } = this.props const { theme } = this.props
const drawerWidth = 300
return { return {
left: { left: {
backgroundColor: theme.palette.background.default, backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary, color: theme.palette.text.primary,
height: 'calc(100vh - 64px)', height: 'calc(100vh - 64px)',
float: 'left', float: 'left',
overflowY: 'scroll',
overflowX: 'hidden',
display: 'block',
width: '60vw', width: '60vw',
},
right: {
height: 'calc(100vh - 64px)',
color: theme.palette.text.primary,
float: 'left',
width: '40vw',
overflowY: 'scroll', overflowY: 'scroll',
overflowX: 'hidden', overflowX: 'hidden',
display: 'block', display: 'block',
}, },
right: { centerContent: {
height: 'calc(100vh - 64px)', width: '100vw',
overflow: 'hidden',
},
content: {
backgroundColor: theme.palette.background.default, backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary, transition: theme.transitions.create('margin', {
float: 'right', width: '40vw', easing: theme.transitions.easing.sharp,
overflowY: 'scroll', duration: theme.transitions.duration.leavingScreen,
overflowX: 'hidden', }),
display: 'block', marginLeft: 0,
},
contentShift: {
padding: 0,
backgroundColor: theme.palette.background.default,
transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen,
}),
marginLeft: drawerWidth,
}, },
} }
} }
public render() { public render() {
return <div> const { settingsVisible } = this.props
<TitleBar /> const { content, contentShift, settings, centerContent } = this.getStyles()
<div> return <div style={centerContent}>
<div style={this.getStyles().left}> <CssBaseline />
<Tree connectionId={this.state.connectionId} didSelectNode={(node: q.TreeNode) => { <Settings />
this.setState({ selectedNode: node }) <div style={settingsVisible ? contentShift : content}>
}} /> <TitleBar />
</div> <div style={centerContent}>
<div style={this.getStyles().right}> <div style={this.getStyles().left}>
<Sidebar node={this.state.selectedNode} /> <Tree connectionId={this.state.connectionId} didSelectNode={(node: q.TreeNode) => {
this.setState({ selectedNode: node })
}} />
</div>
<div style={this.getStyles().right}>
<Sidebar node={this.state.selectedNode} />
</div>
</div> </div>
</div> </div>
<Connection onConnection={(connectionId: string) => this.setState({ connectionId })}/> <Connection onConnection={(connectionId: string) => this.setState({ connectionId })}/>
@@ -70,4 +103,10 @@ class App extends React.Component<Props, State> {
} }
} }
export default withTheme()(App) const mapStateToProps = (state: AppState) => {
return {
settingsVisible: state.settings.visible,
}
}
export default withStyles({}, { withTheme: true })(connect(mapStateToProps)(App))

View File

@@ -0,0 +1,15 @@
import { Action } from 'redux'
import { ActionTypes } from '../reducers'
export const setAutoExpandLimit = (autoExpandLimit: number = 0) => {
return {
autoExpandLimit,
type: ActionTypes.setAutoExpandLimit,
}
}
export const toggleSettingsVisibility = () => {
return {
type: ActionTypes.toggleSettingsVisibility,
}
}

3
app/src/actions/index.ts Normal file
View File

@@ -0,0 +1,3 @@
import * as settingsActions from './Settings'
export { settingsActions }

View File

@@ -85,6 +85,7 @@ class Connection extends React.Component<Props, State> {
this.props.onConnection(connectionId) this.props.onConnection(connectionId)
this.setState({ visible: false }) this.setState({ visible: false })
} else if (state.error) { } else if (state.error) {
console.log('error', state.error)
this.setState({ error: state.error }) this.setState({ error: state.error })
} }
}) })

View File

@@ -0,0 +1,121 @@
import * as React from 'react'
import * as q from '../../../backend/src/Model'
import Drawer from '@material-ui/core/Drawer'
import IconButton from '@material-ui/core/IconButton'
import Paper from '@material-ui/core/Paper'
import Divider from '@material-ui/core/Divider'
import MenuItem from '@material-ui/core/MenuItem'
import Select from '@material-ui/core/Select'
import ChevronRight from '@material-ui/icons/ChevronRight'
import { Typography, InputBase, Input, InputLabel } from '@material-ui/core'
import { withStyles, StyleRulesCallback } from '@material-ui/core/styles'
import { settingsActions } from '../actions'
import { AppState } from '../reducers'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
const styles: StyleRulesCallback = theme => ({
drawer: {
backgroundColor: theme.palette.background.default,
flexShrink: 0,
},
paper: {
width: '300px',
},
title: {
color: theme.palette.text.primary,
paddingTop: `${theme.spacing.unit}px`,
...theme.mixins.toolbar,
},
input: {
margin: `auto ${theme.spacing.unit}px auto ${theme.spacing.unit}px`,
},
})
interface Props {
actions?: any
autoExpandLimit: number,
visible: boolean,
store?: any,
classes: any
}
interface State {
}
class Settings extends React.Component<Props, State> {
constructor(props: any) {
super(props)
this.state = {}
}
public render() {
const { classes, actions, visible } = this.props
return <Drawer
anchor = "left"
className = { classes.drawer }
open = { visible }
variant = "persistent"
>
<div
className = { classes.paper }
tabIndex = { 0 }
role = "button"
onClick = {(e: React.MouseEvent) => e.stopPropagation()}
onKeyDown = {(e: React.KeyboardEvent) => e.stopPropagation()}
>
<Typography className = { classes.title } variant = "h6" color = "inherit">
<IconButton onClick = {actions.toggleSettingsVisibility}>
<ChevronRight />
</IconButton>
Settings
</Typography>
<Divider />
{this.renderAutoExpand()}
</div>
</Drawer>
}
private renderAutoExpand() {
const { classes, actions, autoExpandLimit } = this.props
return <span>
<InputLabel htmlFor="auto-expand">Auto Expand</InputLabel>
<Select
value = { autoExpandLimit }
onChange = { (e: React.ChangeEvent<HTMLSelectElement>) => actions.setAutoExpandLimit(e.target.value) }
input = {<Input name="auto-expand" id="auto-expand-label-placeholder" />}
displayEmpty
name = "auto-expand"
className = {classes.input}
>
<MenuItem value = {0}><em>Disabled</em></MenuItem>
<MenuItem value = {3}>Some</MenuItem>
<MenuItem value = {10}>Most</MenuItem>
<MenuItem value = {1E6}>All</MenuItem>
</Select>
</span>
}
}
const mapStateToProps = (state: AppState) => {
return {
autoExpandLimit: state.settings.autoExpandLimit,
visible: state.settings.visible,
}
}
const mapDispatchToProps = (dispatch: any) => {
return {
actions: bindActionCreators(settingsActions, dispatch),
}
}
export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(Settings))

View File

@@ -13,13 +13,13 @@ import { withStyles, Theme, StyleRulesCallback } from '@material-ui/core/styles'
import Copy from '../Copy' import Copy from '../Copy'
interface Props { interface Props {
node?: q.TreeNode | undefined, node?: q.TreeNode,
classes: any, classes: any,
theme: Theme theme: Theme,
} }
interface State { interface State {
node?: q.TreeNode | undefined node?: q.TreeNode
} }
class Sidebar extends React.Component<Props, State> { class Sidebar extends React.Component<Props, State> {

View File

@@ -2,6 +2,22 @@ import * as React from 'react'
import * as q from '../../../backend/src/Model' import * as q from '../../../backend/src/Model'
import Search from '@material-ui/icons/Search' import Search from '@material-ui/icons/Search'
import Drawer from '@material-ui/core/Drawer'
import IconButton from '@material-ui/core/IconButton'
import Menu from '@material-ui/icons/Menu'
import List from '@material-ui/core/List'
import Divider from '@material-ui/core/Divider'
import ListItem from '@material-ui/core/ListItem'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItemText from '@material-ui/core/ListItemText'
import Slider from '@material-ui/lab/Slider'
import { settingsActions } from '../actions'
import { AppState, SettingsModel } from '../reducers'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { AppBar, Toolbar, Typography, InputBase } from '@material-ui/core' import { AppBar, Toolbar, Typography, InputBase } from '@material-ui/core'
import { withStyles, StyleRulesCallback } from '@material-ui/core/styles' import { withStyles, StyleRulesCallback } from '@material-ui/core/styles'
@@ -53,17 +69,30 @@ const styles: StyleRulesCallback = theme => ({
width: 200, width: 200,
}, },
}, },
menuButton: {
marginLeft: -12,
marginRight: 20,
},
}) })
interface Props { interface Props {
classes: any classes: any
actions: any
} }
class TitleBar extends React.Component<Props, {}> { interface State {
selectedNode?: q.TreeNode
settingsVisible: boolean
autoExpandLimit: number
}
class TitleBar extends React.Component<Props, State> {
constructor(props: any) { constructor(props: any) {
super(props) super(props)
this.state = { this.state = {
selectedNode: undefined, selectedNode: undefined,
settingsVisible: false,
autoExpandLimit: 0,
} }
} }
@@ -72,6 +101,9 @@ class TitleBar extends React.Component<Props, {}> {
return <AppBar position="static"> return <AppBar position="static">
<Toolbar> <Toolbar>
<IconButton className={classes.menuButton} color="inherit" aria-label="Menu" onClick={this.props.actions.toggleSettingsVisibility}>
<Menu />
</IconButton>
<Typography className={classes.title} variant="h6" color="inherit">MQTT-Xplorer</Typography> <Typography className={classes.title} variant="h6" color="inherit">MQTT-Xplorer</Typography>
<div className={classes.search}> <div className={classes.search}>
<div className={classes.searchIcon}> <div className={classes.searchIcon}>
@@ -90,4 +122,10 @@ class TitleBar extends React.Component<Props, {}> {
} }
} }
export default withStyles(styles)(TitleBar) const mapDispatchToProps = (dispatch: any) => {
return {
actions: bindActionCreators(settingsActions, dispatch),
}
}
export default withStyles(styles)(connect(null, mapDispatchToProps)(TitleBar))

View File

@@ -5,13 +5,16 @@ import { Typography } from '@material-ui/core'
import { makeConnectionMessageEvent, rendererEvents } from '../../../../events' import { makeConnectionMessageEvent, rendererEvents } from '../../../../events'
import { } from '../../../../events/Events' import { } from '../../../../events/Events'
const MovingAvaerage = require('moving-average') const MovingAvaerage = require('moving-average')
import { connect } from 'react-redux'
import { AppState } from '../../reducers'
declare const performance: any declare const performance: any
const timeInterval = 10 * 1000 const timeInterval = 10 * 1000
const average = MovingAvaerage(timeInterval) const average = MovingAvaerage(timeInterval)
interface Props{ interface Props {
autoExpandLimit: number
didSelectNode?: (node: q.TreeNode) => void didSelectNode?: (node: q.TreeNode) => void
connectionId?: string connectionId?: string
} }
@@ -21,7 +24,7 @@ interface TreeState {
msg: any msg: any
} }
export class Tree extends React.Component<Props, TreeState> { class Tree extends React.Component<Props, TreeState> {
private renderDuration: number = 300 private renderDuration: number = 300
private updateTimer?: any private updateTimer?: any
private lastUpdate: number = 0 private lastUpdate: number = 0
@@ -98,12 +101,13 @@ export class Tree extends React.Component<Props, TreeState> {
return <Typography style={ style }> return <Typography style={ style }>
<TreeNode <TreeNode
animateChages={true} animateChages = { true }
autoExpandLimit={3000} autoExpandLimit = { this.props.autoExpandLimit }
isRoot={true} isRoot = { true }
didSelectNode={this.props.didSelectNode} didSelectNode = { this.props.didSelectNode }
treeNode={this.state.tree} treeNode = { this.state.tree }
name="/" collapsed={false} name = "/"
collapsed = { false }
key="rootNode" key="rootNode"
performanceCallback={(ms: number) => { performanceCallback={(ms: number) => {
average.push(Date.now(), ms) average.push(Date.now(), ms)
@@ -113,3 +117,11 @@ export class Tree extends React.Component<Props, TreeState> {
</Typography> </Typography>
} }
} }
const mapStateToProps = (state: AppState) => {
return {
autoExpandLimit: state.settings.autoExpandLimit,
}
}
export default connect(mapStateToProps)(Tree)

View File

@@ -1,13 +1,33 @@
import * as React from 'react' import * as React from 'react'
import * as q from '../../../../backend/src/Model' import * as q from '../../../../backend/src/Model'
import { withTheme, Theme } from '@material-ui/core/styles' import { withStyles, Theme } from '@material-ui/core/styles'
import { isElementInViewport } from '../helper/isElementInViewport' import { isElementInViewport } from '../helper/isElementInViewport'
import TreeNodeTitle from './TreeNodeTitle' import TreeNodeTitle from './TreeNodeTitle'
import TreeNodeSubnodes from './TreeNodeSubnodes' import TreeNodeSubnodes from './TreeNodeSubnodes'
declare var performance: any declare var performance: any
export interface TreeNodeProps { const styles = (theme: Theme) => {
return {
collapsedSubnodes: {
color: theme.palette.text.secondary,
},
displayBlock: {
display: 'block',
},
node: {
display: 'block',
marginLeft: '10px',
},
hover: {
'&:hover': {
backgroundColor: 'rgba(80, 80, 80, 0.35)',
},
},
}
}
interface Props {
animateChages: boolean animateChages: boolean
isRoot?: boolean isRoot?: boolean
treeNode: q.TreeNode treeNode: q.TreeNode
@@ -15,20 +35,16 @@ export interface TreeNodeProps {
collapsed?: boolean | undefined collapsed?: boolean | undefined
performanceCallback?: ((ms: number) => void) | undefined performanceCallback?: ((ms: number) => void) | undefined
didSelectNode?: (node: q.TreeNode) => void didSelectNode?: (node: q.TreeNode) => void
theme: Theme classes: any
autoExpandLimit: number autoExpandLimit: number
} }
interface TreeNodeState { interface State {
title: string | undefined
collapsed: boolean
collapsedOverride: boolean | undefined collapsedOverride: boolean | undefined
edgeCount: number
} }
class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> { class TreeNode extends React.Component<Props, State> {
private dirtySubnodes: boolean = true private dirtySubnodes: boolean = true
private dirtyState: boolean = true
private dirtyEdges: boolean = true private dirtyEdges: boolean = true
private dirtyMessage: boolean = true private dirtyMessage: boolean = true
@@ -49,11 +65,12 @@ class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
this.dirtyMessage = true this.dirtyMessage = true
} }
constructor(props: TreeNodeProps) { constructor(props: Props) {
super(props) super(props)
const edgeCount = Object.keys(props.treeNode.edges).length
const collapsed = edgeCount > this.props.autoExpandLimit this.state = {
this.state = { collapsed, edgeCount, collapsedOverride: props.collapsed, title: props.name } collapsedOverride: props.collapsed,
}
} }
public componentDidMount() { public componentDidMount() {
@@ -70,32 +87,18 @@ class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
treeNode.onMessage.unsubscribe(this.messageDidChange) treeNode.onMessage.unsubscribe(this.messageDidChange)
} }
private getStyles() { private stateHasChanged(newState: State) {
return { return this.state.collapsedOverride !== newState.collapsedOverride
collapsedSubnodes: {
color: this.props.theme.palette.text.secondary,
},
displayBlock: {
display: 'block',
},
}
} }
public setState(newState: any) { private propsHasChanged(newProps: Props) {
this.dirtyState = this.stateHasChanged(newState) return this.props.autoExpandLimit !== newProps.autoExpandLimit
super.setState(newState)
} }
private stateHasChanged(newState: any) { public shouldComponentUpdate(nextProps: Props, nextState: State) {
return this.state.collapsed !== newState.collapsed
|| this.state.collapsedOverride !== newState.collapsedOverride
|| this.state.edgeCount !== newState.edgeCount
}
public shouldComponentUpdate() {
const shouldRenderToRemoveCssAnimation = this.cssAnimationWasSetAt !== undefined const shouldRenderToRemoveCssAnimation = this.cssAnimationWasSetAt !== undefined
return this.dirtyState return this.stateHasChanged(nextState)
|| this.propsHasChanged(nextProps)
|| this.dirtyEdges || this.dirtyEdges
|| this.dirtyMessage || this.dirtyMessage
|| this.dirtySubnodes || this.dirtySubnodes
@@ -124,25 +127,26 @@ class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
return this.state.collapsedOverride return this.state.collapsedOverride
} }
return this.state.collapsed return this.props.treeNode.edgeCount() > this.props.autoExpandLimit
}
public componentWillReceiveProps() {
const edgeCount = Object.keys(this.props.treeNode.edges).length
this.setState({ edgeCount, collapsed: edgeCount > this.props.autoExpandLimit })
} }
public render() { public render() {
const { displayBlock } = this.getStyles()
const animationStyle = this.indicatingChangeAnimationStyle() const animationStyle = this.indicatingChangeAnimationStyle()
const { classes } = this.props
this.dirtyEdges = this.dirtyMessage = this.dirtySubnodes = false
this.dirtyState = this.dirtyEdges = this.dirtyMessage = this.dirtySubnodes = false return <div
key={this.props.treeNode.hash()}
return <div key={this.props.treeNode.hash()} style={ { display: 'block', marginLeft: '10px' } }> className={`${classes.node} ${!this.props.isRoot ? classes.hover : ''}`}
onClick={(event) => {
event.stopPropagation()
this.toggle()
this.props.didSelectNode && this.props.didSelectNode(this.props.treeNode)
}}
>
<span ref={this.titleRef} style={animationStyle}> <span ref={this.titleRef} style={animationStyle}>
<TreeNodeTitle <TreeNodeTitle
onClick={() => this.toggle()} onClick={() => this.toggle()}
edgeCount={this.state.edgeCount}
collapsed={this.collapsed()} collapsed={this.collapsed()}
treeNode={this.props.treeNode} treeNode={this.props.treeNode}
name={this.props.name} name={this.props.name}
@@ -186,4 +190,4 @@ class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
} }
} }
export default withTheme()(TreeNode) export default withStyles(styles)(TreeNode)

View File

@@ -1,16 +1,13 @@
import * as React from 'react' import * as React from 'react'
import * as q from '../../../../backend/src/Model' import * as q from '../../../../backend/src/Model'
import { Typography } from '@material-ui/core'
import { withTheme, Theme } from '@material-ui/core/styles' import { withTheme, Theme } from '@material-ui/core/styles'
export interface TreeNodeProps extends React.HTMLAttributes<HTMLElement> { export interface TreeNodeProps extends React.HTMLAttributes<HTMLElement> {
treeNode: q.TreeNode treeNode: q.TreeNode
// ref: React.Ref<HTMLElement>
name?: string | undefined name?: string | undefined
collapsed?: boolean | undefined collapsed?: boolean | undefined
toggleCollapsed: () => void toggleCollapsed: () => void
didSelectNode?: (node: q.TreeNode) => void didSelectNode?: (node: q.TreeNode) => void
edgeCount: number
theme: Theme theme: Theme
} }
@@ -81,7 +78,7 @@ class TreeNodeTitle extends React.Component<TreeNodeProps, {}> {
} }
private renderExpander() { private renderExpander() {
if (this.props.edgeCount === 0) { if (this.props.treeNode.edgeCount() === 0) {
return null return null
} }
@@ -89,7 +86,7 @@ class TreeNodeTitle extends React.Component<TreeNodeProps, {}> {
} }
private renderCollapsedSubnodes() { private renderCollapsedSubnodes() {
if (this.props.edgeCount === 0 || !this.props.collapsed) { if (this.props.treeNode.edgeCount() === 0 || !this.props.collapsed) {
return null return null
} }

View File

@@ -1,6 +1,9 @@
import * as React from 'react' import * as React from 'react'
import * as ReactDOM from 'react-dom' import * as ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import reducers, { AppState } from './reducers'
import App from './App' import App from './App'
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles' import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'
@@ -12,10 +15,23 @@ const theme = createMuiTheme({
}) })
declare var document: any declare var document: any
declare var window: any
const initialAppState = {
settings: {
autoExpandLimit: 0,
visible: false,
},
}
const store = createStore(reducers, initialAppState)
window.reduxStore = store
ReactDOM.render( ReactDOM.render(
<MuiThemeProvider theme={theme}> <MuiThemeProvider theme={theme}>
<App /> <Provider store={store}>
<App name="" />
</Provider>
</MuiThemeProvider>, </MuiThemeProvider>,
document.getElementById('example'), document.getElementById('example'),
) )

50
app/src/reducers/index.ts Normal file
View File

@@ -0,0 +1,50 @@
import { Reducer, Action } from 'redux'
export enum ActionTypes {
setAutoExpandLimit = 'SET_AUTO_EXPAND_LIMIT',
toggleSettingsVisibility = 'TOGGLE_SETTINGS_VISIBILITY',
}
interface SettingsAction extends Action {
type: ActionTypes,
autoExpandLimit: number
}
export interface AppState {
settings: SettingsModel
}
export interface SettingsModel {
autoExpandLimit: number
visible: boolean
}
const reducer: Reducer<AppState | undefined, SettingsAction> = (state, action) => {
if (!state) {
throw Error('No initial state')
}
console.log(action)
switch (action.type) {
case ActionTypes.setAutoExpandLimit:
return {
...state,
settings: {
visible: state.settings.visible,
autoExpandLimit: action.autoExpandLimit,
},
}
case ActionTypes.toggleSettingsVisibility:
return {
...state,
settings: {
visible: !state.settings.visible,
autoExpandLimit: state.settings.autoExpandLimit,
},
}
default:
return state
}
}
export default reducer

View File

@@ -34,25 +34,5 @@
], ],
"sourceMap": true, "sourceMap": true,
"instrument": true "instrument": true
},
"dependencies": {
"mqtt": "^2.18.8",
"sha1": "^1.1.1",
"socket.io": "^2.2.0"
},
"devDependencies": {
"@types/chai": "^4.1.7",
"@types/mocha": "^5.2.5",
"@types/node": "^10.12.18",
"@types/sha1": "^1.1.1",
"@types/socket.io": "^2.1.2",
"chai": "^4.2.0",
"mocha": "^5.2.0",
"nyc": "^13.1.0",
"source-map-support": "^0.5.9",
"ts-node": "^7.0.1",
"tslint": "^5.12.0",
"tslint-strict-null-checks": "^1.0.1",
"typescript": "^3.2.2"
} }
} }

View File

@@ -93,6 +93,10 @@ export class TreeNode {
return this.leafes().length return this.leafes().length
} }
public edgeCount(): number {
return Object.values(this.edges).length
}
public leafes(): TreeNode[] { public leafes(): TreeNode[] {
if (this.cachedLeafes === undefined) { if (this.cachedLeafes === undefined) {
if (Object.values(this.edges).length === 0) { if (Object.values(this.edges).length === 0) {

View File

@@ -6,7 +6,7 @@ const linux: builder.CliOptions = {
armv7l: true, armv7l: true,
arm64: true, arm64: true,
linux: ['snap', 'AppImage', 'deb', 'pacman'], linux: ['snap', 'AppImage', 'deb', 'pacman'],
prepackaged: './build/topackage', projectDir: './build/clean',
} }
const win: builder.CliOptions = { const win: builder.CliOptions = {
@@ -15,7 +15,7 @@ const win: builder.CliOptions = {
armv7l: false, armv7l: false,
arm64: false, arm64: false,
win: ['portable'], win: ['portable'],
prepackaged: './build/topackage', projectDir: './build/clean',
} }
const mac: builder.CliOptions = { const mac: builder.CliOptions = {
@@ -24,12 +24,13 @@ const mac: builder.CliOptions = {
armv7l: false, armv7l: false,
arm64: false, arm64: false,
mac: ['dmg'], mac: ['dmg'],
projectDir: './build/clean',
} }
async function buildAll() { async function buildAll() {
await builder.build(linux) // await builder.build(linux)
await builder.build(mac) await builder.build(mac)
await builder.build(win) // await builder.build(win)
} }
buildAll() buildAll()

View File

@@ -6,6 +6,8 @@ require('./backend/build/backend/src/index.js')
let mainWindow let mainWindow
function createWindow () { function createWindow () {
BrowserWindow.addDevToolsExtension ('/Users/thomas/Library/Application Support/Google/Chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/3.5.0_0');
// Create the browser window. // Create the browser window.
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
width: 1024, width: 1024,

2206
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,9 +11,14 @@
"release": "npm run test && ./release.sh" "release": "npm run test && ./release.sh"
}, },
"build": { "build": {
"appId": "mq-explorer", "appId": "mqtt-explorer",
"mac": { "mac": {
"category": "de.t7n.apps.mq-explorer" "category": "de.t7n.apps.mq-explorer",
"files": [
"**/*",
"!**/*.ts",
"!app/node_modules"
]
}, },
"linux": { "linux": {
"category": "Development", "category": "Development",
@@ -36,11 +41,23 @@
"tslint": "^5.12.0", "tslint": "^5.12.0",
"tslint-config-airbnb": "^5.11.1", "tslint-config-airbnb": "^5.11.1",
"tslint-react": "^3.6.0", "tslint-react": "^3.6.0",
"typescript": "^3.2.2" "typescript": "^3.2.2",
"@types/chai": "^4.1.7",
"@types/mocha": "^5.2.5",
"@types/node": "^10.12.18",
"@types/sha1": "^1.1.1",
"@types/socket.io": "^2.1.2",
"chai": "^4.2.0",
"mocha": "^5.2.0",
"nyc": "^13.1.0",
"source-map-support": "^0.5.9",
"tslint-strict-null-checks": "^1.0.1"
}, },
"dependencies": { "dependencies": {
"@types/electron": "^1.6.10", "@types/electron": "^1.6.10",
"@types/socket.io": "^2.1.2", "@types/socket.io": "^2.1.2",
"socket.io": "^2.2.0" "socket.io": "^2.2.0",
"mqtt": "^2.18.8",
"sha1": "^1.1.1"
} }
} }

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
DIR=build/topackage DIR=build/clean
rm -rf "$DIR" rm -rf "$DIR"
mkdir -p "$DIR" mkdir -p "$DIR"
@@ -14,6 +14,8 @@ npm run build
rm -rf app/node_modules rm -rf app/node_modules
cd "$ORIGINAL_DIR" cd "$ORIGINAL_DIR"
node_modules/.bin/ts-node build.ts
exit 0
docker run --rm -ti \ docker run --rm -ti \
--env ELECTRON_CACHE="/root/.cache/electron" \ --env ELECTRON_CACHE="/root/.cache/electron" \
--env ELECTRON_BUILDER_CACHE="/root/.cache/electron-builder" \ --env ELECTRON_BUILDER_CACHE="/root/.cache/electron-builder" \