feature(redux-devtools-inspector-monitor): add option to sort state tree alphabetically and/or disable collections (#1264)

Co-authored-by: Kent Kwee <kent.kwee@tngtech.com>
This commit is contained in:
kentkwee 2023-04-11 14:21:40 +02:00 committed by GitHub
parent c52cfbe469
commit d54adb76f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 150 additions and 0 deletions

View File

@ -0,0 +1,7 @@
---
'remotedev-redux-devtools-extension': minor
'@redux-devtools/inspector-monitor': minor
---
Option to sort State Tree keys alphabetically
Option to disable collapsing of object keys

View File

@ -60,6 +60,7 @@ class Actions extends Component<Props> {
liftedState, liftedState,
liftedDispatch, liftedDispatch,
position, position,
stateTreeSettings,
} = this.props; } = this.props;
const { features } = options; const { features } = options;
return ( return (
@ -75,6 +76,7 @@ class Actions extends Component<Props> {
monitorState={this.props.monitorState} monitorState={this.props.monitorState}
dispatch={liftedDispatch} dispatch={liftedDispatch}
features={options.features} features={options.features}
stateTreeSettings={stateTreeSettings}
/> />
{sliderIsOpen && options.connectionId && options.features.jump && ( {sliderIsOpen && options.connectionId && options.features.jump && (
<SliderMonitor liftedState={liftedState} dispatch={liftedDispatch} /> <SliderMonitor liftedState={liftedState} dispatch={liftedDispatch} />
@ -149,6 +151,7 @@ const mapStateToProps = (state: StoreState) => {
dispatcherIsOpen: state.monitor.dispatcherIsOpen, dispatcherIsOpen: state.monitor.dispatcherIsOpen,
sliderIsOpen: state.monitor.sliderIsOpen, sliderIsOpen: state.monitor.sliderIsOpen,
reports: state.reports.data, reports: state.reports.data,
stateTreeSettings: state.stateTreeSettings,
}; };
}; };

View File

@ -7,6 +7,7 @@ import {
section, section,
socket, socket,
theme, theme,
stateTreeSettings,
StoreState, StoreState,
} from '@redux-devtools/app'; } from '@redux-devtools/app';
import instances from './instancesReducer'; import instances from './instancesReducer';
@ -22,6 +23,7 @@ const rootReducer: Reducer<StoreState, WindowStoreAction> =
section, section,
theme, theme,
connection, connection,
stateTreeSettings,
}); });
export default rootReducer; export default rootReducer;

View File

@ -25,6 +25,7 @@ import {
GET_REPORT_SUCCESS, GET_REPORT_SUCCESS,
ERROR, ERROR,
SET_PERSIST, SET_PERSIST,
CHANGE_STATE_TREE_SETTINGS,
} from '../constants/actionTypes'; } from '../constants/actionTypes';
import { import {
AUTH_ERROR, AUTH_ERROR,
@ -82,6 +83,27 @@ export function changeTheme(data: ChangeThemeData): ChangeThemeAction {
return { type: CHANGE_THEME, ...data.formData }; return { type: CHANGE_THEME, ...data.formData };
} }
interface ChangeStateTreeSettingsFormData {
readonly sortAlphabetically: boolean;
readonly disableCollection: boolean;
}
interface ChangeStateTreeSettingsData {
readonly formData: ChangeStateTreeSettingsFormData;
}
export interface ChangeStateTreeSettingsAction {
readonly type: typeof CHANGE_STATE_TREE_SETTINGS;
readonly sortAlphabetically: boolean;
readonly disableCollection: boolean;
}
export function changeStateTreeSettings(
data: ChangeStateTreeSettingsData
): ChangeStateTreeSettingsAction {
return { type: CHANGE_STATE_TREE_SETTINGS, ...data.formData };
}
export interface InitMonitorAction { export interface InitMonitorAction {
type: '@@INIT_MONITOR'; type: '@@INIT_MONITOR';
newMonitorState: unknown; newMonitorState: unknown;
@ -568,6 +590,7 @@ interface ReduxPersistRehydrateAction {
export type StoreActionWithoutUpdateStateOrLiftedAction = export type StoreActionWithoutUpdateStateOrLiftedAction =
| ChangeSectionAction | ChangeSectionAction
| ChangeThemeAction | ChangeThemeAction
| ChangeStateTreeSettingsAction
| MonitorActionAction | MonitorActionAction
| SelectInstanceAction | SelectInstanceAction
| SelectMonitorAction | SelectMonitorAction

View File

@ -0,0 +1,52 @@
import React, { Component } from 'react';
import { connect, ResolveThunks } from 'react-redux';
import { Container, Form } from '@redux-devtools/ui';
import { changeStateTreeSettings } from '../../actions';
import { StoreState } from '../../reducers';
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = StateProps & DispatchProps;
export class StateTree extends Component<Props> {
render() {
const stateTree = this.props.theme;
const formData = {
sortAlphabetically: stateTree.sortAlphabetically,
disableCollection: stateTree.disableCollection,
};
return (
<Container>
<Form
schema={{
type: 'object',
properties: {
sortAlphabetically: {
title: 'Sort Alphabetically',
type: 'boolean',
},
disableCollection: {
title: 'Disable collapsing of nodes',
type: 'boolean',
},
},
}}
formData={formData}
noSubmit
onChange={this.props.changeStateTreeSettings}
/>
</Container>
);
}
}
const mapStateToProps = (state: StoreState) => ({
theme: state.stateTreeSettings,
});
const actionCreators = {
changeStateTreeSettings,
};
export default connect(mapStateToProps, actionCreators)(StateTree);

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react';
import { Tabs } from '@redux-devtools/ui'; import { Tabs } from '@redux-devtools/ui';
import Connection from './Connection'; import Connection from './Connection';
import Themes from './Themes'; import Themes from './Themes';
import StateTree from './StateTree';
interface State { interface State {
selected: string; selected: string;
@ -12,6 +13,7 @@ export default class Settings extends Component<{}, State> {
tabs = [ tabs = [
{ name: 'Connection', component: Connection }, { name: 'Connection', component: Connection },
{ name: 'Themes', component: Themes }, { name: 'Themes', component: Themes },
{ name: 'State Tree', component: StateTree },
]; ];
state: State = { selected: 'Connection' }; state: State = { selected: 'Connection' };

View File

@ -1,5 +1,6 @@
export const CHANGE_SECTION = 'main/CHANGE_SECTION'; export const CHANGE_SECTION = 'main/CHANGE_SECTION';
export const CHANGE_THEME = 'main/CHANGE_THEME'; export const CHANGE_THEME = 'main/CHANGE_THEME';
export const CHANGE_STATE_TREE_SETTINGS = 'main/CHANGE_STATE_TREE_SETTINGS';
export const UPDATE_STATE = 'devTools/UPDATE_STATE'; export const UPDATE_STATE = 'devTools/UPDATE_STATE';
export const SET_STATE = 'devTools/SET_STATE'; export const SET_STATE = 'devTools/SET_STATE';

View File

@ -23,6 +23,7 @@ class Actions extends Component<Props> {
options, options,
liftedState, liftedState,
liftedDispatch, liftedDispatch,
stateTreeSettings,
} = this.props; } = this.props;
return ( return (
<Container> <Container>
@ -37,6 +38,7 @@ class Actions extends Component<Props> {
monitorState={this.props.monitorState} monitorState={this.props.monitorState}
dispatch={liftedDispatch} dispatch={liftedDispatch}
features={options.features} features={options.features}
stateTreeSettings={stateTreeSettings}
/> />
{sliderIsOpen && options.connectionId && options.features.jump && ( {sliderIsOpen && options.connectionId && options.features.jump && (
<SliderMonitor liftedState={liftedState} dispatch={liftedDispatch} /> <SliderMonitor liftedState={liftedState} dispatch={liftedDispatch} />
@ -65,6 +67,7 @@ const mapStateToProps = (state: StoreState) => {
dispatcherIsOpen: state.monitor.dispatcherIsOpen, dispatcherIsOpen: state.monitor.dispatcherIsOpen,
sliderIsOpen: state.monitor.sliderIsOpen, sliderIsOpen: state.monitor.sliderIsOpen,
reports: state.reports.data, reports: state.reports.data,
stateTreeSettings: state.stateTreeSettings,
}; };
}; };

View File

@ -7,6 +7,7 @@ import { InitMonitorAction } from '../actions';
import { Features, State } from '../reducers/instances'; import { Features, State } from '../reducers/instances';
import { MonitorStateMonitorState } from '../reducers/monitor'; import { MonitorStateMonitorState } from '../reducers/monitor';
import { ThemeFromProvider } from '@redux-devtools/ui'; import { ThemeFromProvider } from '@redux-devtools/ui';
import { StateTreeSettings } from '../reducers/stateTreeSettings';
interface Props { interface Props {
monitor: string; monitor: string;
@ -17,6 +18,7 @@ interface Props {
) => void; ) => void;
features: Features | undefined; features: Features | undefined;
theme: ThemeFromProvider; theme: ThemeFromProvider;
stateTreeSettings: StateTreeSettings;
} }
class DevTools extends Component<Props> { class DevTools extends Component<Props> {
@ -110,6 +112,12 @@ class DevTools extends Component<Props> {
features={this.props.features} features={this.props.features}
dispatch={this.dispatch} dispatch={this.dispatch}
theme={this.props.theme} theme={this.props.theme}
sortStateTreeAlphabetically={
this.props.stateTreeSettings.sortAlphabetically
}
disableStateTreeCollection={
this.props.stateTreeSettings.disableCollection
}
/> />
</div> </div>
); );

View File

@ -65,5 +65,6 @@ export * from './reducers/reports';
export * from './reducers/section'; export * from './reducers/section';
export * from './reducers/socket'; export * from './reducers/socket';
export * from './reducers/theme'; export * from './reducers/theme';
export * from './reducers/stateTreeSettings';
export * from './utils/monitorActions'; export * from './utils/monitorActions';
export * from './utils/stringifyJSON'; export * from './utils/stringifyJSON';

View File

@ -8,10 +8,12 @@ import { instances, InstancesState } from './instances';
import { reports, ReportsState } from './reports'; import { reports, ReportsState } from './reports';
import { theme, ThemeState } from './theme'; import { theme, ThemeState } from './theme';
import { StoreAction } from '../actions'; import { StoreAction } from '../actions';
import { stateTreeSettings, StateTreeSettings } from './stateTreeSettings';
export interface StoreState { export interface StoreState {
readonly section: SectionState; readonly section: SectionState;
readonly theme: ThemeState; readonly theme: ThemeState;
readonly stateTreeSettings: StateTreeSettings;
readonly connection: ConnectionState; readonly connection: ConnectionState;
readonly socket: SocketState; readonly socket: SocketState;
readonly monitor: MonitorState; readonly monitor: MonitorState;
@ -23,6 +25,7 @@ export interface StoreState {
export const rootReducer = combineReducers<StoreState, StoreAction>({ export const rootReducer = combineReducers<StoreState, StoreAction>({
section, section,
theme, theme,
stateTreeSettings,
connection, connection,
socket, socket,
monitor, monitor,

View File

@ -0,0 +1,23 @@
import { CHANGE_STATE_TREE_SETTINGS } from '../constants/actionTypes';
import { StoreAction } from '../actions';
export interface StateTreeSettings {
readonly sortAlphabetically: boolean;
readonly disableCollection: boolean;
}
export function stateTreeSettings(
state: StateTreeSettings = {
sortAlphabetically: false,
disableCollection: false,
},
action: StoreAction
) {
if (action.type === CHANGE_STATE_TREE_SETTINGS) {
return {
sortAlphabetically: action.sortAlphabetically,
disableCollection: action.disableCollection,
};
}
return state;
}

View File

@ -21,6 +21,8 @@ export interface TabComponentProps<S, A extends Action<unknown>> {
base16Theme: Base16Theme; base16Theme: Base16Theme;
invertTheme: boolean; invertTheme: boolean;
isWideLayout: boolean; isWideLayout: boolean;
sortStateTreeAlphabetically: boolean;
disableStateTreeCollection: boolean;
dataTypeKey: string | symbol | undefined; dataTypeKey: string | symbol | undefined;
delta: Delta | null | undefined | false; delta: Delta | null | undefined | false;
action: A; action: A;
@ -70,6 +72,8 @@ interface Props<S, A extends Action<unknown>> {
onInspectPath: (path: (string | number)[]) => void; onInspectPath: (path: (string | number)[]) => void;
inspectedPath: (string | number)[]; inspectedPath: (string | number)[];
onSelectTab: (tabName: string) => void; onSelectTab: (tabName: string) => void;
sortStateTreeAlphabetically: boolean;
disableStateTreeCollection: boolean;
} }
class ActionPreview<S, A extends Action<unknown>> extends Component< class ActionPreview<S, A extends Action<unknown>> extends Component<
@ -101,6 +105,8 @@ class ActionPreview<S, A extends Action<unknown>> extends Component<
dataTypeKey, dataTypeKey,
monitorState, monitorState,
updateMonitorState, updateMonitorState,
sortStateTreeAlphabetically,
disableStateTreeCollection,
} = this.props; } = this.props;
const renderedTabs: Tab<S, A>[] = const renderedTabs: Tab<S, A>[] =
@ -133,6 +139,8 @@ class ActionPreview<S, A extends Action<unknown>> extends Component<
base16Theme, base16Theme,
invertTheme, invertTheme,
isWideLayout, isWideLayout,
sortStateTreeAlphabetically,
disableStateTreeCollection,
dataTypeKey, dataTypeKey,
delta, delta,
action, action,

View File

@ -151,6 +151,8 @@ export interface ExternalProps<S, A extends Action<unknown>> {
hideMainButtons?: boolean; hideMainButtons?: boolean;
hideActionButtons?: boolean; hideActionButtons?: boolean;
invertTheme: boolean; invertTheme: boolean;
sortStateTreeAlphabetically: boolean;
disableStateTreeCollection: boolean;
dataTypeKey?: string | symbol; dataTypeKey?: string | symbol;
tabs: Tab<S, A>[] | ((tabs: Tab<S, A>[]) => Tab<S, A>[]); tabs: Tab<S, A>[] | ((tabs: Tab<S, A>[]) => Tab<S, A>[]);
} }
@ -177,6 +179,8 @@ export interface DevtoolsInspectorProps<S, A extends Action<unknown>>
diffPropertyFilter?: (name: string, context: DiffContext) => boolean; diffPropertyFilter?: (name: string, context: DiffContext) => boolean;
hideMainButtons?: boolean; hideMainButtons?: boolean;
hideActionButtons?: boolean; hideActionButtons?: boolean;
sortStateTreeAlphabetically: boolean;
disableStateTreeCollection: boolean;
invertTheme: boolean; invertTheme: boolean;
dataTypeKey?: string | symbol; dataTypeKey?: string | symbol;
tabs: Tab<S, A>[] | ((tabs: Tab<S, A>[]) => Tab<S, A>[]); tabs: Tab<S, A>[] | ((tabs: Tab<S, A>[]) => Tab<S, A>[]);
@ -220,6 +224,8 @@ class DevtoolsInspector<S, A extends Action<unknown>> extends PureComponent<
hideMainButtons: PropTypes.bool, hideMainButtons: PropTypes.bool,
hideActionButtons: PropTypes.bool, hideActionButtons: PropTypes.bool,
invertTheme: PropTypes.bool, invertTheme: PropTypes.bool,
sortStateTreeAlphabetically: PropTypes.bool,
disableStateTreeCollection: PropTypes.bool,
skippedActionIds: PropTypes.array, skippedActionIds: PropTypes.array,
dataTypeKey: PropTypes.any, dataTypeKey: PropTypes.any,
tabs: PropTypes.oneOfType([PropTypes.array, PropTypes.func]), tabs: PropTypes.oneOfType([PropTypes.array, PropTypes.func]),
@ -305,6 +311,8 @@ class DevtoolsInspector<S, A extends Action<unknown>> extends PureComponent<
dataTypeKey, dataTypeKey,
hideMainButtons, hideMainButtons,
hideActionButtons, hideActionButtons,
sortStateTreeAlphabetically,
disableStateTreeCollection,
} = this.props; } = this.props;
const { selectedActionId, startActionId, searchValue, tabName } = const { selectedActionId, startActionId, searchValue, tabName } =
monitorState; monitorState;
@ -364,6 +372,8 @@ class DevtoolsInspector<S, A extends Action<unknown>> extends PureComponent<
selectedActionId, selectedActionId,
startActionId, startActionId,
dataTypeKey, dataTypeKey,
sortStateTreeAlphabetically,
disableStateTreeCollection,
}} }}
monitorState={this.props.monitorState} monitorState={this.props.monitorState}
updateMonitorState={this.updateMonitorState} updateMonitorState={this.updateMonitorState}

View File

@ -16,6 +16,8 @@ const StateTab: React.FunctionComponent<
labelRenderer, labelRenderer,
dataTypeKey, dataTypeKey,
isWideLayout, isWideLayout,
sortStateTreeAlphabetically,
disableStateTreeCollection,
}) => ( }) => (
<JSONTree <JSONTree
labelRenderer={labelRenderer} labelRenderer={labelRenderer}
@ -26,6 +28,8 @@ const StateTab: React.FunctionComponent<
} }
invertTheme={invertTheme} invertTheme={invertTheme}
hideRoot hideRoot
sortObjectKeys={sortStateTreeAlphabetically}
{...(disableStateTreeCollection ? { collectionLimit: 0 } : {})}
/> />
); );