Refactor
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import * as q from '../../../../backend/src/Model'
|
||||
import * as React from 'react'
|
||||
import TreeNode from './TreeNode'
|
||||
import { SettingsState } from '../../reducers/Settings'
|
||||
import { sortedNodes } from '../../sortedNodes'
|
||||
import * as q from '../../../../../backend/src/Model'
|
||||
import React from 'react'
|
||||
import TreeNode from '.'
|
||||
import { SettingsState } from '../../../reducers/Settings'
|
||||
import { sortedNodes } from '../../../sortedNodes'
|
||||
import { Theme, withStyles } from '@material-ui/core'
|
||||
import { TopicViewModel } from '../../model/TopicViewModel'
|
||||
import { TopicViewModel } from '../../../model/TopicViewModel'
|
||||
import { treeActions } from '../../../actions'
|
||||
|
||||
export interface Props {
|
||||
treeNode: q.TreeNode<TopicViewModel>
|
||||
@@ -14,6 +15,7 @@ export interface Props {
|
||||
selectedTopic?: q.TreeNode<TopicViewModel>
|
||||
selectTopicAction: (treeNode: q.TreeNode<any>) => void
|
||||
settings: SettingsState
|
||||
actions: typeof treeActions
|
||||
}
|
||||
|
||||
interface State {
|
||||
@@ -60,6 +62,7 @@ class TreeNodeSubnodes extends React.Component<Props, State> {
|
||||
lastUpdate={node.lastUpdate}
|
||||
selectTopicAction={this.props.selectTopicAction}
|
||||
settings={this.props.settings}
|
||||
actions={this.props.actions}
|
||||
/>
|
||||
)
|
||||
})
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as React from 'react'
|
||||
import * as q from '../../../../backend/src/Model'
|
||||
import * as q from '../../../../../backend/src/Model'
|
||||
import { withStyles, Theme } from '@material-ui/core'
|
||||
import { TopicViewModel } from '../../model/TopicViewModel'
|
||||
import { Base64Message } from '../../../../backend/src/Model/Base64Message'
|
||||
import { TopicViewModel } from '../../../model/TopicViewModel'
|
||||
import { Base64Message } from '../../../../../backend/src/Model/Base64Message'
|
||||
|
||||
export interface TreeNodeProps extends React.HTMLAttributes<HTMLElement> {
|
||||
treeNode: q.TreeNode<TopicViewModel>
|
||||
@@ -0,0 +1,22 @@
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
export function useAnimationToIndicateTopicUpdate(
|
||||
lastUpdate: number,
|
||||
selected: boolean,
|
||||
setShowUpdateAnimation: React.Dispatch<React.SetStateAction<boolean>>,
|
||||
showUpdateAnimation: boolean
|
||||
) {
|
||||
useEffect(() => {
|
||||
if (Date.now() - lastUpdate < 3000 && !selected) {
|
||||
setShowUpdateAnimation(true)
|
||||
}
|
||||
}, [lastUpdate, selected])
|
||||
useEffect(() => {
|
||||
if (showUpdateAnimation) {
|
||||
const timeout = setTimeout(() => setShowUpdateAnimation(false), 500)
|
||||
return function cleanup() {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
}
|
||||
}, [showUpdateAnimation])
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import * as q from '../../../../../../backend/src/Model'
|
||||
import React, { useCallback } from 'react'
|
||||
import { KeyCodes } from '../../../../utils/KeyCodes'
|
||||
import { treeActions } from '../../../../actions'
|
||||
|
||||
export function useDeleteKeyCallback(topic: q.TreeNode<any>, actions: typeof treeActions) {
|
||||
return useCallback(
|
||||
(event: React.KeyboardEvent) => {
|
||||
if (event.keyCode === KeyCodes.delete || event.keyCode === KeyCodes.backspace) {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
actions.clearTopic(topic, true, 50)
|
||||
}
|
||||
},
|
||||
[topic]
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Props } from '..'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export function useIsAllowedToAutoExpandState(props: Props): boolean {
|
||||
const { settings, treeNode, isRoot } = props
|
||||
const [isAllowedToAutoExpand, setAllowAutoExpand] = useState(false)
|
||||
useEffect(() => {
|
||||
const newIsAllowedToAutoExpand = isRoot || treeNode.edgeCount() <= settings.get('autoExpandLimit')
|
||||
if (newIsAllowedToAutoExpand !== isAllowedToAutoExpand) {
|
||||
setAllowAutoExpand(newIsAllowedToAutoExpand)
|
||||
}
|
||||
}, [treeNode.edgeCount(), settings.get('autoExpandLimit')])
|
||||
return isAllowedToAutoExpand
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as q from '../../../../backend/src/Model'
|
||||
import * as q from '../../../../../../backend/src/Model'
|
||||
import React, { useEffect } from 'react'
|
||||
import { TopicViewModel } from '../../model/TopicViewModel'
|
||||
import { TopicViewModel } from '../../../../model/TopicViewModel'
|
||||
|
||||
export function useViewModelSubscriptions(
|
||||
treeNode: q.TreeNode<TopicViewModel>,
|
||||
@@ -11,6 +11,7 @@ export function useViewModelSubscriptions(
|
||||
const selectionDidChange = () => {
|
||||
const selected = treeNode.viewModel && treeNode.viewModel.isSelected()
|
||||
treeNode.viewModel && setSelected(Boolean(selected))
|
||||
|
||||
if (selected && nodeRef && nodeRef.current) {
|
||||
nodeRef.current.focus({ preventScroll: false })
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
import * as q from '../../../../backend/src/Model'
|
||||
import * as q from '../../../../../backend/src/Model'
|
||||
import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react'
|
||||
import TreeNodeSubnodes from './TreeNodeSubnodes'
|
||||
import TreeNodeTitle from './TreeNodeTitle'
|
||||
import { SettingsState } from '../../reducers/Settings'
|
||||
import { SettingsState } from '../../../reducers/Settings'
|
||||
import { Theme, withStyles } from '@material-ui/core/styles'
|
||||
import { TopicViewModel } from '../../model/TopicViewModel'
|
||||
import { useViewModelSubscriptions } from './useViewModelSubscriptions'
|
||||
const debounce = require('lodash.debounce')
|
||||
|
||||
declare var performance: any
|
||||
import { TopicViewModel } from '../../../model/TopicViewModel'
|
||||
import { useViewModelSubscriptions } from './effects/useViewModelSubscriptions'
|
||||
import { treeActions } from '../../../actions'
|
||||
import { lightBlue, teal, amber, green, deepPurple, blueGrey } from '@material-ui/core/colors'
|
||||
import { useAnimationToIndicateTopicUpdate } from './effects/useAnimationToIndicateTopicUpdate'
|
||||
import { useDeleteKeyCallback } from './effects/useDeleteKeyCallback'
|
||||
import { useIsAllowedToAutoExpandState } from './effects/useIsAllowedToAutoExpandState'
|
||||
|
||||
const styles = (theme: Theme) => {
|
||||
return {
|
||||
@@ -21,6 +23,9 @@ const styles = (theme: Theme) => {
|
||||
node: {
|
||||
display: 'block',
|
||||
marginLeft: '10px',
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.type === 'light' ? blueGrey[100] : theme.palette.primary.light,
|
||||
},
|
||||
},
|
||||
topicSelect: {
|
||||
float: 'right' as 'right',
|
||||
@@ -32,11 +37,9 @@ const styles = (theme: Theme) => {
|
||||
marginLeft: theme.spacing(1.5),
|
||||
},
|
||||
selected: {
|
||||
backgroundColor: theme.palette.type === 'dark' ? 'rgba(170, 170, 170, 0.55)' : 'rgba(170, 170, 170, 0.55)',
|
||||
},
|
||||
hover: {
|
||||
backgroundColor: theme.palette.type === 'dark' ? 'rgba(100, 100, 100, 0.55)' : 'rgba(200, 200, 200, 0.55)',
|
||||
backgroundColor: (theme.palette.type === 'light' ? blueGrey[300] : theme.palette.primary.main) + ' !important',
|
||||
},
|
||||
hover: {},
|
||||
title: {
|
||||
borderRadius: '4px',
|
||||
lineHeight: '1em',
|
||||
@@ -49,7 +52,7 @@ const styles = (theme: Theme) => {
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
export interface Props {
|
||||
isRoot?: boolean
|
||||
treeNode: q.TreeNode<TopicViewModel>
|
||||
name?: string | undefined
|
||||
@@ -57,44 +60,26 @@ interface Props {
|
||||
classes: any
|
||||
className?: string
|
||||
lastUpdate: number
|
||||
actions: typeof treeActions
|
||||
selectTopicAction: (treeNode: q.TreeNode<any>) => void
|
||||
theme: Theme
|
||||
settings: SettingsState
|
||||
}
|
||||
|
||||
function useIsAllowedToAutoExpandState(props: Props): boolean {
|
||||
const { settings, treeNode, isRoot } = props
|
||||
const [isAllowedToAutoExpand, setAllowAutoExpand] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const newIsAllowedToAutoExpand = isRoot || treeNode.edgeCount() <= settings.get('autoExpandLimit')
|
||||
if (newIsAllowedToAutoExpand !== isAllowedToAutoExpand) {
|
||||
setAllowAutoExpand(newIsAllowedToAutoExpand)
|
||||
}
|
||||
}, [treeNode.edgeCount(), settings.get('autoExpandLimit')])
|
||||
|
||||
return isAllowedToAutoExpand
|
||||
}
|
||||
|
||||
function TreeNodeComponent(props: Props) {
|
||||
const { classes, className, settings, theme, treeNode, lastUpdate, name } = props
|
||||
|
||||
const { actions, classes, className, settings, theme, treeNode, lastUpdate, name } = props
|
||||
const deleteTopicCallback = useDeleteKeyCallback(treeNode, actions)
|
||||
const [showUpdateAnimation, setShowUpdateAnimation] = useState(false)
|
||||
const [collapsedOverride, setCollapsedOverride] = useState<boolean | undefined>(undefined)
|
||||
const [isHovering, setIsHovering] = useState(false)
|
||||
const [selected, setSelected] = useState(false)
|
||||
const nodeRef = useRef<HTMLDivElement>()
|
||||
const isAllowedToAutoExpand = useIsAllowedToAutoExpandState(props)
|
||||
useViewModelSubscriptions(treeNode, nodeRef, setSelected, setCollapsedOverride)
|
||||
useAnimationToIndicateTopicUpdate(lastUpdate, setShowUpdateAnimation, showUpdateAnimation)
|
||||
useAnimationToIndicateTopicUpdate(lastUpdate, selected, setShowUpdateAnimation, showUpdateAnimation)
|
||||
|
||||
const isCollapsed =
|
||||
Boolean(collapsedOverride) === collapsedOverride ? Boolean(collapsedOverride) : !isAllowedToAutoExpand
|
||||
|
||||
const setHover = debounce((hover: boolean) => {
|
||||
setIsHovering(hover)
|
||||
}, 45)
|
||||
|
||||
const toggle = useCallback(() => {
|
||||
setCollapsedOverride(!isCollapsed)
|
||||
}, [isCollapsed])
|
||||
@@ -130,17 +115,11 @@ function TreeNodeComponent(props: Props) {
|
||||
|
||||
const mouseOver = (event: React.MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
setHover(true)
|
||||
if (settings.get('selectTopicWithMouseOver') && treeNode && treeNode.message && treeNode.message.value) {
|
||||
didSelectTopic()
|
||||
}
|
||||
}
|
||||
|
||||
const mouseOut = (event: React.MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
setHover(false)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
treeNode.viewModel && treeNode.viewModel.setExpanded(!isCollapsed, false)
|
||||
}, [isCollapsed])
|
||||
@@ -157,6 +136,7 @@ function TreeNodeComponent(props: Props) {
|
||||
lastUpdate={treeNode.lastUpdate}
|
||||
selectTopicAction={props.selectTopicAction}
|
||||
settings={settings}
|
||||
actions={props.actions}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -167,19 +147,19 @@ function TreeNodeComponent(props: Props) {
|
||||
? { willChange: 'auto', translateZ: 0, animation: `${animationName} 0.5s` }
|
||||
: {}
|
||||
|
||||
const highlightClass = selected ? classes.selected : isHovering ? classes.hover : ''
|
||||
const highlightClass = selected ? classes.selected : ''
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
key={treeNode.hash()}
|
||||
className={`${classes.node} ${className} ${highlightClass} ${classes.title}`}
|
||||
style={animation}
|
||||
onMouseOver={mouseOver}
|
||||
onMouseOut={mouseOut}
|
||||
onMouseEnter={mouseOver}
|
||||
onFocus={didObtainFocus}
|
||||
onClick={didClickTitle}
|
||||
ref={nodeRef as any}
|
||||
tabIndex={-1}
|
||||
onKeyDown={deleteTopicCallback}
|
||||
>
|
||||
<TreeNodeTitle
|
||||
toggleCollapsed={toggleCollapsed}
|
||||
@@ -192,26 +172,7 @@ function TreeNodeComponent(props: Props) {
|
||||
{renderNodes()}
|
||||
</div>
|
||||
)
|
||||
}, [lastUpdate, treeNode, name, isCollapsed, selected, showUpdateAnimation, isHovering])
|
||||
}, [lastUpdate, treeNode, name, isCollapsed, selected, theme, showUpdateAnimation])
|
||||
}
|
||||
|
||||
export default withStyles(styles, { withTheme: true })(TreeNodeComponent)
|
||||
function useAnimationToIndicateTopicUpdate(
|
||||
lastUpdate: number,
|
||||
setShowUpdateAnimation: React.Dispatch<React.SetStateAction<boolean>>,
|
||||
showUpdateAnimation: boolean
|
||||
) {
|
||||
useEffect(() => {
|
||||
if (Date.now() - lastUpdate < 3000) {
|
||||
setShowUpdateAnimation(true)
|
||||
}
|
||||
}, [lastUpdate])
|
||||
useEffect(() => {
|
||||
if (showUpdateAnimation) {
|
||||
const timeout = setTimeout(() => setShowUpdateAnimation(false), 500)
|
||||
return function cleanup() {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
}
|
||||
}, [showUpdateAnimation])
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as q from '../../../../backend/src/Model'
|
||||
import React from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import TreeNode from './TreeNode'
|
||||
import { AppState } from '../../reducers'
|
||||
import { bindActionCreators } from 'redux'
|
||||
@@ -7,7 +7,6 @@ import { connect } from 'react-redux'
|
||||
import { SettingsState } from '../../reducers/Settings'
|
||||
import { TopicViewModel } from '../../model/TopicViewModel'
|
||||
import { treeActions } from '../../actions'
|
||||
import { useGlobalKeyEventHandler } from '../../effects/useGlobalKeyEventHandler'
|
||||
import { KeyCodes } from '../../utils/KeyCodes'
|
||||
|
||||
const MovingAverage = require('moving-average')
|
||||
@@ -30,16 +29,27 @@ interface State {
|
||||
lastUpdate: number
|
||||
}
|
||||
|
||||
function ArrowKeyHandler(props: {
|
||||
action: (direction: 'next' | 'previous') => any
|
||||
leftAction: () => void
|
||||
rightAction: () => void
|
||||
}) {
|
||||
useGlobalKeyEventHandler(KeyCodes.arrow_down, () => props.action('next'), [props.action])
|
||||
useGlobalKeyEventHandler(KeyCodes.arrow_up, () => props.action('previous'), [props.action])
|
||||
useGlobalKeyEventHandler(KeyCodes.arrow_right, props.rightAction, [props.action])
|
||||
useGlobalKeyEventHandler(KeyCodes.arrow_left, props.leftAction, [props.action])
|
||||
return <div />
|
||||
function useArrowKeyEventHandler(actions: typeof treeActions) {
|
||||
return (event: React.KeyboardEvent) => {
|
||||
switch (event.keyCode) {
|
||||
case KeyCodes.arrow_down:
|
||||
actions.moveSelectionUpOrDownwards('next')
|
||||
event.preventDefault()
|
||||
break
|
||||
case KeyCodes.arrow_up:
|
||||
actions.moveSelectionUpOrDownwards('previous')
|
||||
event.preventDefault()
|
||||
break
|
||||
case KeyCodes.arrow_left:
|
||||
actions.moveOutward()
|
||||
event.preventDefault()
|
||||
break
|
||||
case KeyCodes.arrow_right:
|
||||
actions.moveInward()
|
||||
event.preventDefault()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TreeComponent extends React.PureComponent<Props, State> {
|
||||
@@ -52,6 +62,7 @@ class TreeComponent extends React.PureComponent<Props, State> {
|
||||
this.state = { lastUpdate: 0 }
|
||||
}
|
||||
|
||||
private keyEventHandler = useArrowKeyEventHandler(this.props.actions)
|
||||
private performanceCallback = (ms: number) => {
|
||||
average.push(Date.now(), ms)
|
||||
}
|
||||
@@ -124,16 +135,12 @@ class TreeComponent extends React.PureComponent<Props, State> {
|
||||
overflowX: 'hidden',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
outline: '24px black !important',
|
||||
paddingBottom: '16px', // avoid conflict with chart panel Resizer
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
<ArrowKeyHandler
|
||||
action={this.props.actions.moveSelectionUpOrDownwards}
|
||||
leftAction={this.props.actions.moveOutward}
|
||||
rightAction={this.props.actions.moveInward}
|
||||
/>
|
||||
<div style={style} tabIndex={0} onKeyDown={this.keyEventHandler}>
|
||||
<TreeNode
|
||||
key={tree.hash()}
|
||||
isRoot={true}
|
||||
@@ -142,6 +149,7 @@ class TreeComponent extends React.PureComponent<Props, State> {
|
||||
collapsed={false}
|
||||
settings={this.props.settings}
|
||||
lastUpdate={tree.lastUpdate}
|
||||
actions={this.props.actions}
|
||||
selectTopicAction={this.props.actions.selectTopic}
|
||||
/>
|
||||
</div>
|
||||
Reference in New Issue
Block a user