Adapt redux
Add hover-effect on nodes Add Setting drawer Ass auto expansion setting
This commit is contained in:
@@ -1,13 +1,17 @@
|
||||
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 { Tree } from './components/Tree/Tree'
|
||||
import Tree from './components/Tree/Tree'
|
||||
import TitleBar from './components/TitleBar'
|
||||
import Sidebar from './components/Sidebar/Sidebar'
|
||||
import Connection from './components/ConnectionSetup/Connection'
|
||||
// import { default as EventBus } from '../../events'
|
||||
|
||||
import { withTheme, Theme } from '@material-ui/core/styles'
|
||||
import Settings from './components/Settings'
|
||||
|
||||
interface State {
|
||||
selectedNode?: q.TreeNode,
|
||||
@@ -17,6 +21,7 @@ interface State {
|
||||
interface Props {
|
||||
name: string
|
||||
theme: Theme
|
||||
settingsVisible: boolean
|
||||
}
|
||||
|
||||
class App extends React.Component<Props, State> {
|
||||
@@ -29,40 +34,68 @@ class App extends React.Component<Props, State> {
|
||||
|
||||
private getStyles(): {[s: string]: React.CSSProperties} {
|
||||
const { theme } = this.props
|
||||
const drawerWidth = 300
|
||||
return {
|
||||
left: {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
height: 'calc(100vh - 64px)',
|
||||
float: 'left',
|
||||
overflowY: 'scroll',
|
||||
overflowX: 'hidden',
|
||||
display: 'block',
|
||||
width: '60vw',
|
||||
},
|
||||
right: {
|
||||
height: 'calc(100vh - 64px)',
|
||||
color: theme.palette.text.primary,
|
||||
float: 'left',
|
||||
width: '40vw',
|
||||
overflowY: 'scroll',
|
||||
overflowX: 'hidden',
|
||||
display: 'block',
|
||||
},
|
||||
right: {
|
||||
height: 'calc(100vh - 64px)',
|
||||
centerContent: {
|
||||
width: '100vw',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
content: {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
float: 'right', width: '40vw',
|
||||
overflowY: 'scroll',
|
||||
overflowX: 'hidden',
|
||||
display: 'block',
|
||||
transition: theme.transitions.create('margin', {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
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() {
|
||||
return <div>
|
||||
<TitleBar />
|
||||
<div>
|
||||
<div style={this.getStyles().left}>
|
||||
<Tree connectionId={this.state.connectionId} didSelectNode={(node: q.TreeNode) => {
|
||||
this.setState({ selectedNode: node })
|
||||
}} />
|
||||
</div>
|
||||
<div style={this.getStyles().right}>
|
||||
<Sidebar node={this.state.selectedNode} />
|
||||
const { settingsVisible } = this.props
|
||||
const { content, contentShift, settings, centerContent } = this.getStyles()
|
||||
return <div style={centerContent}>
|
||||
<CssBaseline />
|
||||
<Settings />
|
||||
<div style={settingsVisible ? contentShift : content}>
|
||||
<TitleBar />
|
||||
<div style={centerContent}>
|
||||
<div style={this.getStyles().left}>
|
||||
<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>
|
||||
<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))
|
||||
|
||||
15
app/src/actions/Settings.ts
Normal file
15
app/src/actions/Settings.ts
Normal 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
3
app/src/actions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import * as settingsActions from './Settings'
|
||||
|
||||
export { settingsActions }
|
||||
@@ -85,6 +85,7 @@ class Connection extends React.Component<Props, State> {
|
||||
this.props.onConnection(connectionId)
|
||||
this.setState({ visible: false })
|
||||
} else if (state.error) {
|
||||
console.log('error', state.error)
|
||||
this.setState({ error: state.error })
|
||||
}
|
||||
})
|
||||
|
||||
121
app/src/components/Settings.tsx
Normal file
121
app/src/components/Settings.tsx
Normal 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))
|
||||
@@ -13,13 +13,13 @@ import { withStyles, Theme, StyleRulesCallback } from '@material-ui/core/styles'
|
||||
import Copy from '../Copy'
|
||||
|
||||
interface Props {
|
||||
node?: q.TreeNode | undefined,
|
||||
node?: q.TreeNode,
|
||||
classes: any,
|
||||
theme: Theme
|
||||
theme: Theme,
|
||||
}
|
||||
|
||||
interface State {
|
||||
node?: q.TreeNode | undefined
|
||||
node?: q.TreeNode
|
||||
}
|
||||
|
||||
class Sidebar extends React.Component<Props, State> {
|
||||
|
||||
@@ -2,6 +2,22 @@ import * as React from 'react'
|
||||
import * as q from '../../../backend/src/Model'
|
||||
|
||||
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 { withStyles, StyleRulesCallback } from '@material-ui/core/styles'
|
||||
@@ -53,17 +69,30 @@ const styles: StyleRulesCallback = theme => ({
|
||||
width: 200,
|
||||
},
|
||||
},
|
||||
menuButton: {
|
||||
marginLeft: -12,
|
||||
marginRight: 20,
|
||||
},
|
||||
})
|
||||
|
||||
interface Props {
|
||||
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) {
|
||||
super(props)
|
||||
this.state = {
|
||||
selectedNode: undefined,
|
||||
settingsVisible: false,
|
||||
autoExpandLimit: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +101,9 @@ class TitleBar extends React.Component<Props, {}> {
|
||||
|
||||
return <AppBar position="static">
|
||||
<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>
|
||||
<div className={classes.search}>
|
||||
<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))
|
||||
|
||||
@@ -5,13 +5,16 @@ import { Typography } from '@material-ui/core'
|
||||
import { makeConnectionMessageEvent, rendererEvents } from '../../../../events'
|
||||
import { } from '../../../../events/Events'
|
||||
const MovingAvaerage = require('moving-average')
|
||||
import { connect } from 'react-redux'
|
||||
import { AppState } from '../../reducers'
|
||||
|
||||
declare const performance: any
|
||||
|
||||
const timeInterval = 10 * 1000
|
||||
const average = MovingAvaerage(timeInterval)
|
||||
|
||||
interface Props{
|
||||
interface Props {
|
||||
autoExpandLimit: number
|
||||
didSelectNode?: (node: q.TreeNode) => void
|
||||
connectionId?: string
|
||||
}
|
||||
@@ -21,7 +24,7 @@ interface TreeState {
|
||||
msg: any
|
||||
}
|
||||
|
||||
export class Tree extends React.Component<Props, TreeState> {
|
||||
class Tree extends React.Component<Props, TreeState> {
|
||||
private renderDuration: number = 300
|
||||
private updateTimer?: any
|
||||
private lastUpdate: number = 0
|
||||
@@ -98,12 +101,13 @@ export class Tree extends React.Component<Props, TreeState> {
|
||||
|
||||
return <Typography style={ style }>
|
||||
<TreeNode
|
||||
animateChages={true}
|
||||
autoExpandLimit={3000}
|
||||
isRoot={true}
|
||||
didSelectNode={this.props.didSelectNode}
|
||||
treeNode={this.state.tree}
|
||||
name="/" collapsed={false}
|
||||
animateChages = { true }
|
||||
autoExpandLimit = { this.props.autoExpandLimit }
|
||||
isRoot = { true }
|
||||
didSelectNode = { this.props.didSelectNode }
|
||||
treeNode = { this.state.tree }
|
||||
name = "/"
|
||||
collapsed = { false }
|
||||
key="rootNode"
|
||||
performanceCallback={(ms: number) => {
|
||||
average.push(Date.now(), ms)
|
||||
@@ -113,3 +117,11 @@ export class Tree extends React.Component<Props, TreeState> {
|
||||
</Typography>
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
return {
|
||||
autoExpandLimit: state.settings.autoExpandLimit,
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Tree)
|
||||
|
||||
@@ -1,13 +1,33 @@
|
||||
import * as React from 'react'
|
||||
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 TreeNodeTitle from './TreeNodeTitle'
|
||||
import TreeNodeSubnodes from './TreeNodeSubnodes'
|
||||
|
||||
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
|
||||
isRoot?: boolean
|
||||
treeNode: q.TreeNode
|
||||
@@ -15,20 +35,16 @@ export interface TreeNodeProps {
|
||||
collapsed?: boolean | undefined
|
||||
performanceCallback?: ((ms: number) => void) | undefined
|
||||
didSelectNode?: (node: q.TreeNode) => void
|
||||
theme: Theme
|
||||
classes: any
|
||||
autoExpandLimit: number
|
||||
}
|
||||
|
||||
interface TreeNodeState {
|
||||
title: string | undefined
|
||||
collapsed: boolean
|
||||
interface State {
|
||||
collapsedOverride: boolean | undefined
|
||||
edgeCount: number
|
||||
}
|
||||
|
||||
class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
||||
class TreeNode extends React.Component<Props, State> {
|
||||
private dirtySubnodes: boolean = true
|
||||
private dirtyState: boolean = true
|
||||
private dirtyEdges: boolean = true
|
||||
private dirtyMessage: boolean = true
|
||||
|
||||
@@ -49,11 +65,12 @@ class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
||||
this.dirtyMessage = true
|
||||
}
|
||||
|
||||
constructor(props: TreeNodeProps) {
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
const edgeCount = Object.keys(props.treeNode.edges).length
|
||||
const collapsed = edgeCount > this.props.autoExpandLimit
|
||||
this.state = { collapsed, edgeCount, collapsedOverride: props.collapsed, title: props.name }
|
||||
|
||||
this.state = {
|
||||
collapsedOverride: props.collapsed,
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
@@ -70,32 +87,18 @@ class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
||||
treeNode.onMessage.unsubscribe(this.messageDidChange)
|
||||
}
|
||||
|
||||
private getStyles() {
|
||||
return {
|
||||
collapsedSubnodes: {
|
||||
color: this.props.theme.palette.text.secondary,
|
||||
},
|
||||
displayBlock: {
|
||||
display: 'block',
|
||||
},
|
||||
}
|
||||
private stateHasChanged(newState: State) {
|
||||
return this.state.collapsedOverride !== newState.collapsedOverride
|
||||
}
|
||||
|
||||
public setState(newState: any) {
|
||||
this.dirtyState = this.stateHasChanged(newState)
|
||||
|
||||
super.setState(newState)
|
||||
private propsHasChanged(newProps: Props) {
|
||||
return this.props.autoExpandLimit !== newProps.autoExpandLimit
|
||||
}
|
||||
|
||||
private stateHasChanged(newState: any) {
|
||||
return this.state.collapsed !== newState.collapsed
|
||||
|| this.state.collapsedOverride !== newState.collapsedOverride
|
||||
|| this.state.edgeCount !== newState.edgeCount
|
||||
}
|
||||
|
||||
public shouldComponentUpdate() {
|
||||
public shouldComponentUpdate(nextProps: Props, nextState: State) {
|
||||
const shouldRenderToRemoveCssAnimation = this.cssAnimationWasSetAt !== undefined
|
||||
return this.dirtyState
|
||||
return this.stateHasChanged(nextState)
|
||||
|| this.propsHasChanged(nextProps)
|
||||
|| this.dirtyEdges
|
||||
|| this.dirtyMessage
|
||||
|| this.dirtySubnodes
|
||||
@@ -124,25 +127,26 @@ class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
||||
return this.state.collapsedOverride
|
||||
}
|
||||
|
||||
return this.state.collapsed
|
||||
}
|
||||
|
||||
public componentWillReceiveProps() {
|
||||
const edgeCount = Object.keys(this.props.treeNode.edges).length
|
||||
this.setState({ edgeCount, collapsed: edgeCount > this.props.autoExpandLimit })
|
||||
return this.props.treeNode.edgeCount() > this.props.autoExpandLimit
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { displayBlock } = this.getStyles()
|
||||
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()} style={ { display: 'block', marginLeft: '10px' } }>
|
||||
return <div
|
||||
key={this.props.treeNode.hash()}
|
||||
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}>
|
||||
<TreeNodeTitle
|
||||
onClick={() => this.toggle()}
|
||||
edgeCount={this.state.edgeCount}
|
||||
collapsed={this.collapsed()}
|
||||
treeNode={this.props.treeNode}
|
||||
name={this.props.name}
|
||||
@@ -186,4 +190,4 @@ class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme()(TreeNode)
|
||||
export default withStyles(styles)(TreeNode)
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import * as React from 'react'
|
||||
import * as q from '../../../../backend/src/Model'
|
||||
import { Typography } from '@material-ui/core'
|
||||
import { withTheme, Theme } from '@material-ui/core/styles'
|
||||
|
||||
export interface TreeNodeProps extends React.HTMLAttributes<HTMLElement> {
|
||||
treeNode: q.TreeNode
|
||||
// ref: React.Ref<HTMLElement>
|
||||
name?: string | undefined
|
||||
collapsed?: boolean | undefined
|
||||
toggleCollapsed: () => void
|
||||
didSelectNode?: (node: q.TreeNode) => void
|
||||
edgeCount: number
|
||||
theme: Theme
|
||||
}
|
||||
|
||||
@@ -81,7 +78,7 @@ class TreeNodeTitle extends React.Component<TreeNodeProps, {}> {
|
||||
}
|
||||
|
||||
private renderExpander() {
|
||||
if (this.props.edgeCount === 0) {
|
||||
if (this.props.treeNode.edgeCount() === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -89,7 +86,7 @@ class TreeNodeTitle extends React.Component<TreeNodeProps, {}> {
|
||||
}
|
||||
|
||||
private renderCollapsedSubnodes() {
|
||||
if (this.props.edgeCount === 0 || !this.props.collapsed) {
|
||||
if (this.props.treeNode.edgeCount() === 0 || !this.props.collapsed) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import * as React from 'react'
|
||||
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 { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'
|
||||
|
||||
@@ -12,10 +15,23 @@ const theme = createMuiTheme({
|
||||
})
|
||||
|
||||
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(
|
||||
<MuiThemeProvider theme={theme}>
|
||||
<App />
|
||||
<Provider store={store}>
|
||||
<App name="" />
|
||||
</Provider>
|
||||
</MuiThemeProvider>,
|
||||
document.getElementById('example'),
|
||||
)
|
||||
|
||||
50
app/src/reducers/index.ts
Normal file
50
app/src/reducers/index.ts
Normal 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
|
||||
Reference in New Issue
Block a user