Add tree navigation via arrow keys
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
import * as q from '../../../../backend/src/Model'
|
||||
import * as React from 'react'
|
||||
import React from 'react'
|
||||
import TreeNode from './TreeNode'
|
||||
import { AppState } from '../../reducers'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { connect } from 'react-redux'
|
||||
import { Record } from 'immutable'
|
||||
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')
|
||||
|
||||
@@ -20,16 +21,27 @@ interface Props {
|
||||
actions: typeof treeActions
|
||||
connectionId?: string
|
||||
tree?: q.Tree<TopicViewModel>
|
||||
filter: string
|
||||
host?: string
|
||||
paused: boolean
|
||||
settings: Record<SettingsState>
|
||||
settings: SettingsState
|
||||
}
|
||||
|
||||
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 />
|
||||
}
|
||||
|
||||
class TreeComponent extends React.PureComponent<Props, State> {
|
||||
private updateTimer?: any
|
||||
private perf: number = 0
|
||||
@@ -99,7 +111,7 @@ class TreeComponent extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { tree, filter } = this.props
|
||||
const { tree } = this.props
|
||||
if (!tree) {
|
||||
return null
|
||||
}
|
||||
@@ -116,6 +128,11 @@ class TreeComponent extends React.PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
<ArrowKeyHandler
|
||||
action={this.props.actions.moveSelectionUpOrDownwards}
|
||||
leftAction={this.props.actions.moveOutward}
|
||||
rightAction={this.props.actions.moveInward}
|
||||
/>
|
||||
<TreeNode
|
||||
key={tree.hash()}
|
||||
isRoot={true}
|
||||
|
||||
@@ -2,11 +2,9 @@ import * as q from '../../../../backend/src/Model'
|
||||
import * as React from 'react'
|
||||
import TreeNodeSubnodes from './TreeNodeSubnodes'
|
||||
import TreeNodeTitle from './TreeNodeTitle'
|
||||
import { Record } from 'immutable'
|
||||
import { SettingsState } from '../../reducers/Settings'
|
||||
import { Theme, withStyles } from '@material-ui/core/styles'
|
||||
import { TopicViewModel } from '../../model/TopicViewModel'
|
||||
|
||||
const debounce = require('lodash.debounce')
|
||||
|
||||
declare var performance: any
|
||||
@@ -61,7 +59,7 @@ interface Props {
|
||||
lastUpdate: number
|
||||
didSelectTopic: any
|
||||
theme: Theme
|
||||
settings: Record<SettingsState>
|
||||
settings: SettingsState
|
||||
}
|
||||
|
||||
interface State {
|
||||
@@ -72,9 +70,7 @@ interface State {
|
||||
|
||||
class TreeNodeComponent extends React.Component<Props, State> {
|
||||
private animationDirty: boolean = false
|
||||
|
||||
private cssAnimationWasSetAt?: number
|
||||
|
||||
private willUpdateTime: number = performance.now()
|
||||
private nodeRef?: React.RefObject<HTMLDivElement> = React.createRef<HTMLDivElement>()
|
||||
|
||||
@@ -94,16 +90,24 @@ class TreeNodeComponent extends React.Component<Props, State> {
|
||||
|
||||
private addSubscriber(treeNode: q.TreeNode<TopicViewModel>) {
|
||||
treeNode.viewModel = new TopicViewModel()
|
||||
treeNode.viewModel.change.subscribe(this.viewStateHasChanged)
|
||||
treeNode.viewModel.selectionChange.subscribe(this.selectionDidChange)
|
||||
treeNode.viewModel.expandedChange.subscribe(this.expandedDidChange)
|
||||
}
|
||||
|
||||
private viewStateHasChanged = () => {
|
||||
private selectionDidChange = () => {
|
||||
this.props.treeNode.viewModel && this.setState({ selected: this.props.treeNode.viewModel.isSelected() })
|
||||
}
|
||||
|
||||
private expandedDidChange = () => {
|
||||
this.props.treeNode.viewModel && this.setState({ collapsedOverride: !this.props.treeNode.viewModel.isExpanded() })
|
||||
}
|
||||
|
||||
private removeSubscriber(treeNode: q.TreeNode<TopicViewModel>) {
|
||||
treeNode.viewModel && treeNode.viewModel.change.unsubscribe(this.viewStateHasChanged)
|
||||
treeNode.viewModel = undefined
|
||||
if (treeNode.viewModel) {
|
||||
treeNode.viewModel.selectionChange.unsubscribe(this.selectionDidChange)
|
||||
treeNode.viewModel.expandedChange.unsubscribe(this.expandedDidChange)
|
||||
treeNode.viewModel = undefined
|
||||
}
|
||||
}
|
||||
|
||||
private stateHasChanged(newState: State) {
|
||||
@@ -160,6 +164,9 @@ class TreeNodeComponent extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
private renderNodes() {
|
||||
const isCollapsed = this.collapsed()
|
||||
this.props.treeNode.viewModel && this.props.treeNode.viewModel.setExpanded(!isCollapsed, false)
|
||||
|
||||
if (this.collapsed()) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as q from '../../../../backend/src/Model'
|
||||
import * as React from 'react'
|
||||
import TreeNode from './TreeNode'
|
||||
import { Record } from 'immutable'
|
||||
import { SettingsState, TopicOrder } from '../../reducers/Settings'
|
||||
import { SettingsState } from '../../reducers/Settings'
|
||||
import { sortedNodes } from '../../sortedNodes'
|
||||
import { Theme, withStyles } from '@material-ui/core'
|
||||
import { TopicViewModel } from '../../model/TopicViewModel'
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface Props {
|
||||
lastUpdate: number
|
||||
selectedTopic?: q.TreeNode<TopicViewModel>
|
||||
didSelectTopic: any
|
||||
settings: Record<SettingsState>
|
||||
settings: SettingsState
|
||||
}
|
||||
|
||||
interface State {
|
||||
@@ -28,26 +28,6 @@ class TreeNodeSubnodes extends React.Component<Props, State> {
|
||||
this.state = { alreadyAdded: 10 }
|
||||
}
|
||||
|
||||
private sortedNodes(): Array<q.TreeNode<TopicViewModel>> {
|
||||
const { settings, treeNode } = this.props
|
||||
const topicOrder = settings.get('topicOrder')
|
||||
|
||||
let edges = treeNode.edgeArray
|
||||
if (topicOrder === TopicOrder.abc) {
|
||||
edges = edges.sort((a, b) => a.name.localeCompare(b.name))
|
||||
}
|
||||
|
||||
let nodes = edges.map(edge => edge.target)
|
||||
if (topicOrder === TopicOrder.messages) {
|
||||
nodes = nodes.sort((a, b) => b.leafMessageCount() - a.leafMessageCount())
|
||||
}
|
||||
if (topicOrder === TopicOrder.topics) {
|
||||
nodes = nodes.sort((a, b) => b.childTopicCount() - a.childTopicCount())
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
private renderMore() {
|
||||
this.renderMoreAnimationFrame = (window as any).requestIdleCallback(
|
||||
() => {
|
||||
@@ -71,7 +51,7 @@ class TreeNodeSubnodes extends React.Component<Props, State> {
|
||||
this.renderMore()
|
||||
}
|
||||
|
||||
const nodes = this.sortedNodes().slice(0, this.state.alreadyAdded)
|
||||
const nodes = sortedNodes(this.props.settings, this.props.treeNode).slice(0, this.state.alreadyAdded)
|
||||
const listItems = nodes.map(node => {
|
||||
return (
|
||||
<TreeNode
|
||||
|
||||
Reference in New Issue
Block a user