mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-07-15 10:42:25 +03:00
Merge e012515e13
into 04858cd514
This commit is contained in:
commit
b30cb9ea5e
|
@ -10,6 +10,7 @@ import {
|
||||||
stateTreeSettings,
|
stateTreeSettings,
|
||||||
StoreAction,
|
StoreAction,
|
||||||
StoreState,
|
StoreState,
|
||||||
|
stateFilter,
|
||||||
theme,
|
theme,
|
||||||
} from '@redux-devtools/app';
|
} from '@redux-devtools/app';
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ const rootReducer: Reducer<
|
||||||
socket,
|
socket,
|
||||||
theme,
|
theme,
|
||||||
connection,
|
connection,
|
||||||
|
stateFilter,
|
||||||
stateTreeSettings,
|
stateTreeSettings,
|
||||||
}) as any;
|
}) as any;
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,8 @@
|
||||||
"javascript-stringify": "^2.1.0",
|
"javascript-stringify": "^2.1.0",
|
||||||
"jsan": "^3.1.14",
|
"jsan": "^3.1.14",
|
||||||
"jsondiffpatch": "^0.6.0",
|
"jsondiffpatch": "^0.6.0",
|
||||||
|
"jsonpath-plus": "github:80avin/JSONPath#de0566aee8abf14b7f0f57e2711dbea06cf997fa",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
"react-icons": "^5.3.0",
|
"react-icons": "^5.3.0",
|
||||||
"react-is": "^18.3.1"
|
"react-is": "^18.3.1"
|
||||||
},
|
},
|
||||||
|
@ -69,6 +71,7 @@
|
||||||
"@types/jest": "^29.5.13",
|
"@types/jest": "^29.5.13",
|
||||||
"@types/jsan": "^3.1.5",
|
"@types/jsan": "^3.1.5",
|
||||||
"@types/json-schema": "^7.0.15",
|
"@types/json-schema": "^7.0.15",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^20.16.5",
|
"@types/node": "^20.16.5",
|
||||||
"@types/react": "^18.3.6",
|
"@types/react": "^18.3.6",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {
|
||||||
GET_REPORT_SUCCESS,
|
GET_REPORT_SUCCESS,
|
||||||
ERROR,
|
ERROR,
|
||||||
SET_PERSIST,
|
SET_PERSIST,
|
||||||
|
SET_STATE_FILTER,
|
||||||
CHANGE_STATE_TREE_SETTINGS,
|
CHANGE_STATE_TREE_SETTINGS,
|
||||||
CLEAR_INSTANCES,
|
CLEAR_INSTANCES,
|
||||||
} from '../constants/actionTypes';
|
} from '../constants/actionTypes';
|
||||||
|
@ -33,6 +34,7 @@ import { MonitorStateMonitorState } from '../reducers/monitor';
|
||||||
import { LiftedAction } from '@redux-devtools/core';
|
import { LiftedAction } from '@redux-devtools/core';
|
||||||
import { Data } from '../reducers/reports';
|
import { Data } from '../reducers/reports';
|
||||||
import { LiftedState } from '@redux-devtools/core';
|
import { LiftedState } from '@redux-devtools/core';
|
||||||
|
import { StateFilterState } from '../reducers/stateFilter';
|
||||||
|
|
||||||
let monitorReducer: (
|
let monitorReducer: (
|
||||||
monitorProps: unknown,
|
monitorProps: unknown,
|
||||||
|
@ -67,6 +69,20 @@ export function changeTheme(data: ChangeThemeData): ChangeThemeAction {
|
||||||
return { type: CHANGE_THEME, ...data.formData! };
|
return { type: CHANGE_THEME, ...data.formData! };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SetStateFilterAction {
|
||||||
|
readonly type: typeof SET_STATE_FILTER;
|
||||||
|
stateFilter: Partial<StateFilterState>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setStateFilter(
|
||||||
|
stateFilter: SetStateFilterAction['stateFilter'],
|
||||||
|
): SetStateFilterAction {
|
||||||
|
return {
|
||||||
|
type: SET_STATE_FILTER,
|
||||||
|
stateFilter,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface ChangeStateTreeSettingsFormData {
|
interface ChangeStateTreeSettingsFormData {
|
||||||
readonly sortAlphabetically: boolean;
|
readonly sortAlphabetically: boolean;
|
||||||
readonly disableCollection: boolean;
|
readonly disableCollection: boolean;
|
||||||
|
@ -487,6 +503,7 @@ export interface ReduxPersistRehydrateAction {
|
||||||
export type CoreStoreActionWithoutUpdateStateOrLiftedAction =
|
export type CoreStoreActionWithoutUpdateStateOrLiftedAction =
|
||||||
| ChangeSectionAction
|
| ChangeSectionAction
|
||||||
| ChangeThemeAction
|
| ChangeThemeAction
|
||||||
|
| SetStateFilterAction
|
||||||
| ChangeStateTreeSettingsAction
|
| ChangeStateTreeSettingsAction
|
||||||
| MonitorActionAction
|
| MonitorActionAction
|
||||||
| SelectInstanceAction
|
| SelectInstanceAction
|
||||||
|
|
|
@ -19,6 +19,7 @@ export const TOGGLE_DISPATCHER = 'devTools/TOGGLE_DISPATCHER';
|
||||||
export const EXPORT = 'devTools/EXPORT';
|
export const EXPORT = 'devTools/EXPORT';
|
||||||
export const SHOW_NOTIFICATION = 'devTools/SHOW_NOTIFICATION';
|
export const SHOW_NOTIFICATION = 'devTools/SHOW_NOTIFICATION';
|
||||||
export const CLEAR_NOTIFICATION = 'devTools/CLEAR_NOTIFICATION';
|
export const CLEAR_NOTIFICATION = 'devTools/CLEAR_NOTIFICATION';
|
||||||
|
export const SET_STATE_FILTER = 'devTools/SET_STATE_FILTER';
|
||||||
|
|
||||||
export const UPDATE_REPORTS = 'reports/UPDATE';
|
export const UPDATE_REPORTS = 'reports/UPDATE';
|
||||||
export const GET_REPORT_REQUEST = 'reports/GET_REPORT_REQUEST';
|
export const GET_REPORT_REQUEST = 'reports/GET_REPORT_REQUEST';
|
||||||
|
|
|
@ -8,12 +8,14 @@ import {
|
||||||
DiffTab,
|
DiffTab,
|
||||||
} from '@redux-devtools/inspector-monitor';
|
} from '@redux-devtools/inspector-monitor';
|
||||||
import { Action } from 'redux';
|
import { Action } from 'redux';
|
||||||
import { selectMonitorTab } from '../../../actions';
|
import { selectMonitorTab, setStateFilter } from '../../../actions';
|
||||||
import RawTab from './RawTab';
|
import RawTab from './RawTab';
|
||||||
import ChartTab from './ChartTab';
|
import ChartTab from './ChartTab';
|
||||||
import VisualDiffTab from './VisualDiffTab';
|
import VisualDiffTab from './VisualDiffTab';
|
||||||
import { CoreStoreState } from '../../../reducers';
|
import { CoreStoreState } from '../../../reducers';
|
||||||
import type { Delta } from 'jsondiffpatch';
|
import type { Delta } from 'jsondiffpatch';
|
||||||
|
import { filter } from '../../../utils/searchUtils';
|
||||||
|
import { StateFilterValue } from '@redux-devtools/ui/lib/types/StateFilter/StateFilter';
|
||||||
|
|
||||||
type StateProps = ReturnType<typeof mapStateToProps>;
|
type StateProps = ReturnType<typeof mapStateToProps>;
|
||||||
type DispatchProps = ResolveThunks<typeof actionCreators>;
|
type DispatchProps = ResolveThunks<typeof actionCreators>;
|
||||||
|
@ -35,17 +37,38 @@ class SubTabs extends Component<Props> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selector = () => {
|
filteredData = () => {
|
||||||
switch (this.props.parentTab) {
|
const [data, _error] = filter(
|
||||||
case 'Action':
|
this.props.nextState as object,
|
||||||
return { data: this.props.action };
|
this.props.stateFilter,
|
||||||
case 'Diff':
|
);
|
||||||
return { data: this.props.delta };
|
return data;
|
||||||
default:
|
|
||||||
return { data: this.props.nextState };
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
treeSelector = (): Props => {
|
||||||
|
const props = {
|
||||||
|
...this.props,
|
||||||
|
};
|
||||||
|
if (this.props.nextState) props.nextState = this.filteredData();
|
||||||
|
return props;
|
||||||
|
};
|
||||||
|
|
||||||
|
selectorCreator =
|
||||||
|
(parentTab: Props['parentTab'], selected: Props['selected']) => () => {
|
||||||
|
if (selected === 'Tree')
|
||||||
|
// FIXME change to (this.props.nextState ? { nextState: data } : {})
|
||||||
|
return this.treeSelector();
|
||||||
|
switch (parentTab) {
|
||||||
|
case 'Action':
|
||||||
|
return { data: this.props.action };
|
||||||
|
case 'Diff':
|
||||||
|
return { data: this.props.delta };
|
||||||
|
default:
|
||||||
|
return { data: this.filteredData() };
|
||||||
|
}
|
||||||
|
// {a: 1, b: {c: 2}, e: 5, c: {d: 1}, d: 2}
|
||||||
|
};
|
||||||
|
|
||||||
updateTabs(props: Props) {
|
updateTabs(props: Props) {
|
||||||
const parentTab = props.parentTab;
|
const parentTab = props.parentTab;
|
||||||
|
|
||||||
|
@ -54,12 +77,17 @@ class SubTabs extends Component<Props> {
|
||||||
{
|
{
|
||||||
name: 'Tree',
|
name: 'Tree',
|
||||||
component: DiffTab,
|
component: DiffTab,
|
||||||
selector: () => this.props,
|
selector: this.selectorCreator(
|
||||||
|
this.props.parentTab,
|
||||||
|
'Tree',
|
||||||
|
) as () => Props,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Raw',
|
name: 'Raw',
|
||||||
component: VisualDiffTab,
|
component: VisualDiffTab,
|
||||||
selector: this.selector as () => { data?: Delta },
|
selector: this.selectorCreator(this.props.parentTab, 'Raw') as () => {
|
||||||
|
data?: Delta;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
return;
|
return;
|
||||||
|
@ -69,21 +97,37 @@ class SubTabs extends Component<Props> {
|
||||||
{
|
{
|
||||||
name: 'Tree',
|
name: 'Tree',
|
||||||
component: parentTab === 'Action' ? ActionTab : StateTab,
|
component: parentTab === 'Action' ? ActionTab : StateTab,
|
||||||
selector: () => this.props,
|
selector: this.selectorCreator(
|
||||||
|
this.props.parentTab,
|
||||||
|
'Tree',
|
||||||
|
) as () => Props,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Chart',
|
name: 'Chart',
|
||||||
component: ChartTab,
|
component: ChartTab,
|
||||||
selector: this.selector,
|
selector: this.selectorCreator(this.props.parentTab, 'Chart') as () => {
|
||||||
|
data: unknown;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Raw',
|
name: 'Raw',
|
||||||
component: RawTab,
|
component: RawTab,
|
||||||
selector: this.selector,
|
selector: this.selectorCreator(this.props.parentTab, 'Raw') as () => {
|
||||||
|
data: Delta;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setFilter = (value: StateFilterValue) => {
|
||||||
|
this.setState({
|
||||||
|
stateFilter: {
|
||||||
|
isJsonPath: value.isJsonPath,
|
||||||
|
searchString: value.searchString,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let selected = this.props.selected;
|
let selected = this.props.selected;
|
||||||
if (selected === 'Chart' && this.props.parentTab === 'Diff')
|
if (selected === 'Chart' && this.props.parentTab === 'Diff')
|
||||||
|
@ -94,6 +138,8 @@ class SubTabs extends Component<Props> {
|
||||||
tabs={this.tabs! as any}
|
tabs={this.tabs! as any}
|
||||||
selected={selected || 'Tree'}
|
selected={selected || 'Tree'}
|
||||||
onClick={this.props.selectMonitorTab}
|
onClick={this.props.selectMonitorTab}
|
||||||
|
setFilter={this.props.setStateFilter}
|
||||||
|
stateFilterValue={this.props.stateFilter}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -102,10 +148,12 @@ class SubTabs extends Component<Props> {
|
||||||
const mapStateToProps = (state: CoreStoreState) => ({
|
const mapStateToProps = (state: CoreStoreState) => ({
|
||||||
parentTab: state.monitor.monitorState!.tabName,
|
parentTab: state.monitor.monitorState!.tabName,
|
||||||
selected: state.monitor.monitorState!.subTabName,
|
selected: state.monitor.monitorState!.subTabName,
|
||||||
|
stateFilter: state.stateFilter,
|
||||||
});
|
});
|
||||||
|
|
||||||
const actionCreators = {
|
const actionCreators = {
|
||||||
selectMonitorTab,
|
selectMonitorTab,
|
||||||
|
setStateFilter,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, actionCreators)(SubTabs);
|
export default connect(mapStateToProps, actionCreators)(SubTabs);
|
||||||
|
|
|
@ -22,5 +22,6 @@ export * from './reducers/notification';
|
||||||
export * from './reducers/reports';
|
export * from './reducers/reports';
|
||||||
export * from './reducers/section';
|
export * from './reducers/section';
|
||||||
export * from './reducers/theme';
|
export * from './reducers/theme';
|
||||||
|
export * from './reducers/stateFilter';
|
||||||
export * from './reducers/stateTreeSettings';
|
export * from './reducers/stateTreeSettings';
|
||||||
export * from './utils/stringifyJSON';
|
export * from './utils/stringifyJSON';
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { notification, NotificationState } from './notification';
|
||||||
import { instances, InstancesState } from './instances';
|
import { instances, InstancesState } from './instances';
|
||||||
import { reports, ReportsState } from './reports';
|
import { reports, ReportsState } from './reports';
|
||||||
import { theme, ThemeState } from './theme';
|
import { theme, ThemeState } from './theme';
|
||||||
|
import { stateFilter, StateFilterState } from './stateFilter';
|
||||||
import { stateTreeSettings, StateTreeSettings } from './stateTreeSettings';
|
import { stateTreeSettings, StateTreeSettings } from './stateTreeSettings';
|
||||||
|
|
||||||
export interface CoreStoreState {
|
export interface CoreStoreState {
|
||||||
|
@ -14,6 +15,7 @@ export interface CoreStoreState {
|
||||||
readonly instances: InstancesState;
|
readonly instances: InstancesState;
|
||||||
readonly reports: ReportsState;
|
readonly reports: ReportsState;
|
||||||
readonly notification: NotificationState;
|
readonly notification: NotificationState;
|
||||||
|
readonly stateFilter: StateFilterState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const coreReducers = {
|
export const coreReducers = {
|
||||||
|
@ -24,4 +26,5 @@ export const coreReducers = {
|
||||||
instances,
|
instances,
|
||||||
reports,
|
reports,
|
||||||
notification,
|
notification,
|
||||||
|
stateFilter,
|
||||||
};
|
};
|
||||||
|
|
18
packages/redux-devtools-app-core/src/reducers/stateFilter.ts
Normal file
18
packages/redux-devtools-app-core/src/reducers/stateFilter.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { StateFilterValue } from '@redux-devtools/ui/lib/types/StateFilter/StateFilter';
|
||||||
|
import { CoreStoreAction } from '../actions';
|
||||||
|
import { SET_STATE_FILTER } from '../constants/actionTypes';
|
||||||
|
|
||||||
|
export type StateFilterState = StateFilterValue;
|
||||||
|
|
||||||
|
export function stateFilter(
|
||||||
|
state: StateFilterState = {
|
||||||
|
isJsonPath: false,
|
||||||
|
searchString: '',
|
||||||
|
},
|
||||||
|
action: CoreStoreAction,
|
||||||
|
): StateFilterState {
|
||||||
|
if (action.type === SET_STATE_FILTER) {
|
||||||
|
return { ...state, ...action.stateFilter };
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
78
packages/redux-devtools-app-core/src/utils/searchUtils.ts
Normal file
78
packages/redux-devtools-app-core/src/utils/searchUtils.ts
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import { StateFilterValue } from '@redux-devtools/ui/lib/types/StateFilter/StateFilter';
|
||||||
|
import { JSONPath } from 'jsonpath-plus';
|
||||||
|
import _ from 'lodash-es';
|
||||||
|
|
||||||
|
type Path = string[];
|
||||||
|
|
||||||
|
const _filterByPaths = (obj: any, paths: Path[]) => {
|
||||||
|
if (typeof obj !== 'object' || obj === null) return obj;
|
||||||
|
if (paths.length === 1 && !paths[0].length) return obj;
|
||||||
|
|
||||||
|
// groupBy top level key and,
|
||||||
|
// remove that key from grouped path values
|
||||||
|
// [['a', 'b'], ['a', 'c']] => {'a': ['b', 'c']}
|
||||||
|
const groupedTopPaths = _.mapValues(_.groupBy(paths, 0), (val) =>
|
||||||
|
val.map((v) => v.slice(1)),
|
||||||
|
);
|
||||||
|
const topKeys = Object.keys(groupedTopPaths);
|
||||||
|
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
obj = obj.flatMap((_, i) =>
|
||||||
|
i in groupedTopPaths ? _filterByPaths(obj[i], groupedTopPaths[i]) : [],
|
||||||
|
);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
if (typeof obj === 'object') obj = _.pick(obj, topKeys);
|
||||||
|
|
||||||
|
for (const k of topKeys) {
|
||||||
|
if (!(k in obj)) continue;
|
||||||
|
obj[k] = _filterByPaths(obj[k], groupedTopPaths[k]);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterByPaths = (obj: any, paths: Path[]) => {
|
||||||
|
const sortedUniqPaths = _.sortBy(_.uniqBy(paths, JSON.stringify), ['length']);
|
||||||
|
// remove unnecessary depths
|
||||||
|
// [['a'], ['a', 'b']] => [['a']]
|
||||||
|
const filteredPaths = sortedUniqPaths.filter((s, i, arr) => {
|
||||||
|
if (i === 0) return true;
|
||||||
|
if (!_.isEqual(arr[i].slice(0, arr[i - 1].length), arr[i - 1])) return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
return _filterByPaths(obj, filteredPaths);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const filterByJsonPath = (obj: any, jsonpath: string) => {
|
||||||
|
const paths = JSONPath({
|
||||||
|
json: obj,
|
||||||
|
path: jsonpath,
|
||||||
|
resultType: 'path',
|
||||||
|
eval: 'safe',
|
||||||
|
}).map((jp: string) => JSONPath.toPathArray(jp).slice(1)) as Path[];
|
||||||
|
|
||||||
|
if (paths.some((path: Path) => !path.length)) return obj;
|
||||||
|
|
||||||
|
const result = filterByPaths(obj, paths);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FilterType = 'jsonpath' | 'regexp-glob';
|
||||||
|
export type FilterResult = [any, Error | null];
|
||||||
|
|
||||||
|
export const filter = (
|
||||||
|
obj: any,
|
||||||
|
stateFilter: StateFilterValue,
|
||||||
|
): FilterResult => {
|
||||||
|
try {
|
||||||
|
const { isJsonPath, searchString } = stateFilter;
|
||||||
|
if (!searchString) return [obj, null];
|
||||||
|
|
||||||
|
if (isJsonPath) return [filterByJsonPath(obj, searchString), null];
|
||||||
|
else return [filterByJsonPath(obj, `$..${searchString}`), null];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Filter Error', error);
|
||||||
|
return [{}, error as Error];
|
||||||
|
}
|
||||||
|
};
|
61
packages/redux-devtools-ui/src/StateFilter/StateFilter.tsx
Normal file
61
packages/redux-devtools-ui/src/StateFilter/StateFilter.tsx
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import React, {
|
||||||
|
ChangeEvent,
|
||||||
|
MouseEventHandler,
|
||||||
|
PureComponent,
|
||||||
|
ReactNode,
|
||||||
|
SyntheticEvent,
|
||||||
|
} from 'react';
|
||||||
|
import createStyledComponent from '../utils/createStyledComponent';
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
export interface StateFilterValue {
|
||||||
|
searchString: string;
|
||||||
|
isJsonPath: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StateFilterProps {
|
||||||
|
value: StateFilterValue;
|
||||||
|
onChange: (value: Partial<StateFilterValue>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormContainer = createStyledComponent(styles, 'form');
|
||||||
|
|
||||||
|
const searchPlaceholder = 'filter state...';
|
||||||
|
|
||||||
|
export class StateFilter extends PureComponent<StateFilterProps> {
|
||||||
|
handleSubmit = (e: SyntheticEvent<HTMLFormElement, SubmitEvent>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.props.onChange({
|
||||||
|
searchString: e.currentTarget.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
toggleJSONPath: MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||||
|
this.props.onChange({
|
||||||
|
isJsonPath: !this.props.value.isJsonPath,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
render(): ReactNode {
|
||||||
|
const { searchString, isJsonPath } = this.props.value;
|
||||||
|
return (
|
||||||
|
<FormContainer onSubmit={this.handleSubmit}>
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
onChange={this.handleChange}
|
||||||
|
value={searchString}
|
||||||
|
placeholder={searchPlaceholder}
|
||||||
|
/>
|
||||||
|
{/* <button type="reset" onClick={this.handleClear}>x</button> */}
|
||||||
|
<button
|
||||||
|
aria-pressed={isJsonPath}
|
||||||
|
title="Use JSONPath to filter state"
|
||||||
|
type="button"
|
||||||
|
onClick={this.toggleJSONPath}
|
||||||
|
>
|
||||||
|
{'{;}'}
|
||||||
|
</button>
|
||||||
|
</FormContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
30
packages/redux-devtools-ui/src/StateFilter/styles/index.ts
Normal file
30
packages/redux-devtools-ui/src/StateFilter/styles/index.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { css, ThemedStyledProps } from 'styled-components';
|
||||||
|
|
||||||
|
import { Theme } from '../../themes/default';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
export default ({ theme }: ThemedStyledProps<{}, Theme>) => css`
|
||||||
|
align-self: center;
|
||||||
|
height: 100%;
|
||||||
|
border-bottom: 0.5px solid;
|
||||||
|
|
||||||
|
input {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
color: ${theme.base06};
|
||||||
|
}
|
||||||
|
input::-webkit-input-placeholder {
|
||||||
|
color: ${theme.base06};
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
input::-moz-placeholder {
|
||||||
|
color: ${theme.base06};
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
button[aria-pressed='true'] {
|
||||||
|
filter: invert();
|
||||||
|
}
|
||||||
|
button:focus {
|
||||||
|
}
|
||||||
|
`;
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import TabsHeader, { ReactButtonElement, Tab } from './TabsHeader';
|
import TabsHeader, { ReactButtonElement, Tab } from './TabsHeader';
|
||||||
import { TabsContainer } from './styles/common';
|
import { TabsContainer } from './styles/common';
|
||||||
|
import { StateFilterValue } from '../StateFilter/StateFilter';
|
||||||
|
|
||||||
export type Position = 'left' | 'right' | 'center';
|
export type Position = 'left' | 'right' | 'center';
|
||||||
|
|
||||||
|
@ -11,6 +12,8 @@ export interface TabsProps<P> {
|
||||||
onClick: (value: string) => void;
|
onClick: (value: string) => void;
|
||||||
collapsible?: boolean;
|
collapsible?: boolean;
|
||||||
position: Position;
|
position: Position;
|
||||||
|
setFilter?: (value: Partial<StateFilterValue>) => void;
|
||||||
|
stateFilterValue?: StateFilterValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Tabs<P extends object> extends Component<TabsProps<P>> {
|
export default class Tabs<P extends object> extends Component<TabsProps<P>> {
|
||||||
|
@ -63,6 +66,8 @@ export default class Tabs<P extends object> extends Component<TabsProps<P>> {
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
selected={this.props.selected}
|
selected={this.props.selected}
|
||||||
position={this.props.position}
|
position={this.props.position}
|
||||||
|
setFilter={this.props.setFilter}
|
||||||
|
stateFilterValue={this.props.stateFilterValue}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { FaAngleDoubleRight } from 'react-icons/fa';
|
||||||
import ContextMenu from '../ContextMenu';
|
import ContextMenu from '../ContextMenu';
|
||||||
import createStyledComponent from '../utils/createStyledComponent';
|
import createStyledComponent from '../utils/createStyledComponent';
|
||||||
import * as styles from './styles';
|
import * as styles from './styles';
|
||||||
|
import { StateFilter, StateFilterValue } from '../StateFilter/StateFilter';
|
||||||
|
|
||||||
const TabsWrapper = createStyledComponent(styles);
|
const TabsWrapper = createStyledComponent(styles);
|
||||||
|
|
||||||
|
@ -19,7 +20,7 @@ export interface Tab<P> {
|
||||||
selector?: (tab: this) => P;
|
selector?: (tab: this) => P;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props<P> {
|
interface PropsWithoutFilter<P> {
|
||||||
tabs: ReactButtonElement[];
|
tabs: ReactButtonElement[];
|
||||||
items: Tab<P>[];
|
items: Tab<P>[];
|
||||||
main: boolean | undefined;
|
main: boolean | undefined;
|
||||||
|
@ -29,6 +30,13 @@ interface Props<P> {
|
||||||
selected: string | undefined;
|
selected: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PropsWithFilter<P> extends PropsWithoutFilter<P> {
|
||||||
|
setFilter: (value: Partial<StateFilterValue>) => void;
|
||||||
|
stateFilterValue: StateFilterValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props<P> = PropsWithoutFilter<P> | PropsWithFilter<P>;
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
visibleTabs: ReactButtonElement[];
|
visibleTabs: ReactButtonElement[];
|
||||||
hiddenTabs: ReactButtonElement[];
|
hiddenTabs: ReactButtonElement[];
|
||||||
|
@ -220,6 +228,12 @@ export default class TabsHeader<P> extends Component<Props<P>, State> {
|
||||||
<FaAngleDoubleRight />
|
<FaAngleDoubleRight />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
{'setFilter' in this.props && this.props.setFilter ? (
|
||||||
|
<StateFilter
|
||||||
|
value={this.props.stateFilterValue}
|
||||||
|
onChange={this.props.setFilter}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{this.props.collapsible && contextMenu && (
|
{this.props.collapsible && contextMenu && (
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
|
|
4072
pnpm-lock.yaml
4072
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user