mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-07-22 14:09:46 +03:00
Add object search and prepare types
This commit is contained in:
parent
27da2edc1a
commit
2097b49ffe
|
@ -1,6 +1,6 @@
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import JSONArrow from './JSONArrow';
|
import JSONArrow from './JSONArrow';
|
||||||
import type { CircularCache, CommonInternalProps } from './types';
|
import type { CircularCache, CommonInternalProps, KeyPath } from './types';
|
||||||
|
|
||||||
interface Props extends CommonInternalProps {
|
interface Props extends CommonInternalProps {
|
||||||
data: unknown;
|
data: unknown;
|
||||||
|
@ -13,12 +13,25 @@ interface Props extends CommonInternalProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ItemRange(props: Props) {
|
export default function ItemRange(props: Props) {
|
||||||
const { styling, from, to, renderChildNodes, nodeType } = props;
|
const {
|
||||||
|
styling,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
renderChildNodes,
|
||||||
|
nodeType,
|
||||||
|
keyPath,
|
||||||
|
searchResultPath,
|
||||||
|
level,
|
||||||
|
} = props;
|
||||||
|
|
||||||
const [expanded, setExpanded] = useState<boolean>(false);
|
const [userExpanded, setUserExpanded] = useState<boolean>(false);
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
setExpanded(!expanded);
|
setUserExpanded(!userExpanded);
|
||||||
}, [expanded]);
|
}, [userExpanded]);
|
||||||
|
|
||||||
|
const expanded =
|
||||||
|
userExpanded ||
|
||||||
|
containsSearchResult(keyPath, from, to, searchResultPath, level);
|
||||||
|
|
||||||
return expanded ? (
|
return expanded ? (
|
||||||
<div {...styling('itemRange', expanded)}>
|
<div {...styling('itemRange', expanded)}>
|
||||||
|
@ -37,3 +50,15 @@ export default function ItemRange(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const containsSearchResult = (
|
||||||
|
ownKeyPath: KeyPath,
|
||||||
|
from: number,
|
||||||
|
to: number,
|
||||||
|
resultKeyPath: KeyPath,
|
||||||
|
level: number
|
||||||
|
): boolean => {
|
||||||
|
const searchLevel = level > 1 ? level - 1 : level;
|
||||||
|
const nextKey = Number(resultKeyPath[searchLevel]);
|
||||||
|
return !isNaN(nextKey) && nextKey >= from && nextKey <= to;
|
||||||
|
};
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import JSONArrow from './JSONArrow';
|
|
||||||
import getCollectionEntries from './getCollectionEntries';
|
|
||||||
import JSONNode from './JSONNode';
|
|
||||||
import ItemRange from './ItemRange';
|
import ItemRange from './ItemRange';
|
||||||
import type { CircularCache, CommonInternalProps } from './types';
|
import JSONArrow from './JSONArrow';
|
||||||
|
import JSONNode from './JSONNode';
|
||||||
|
import getCollectionEntries from './getCollectionEntries';
|
||||||
|
import type { CircularCache, CommonInternalProps, KeyPath } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders nested values (eg. objects, arrays, lists, etc.)
|
* Renders nested values (eg. objects, arrays, lists, etc.)
|
||||||
|
@ -110,17 +110,21 @@ export default function JSONNestedNode(props: Props) {
|
||||||
nodeType,
|
nodeType,
|
||||||
nodeTypeIndicator,
|
nodeTypeIndicator,
|
||||||
shouldExpandNodeInitially,
|
shouldExpandNodeInitially,
|
||||||
|
searchResultPath,
|
||||||
styling,
|
styling,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [expanded, setExpanded] = useState<boolean>(
|
const [userExpanded, setUserExpanded] = useState<boolean>(
|
||||||
// calculate individual node expansion if necessary
|
// calculate individual node expansion if necessary
|
||||||
isCircular ? false : shouldExpandNodeInitially(keyPath, data, level),
|
isCircular ? false : shouldExpandNodeInitially(keyPath, data, level),
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
if (expandable) setExpanded(!expanded);
|
if (expandable) setUserExpanded(!userExpanded);
|
||||||
}, [expandable, expanded]);
|
}, [expandable, userExpanded]);
|
||||||
|
|
||||||
|
const expanded =
|
||||||
|
userExpanded || containsSearchResult(keyPath, searchResultPath, level);
|
||||||
|
|
||||||
const renderedChildren =
|
const renderedChildren =
|
||||||
expanded || (hideRoot && level === 0)
|
expanded || (hideRoot && level === 0)
|
||||||
|
@ -175,3 +179,15 @@ export default function JSONNestedNode(props: Props) {
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const containsSearchResult = (
|
||||||
|
ownKeyPath: KeyPath,
|
||||||
|
resultKeyPath: KeyPath,
|
||||||
|
level: number
|
||||||
|
): boolean => {
|
||||||
|
const searchLevel = level > 0 ? level - 1 : level;
|
||||||
|
const currKey = [...ownKeyPath].reverse()[searchLevel];
|
||||||
|
return resultKeyPath && currKey !== undefined
|
||||||
|
? resultKeyPath[searchLevel] === currKey.toString()
|
||||||
|
: false;
|
||||||
|
};
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
// port by Daniele Zannotti http://www.github.com/dzannotti <dzannotti@me.com>
|
// port by Daniele Zannotti http://www.github.com/dzannotti <dzannotti@me.com>
|
||||||
|
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
import type { StylingValue, Theme } from 'react-base16-styling';
|
||||||
|
import { invertTheme } from 'react-base16-styling';
|
||||||
import JSONNode from './JSONNode';
|
import JSONNode from './JSONNode';
|
||||||
import createStylingFromTheme from './createStylingFromTheme';
|
import createStylingFromTheme from './createStylingFromTheme';
|
||||||
import { invertTheme } from 'react-base16-styling';
|
|
||||||
import type { StylingValue, Theme } from 'react-base16-styling';
|
|
||||||
import type {
|
import type {
|
||||||
CommonExternalProps,
|
CommonExternalProps,
|
||||||
GetItemString,
|
GetItemString,
|
||||||
|
@ -41,6 +41,8 @@ export function JSONTree({
|
||||||
labelRenderer = defaultLabelRenderer,
|
labelRenderer = defaultLabelRenderer,
|
||||||
valueRenderer = identity,
|
valueRenderer = identity,
|
||||||
shouldExpandNodeInitially = expandRootNode,
|
shouldExpandNodeInitially = expandRootNode,
|
||||||
|
isSearchInProgress = false,
|
||||||
|
searchResultPath = [],
|
||||||
hideRoot = false,
|
hideRoot = false,
|
||||||
getItemString = defaultItemString,
|
getItemString = defaultItemString,
|
||||||
postprocessValue = identity,
|
postprocessValue = identity,
|
||||||
|
@ -64,6 +66,8 @@ export function JSONTree({
|
||||||
labelRenderer={labelRenderer}
|
labelRenderer={labelRenderer}
|
||||||
valueRenderer={valueRenderer}
|
valueRenderer={valueRenderer}
|
||||||
shouldExpandNodeInitially={shouldExpandNodeInitially}
|
shouldExpandNodeInitially={shouldExpandNodeInitially}
|
||||||
|
searchResultPath={searchResultPath}
|
||||||
|
isSearchInProgress={isSearchInProgress}
|
||||||
hideRoot={hideRoot}
|
hideRoot={hideRoot}
|
||||||
getItemString={getItemString}
|
getItemString={getItemString}
|
||||||
postprocessValue={postprocessValue}
|
postprocessValue={postprocessValue}
|
||||||
|
@ -75,16 +79,16 @@ export function JSONTree({
|
||||||
}
|
}
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
|
CommonExternalProps,
|
||||||
|
GetItemString,
|
||||||
|
IsCustomNode,
|
||||||
Key,
|
Key,
|
||||||
KeyPath,
|
KeyPath,
|
||||||
GetItemString,
|
|
||||||
LabelRenderer,
|
LabelRenderer,
|
||||||
ValueRenderer,
|
|
||||||
ShouldExpandNodeInitially,
|
|
||||||
PostprocessValue,
|
PostprocessValue,
|
||||||
IsCustomNode,
|
ShouldExpandNodeInitially,
|
||||||
SortObjectKeys,
|
SortObjectKeys,
|
||||||
Styling,
|
Styling,
|
||||||
CommonExternalProps,
|
ValueRenderer,
|
||||||
} from './types';
|
} from './types';
|
||||||
export type { StylingValue };
|
export type { StylingValue };
|
||||||
|
|
|
@ -47,6 +47,8 @@ export interface CommonExternalProps {
|
||||||
labelRenderer: LabelRenderer;
|
labelRenderer: LabelRenderer;
|
||||||
valueRenderer: ValueRenderer;
|
valueRenderer: ValueRenderer;
|
||||||
shouldExpandNodeInitially: ShouldExpandNodeInitially;
|
shouldExpandNodeInitially: ShouldExpandNodeInitially;
|
||||||
|
searchResultPath: KeyPath;
|
||||||
|
isSearchInProgress: boolean;
|
||||||
hideRoot: boolean;
|
hideRoot: boolean;
|
||||||
getItemString: GetItemString;
|
getItemString: GetItemString;
|
||||||
postprocessValue: PostprocessValue;
|
postprocessValue: PostprocessValue;
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { SearchQuery } from '../searchPanel/SearchPanel';
|
||||||
|
import { Value } from './searchWorker';
|
||||||
|
|
||||||
|
export function searchInObject(
|
||||||
|
objectToSearch: Value,
|
||||||
|
query: SearchQuery
|
||||||
|
): Promise<string[]> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const worker = new Worker(new URL('./searchWorker.js', import.meta.url));
|
||||||
|
|
||||||
|
worker.onmessage = (event: MessageEvent<string[]>) => {
|
||||||
|
resolve(event.data);
|
||||||
|
worker.terminate();
|
||||||
|
};
|
||||||
|
|
||||||
|
worker.postMessage({ objectToSearch, query });
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
import { SearchQuery } from '../searchPanel/SearchPanel';
|
||||||
|
|
||||||
|
type PrimitiveValue = string | number | boolean | undefined | null;
|
||||||
|
export type Value =
|
||||||
|
| PrimitiveValue
|
||||||
|
| Value[]
|
||||||
|
| { [key: string | number]: Value };
|
||||||
|
|
||||||
|
function getKeys(object: Value): string[] {
|
||||||
|
if (object === undefined || object === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['string', 'number', 'boolean'].includes(typeof object)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(object)) {
|
||||||
|
return [...object.keys()].map((el) => el.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKeyPaths(object: Value): string[] {
|
||||||
|
const keys = getKeys(object) as (keyof object)[];
|
||||||
|
const appendKey = (path: string, branch: string | number): string =>
|
||||||
|
path ? `${path}.${branch}` : `${branch}`;
|
||||||
|
|
||||||
|
return keys.flatMap((key) => [
|
||||||
|
key,
|
||||||
|
...getKeyPaths(object?.[key]).map((subKey) => appendKey(key, subKey)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMatchable = (value: Value): boolean => {
|
||||||
|
return (
|
||||||
|
value === undefined ||
|
||||||
|
value === null ||
|
||||||
|
['string', 'number', 'boolean'].includes(typeof value)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function doSearch(objectToSearch: Value, query: SearchQuery): string[] {
|
||||||
|
const {
|
||||||
|
queryText,
|
||||||
|
location: { keys: matchKeys, values: matchValues },
|
||||||
|
} = query;
|
||||||
|
|
||||||
|
if (!matchKeys && !matchValues) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyPaths = getKeyPaths(objectToSearch);
|
||||||
|
const getValueFromKeyPath = (obj: Value, path: string): Value => {
|
||||||
|
return path
|
||||||
|
.split('.')
|
||||||
|
.reduce(
|
||||||
|
(intermediateObj, key) => intermediateObj?.[key as keyof object],
|
||||||
|
obj
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const match = (value: Value, searchText: string): boolean => {
|
||||||
|
return isMatchable(value) && String(value).includes(searchText);
|
||||||
|
};
|
||||||
|
|
||||||
|
return keyPaths.filter((keyPath) => {
|
||||||
|
const valuesToCheck = [];
|
||||||
|
matchKeys && valuesToCheck.push(keyPath.split('.').splice(-1)[0]);
|
||||||
|
matchValues &&
|
||||||
|
valuesToCheck.push(getValueFromKeyPath(objectToSearch, keyPath));
|
||||||
|
|
||||||
|
return valuesToCheck.some((value) => match(value, queryText));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onmessage = (
|
||||||
|
event: MessageEvent<{ objectToSearch: Value; query: SearchQuery }>
|
||||||
|
) => {
|
||||||
|
const { objectToSearch, query } = event.data;
|
||||||
|
const result = doSearch(objectToSearch, query);
|
||||||
|
self.postMessage(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
export {};
|
Loading…
Reference in New Issue
Block a user