From 9c63c712fc03bd912299434a0c703b0471da50db Mon Sep 17 00:00:00 2001 From: FaberVitale Date: Sat, 24 Jul 2021 12:56:08 +0200 Subject: [PATCH] feat(inspector-monitor): add noop action toggle button --- .../src/ActionList.tsx | 28 +++--- .../src/ActionListHeader.tsx | 97 +++++++++++++------ .../src/DevtoolsInspector.tsx | 14 ++- .../src/redux.ts | 54 +++++++++-- .../src/utils/createStylingFromTheme.ts | 4 + .../src/utils/filters.ts | 75 ++++++++++++++ 6 files changed, 214 insertions(+), 58 deletions(-) create mode 100644 packages/redux-devtools-inspector-monitor/src/utils/filters.ts diff --git a/packages/redux-devtools-inspector-monitor/src/ActionList.tsx b/packages/redux-devtools-inspector-monitor/src/ActionList.tsx index 22085b0e..4bfbd2af 100644 --- a/packages/redux-devtools-inspector-monitor/src/ActionList.tsx +++ b/packages/redux-devtools-inspector-monitor/src/ActionList.tsx @@ -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>( actions: { [actionId: number]: PerformAction }, @@ -21,15 +23,16 @@ function getTimestamps>( }; } -interface Props> { +interface Props> { actions: { [actionId: number]: PerformAction }; 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> { onCommit: () => void; onSweep: () => void; onReorderAction: (actionId: number, beforeActionId: number) => void; + onActionFormChange: (formValues: Partial) => void; currentActionId: number; lastActionId: number; } export default class ActionList< + S, A extends Action -> extends PureComponent> { +> extends PureComponent> { node?: HTMLDivElement | null; scrollDown?: boolean; drake?: Drake; - UNSAFE_componentWillReceiveProps(nextProps: Props) { + UNSAFE_componentWillReceiveProps(nextProps: Props) { 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(this.props); return (
0} hasStagedActions={actionIds.length > 1} diff --git a/packages/redux-devtools-inspector-monitor/src/ActionListHeader.tsx b/packages/redux-devtools-inspector-monitor/src/ActionListHeader.tsx index f913b2ed..de9be742 100644 --- a/packages/redux-devtools-inspector-monitor/src/ActionListHeader.tsx +++ b/packages/redux-devtools-inspector-monitor/src/ActionListHeader.tsx @@ -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) => void; } const ActionListHeader: FunctionComponent = ({ @@ -26,41 +29,66 @@ const ActionListHeader: FunctionComponent = ({ onCommit, onSweep, hideMainButtons, -}) => ( -
- onSearch(e.target.value)} - placeholder="filter..." - /> - {!hideMainButtons && ( -
- -
- {getActiveButtons(hasSkippedActions).map((btn) => ( -
- ({ - Commit: onCommit, - Sweep: onSweep, - }[btn]()) - } - {...styling( - ['selectorButton', 'selectorButtonSmall'], - false, - true - )} - > - {btn} + actionForm, + onActionFormChange, +}) => { + const { isNoopFilterActive } = actionForm; + + return ( + <> +
+ onSearch(e.target.value)} + placeholder="filter..." + /> + {!hideMainButtons && ( +
+ +
+ {getActiveButtons(hasSkippedActions).map((btn) => ( +
+ ({ + Commit: onCommit, + Sweep: onSweep, + }[btn]()) + } + {...styling( + ['selectorButton', 'selectorButtonSmall'], + false, + true + )} + > + {btn} +
+ ))}
- ))} +
- + )}
- )} -
-); +
+ +
+ + ); +}; 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; diff --git a/packages/redux-devtools-inspector-monitor/src/DevtoolsInspector.tsx b/packages/redux-devtools-inspector-monitor/src/DevtoolsInspector.tsx index aa9803fa..3681fc5a 100644 --- a/packages/redux-devtools-inspector-monitor/src/DevtoolsInspector.tsx +++ b/packages/redux-devtools-inspector-monitor/src/DevtoolsInspector.tsx @@ -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> extends PureComponent< this.props.dispatch(updateMonitorState(monitorState)); }; + handleActionFormChange = (formValues: Partial) => { + this.props.dispatch(changeActionFormValues(formValues)); + }; + updateSizeMode() { const isWideLayout = this.inspectorRef!.offsetWidth > 500; @@ -301,7 +307,7 @@ class DevtoolsInspector> 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> extends PureComponent< actions, actionIds, isWideLayout, - searchValue, selectedActionId, startActionId, skippedActionIds, @@ -332,7 +337,10 @@ class DevtoolsInspector> 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> extends PureComponent< }; handleSearch = (val: string) => { - this.updateMonitorState({ searchValue: val }); + this.handleActionFormChange({ searchValue: val }); }; handleSelectAction = ( diff --git a/packages/redux-devtools-inspector-monitor/src/redux.ts b/packages/redux-devtools-inspector-monitor/src/redux.ts index 93b2835d..b82c742b 100644 --- a/packages/redux-devtools-inspector-monitor/src/redux.ts +++ b/packages/redux-devtools-inspector-monitor/src/redux.ts @@ -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; } + +export interface ChangeActionFormAction { + type: typeof ACTION_FORM_VALUE_CHANGE; + formValues: Partial; +} + export function updateMonitorState( monitorState: Partial ): UpdateMonitorStateAction { return { type: UPDATE_MONITOR_STATE, monitorState }; } -export type DevtoolsInspectorAction = UpdateMonitorStateAction; +export function changeActionFormValues( + formValues: Partial +): 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, - } - : state; + }; + case ACTION_FORM_VALUE_CHANGE: + return { + ...state, + actionForm: { + ...state.actionForm, + ...action.formValues, + }, + }; + default: + return state; + } } export function reducer>( @@ -51,6 +87,6 @@ export function reducer>( action: DevtoolsInspectorAction ) { return { - ...reduceUpdateState(state, action), + ...internalMonitorActionReducer(state, action), }; } diff --git a/packages/redux-devtools-inspector-monitor/src/utils/createStylingFromTheme.ts b/packages/redux-devtools-inspector-monitor/src/utils/createStylingFromTheme.ts index a8e5f88f..0ef71768 100644 --- a/packages/redux-devtools-inspector-monitor/src/utils/createStylingFromTheme.ts +++ b/packages/redux-devtools-inspector-monitor/src/utils/createStylingFromTheme.ts @@ -83,6 +83,10 @@ const getSheetFromColorMap = (map: ColorMap) => ({ 'border-color': map.LIST_BORDER_COLOR, }, + actionListHeaderSecondRow: { + padding: '5px 10px', + }, + actionListRows: { overflow: 'auto', diff --git a/packages/redux-devtools-inspector-monitor/src/utils/filters.ts b/packages/redux-devtools-inspector-monitor/src/utils/filters.ts new file mode 100644 index 00000000..a4cfe451 --- /dev/null +++ b/packages/redux-devtools-inspector-monitor/src/utils/filters.ts @@ -0,0 +1,75 @@ +import type { Action } from 'redux'; +import type { LiftedState, PerformAction } from '@redux-devtools/core'; +import { ActionForm } from '../redux'; + +type ComputedStates = LiftedState< + S, + Action, + unknown +>['computedStates']; + +function isNoopAction( + actionId: number, + computedStates: ComputedStates +): boolean { + return ( + actionId === 0 || + computedStates[actionId]?.state === computedStates[actionId - 1]?.state + ); +} + +function filterStateChangingAction( + actionIds: number[], + computedStates: ComputedStates +): number[] { + return actionIds.filter( + (actionId) => !isNoopAction(actionId, computedStates) + ); +} + +function filterActionsBySearchValue
>( + searchValue: string | undefined, + actionIds: number[], + actions: Record> +): 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> { + readonly actionIds: number[]; + readonly actions: Record>; + readonly computedStates: ComputedStates; + readonly actionForm: ActionForm; +} + +export function filterActions>({ + actionIds, + actions, + computedStates, + actionForm, +}: FilterActionsPayload): number[] { + let output = filterActionsBySearchValue( + actionForm.searchValue, + actionIds, + actions + ); + + if (actionForm.isNoopFilterActive) { + output = filterStateChangingAction(actionIds, computedStates); + } + + return output; +}