feat(rtk-query): add regex search

This commit is contained in:
FaberVitale 2021-08-01 12:13:55 +02:00
parent 6a87019cb7
commit 95da893616
7 changed files with 117 additions and 5 deletions

View File

@ -7,9 +7,11 @@ import debounce from 'lodash.debounce';
import { sortQueryOptions, QueryComparators } from '../utils/comparators';
import { QueryFilters, filterQueryOptions } from '../utils/filters';
import { SortOrderButton } from './SortOrderButton';
import { RegexIcon } from './RegexIcon';
export interface QueryFormProps {
values: QueryFormValues;
searchQueryRegex: RegExp | null;
onFormValuesChange: (values: Partial<QueryFormValues>) => void;
}
@ -22,6 +24,13 @@ const searchId = 'rtk-query-search-query';
const filterSelectId = 'rtk-query-search-query-select';
const searchPlaceholder = 'filter query by...';
const labels = {
regexToggle: {
info: 'Use regular expression search',
error: 'Invalid regular expression provided',
},
};
export class QueryForm extends React.PureComponent<
QueryFormProps,
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 => {
window.requestAnimationFrame(() => {
if (this.inputSearchRef.current) {
@ -92,13 +107,21 @@ export class QueryForm extends React.PureComponent<
render(): ReactNode {
const {
searchQueryRegex,
values: {
isAscendingQueryComparatorOrder: isAsc,
queryComparator,
searchValue,
queryFilter,
isRegexSearch,
},
} = this.props;
const isRegexInvalid =
isRegexSearch && searchValue.length > 0 && searchQueryRegex == null;
const regexToggleType = isRegexInvalid ? 'error' : 'info';
const regexToggleLabel = labels.regexToggle[regexToggleType];
return (
<StyleUtilsContext.Consumer>
{({ styling, base16Theme }) => {
@ -129,6 +152,17 @@ export class QueryForm extends React.PureComponent<
onClick={this.handleClearSearchClick}
{...styling('closeButton')}
/>
<button
type="button"
aria-label={regexToggleLabel}
title={regexToggleLabel}
data-type={regexToggleType}
aria-pressed={isRegexSearch}
onClick={this.handleRegexSearchClick}
{...styling('toggleButton')}
>
<RegexIcon />
</button>
</div>
<label htmlFor={selectId} {...styling('srOnly')}>
filter by

View File

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

View File

@ -124,6 +124,9 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends PureComponent<
const hasNoApi = apiStates == null;
const searchQueryRegex =
this.selectors.selectSearchQueryRegex(selectorsSource);
return (
<div
ref={this.inspectorRef}
@ -135,6 +138,7 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends PureComponent<
data-wide-layout={+this.state.isWideLayout}
>
<QueryForm
searchQueryRegex={searchQueryRegex}
values={selectorsSource.monitorState.queryForm.values}
onFormValuesChange={this.handleQueryFormValuesChange}
/>

View File

@ -16,6 +16,7 @@ const initialState: RtkQueryMonitorState = {
queryComparator: QueryComparators.fulfilledTimeStamp,
isAscendingQueryComparatorOrder: false,
searchValue: '',
isRegexSearch: false,
queryFilter: QueryFilters.queryKey,
},
},

View File

@ -111,10 +111,21 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
const selectSearchQueryRegex = createSelector(
({ monitorState }: SelectorsSource<S>) =>
monitorState.queryForm.values.searchValue,
(searchValue) => {
if (searchValue.length >= 3) {
return new RegExp(escapeRegExpSpecialCharacter(searchValue), 'i');
({ monitorState }: SelectorsSource<S>) =>
monitorState.queryForm.values.isRegexSearch,
(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;
}
);

View File

@ -43,6 +43,9 @@ export const colorMap = (theme: reduxThemes.Base16Theme) =>
ULIST_COLOR: rgba(theme.base06, 60),
ULIST_STRONG_COLOR: theme.base0B,
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);
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: {
display: 'flex',
flexFlow: 'column nowrap',
@ -323,6 +357,7 @@ const getSheetFromColorMap = (map: ColorMap) => {
outline: 'none',
boxShadow: 'none',
display: 'block',
flex: '0 0 auto',
cursor: 'pointer',
background: 'transparent',
position: 'relative',
@ -334,10 +369,13 @@ const getSheetFromColorMap = (map: ColorMap) => {
content: '"\u00d7"',
display: 'block',
padding: 4,
fontSize: '16px',
color: map.TEXT_COLOR,
fontSize: '1.2em',
color: map.TEXT_PLACEHOLDER_COLOR,
background: 'transparent',
},
'&:hover::after': {
color: map.TEXT_COLOR,
},
},
noApiFound: {

View File

@ -19,6 +19,7 @@ export interface QueryFormValues {
queryComparator: QueryComparators;
isAscendingQueryComparatorOrder: boolean;
searchValue: string;
isRegexSearch: boolean;
queryFilter: QueryFilters;
}
export interface RtkQueryMonitorState {