Fix updating tree nodes when settings change

This commit is contained in:
Thomas Nordquist
2019-04-07 22:56:10 +02:00
parent 3e47b07ba7
commit 436b569e93
12 changed files with 85 additions and 126 deletions

View File

@@ -157,7 +157,7 @@ function autoExpandLimitForTree(tree: q.Tree<TopicViewModel>) {
export const toggleTheme = () => (dispatch: Dispatch<any>, getState: () => AppState) => {
dispatch({
type: getState().settings.theme === 'light' ? ActionTypes.SETTINGS_SET_THEME_DARK : ActionTypes.SETTINGS_SET_THEME_LIGHT,
type: getState().settings.get('theme') === 'light' ? ActionTypes.SETTINGS_SET_THEME_DARK : ActionTypes.SETTINGS_SET_THEME_LIGHT,
})
dispatch(storeSettings())
}

View File

@@ -131,10 +131,10 @@ const mapDispatchToProps = (dispatch: any) => {
const mapStateToProps = (state: AppState) => {
return {
settingsVisible: state.settings.visible,
settingsVisible: state.settings.get('visible'),
connectionId: state.connection.connectionId,
error: state.globalState.error,
highlightTopicUpdates: state.settings.highlightTopicUpdates,
highlightTopicUpdates: state.settings.get('highlightTopicUpdates'),
launching: state.globalState.launching,
}
}

View File

@@ -142,7 +142,7 @@ class TitleBar extends React.Component<Props, {}> {
const mapStateToProps = (state: AppState) => {
return {
topicFilter: state.settings.topicFilter,
topicFilter: state.settings.get('topicFilter'),
}
}

View File

@@ -236,12 +236,12 @@ class Settings extends React.Component<Props, {}> {
const mapStateToProps = (state: AppState) => {
return {
autoExpandLimit: state.settings.autoExpandLimit,
topicOrder: state.settings.topicOrder,
visible: state.settings.visible,
highlightTopicUpdates: state.settings.highlightTopicUpdates,
selectTopicWithMouseOver: state.settings.selectTopicWithMouseOver,
theme: state.settings.theme,
autoExpandLimit: state.settings.get('autoExpandLimit'),
topicOrder: state.settings.get('topicOrder'),
visible: state.settings.get('visible'),
highlightTopicUpdates: state.settings.get('highlightTopicUpdates'),
selectTopicWithMouseOver: state.settings.get('selectTopicWithMouseOver'),
theme: state.settings.get('theme'),
}
}

View File

@@ -163,7 +163,7 @@ const mapDispatchToProps = (dispatch: any) => {
const mapStateToProps = (state: AppState) => {
return {
valueRendererDisplayMode: state.settings.valueRendererDisplayMode,
valueRendererDisplayMode: state.settings.get('valueRendererDisplayMode'),
node: state.tree.selectedTopic,
}
}

View File

@@ -105,7 +105,7 @@ class ValueRenderer extends React.Component<Props, State> {
const mapStateToProps = (state: AppState) => {
return {
renderMode: state.settings.valueRendererDisplayMode,
renderMode: state.settings.get('valueRendererDisplayMode'),
}
}

View File

@@ -4,7 +4,8 @@ import TreeNode from './TreeNode'
import { AppState } from '../../reducers'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { TopicOrder } from '../../reducers/Settings'
import { Record } from 'immutable'
import { SettingsState } from '../../reducers/Settings'
import { TopicViewModel } from '../../model/TopicViewModel'
import { treeActions } from '../../actions'
@@ -21,11 +22,8 @@ interface Props {
tree?: q.Tree<TopicViewModel>
filter: string
host?: string
topicOrder: TopicOrder
autoExpandLimit: number
highlightTopicUpdates: boolean
selectTopicWithMouseOver: boolean
paused: boolean
settings: Record<SettingsState>
}
interface State {
@@ -109,18 +107,14 @@ class Tree extends React.PureComponent<Props, State> {
<div style={style}>
<TreeNode
key={tree.hash()}
animateChages={true}
isRoot={true}
treeNode={tree}
name={this.props.host}
collapsed={false}
performanceCallback={this.performanceCallback}
autoExpandLimit={this.props.autoExpandLimit}
topicOrder={this.props.topicOrder}
settings={this.props.settings}
lastUpdate={tree.lastUpdate}
didSelectTopic={this.props.actions.selectTopic}
highlightTopicUpdates={this.props.highlightTopicUpdates}
selectTopicWithMouseOver={this.props.selectTopicWithMouseOver}
/>
</div>
)
@@ -133,10 +127,7 @@ const mapStateToProps = (state: AppState) => {
paused: state.tree.paused,
filter: state.tree.filter,
host: state.connection.host,
autoExpandLimit: state.settings.autoExpandLimit,
topicOrder: state.settings.topicOrder,
highlightTopicUpdates: state.settings.highlightTopicUpdates,
selectTopicWithMouseOver: state.settings.selectTopicWithMouseOver,
settings: state.settings,
}
}

View File

@@ -2,8 +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 { TopicOrder } from '../../reducers/Settings'
import { TopicViewModel } from '../../model/TopicViewModel'
const debounce = require('lodash.debounce')
@@ -41,7 +42,6 @@ const styles = (theme: Theme) => {
}
interface Props {
animateChages: boolean
isRoot?: boolean
treeNode: q.TreeNode<TopicViewModel>
name?: string | undefined
@@ -49,13 +49,10 @@ interface Props {
performanceCallback?: ((ms: number) => void) | undefined
classes: any
className?: string
topicOrder: TopicOrder
autoExpandLimit: number
lastUpdate: number
didSelectTopic: any
highlightTopicUpdates: boolean
selectTopicWithMouseOver: boolean
theme: Theme
settings: Record<SettingsState>
}
interface State {
@@ -127,10 +124,6 @@ class TreeNode extends React.Component<Props, State> {
|| this.state.selected !== newState.selected
}
private propsHasChanged(newProps: Props) {
return this.props.autoExpandLimit !== newProps.autoExpandLimit
}
private toggle() {
this.setState({ collapsedOverride: !this.collapsed() })
}
@@ -140,7 +133,7 @@ class TreeNode extends React.Component<Props, State> {
return this.state.collapsedOverride
}
return this.props.treeNode.edgeCount() > this.props.autoExpandLimit
return this.props.treeNode.edgeCount() > this.props.settings.get('autoExpandLimit')
}
private didSelectTopic = () => {
@@ -156,7 +149,7 @@ class TreeNode extends React.Component<Props, State> {
private mouseOver = (event: React.MouseEvent) => {
event.stopPropagation()
this.setHover(true)
if (this.props.selectTopicWithMouseOver && this.props.treeNode && this.props.treeNode.message && this.props.treeNode.message.value) {
if (this.props.settings.get('selectTopicWithMouseOver') && this.props.treeNode && this.props.treeNode.message && this.props.treeNode.message.value) {
this.props.didSelectTopic(this.props.treeNode)
}
}
@@ -178,15 +171,11 @@ class TreeNode extends React.Component<Props, State> {
return (
<TreeNodeSubnodes
animateChanges={this.props.animateChages}
collapsed={this.collapsed()}
treeNode={this.props.treeNode}
autoExpandLimit={this.props.autoExpandLimit}
topicOrder={this.props.topicOrder}
lastUpdate={this.props.treeNode.lastUpdate}
didSelectTopic={this.props.didSelectTopic}
highlightTopicUpdates={this.props.highlightTopicUpdates}
selectTopicWithMouseOver={this.props.selectTopicWithMouseOver}
settings={this.props.settings}
/>
)
}
@@ -212,7 +201,7 @@ class TreeNode extends React.Component<Props, State> {
public shouldComponentUpdate(nextProps: Props, nextState: State) {
const shouldRenderToRemoveCssAnimation = this.cssAnimationWasSetAt !== undefined
return this.stateHasChanged(nextState)
|| this.propsHasChanged(nextProps)
|| this.props.settings !== nextProps.settings
|| (this.dirtyEdges || this.dirtyMessage || this.dirtySubnodes)
|| this.animationDirty
|| shouldRenderToRemoveCssAnimation
@@ -236,7 +225,7 @@ class TreeNode extends React.Component<Props, State> {
const isDirty = this.dirtyEdges || this.dirtyMessage || this.dirtySubnodes
this.dirtyEdges = this.dirtyMessage = this.dirtySubnodes = false
const shouldStartAnimation = (isDirty && !this.animationDirty) && !this.props.isRoot && this.props.highlightTopicUpdates
const shouldStartAnimation = (isDirty && !this.animationDirty) && !this.props.isRoot && this.props.settings.get('highlightTopicUpdates')
const animationName = this.props.theme.palette.type === 'light' ? 'updateLight' : 'updateDark'
const animation = shouldStartAnimation ? { willChange: 'auto', translateZ: 0, animation: `${animationName} 0.5s` } : {}
this.animationDirty = shouldStartAnimation

View File

@@ -1,24 +1,20 @@
import * as React from 'react'
import * as q from '../../../../backend/src/Model'
import * as React from 'react'
import TreeNode from './TreeNode'
import { TopicOrder } from '../../reducers/Settings'
import { Record } from 'immutable'
import { SettingsState, TopicOrder } from '../../reducers/Settings'
import { Theme, withStyles } from '@material-ui/core'
import { TopicViewModel } from '../../model/TopicViewModel'
export interface Props {
animateChanges: boolean
treeNode: q.TreeNode<TopicViewModel>
filter?: string
collapsed?: boolean | undefined
classes: any
lastUpdate: number
topicOrder: TopicOrder
selectedTopic?: q.TreeNode<TopicViewModel>
autoExpandLimit: number
didSelectTopic: any
highlightTopicUpdates: boolean
selectTopicWithMouseOver: boolean
settings: Record<SettingsState>
}
interface State {
@@ -33,7 +29,8 @@ class TreeNodeSubnodes extends React.Component<Props, State> {
}
private sortedNodes(): Array<q.TreeNode<TopicViewModel>> {
const { topicOrder, treeNode } = this.props
const { settings, treeNode } = this.props
const topicOrder = settings.get('topicOrder')
let edges = treeNode.edgeArray
if (topicOrder === TopicOrder.abc) {
@@ -76,15 +73,11 @@ class TreeNodeSubnodes extends React.Component<Props, State> {
return (
<TreeNode
key={`${node.hash()}-${this.props.filter}`}
animateChages={this.props.animateChanges}
treeNode={node}
className={this.props.classes.listItem}
topicOrder={this.props.topicOrder}
autoExpandLimit={this.props.autoExpandLimit}
lastUpdate={node.lastUpdate}
didSelectTopic={this.props.didSelectTopic}
highlightTopicUpdates={this.props.highlightTopicUpdates}
selectTopicWithMouseOver={this.props.selectTopicWithMouseOver}
settings={this.props.settings}
/>
)
})

View File

@@ -64,7 +64,7 @@ function ApplicationRenderer(props: {theme: 'light' | 'dark'}) {
const mapStateToProps = (state: AppState) => {
return {
theme: state.settings.theme,
theme: state.settings.get('theme'),
}
}

View File

@@ -1,5 +1,5 @@
import { Action } from 'redux'
import { createReducer } from './lib'
import { Record } from 'immutable'
export enum TopicOrder {
none = 'none',
@@ -21,7 +21,15 @@ export interface SettingsState {
theme: 'light' | 'dark'
}
export type Action = SetAutoExpandLimit | ToggleVisibility | SetTopicOrder | FilterTopics | TogglehighlightTopicUpdates | SetValueRendererDisplayMode | SetTheme
export type Actions = SetAutoExpandLimitAction
& DidLoadSettingsAction
& ToggleVisibilityAction
& SetTopicOrderAction
& FilterTopicsAction
& ToggleHighlightTopicUpdatesAction
& SetValueRendererDisplayModeAction
& SetTheme
& SetSelectTopicWithMouseOverAction
export enum ActionTypes {
SETTINGS_SET_AUTO_EXPAND_LIMIT = 'SETTINGS_SET_AUTO_EXPAND_LIMIT',
@@ -36,131 +44,108 @@ export enum ActionTypes {
SETTINGS_SET_THEME_DARK = 'SETTINGS_SET_THEME_DARK',
}
const initialState: SettingsState = {
const initialState = Record<SettingsState>({
autoExpandLimit: 0,
topicOrder: TopicOrder.none,
visible: false,
highlightTopicUpdates: true,
valueRendererDisplayMode: 'diff',
selectTopicWithMouseOver: false,
theme: 'dark',
theme: 'light',
})
const setTheme = (theme: 'light' | 'dark') => (state: Record<SettingsState>) => {
return state.set('theme', theme)
}
const setTheme = (theme: 'light' | 'dark') => (state: SettingsState) => {
return {
...state,
theme,
}
}
export const settingsReducer = createReducer(initialState, {
const reducerActions: {[s: string]: (state: Record<SettingsState>, action: Actions) => Record<SettingsState>} = {
SETTINGS_SET_AUTO_EXPAND_LIMIT: setAutoExpandLimit,
SETTINGS_TOGGLE_VISIBILITY: toggleVisibility,
SETTINGS_SET_TOPIC_ORDER: setTopicOrder,
SETTINGS_FILTER_TOPICS: filterTopics,
SETTINGS_TOGGLE_HIGHLIGHT_ACTIVITY: togglehighlightTopicUpdates,
SETTINGS_TOGGLE_HIGHLIGHT_ACTIVITY: toggleHighlightTopicUpdates,
SETTINGS_DID_LOAD_SETTINGS: didLoadSettings,
SETTINGS_SET_VALUE_RENDERER_DISPLAY_MODE: setValueRendererDisplayMode,
SETTINGS_SET_SELECT_TOPIC_WITH_MOUSE_OVER: setSelectTopicWithMouseOver,
SETTINGS_SET_THEME_LIGHT: setTheme('light'),
SETTINGS_SET_THEME_DARK: setTheme('dark'),
})
}
export const settingsReducer = createReducer(initialState(), reducerActions)
export interface SetTheme {
type: ActionTypes.SETTINGS_SET_THEME_LIGHT | ActionTypes.SETTINGS_SET_THEME_DARK
theme: 'light'
}
export interface DidLoadSettings {
export interface DidLoadSettingsAction {
type: ActionTypes.SETTINGS_DID_LOAD_SETTINGS
settings: Partial<SettingsState>
}
function didLoadSettings(state: SettingsState, action: DidLoadSettings) {
return {
...state,
...action.settings,
}
function didLoadSettings(state: Record<SettingsState>, action: DidLoadSettingsAction) {
return state.merge(action.settings)
}
export interface SetSelectTopicWithMouseOver {
export interface SetSelectTopicWithMouseOverAction {
type: ActionTypes.SETTINGS_SET_SELECT_TOPIC_WITH_MOUSE_OVER
selectTopicWithMouseOver: boolean
}
export function setSelectTopicWithMouseOver(state: SettingsState, action: SetSelectTopicWithMouseOver) {
return {
...state,
selectTopicWithMouseOver: action.selectTopicWithMouseOver,
}
export function setSelectTopicWithMouseOver(state: Record<SettingsState>, action: SetSelectTopicWithMouseOverAction) {
return state.set('selectTopicWithMouseOver', !state.get('selectTopicWithMouseOver'))
}
export interface SetValueRendererDisplayMode {
export interface SetValueRendererDisplayModeAction {
type: ActionTypes.SETTINGS_SET_VALUE_RENDERER_DISPLAY_MODE
valueRendererDisplayMode: ValueRendererDisplayMode
}
export function setValueRendererDisplayMode(state: SettingsState, action: SetValueRendererDisplayMode) {
return {
...state,
valueRendererDisplayMode: action.valueRendererDisplayMode,
}
export function setValueRendererDisplayMode(state: Record<SettingsState>, action: SetValueRendererDisplayModeAction) {
return state.set('valueRendererDisplayMode', action.valueRendererDisplayMode)
}
export interface SetAutoExpandLimit {
export interface SetAutoExpandLimitAction {
type: ActionTypes.SETTINGS_SET_AUTO_EXPAND_LIMIT
autoExpandLimit: number
}
function setAutoExpandLimit(state: SettingsState, action: SetAutoExpandLimit) {
return {
...state,
autoExpandLimit: action.autoExpandLimit,
}
function setAutoExpandLimit(state: Record<SettingsState>, action: SetAutoExpandLimitAction) {
return state.set('autoExpandLimit', action.autoExpandLimit)
}
export interface TogglehighlightTopicUpdates {
export interface ToggleHighlightTopicUpdatesAction {
type: ActionTypes.SETTINGS_TOGGLE_HIGHLIGHT_ACTIVITY
}
function togglehighlightTopicUpdates(state: SettingsState, _action: TogglehighlightTopicUpdates) {
return {
...state,
highlightTopicUpdates: !state.highlightTopicUpdates,
}
function toggleHighlightTopicUpdates(state: Record<SettingsState>, action: ToggleHighlightTopicUpdatesAction) {
return state.set('highlightTopicUpdates', !state.get('highlightTopicUpdates'))
}
export interface ToggleVisibility {
export interface ToggleVisibilityAction {
type: ActionTypes.SETTINGS_TOGGLE_VISIBILITY
}
function toggleVisibility(state: SettingsState, action: ToggleVisibility) {
return {
...state,
visible: !state.visible,
}
// Todo: Should not be part of the settings store, it would require all tree nodes to re-render when toggeling settings
function toggleVisibility(state: Record<SettingsState>, action: ToggleVisibilityAction) {
return state.set('visible', !state.get('visible'))
}
function setTopicOrder(state: SettingsState, action: SetTopicOrder) {
return {
...state,
topicOrder: action.topicOrder,
}
}
export interface SetTopicOrder {
export interface SetTopicOrderAction {
type: ActionTypes.SETTINGS_SET_TOPIC_ORDER
topicOrder: string
topicOrder: TopicOrder
}
function filterTopics(state: SettingsState, action: FilterTopics) {
return {
...state,
topicFilter: action.topicFilter,
}
function setTopicOrder(state: Record<SettingsState>, action: SetTopicOrderAction) {
return state.set('topicOrder', action.topicOrder)
}
export interface FilterTopics {
export interface FilterTopicsAction {
type: ActionTypes.SETTINGS_FILTER_TOPICS
topicFilter: string
}
// @Todo: move to tree reducer, should not be persisted / is no application setting
function filterTopics(state: Record<SettingsState>, action: FilterTopicsAction) {
return state.set('topicFilter', action.topicFilter)
}

View File

@@ -2,6 +2,7 @@ import { Action, combineReducers, Reducer } from 'redux'
import { connectionManagerReducer, ConnectionManagerState } from './ConnectionManager'
import { connectionReducer, ConnectionState } from './Connection'
import { publishReducer, PublishState } from './Publish'
import { Record } from 'immutable'
import { settingsReducer, SettingsState } from './Settings'
import { trackEvent } from '../utils/tracking'
import { treeReducer, TreeState } from './Tree'
@@ -23,7 +24,7 @@ export interface CustomAction extends Action {
export interface AppState {
globalState: GlobalState
tree: TreeState
settings: SettingsState,
settings: Record<SettingsState>,
publish: PublishState
connection: ConnectionState
connectionManager: ConnectionManagerState