mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-07-27 08:30:02 +03:00
feat(inspector-monitor): add noop action toggle button
This commit is contained in:
parent
326cfdf217
commit
9c63c712fc
|
@ -6,6 +6,8 @@ import { PerformAction } from '@redux-devtools/core';
|
|||
import { StylingFunction } from 'react-base16-styling';
|
||||
import ActionListRow from './ActionListRow';
|
||||
import ActionListHeader from './ActionListHeader';
|
||||
import { filterActions } from './utils/filters';
|
||||
import { ActionForm } from './redux';
|
||||
|
||||
function getTimestamps<A extends Action<unknown>>(
|
||||
actions: { [actionId: number]: PerformAction<A> },
|
||||
|
@ -21,15 +23,16 @@ function getTimestamps<A extends Action<unknown>>(
|
|||
};
|
||||
}
|
||||
|
||||
interface Props<A extends Action<unknown>> {
|
||||
interface Props<S, A extends Action<unknown>> {
|
||||
actions: { [actionId: number]: PerformAction<A> };
|
||||
actionIds: number[];
|
||||
isWideLayout: boolean;
|
||||
searchValue: string | undefined;
|
||||
selectedActionId: number | null;
|
||||
startActionId: number | null;
|
||||
skippedActionIds: number[];
|
||||
computedStates: { state: S; error?: string }[];
|
||||
draggableActions: boolean;
|
||||
actionForm: ActionForm;
|
||||
hideMainButtons: boolean | undefined;
|
||||
hideActionButtons: boolean | undefined;
|
||||
styling: StylingFunction;
|
||||
|
@ -40,18 +43,20 @@ interface Props<A extends Action<unknown>> {
|
|||
onCommit: () => void;
|
||||
onSweep: () => void;
|
||||
onReorderAction: (actionId: number, beforeActionId: number) => void;
|
||||
onActionFormChange: (formValues: Partial<ActionForm>) => void;
|
||||
currentActionId: number;
|
||||
lastActionId: number;
|
||||
}
|
||||
|
||||
export default class ActionList<
|
||||
S,
|
||||
A extends Action<unknown>
|
||||
> extends PureComponent<Props<A>> {
|
||||
> extends PureComponent<Props<S, A>> {
|
||||
node?: HTMLDivElement | null;
|
||||
scrollDown?: boolean;
|
||||
drake?: Drake;
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps: Props<A>) {
|
||||
UNSAFE_componentWillReceiveProps(nextProps: Props<S, A>) {
|
||||
const node = this.node;
|
||||
if (!node) {
|
||||
this.scrollDown = true;
|
||||
|
@ -119,23 +124,16 @@ export default class ActionList<
|
|||
startActionId,
|
||||
onSelect,
|
||||
onSearch,
|
||||
searchValue,
|
||||
currentActionId,
|
||||
hideMainButtons,
|
||||
hideActionButtons,
|
||||
onCommit,
|
||||
onSweep,
|
||||
actionForm,
|
||||
onActionFormChange,
|
||||
onJumpToState,
|
||||
} = this.props;
|
||||
const lowerSearchValue = searchValue && searchValue.toLowerCase();
|
||||
const filteredActionIds = searchValue
|
||||
? actionIds.filter(
|
||||
(id) =>
|
||||
(actions[id].action.type as string)
|
||||
.toLowerCase()
|
||||
.indexOf(lowerSearchValue as string) !== -1
|
||||
)
|
||||
: actionIds;
|
||||
const filteredActionIds = filterActions<unknown, A>(this.props);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -150,6 +148,8 @@ export default class ActionList<
|
|||
onSearch={onSearch}
|
||||
onCommit={onCommit}
|
||||
onSweep={onSweep}
|
||||
actionForm={actionForm}
|
||||
onActionFormChange={onActionFormChange}
|
||||
hideMainButtons={hideMainButtons}
|
||||
hasSkippedActions={skippedActionIds.length > 0}
|
||||
hasStagedActions={actionIds.length > 1}
|
||||
|
|
|
@ -2,6 +2,7 @@ import React, { FunctionComponent } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { StylingFunction } from 'react-base16-styling';
|
||||
import RightSlider from './RightSlider';
|
||||
import { ActionForm } from './redux';
|
||||
|
||||
const getActiveButtons = (hasSkippedActions: boolean): ('Sweep' | 'Commit')[] =>
|
||||
[hasSkippedActions && 'Sweep', 'Commit'].filter(
|
||||
|
@ -16,6 +17,8 @@ interface Props {
|
|||
hideMainButtons: boolean | undefined;
|
||||
hasSkippedActions: boolean;
|
||||
hasStagedActions: boolean;
|
||||
actionForm: ActionForm;
|
||||
onActionFormChange: (formValues: Partial<ActionForm>) => void;
|
||||
}
|
||||
|
||||
const ActionListHeader: FunctionComponent<Props> = ({
|
||||
|
@ -26,7 +29,13 @@ const ActionListHeader: FunctionComponent<Props> = ({
|
|||
onCommit,
|
||||
onSweep,
|
||||
hideMainButtons,
|
||||
}) => (
|
||||
actionForm,
|
||||
onActionFormChange,
|
||||
}) => {
|
||||
const { isNoopFilterActive } = actionForm;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div {...styling('actionListHeader')}>
|
||||
<input
|
||||
{...styling('actionListHeaderSearch')}
|
||||
|
@ -60,7 +69,26 @@ const ActionListHeader: FunctionComponent<Props> = ({
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
<div {...styling(['actionListHeader', 'actionListHeaderSecondRow'])}>
|
||||
<button
|
||||
title="Toggle visibility of noop actions"
|
||||
aria-label="Toggle visibility of noop actions"
|
||||
aria-pressed={!isNoopFilterActive}
|
||||
onClick={() =>
|
||||
onActionFormChange({ isNoopFilterActive: !isNoopFilterActive })
|
||||
}
|
||||
type="button"
|
||||
{...styling(
|
||||
['selectorButton', 'selectorButtonSmall', !isNoopFilterActive && 'selectorButtonSelected'],
|
||||
isNoopFilterActive
|
||||
)}
|
||||
>
|
||||
noop
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ActionListHeader.propTypes = {
|
||||
styling: PropTypes.func.isRequired,
|
||||
|
@ -70,6 +98,11 @@ ActionListHeader.propTypes = {
|
|||
hideMainButtons: PropTypes.bool,
|
||||
hasSkippedActions: PropTypes.bool.isRequired,
|
||||
hasStagedActions: PropTypes.bool.isRequired,
|
||||
actionForm: PropTypes.shape({
|
||||
searchValue: PropTypes.string.isRequired,
|
||||
isNoopFilterActive: PropTypes.bool.isRequired,
|
||||
}).isRequired,
|
||||
onActionFormChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ActionListHeader;
|
||||
|
|
|
@ -22,6 +22,8 @@ import ActionPreview, { Tab } from './ActionPreview';
|
|||
import getInspectedState from './utils/getInspectedState';
|
||||
import createDiffPatcher from './createDiffPatcher';
|
||||
import {
|
||||
ActionForm,
|
||||
changeActionFormValues,
|
||||
DevtoolsInspectorAction,
|
||||
DevtoolsInspectorState,
|
||||
reducer,
|
||||
|
@ -249,6 +251,10 @@ class DevtoolsInspector<S, A extends Action<unknown>> extends PureComponent<
|
|||
this.props.dispatch(updateMonitorState(monitorState));
|
||||
};
|
||||
|
||||
handleActionFormChange = (formValues: Partial<ActionForm>) => {
|
||||
this.props.dispatch(changeActionFormValues(formValues));
|
||||
};
|
||||
|
||||
updateSizeMode() {
|
||||
const isWideLayout = this.inspectorRef!.offsetWidth > 500;
|
||||
|
||||
|
@ -301,7 +307,7 @@ class DevtoolsInspector<S, A extends Action<unknown>> extends PureComponent<
|
|||
hideMainButtons,
|
||||
hideActionButtons,
|
||||
} = this.props;
|
||||
const { selectedActionId, startActionId, searchValue, tabName } =
|
||||
const { selectedActionId, startActionId, actionForm, tabName } =
|
||||
monitorState;
|
||||
const inspectedPathType =
|
||||
tabName === 'Action' ? 'inspectedActionPath' : 'inspectedStatePath';
|
||||
|
@ -323,7 +329,6 @@ class DevtoolsInspector<S, A extends Action<unknown>> extends PureComponent<
|
|||
actions,
|
||||
actionIds,
|
||||
isWideLayout,
|
||||
searchValue,
|
||||
selectedActionId,
|
||||
startActionId,
|
||||
skippedActionIds,
|
||||
|
@ -332,7 +337,10 @@ class DevtoolsInspector<S, A extends Action<unknown>> extends PureComponent<
|
|||
hideActionButtons,
|
||||
styling,
|
||||
}}
|
||||
actionForm={actionForm}
|
||||
computedStates={computedStates}
|
||||
onSearch={this.handleSearch}
|
||||
onActionFormChange={this.handleActionFormChange}
|
||||
onSelect={this.handleSelectAction}
|
||||
onToggleAction={this.handleToggleAction}
|
||||
onJumpToState={this.handleJumpToState}
|
||||
|
@ -400,7 +408,7 @@ class DevtoolsInspector<S, A extends Action<unknown>> extends PureComponent<
|
|||
};
|
||||
|
||||
handleSearch = (val: string) => {
|
||||
this.updateMonitorState({ searchValue: val });
|
||||
this.handleActionFormChange({ searchValue: val });
|
||||
};
|
||||
|
||||
handleSelectAction = (
|
||||
|
|
|
@ -4,17 +4,38 @@ import { DevtoolsInspectorProps } from './DevtoolsInspector';
|
|||
const UPDATE_MONITOR_STATE =
|
||||
'@@redux-devtools-inspector-monitor/UPDATE_MONITOR_STATE';
|
||||
|
||||
const ACTION_FORM_VALUE_CHANGE =
|
||||
'@@redux-devtools-inspector-monitor/ACTION_FORM_VALUE_CHANGE';
|
||||
|
||||
export interface ActionForm {
|
||||
searchValue: string;
|
||||
isNoopFilterActive: boolean;
|
||||
}
|
||||
export interface UpdateMonitorStateAction {
|
||||
type: typeof UPDATE_MONITOR_STATE;
|
||||
monitorState: Partial<DevtoolsInspectorState>;
|
||||
}
|
||||
|
||||
export interface ChangeActionFormAction {
|
||||
type: typeof ACTION_FORM_VALUE_CHANGE;
|
||||
formValues: Partial<ActionForm>;
|
||||
}
|
||||
|
||||
export function updateMonitorState(
|
||||
monitorState: Partial<DevtoolsInspectorState>
|
||||
): UpdateMonitorStateAction {
|
||||
return { type: UPDATE_MONITOR_STATE, monitorState };
|
||||
}
|
||||
|
||||
export type DevtoolsInspectorAction = UpdateMonitorStateAction;
|
||||
export function changeActionFormValues(
|
||||
formValues: Partial<ActionForm>
|
||||
): ChangeActionFormAction {
|
||||
return { type: ACTION_FORM_VALUE_CHANGE, formValues };
|
||||
}
|
||||
|
||||
export type DevtoolsInspectorAction =
|
||||
| UpdateMonitorStateAction
|
||||
| ChangeActionFormAction;
|
||||
|
||||
export interface DevtoolsInspectorState {
|
||||
selectedActionId: number | null;
|
||||
|
@ -22,7 +43,7 @@ export interface DevtoolsInspectorState {
|
|||
inspectedActionPath: (string | number)[];
|
||||
inspectedStatePath: (string | number)[];
|
||||
tabName: string;
|
||||
searchValue?: string;
|
||||
actionForm: ActionForm;
|
||||
}
|
||||
|
||||
export const DEFAULT_STATE: DevtoolsInspectorState = {
|
||||
|
@ -31,18 +52,33 @@ export const DEFAULT_STATE: DevtoolsInspectorState = {
|
|||
inspectedActionPath: [],
|
||||
inspectedStatePath: [],
|
||||
tabName: 'Diff',
|
||||
actionForm: {
|
||||
searchValue: '',
|
||||
isNoopFilterActive: false,
|
||||
},
|
||||
};
|
||||
|
||||
function reduceUpdateState(
|
||||
function internalMonitorActionReducer(
|
||||
state: DevtoolsInspectorState,
|
||||
action: DevtoolsInspectorAction
|
||||
) {
|
||||
return action.type === UPDATE_MONITOR_STATE
|
||||
? {
|
||||
): DevtoolsInspectorState {
|
||||
switch (action.type) {
|
||||
case UPDATE_MONITOR_STATE:
|
||||
return {
|
||||
...state,
|
||||
...action.monitorState,
|
||||
};
|
||||
case ACTION_FORM_VALUE_CHANGE:
|
||||
return {
|
||||
...state,
|
||||
actionForm: {
|
||||
...state.actionForm,
|
||||
...action.formValues,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
: state;
|
||||
}
|
||||
|
||||
export function reducer<S, A extends Action<unknown>>(
|
||||
|
@ -51,6 +87,6 @@ export function reducer<S, A extends Action<unknown>>(
|
|||
action: DevtoolsInspectorAction
|
||||
) {
|
||||
return {
|
||||
...reduceUpdateState(state, action),
|
||||
...internalMonitorActionReducer(state, action),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -83,6 +83,10 @@ const getSheetFromColorMap = (map: ColorMap) => ({
|
|||
'border-color': map.LIST_BORDER_COLOR,
|
||||
},
|
||||
|
||||
actionListHeaderSecondRow: {
|
||||
padding: '5px 10px',
|
||||
},
|
||||
|
||||
actionListRows: {
|
||||
overflow: 'auto',
|
||||
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
import type { Action } from 'redux';
|
||||
import type { LiftedState, PerformAction } from '@redux-devtools/core';
|
||||
import { ActionForm } from '../redux';
|
||||
|
||||
type ComputedStates<S> = LiftedState<
|
||||
S,
|
||||
Action<unknown>,
|
||||
unknown
|
||||
>['computedStates'];
|
||||
|
||||
function isNoopAction<S>(
|
||||
actionId: number,
|
||||
computedStates: ComputedStates<S>
|
||||
): boolean {
|
||||
return (
|
||||
actionId === 0 ||
|
||||
computedStates[actionId]?.state === computedStates[actionId - 1]?.state
|
||||
);
|
||||
}
|
||||
|
||||
function filterStateChangingAction<S>(
|
||||
actionIds: number[],
|
||||
computedStates: ComputedStates<S>
|
||||
): number[] {
|
||||
return actionIds.filter(
|
||||
(actionId) => !isNoopAction(actionId, computedStates)
|
||||
);
|
||||
}
|
||||
|
||||
function filterActionsBySearchValue<A extends Action<unknown>>(
|
||||
searchValue: string | undefined,
|
||||
actionIds: number[],
|
||||
actions: Record<number, PerformAction<A>>
|
||||
): number[] {
|
||||
const lowerSearchValue = searchValue && searchValue.toLowerCase();
|
||||
|
||||
if (!lowerSearchValue) {
|
||||
return actionIds;
|
||||
}
|
||||
|
||||
return actionIds.filter((id) => {
|
||||
const type = actions[id].action.type;
|
||||
|
||||
return (
|
||||
type != null &&
|
||||
`${type as string}`.toLowerCase().includes(lowerSearchValue)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export interface FilterActionsPayload<S, A extends Action<unknown>> {
|
||||
readonly actionIds: number[];
|
||||
readonly actions: Record<number, PerformAction<A>>;
|
||||
readonly computedStates: ComputedStates<S>;
|
||||
readonly actionForm: ActionForm;
|
||||
}
|
||||
|
||||
export function filterActions<S, A extends Action<unknown>>({
|
||||
actionIds,
|
||||
actions,
|
||||
computedStates,
|
||||
actionForm,
|
||||
}: FilterActionsPayload<S, A>): number[] {
|
||||
let output = filterActionsBySearchValue(
|
||||
actionForm.searchValue,
|
||||
actionIds,
|
||||
actions
|
||||
);
|
||||
|
||||
if (actionForm.isNoopFilterActive) {
|
||||
output = filterStateChangingAction(actionIds, computedStates);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
Loading…
Reference in New Issue
Block a user