diff --git a/app/src/actions/Settings.ts b/app/src/actions/Settings.ts index 638a666..e74c46b 100644 --- a/app/src/actions/Settings.ts +++ b/app/src/actions/Settings.ts @@ -157,7 +157,7 @@ function autoExpandLimitForTree(tree: q.Tree) { export const toggleTheme = () => (dispatch: Dispatch, 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()) } diff --git a/app/src/components/App.tsx b/app/src/components/App.tsx index 15952e1..7375db0 100644 --- a/app/src/components/App.tsx +++ b/app/src/components/App.tsx @@ -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, } } diff --git a/app/src/components/Layout/TitleBar.tsx b/app/src/components/Layout/TitleBar.tsx index 1f0bc2a..b048663 100644 --- a/app/src/components/Layout/TitleBar.tsx +++ b/app/src/components/Layout/TitleBar.tsx @@ -142,7 +142,7 @@ class TitleBar extends React.Component { const mapStateToProps = (state: AppState) => { return { - topicFilter: state.settings.topicFilter, + topicFilter: state.settings.get('topicFilter'), } } diff --git a/app/src/components/SettingsDrawer/Settings.tsx b/app/src/components/SettingsDrawer/Settings.tsx index efe0534..fd03ec5 100644 --- a/app/src/components/SettingsDrawer/Settings.tsx +++ b/app/src/components/SettingsDrawer/Settings.tsx @@ -236,12 +236,12 @@ class Settings extends React.Component { 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'), } } diff --git a/app/src/components/Sidebar/ValueRenderer/ValuePanel.tsx b/app/src/components/Sidebar/ValueRenderer/ValuePanel.tsx index bc0e7e6..3ec31dd 100644 --- a/app/src/components/Sidebar/ValueRenderer/ValuePanel.tsx +++ b/app/src/components/Sidebar/ValueRenderer/ValuePanel.tsx @@ -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, } } diff --git a/app/src/components/Sidebar/ValueRenderer/ValueRenderer.tsx b/app/src/components/Sidebar/ValueRenderer/ValueRenderer.tsx index cf25516..5e8dcbd 100644 --- a/app/src/components/Sidebar/ValueRenderer/ValueRenderer.tsx +++ b/app/src/components/Sidebar/ValueRenderer/ValueRenderer.tsx @@ -105,7 +105,7 @@ class ValueRenderer extends React.Component { const mapStateToProps = (state: AppState) => { return { - renderMode: state.settings.valueRendererDisplayMode, + renderMode: state.settings.get('valueRendererDisplayMode'), } } diff --git a/app/src/components/Tree/Tree.tsx b/app/src/components/Tree/Tree.tsx index e83e721..497ae77 100644 --- a/app/src/components/Tree/Tree.tsx +++ b/app/src/components/Tree/Tree.tsx @@ -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 filter: string host?: string - topicOrder: TopicOrder - autoExpandLimit: number - highlightTopicUpdates: boolean - selectTopicWithMouseOver: boolean paused: boolean + settings: Record } interface State { @@ -109,18 +107,14 @@ class Tree extends React.PureComponent {
) @@ -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, } } diff --git a/app/src/components/Tree/TreeNode.tsx b/app/src/components/Tree/TreeNode.tsx index 549a935..c41e1a1 100644 --- a/app/src/components/Tree/TreeNode.tsx +++ b/app/src/components/Tree/TreeNode.tsx @@ -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 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 } interface State { @@ -127,10 +124,6 @@ class TreeNode extends React.Component { || 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 { 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 { 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 { return ( ) } @@ -212,7 +201,7 @@ class TreeNode extends React.Component { 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 { 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 diff --git a/app/src/components/Tree/TreeNodeSubnodes.tsx b/app/src/components/Tree/TreeNodeSubnodes.tsx index ac4432c..4bda1ad 100644 --- a/app/src/components/Tree/TreeNodeSubnodes.tsx +++ b/app/src/components/Tree/TreeNodeSubnodes.tsx @@ -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 filter?: string collapsed?: boolean | undefined classes: any lastUpdate: number - topicOrder: TopicOrder selectedTopic?: q.TreeNode - autoExpandLimit: number didSelectTopic: any - highlightTopicUpdates: boolean - selectTopicWithMouseOver: boolean + settings: Record } interface State { @@ -33,7 +29,8 @@ class TreeNodeSubnodes extends React.Component { } private sortedNodes(): Array> { - 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 { return ( ) }) diff --git a/app/src/index.tsx b/app/src/index.tsx index 0bf454e..7113e79 100644 --- a/app/src/index.tsx +++ b/app/src/index.tsx @@ -64,7 +64,7 @@ function ApplicationRenderer(props: {theme: 'light' | 'dark'}) { const mapStateToProps = (state: AppState) => { return { - theme: state.settings.theme, + theme: state.settings.get('theme'), } } diff --git a/app/src/reducers/Settings.ts b/app/src/reducers/Settings.ts index c38bce3..4416f73 100644 --- a/app/src/reducers/Settings.ts +++ b/app/src/reducers/Settings.ts @@ -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({ autoExpandLimit: 0, topicOrder: TopicOrder.none, visible: false, highlightTopicUpdates: true, valueRendererDisplayMode: 'diff', selectTopicWithMouseOver: false, - theme: 'dark', + theme: 'light', +}) + +const setTheme = (theme: 'light' | 'dark') => (state: Record) => { + 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, action: Actions) => Record} = { 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 } -function didLoadSettings(state: SettingsState, action: DidLoadSettings) { - return { - ...state, - ...action.settings, - } +function didLoadSettings(state: Record, 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, 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, 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, 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, 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, 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, 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, action: FilterTopicsAction) { + return state.set('topicFilter', action.topicFilter) +} diff --git a/app/src/reducers/index.ts b/app/src/reducers/index.ts index 2500ec8..b73ef10 100644 --- a/app/src/reducers/index.ts +++ b/app/src/reducers/index.ts @@ -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, publish: PublishState connection: ConnectionState connectionManager: ConnectionManagerState