mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-07-26 07:59:48 +03:00
feat(rtk-query): add regex search
This commit is contained in:
parent
6a87019cb7
commit
95da893616
|
@ -7,9 +7,11 @@ import debounce from 'lodash.debounce';
|
||||||
import { sortQueryOptions, QueryComparators } from '../utils/comparators';
|
import { sortQueryOptions, QueryComparators } from '../utils/comparators';
|
||||||
import { QueryFilters, filterQueryOptions } from '../utils/filters';
|
import { QueryFilters, filterQueryOptions } from '../utils/filters';
|
||||||
import { SortOrderButton } from './SortOrderButton';
|
import { SortOrderButton } from './SortOrderButton';
|
||||||
|
import { RegexIcon } from './RegexIcon';
|
||||||
|
|
||||||
export interface QueryFormProps {
|
export interface QueryFormProps {
|
||||||
values: QueryFormValues;
|
values: QueryFormValues;
|
||||||
|
searchQueryRegex: RegExp | null;
|
||||||
onFormValuesChange: (values: Partial<QueryFormValues>) => void;
|
onFormValuesChange: (values: Partial<QueryFormValues>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +24,13 @@ const searchId = 'rtk-query-search-query';
|
||||||
const filterSelectId = 'rtk-query-search-query-select';
|
const filterSelectId = 'rtk-query-search-query-select';
|
||||||
const searchPlaceholder = 'filter query by...';
|
const searchPlaceholder = 'filter query by...';
|
||||||
|
|
||||||
|
const labels = {
|
||||||
|
regexToggle: {
|
||||||
|
info: 'Use regular expression search',
|
||||||
|
error: 'Invalid regular expression provided',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export class QueryForm extends React.PureComponent<
|
export class QueryForm extends React.PureComponent<
|
||||||
QueryFormProps,
|
QueryFormProps,
|
||||||
QueryFormState
|
QueryFormState
|
||||||
|
@ -60,6 +69,12 @@ export class QueryForm extends React.PureComponent<
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleRegexSearchClick = (): void => {
|
||||||
|
this.props.onFormValuesChange({
|
||||||
|
isRegexSearch: !this.props.values.isRegexSearch,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
restoreCaretPosition = (start: number | null, end: number | null): void => {
|
restoreCaretPosition = (start: number | null, end: number | null): void => {
|
||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
if (this.inputSearchRef.current) {
|
if (this.inputSearchRef.current) {
|
||||||
|
@ -92,13 +107,21 @@ export class QueryForm extends React.PureComponent<
|
||||||
|
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
const {
|
const {
|
||||||
|
searchQueryRegex,
|
||||||
values: {
|
values: {
|
||||||
isAscendingQueryComparatorOrder: isAsc,
|
isAscendingQueryComparatorOrder: isAsc,
|
||||||
queryComparator,
|
queryComparator,
|
||||||
|
searchValue,
|
||||||
queryFilter,
|
queryFilter,
|
||||||
|
isRegexSearch,
|
||||||
},
|
},
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const isRegexInvalid =
|
||||||
|
isRegexSearch && searchValue.length > 0 && searchQueryRegex == null;
|
||||||
|
const regexToggleType = isRegexInvalid ? 'error' : 'info';
|
||||||
|
const regexToggleLabel = labels.regexToggle[regexToggleType];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyleUtilsContext.Consumer>
|
<StyleUtilsContext.Consumer>
|
||||||
{({ styling, base16Theme }) => {
|
{({ styling, base16Theme }) => {
|
||||||
|
@ -129,6 +152,17 @@ export class QueryForm extends React.PureComponent<
|
||||||
onClick={this.handleClearSearchClick}
|
onClick={this.handleClearSearchClick}
|
||||||
{...styling('closeButton')}
|
{...styling('closeButton')}
|
||||||
/>
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={regexToggleLabel}
|
||||||
|
title={regexToggleLabel}
|
||||||
|
data-type={regexToggleType}
|
||||||
|
aria-pressed={isRegexSearch}
|
||||||
|
onClick={this.handleRegexSearchClick}
|
||||||
|
{...styling('toggleButton')}
|
||||||
|
>
|
||||||
|
<RegexIcon />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<label htmlFor={selectId} {...styling('srOnly')}>
|
<label htmlFor={selectId} {...styling('srOnly')}>
|
||||||
filter by
|
filter by
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
export type RegexIconProps = Omit<
|
||||||
|
React.HTMLAttributes<SVGElement>,
|
||||||
|
'viewBox' | 'children'
|
||||||
|
>;
|
||||||
|
|
||||||
|
// `OOjs_UI_icon_regular-expression.svg` (MIT License)
|
||||||
|
// from https://commons.wikimedia.org/wiki/File:OOjs_UI_icon_regular-expression.svg
|
||||||
|
export function RegexIcon(
|
||||||
|
props: React.HTMLAttributes<SVGElement>
|
||||||
|
): JSX.Element {
|
||||||
|
return (
|
||||||
|
<svg fill="currentColor" {...props} viewBox="0 0 24 24">
|
||||||
|
<g>
|
||||||
|
<path d="M3 12.045c0-.99.15-1.915.45-2.777A6.886 6.886 0 0 1 4.764 7H6.23a7.923 7.923 0 0 0-1.25 2.374 8.563 8.563 0 0 0 .007 5.314c.29.85.7 1.622 1.23 2.312h-1.45a6.53 6.53 0 0 1-1.314-2.223 8.126 8.126 0 0 1-.45-2.732" />
|
||||||
|
<path id="dot" d="M10 16a1 1 0 1 1-2 0 1 1 0 0 1 2 0z" />
|
||||||
|
<path d="M14.25 7.013l-.24 2.156 2.187-.61.193 1.47-1.992.14 1.307 1.74-1.33.71-.914-1.833-.8 1.822-1.38-.698 1.296-1.74-1.98-.152.23-1.464 2.14.61-.24-2.158h1.534" />
|
||||||
|
<path d="M21 12.045c0 .982-.152 1.896-.457 2.744A6.51 6.51 0 0 1 19.236 17h-1.453a8.017 8.017 0 0 0 1.225-2.31c.29-.855.434-1.74.434-2.66 0-.91-.14-1.797-.422-2.66a7.913 7.913 0 0 0-1.248-2.374h1.465a6.764 6.764 0 0 1 1.313 2.28c.3.86.45 1.782.45 2.764" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
|
@ -124,6 +124,9 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends PureComponent<
|
||||||
|
|
||||||
const hasNoApi = apiStates == null;
|
const hasNoApi = apiStates == null;
|
||||||
|
|
||||||
|
const searchQueryRegex =
|
||||||
|
this.selectors.selectSearchQueryRegex(selectorsSource);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={this.inspectorRef}
|
ref={this.inspectorRef}
|
||||||
|
@ -135,6 +138,7 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends PureComponent<
|
||||||
data-wide-layout={+this.state.isWideLayout}
|
data-wide-layout={+this.state.isWideLayout}
|
||||||
>
|
>
|
||||||
<QueryForm
|
<QueryForm
|
||||||
|
searchQueryRegex={searchQueryRegex}
|
||||||
values={selectorsSource.monitorState.queryForm.values}
|
values={selectorsSource.monitorState.queryForm.values}
|
||||||
onFormValuesChange={this.handleQueryFormValuesChange}
|
onFormValuesChange={this.handleQueryFormValuesChange}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -16,6 +16,7 @@ const initialState: RtkQueryMonitorState = {
|
||||||
queryComparator: QueryComparators.fulfilledTimeStamp,
|
queryComparator: QueryComparators.fulfilledTimeStamp,
|
||||||
isAscendingQueryComparatorOrder: false,
|
isAscendingQueryComparatorOrder: false,
|
||||||
searchValue: '',
|
searchValue: '',
|
||||||
|
isRegexSearch: false,
|
||||||
queryFilter: QueryFilters.queryKey,
|
queryFilter: QueryFilters.queryKey,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -111,10 +111,21 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
||||||
const selectSearchQueryRegex = createSelector(
|
const selectSearchQueryRegex = createSelector(
|
||||||
({ monitorState }: SelectorsSource<S>) =>
|
({ monitorState }: SelectorsSource<S>) =>
|
||||||
monitorState.queryForm.values.searchValue,
|
monitorState.queryForm.values.searchValue,
|
||||||
(searchValue) => {
|
({ monitorState }: SelectorsSource<S>) =>
|
||||||
if (searchValue.length >= 3) {
|
monitorState.queryForm.values.isRegexSearch,
|
||||||
return new RegExp(escapeRegExpSpecialCharacter(searchValue), 'i');
|
(searchValue, isRegexSearch) => {
|
||||||
|
if (searchValue) {
|
||||||
|
try {
|
||||||
|
const regexPattern = isRegexSearch
|
||||||
|
? searchValue
|
||||||
|
: escapeRegExpSpecialCharacter(searchValue);
|
||||||
|
|
||||||
|
return new RegExp(regexPattern, 'i');
|
||||||
|
} catch (err) {
|
||||||
|
// We notify that the search regex provided is not valid
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -43,6 +43,9 @@ export const colorMap = (theme: reduxThemes.Base16Theme) =>
|
||||||
ULIST_COLOR: rgba(theme.base06, 60),
|
ULIST_COLOR: rgba(theme.base06, 60),
|
||||||
ULIST_STRONG_COLOR: theme.base0B,
|
ULIST_STRONG_COLOR: theme.base0B,
|
||||||
TAB_CONTENT_COLOR: rgba(theme.base06, 60),
|
TAB_CONTENT_COLOR: rgba(theme.base06, 60),
|
||||||
|
TOGGLE_BUTTON_BACKGROUND: rgba(theme.base00, 70),
|
||||||
|
TOGGLE_BUTTON_SELECTED_BACKGROUND: theme.base04,
|
||||||
|
TOGGLE_BUTTON_ERROR: rgba(theme.base08, 40),
|
||||||
} as const);
|
} as const);
|
||||||
|
|
||||||
type Color = keyof ReturnType<typeof colorMap>;
|
type Color = keyof ReturnType<typeof colorMap>;
|
||||||
|
@ -264,6 +267,37 @@ const getSheetFromColorMap = (map: ColorMap) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
toggleButton: {
|
||||||
|
width: '24px',
|
||||||
|
height: '24px',
|
||||||
|
display: 'inline-block',
|
||||||
|
flex: '0 0 auto',
|
||||||
|
color: map.TEXT_PLACEHOLDER_COLOR,
|
||||||
|
cursor: 'pointer',
|
||||||
|
padding: 0,
|
||||||
|
fontSize: '0.7em',
|
||||||
|
letterSpacing: '-0.7px',
|
||||||
|
outline: 'none',
|
||||||
|
boxShadow: 'none',
|
||||||
|
fontWeight: '700',
|
||||||
|
border: 'none',
|
||||||
|
|
||||||
|
'&:hover': {
|
||||||
|
color: map.TEXT_COLOR,
|
||||||
|
},
|
||||||
|
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
'&[aria-pressed="true"]': {
|
||||||
|
color: map.BACKGROUND_COLOR,
|
||||||
|
backgroundColor: map.TEXT_COLOR,
|
||||||
|
},
|
||||||
|
|
||||||
|
'&[data-type="error"]': {
|
||||||
|
color: map.TEXT_COLOR,
|
||||||
|
backgroundColor: map.TOGGLE_BUTTON_ERROR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
queryForm: {
|
queryForm: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexFlow: 'column nowrap',
|
flexFlow: 'column nowrap',
|
||||||
|
@ -323,6 +357,7 @@ const getSheetFromColorMap = (map: ColorMap) => {
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
display: 'block',
|
display: 'block',
|
||||||
|
flex: '0 0 auto',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
@ -334,10 +369,13 @@ const getSheetFromColorMap = (map: ColorMap) => {
|
||||||
content: '"\u00d7"',
|
content: '"\u00d7"',
|
||||||
display: 'block',
|
display: 'block',
|
||||||
padding: 4,
|
padding: 4,
|
||||||
fontSize: '16px',
|
fontSize: '1.2em',
|
||||||
color: map.TEXT_COLOR,
|
color: map.TEXT_PLACEHOLDER_COLOR,
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
},
|
},
|
||||||
|
'&:hover::after': {
|
||||||
|
color: map.TEXT_COLOR,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
noApiFound: {
|
noApiFound: {
|
||||||
|
|
|
@ -19,6 +19,7 @@ export interface QueryFormValues {
|
||||||
queryComparator: QueryComparators;
|
queryComparator: QueryComparators;
|
||||||
isAscendingQueryComparatorOrder: boolean;
|
isAscendingQueryComparatorOrder: boolean;
|
||||||
searchValue: string;
|
searchValue: string;
|
||||||
|
isRegexSearch: boolean;
|
||||||
queryFilter: QueryFilters;
|
queryFilter: QueryFilters;
|
||||||
}
|
}
|
||||||
export interface RtkQueryMonitorState {
|
export interface RtkQueryMonitorState {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user