feat(inspector-monitor): add noop action toggle button

This commit is contained in:
FaberVitale 2021-07-24 12:56:08 +02:00
parent 326cfdf217
commit 9c63c712fc
6 changed files with 214 additions and 58 deletions

View File

@ -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}

View File

@ -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;

View File

@ -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 = (

View File

@ -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),
};
}

View File

@ -83,6 +83,10 @@ const getSheetFromColorMap = (map: ColorMap) => ({
'border-color': map.LIST_BORDER_COLOR,
},
actionListHeaderSecondRow: {
padding: '5px 10px',
},
actionListRows: {
overflow: 'auto',

View File

@ -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;
}