mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-07-26 16:09:50 +03:00
feat: complete inspector layout and add initial JSONTree setup
This commit is contained in:
parent
e9f397bd1e
commit
75aa663d64
|
@ -17,7 +17,8 @@
|
|||
"react-redux": "^7.2.1",
|
||||
"react-scripts": "4.0.2",
|
||||
"redux": "^4.0.5",
|
||||
"redux-devtools-themes": "^1.0.0"
|
||||
"redux-devtools-themes": "^1.0.0",
|
||||
"react-json-tree": "^0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "17.0.0",
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="description" content="Web site created using create-snowpack-app" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-snowpack-app"
|
||||
/>
|
||||
<title>Snowpack App</title>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -10,5 +10,5 @@ export default createDevTools(
|
|||
changeMonitorKey="ctrl-m"
|
||||
>
|
||||
<RtkQueryInspectorMonitor />
|
||||
</DockMonitor>,
|
||||
</DockMonitor>
|
||||
);
|
||||
|
|
|
@ -12,5 +12,5 @@ ReactDOM.render(
|
|||
<App />
|
||||
<DevTools />
|
||||
</Provider>,
|
||||
rootElement,
|
||||
rootElement
|
||||
);
|
||||
|
|
|
@ -9,5 +9,5 @@ export const store = configureStore({
|
|||
// adding the api middleware enables caching, invalidation, polling and other features of `rtk-query`
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware().concat(pokemonApi.middleware),
|
||||
enhancers: [DevTools.instrument()]
|
||||
enhancers: [DevTools.instrument()],
|
||||
});
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
{
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
],
|
||||
"include": ["./src/**/*"],
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
],
|
||||
"lib": ["dom", "es2015"],
|
||||
"jsx": "react-jsx",
|
||||
"target": "es5",
|
||||
"allowJs": true,
|
||||
|
|
|
@ -9411,6 +9411,15 @@ react-is@^17.0.1:
|
|||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||
|
||||
react-json-tree@^0.15.0:
|
||||
version "0.15.0"
|
||||
resolved "https://registry.yarnpkg.com/react-json-tree/-/react-json-tree-0.15.0.tgz#16a5bbed761f711d1656de6c62818d40ddb09442"
|
||||
integrity sha512-/bEFXZBfLFiep6ReuzatR8mz9G7sRmejElRDgcAuqY0Jsx7llouax2DM03rlQifrUJgmvTGmPA+olyWYyGagqA==
|
||||
dependencies:
|
||||
"@types/prop-types" "^15.7.3"
|
||||
prop-types "^15.7.2"
|
||||
react-base16-styling "^0.8.0"
|
||||
|
||||
react-redux@^7.2.1:
|
||||
version "7.2.4"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225"
|
||||
|
|
|
@ -45,7 +45,8 @@
|
|||
"@redux-devtools/dock-monitor": "^1.4.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"redux-devtools-themes": "^1.0.0",
|
||||
"devui": "^1.0.0-8"
|
||||
"devui": "^1.0.0-8",
|
||||
"react-json-tree": "^0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@redux-devtools/core": "^3.9.0",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { Component, createRef, CSSProperties, ReactNode } from 'react';
|
||||
import React, { Component, createRef, ReactNode } from 'react';
|
||||
import { AnyAction, Dispatch, Action } from 'redux';
|
||||
import { LiftedAction, LiftedState } from '@redux-devtools/core';
|
||||
import * as themes from 'redux-devtools-themes';
|
||||
|
@ -9,11 +9,7 @@ import { selectQueryKey } from './reducers';
|
|||
import { QueryList } from './components/QueryList';
|
||||
import { StyleUtils } from './styles/createStylingFromTheme';
|
||||
import { QueryForm } from './components/QueryForm';
|
||||
|
||||
const wrapperStyle: CSSProperties = {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
};
|
||||
import { QueryPreview } from './components/QueryPreview';
|
||||
|
||||
type SelectorsSource<S> = {
|
||||
currentState: S | null;
|
||||
|
@ -29,26 +25,34 @@ export interface RtkQueryInspectorProps<S, A extends Action<unknown>>
|
|||
styleUtils: StyleUtils;
|
||||
}
|
||||
|
||||
type RtkQueryInspectorState<S> = { selectorsSource: SelectorsSource<S> };
|
||||
type RtkQueryInspectorState<S> = {
|
||||
selectorsSource: SelectorsSource<S>;
|
||||
isWideLayout: boolean;
|
||||
};
|
||||
|
||||
class RtkQueryInspector<S, A extends Action<unknown>> extends Component<
|
||||
RtkQueryInspectorProps<S, A>,
|
||||
RtkQueryInspectorState<S>
|
||||
> {
|
||||
divRef = createRef<HTMLDivElement>();
|
||||
inspectorRef = createRef<HTMLDivElement>();
|
||||
|
||||
isWideIntervalRef: number | NodeJS.Timeout | null = null;
|
||||
|
||||
constructor(props: RtkQueryInspectorProps<S, A>) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isWideLayout: true,
|
||||
selectorsSource: computeSelectorSource(props, null),
|
||||
};
|
||||
}
|
||||
|
||||
static wideLayout = 500;
|
||||
|
||||
static getDerivedStateFromProps(
|
||||
props: RtkQueryInspectorProps<unknown, Action<unknown>>,
|
||||
state: RtkQueryInspectorState<unknown>
|
||||
): null | RtkQueryInspectorState<unknown> {
|
||||
): null | Partial<RtkQueryInspectorState<unknown>> {
|
||||
const selectorsSource = computeSelectorSource<unknown, Action<unknown>>(
|
||||
props,
|
||||
state.selectorsSource
|
||||
|
@ -65,12 +69,35 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends Component<
|
|||
|
||||
selectors = createInspectorSelectors<S>();
|
||||
|
||||
handleSelectQuery = (queryInfo: QueryInfo) => {
|
||||
updateSizeMode = (): void => {
|
||||
if (this.inspectorRef.current) {
|
||||
const isWideLayout =
|
||||
this.inspectorRef.current.offsetWidth > RtkQueryInspector.wideLayout;
|
||||
|
||||
if (isWideLayout !== this.state.isWideLayout) {
|
||||
this.setState({ isWideLayout });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.updateSizeMode();
|
||||
|
||||
this.isWideIntervalRef = setInterval(this.updateSizeMode, 200);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.isWideIntervalRef) {
|
||||
clearTimeout(this.isWideIntervalRef as any);
|
||||
}
|
||||
}
|
||||
|
||||
handleSelectQuery = (queryInfo: QueryInfo): void => {
|
||||
this.props.dispatch(selectQueryKey(queryInfo) as AnyAction);
|
||||
};
|
||||
|
||||
render(): ReactNode {
|
||||
const { selectorsSource } = this.state;
|
||||
const { selectorsSource, isWideLayout } = this.state;
|
||||
const {
|
||||
styleUtils: { styling },
|
||||
} = this.props;
|
||||
|
@ -79,11 +106,27 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends Component<
|
|||
selectorsSource
|
||||
);
|
||||
|
||||
console.log('inspector', { apiStates, allSortedQueries, selectorsSource });
|
||||
const currentQueryInfo = this.selectors.selectorCurrentQueryInfo(
|
||||
selectorsSource
|
||||
);
|
||||
|
||||
console.log('inspector', {
|
||||
apiStates,
|
||||
allSortedQueries,
|
||||
selectorsSource,
|
||||
currentQueryInfo,
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={wrapperStyle} ref={this.divRef}>
|
||||
<div {...styling('querySectionWrapper')}>
|
||||
<div
|
||||
ref={this.inspectorRef}
|
||||
data-wide-layout={+this.state.isWideLayout}
|
||||
{...styling('inspector')}
|
||||
>
|
||||
<div
|
||||
{...styling('querySectionWrapper')}
|
||||
data-wide-layout={+this.state.isWideLayout}
|
||||
>
|
||||
<QueryForm
|
||||
dispatch={this.props.dispatch}
|
||||
queryComparator={selectorsSource.monitorState.queryComparator}
|
||||
|
@ -97,6 +140,11 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends Component<
|
|||
selectedQueryKey={selectorsSource.monitorState.selectedQueryKey}
|
||||
/>
|
||||
</div>
|
||||
<QueryPreview
|
||||
selectedQueryInfo={currentQueryInfo}
|
||||
styling={styling}
|
||||
isWideLayout={isWideLayout}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { CSSProperties, PureComponent } from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as themes from 'redux-devtools-themes';
|
||||
import { Action } from 'redux';
|
||||
|
@ -15,18 +15,6 @@ import {
|
|||
StyleUtils,
|
||||
StyleUtilsContext,
|
||||
} from './styles/createStylingFromTheme';
|
||||
|
||||
const styles: { container: CSSProperties } = {
|
||||
container: {
|
||||
fontFamily: 'monaco, Consolas, Lucida Console, monospace',
|
||||
position: 'relative',
|
||||
overflowY: 'hidden',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
minWidth: 300,
|
||||
},
|
||||
};
|
||||
|
||||
interface DefaultProps<S> {
|
||||
select: (state: unknown) => unknown;
|
||||
theme: keyof typeof themes | Base16Theme;
|
||||
|
@ -85,20 +73,18 @@ class RtkQueryInspectorMonitor<
|
|||
|
||||
render() {
|
||||
const {
|
||||
styleUtils: { base16Theme, styling },
|
||||
styleUtils: { base16Theme },
|
||||
} = this.state;
|
||||
|
||||
const RtkQueryInspectorAsAny = RtkQueryInspector as any;
|
||||
|
||||
return (
|
||||
<StyleUtilsContext.Provider value={this.state.styleUtils}>
|
||||
<div {...styling(['inspector'])}>
|
||||
<RtkQueryInspectorAsAny
|
||||
{...this.props}
|
||||
theme={base16Theme}
|
||||
styleUtils={this.state.styleUtils}
|
||||
/>
|
||||
</div>
|
||||
</StyleUtilsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
import JSONTree from 'react-json-tree';
|
||||
import { StylingFunction } from 'react-base16-styling';
|
||||
import { DATA_TYPE_KEY } from '../monitor-config';
|
||||
import {
|
||||
getJsonTreeTheme,
|
||||
StyleUtilsContext,
|
||||
} from '../styles/createStylingFromTheme';
|
||||
import { createTreeItemLabelRenderer, getItemString } from '../styles/tree';
|
||||
import { QueryInfo } from '../types';
|
||||
|
||||
export interface QueryPreviewProps {
|
||||
selectedQueryInfo: QueryInfo | null;
|
||||
styling: StylingFunction;
|
||||
isWideLayout: boolean;
|
||||
}
|
||||
|
||||
export class QueryPreview extends React.PureComponent<QueryPreviewProps> {
|
||||
readonly labelRenderer: ReturnType<typeof createTreeItemLabelRenderer>;
|
||||
|
||||
constructor(props: QueryPreviewProps) {
|
||||
super(props);
|
||||
|
||||
this.labelRenderer = createTreeItemLabelRenderer(this.props.styling);
|
||||
}
|
||||
|
||||
render(): ReactNode {
|
||||
const { selectedQueryInfo, isWideLayout } = this.props;
|
||||
|
||||
if (!selectedQueryInfo) {
|
||||
return (
|
||||
<StyleUtilsContext.Consumer>
|
||||
{({ styling }) => <div {...styling('queryPreview')} />}
|
||||
</StyleUtilsContext.Consumer>
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
query: {
|
||||
endpointName,
|
||||
fulfilledTimeStamp,
|
||||
status,
|
||||
startedTimeStamp,
|
||||
data,
|
||||
},
|
||||
reducerPath,
|
||||
} = selectedQueryInfo;
|
||||
|
||||
const startedAt = startedTimeStamp
|
||||
? new Date(startedTimeStamp).toISOString()
|
||||
: '-';
|
||||
|
||||
const latestFetch = fulfilledTimeStamp
|
||||
? new Date(fulfilledTimeStamp).toISOString()
|
||||
: '-';
|
||||
|
||||
return (
|
||||
<StyleUtilsContext.Consumer>
|
||||
{({ styling, base16Theme, invertTheme }) => {
|
||||
return (
|
||||
<div {...styling('queryPreview')}>
|
||||
<React.Fragment>
|
||||
<div {...styling('previewHeader')}></div>
|
||||
<ul>
|
||||
<li>{`reducerPath: ${reducerPath ?? '-'}`}</li>
|
||||
<li>{`endpointName: ${endpointName ?? '-'}`}</li>
|
||||
<li>{`status: ${status}`}</li>
|
||||
<li>{`loaded at: ${latestFetch}`}</li>
|
||||
<li>{`requested at: ${startedAt}`}</li>
|
||||
</ul>
|
||||
<div style={{ padding: '1em' }}>
|
||||
<JSONTree
|
||||
data={data}
|
||||
labelRenderer={this.labelRenderer}
|
||||
theme={getJsonTreeTheme(base16Theme)}
|
||||
invertTheme={invertTheme}
|
||||
getItemString={(type, data) =>
|
||||
getItemString(
|
||||
styling,
|
||||
type,
|
||||
data,
|
||||
DATA_TYPE_KEY,
|
||||
isWideLayout
|
||||
)
|
||||
}
|
||||
hideRoot
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</StyleUtilsContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export const DATA_TYPE_KEY = Symbol.for('__serializedType__');
|
|
@ -49,6 +49,7 @@ export interface InspectorSelectors<S> {
|
|||
ReturnType<typeof extractAllApiQueries>
|
||||
>;
|
||||
readonly selectAllSortedQueries: InspectorSelector<S, QueryInfo[]>;
|
||||
readonly selectorCurrentQueryInfo: InspectorSelector<S, QueryInfo | null>;
|
||||
}
|
||||
|
||||
export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
||||
|
@ -85,10 +86,30 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
|||
}
|
||||
);
|
||||
|
||||
const selectorCurrentQueryInfo = createSelector(
|
||||
selectAllQueries,
|
||||
({ monitorState }: SelectorsSource<S>) => monitorState.selectedQueryKey,
|
||||
(allQueries, selectedQueryKey) => {
|
||||
if (!selectedQueryKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const currentQueryInfo =
|
||||
allQueries.find(
|
||||
(query) =>
|
||||
query.queryKey === selectedQueryKey.queryKey &&
|
||||
selectedQueryKey.reducerPath === query.reducerPath
|
||||
) || null;
|
||||
|
||||
return currentQueryInfo;
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
selectQueryComparator,
|
||||
selectApiStates,
|
||||
selectAllQueries,
|
||||
selectAllSortedQueries,
|
||||
selectorCurrentQueryInfo,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import jss, { StyleSheet } from 'jss';
|
||||
import preset from 'jss-preset-default';
|
||||
import { StylingConfig } from 'react-base16-styling';
|
||||
import {
|
||||
createStyling,
|
||||
getBase16Theme,
|
||||
|
@ -50,7 +51,7 @@ type ColorMap = {
|
|||
const getSheetFromColorMap = (map: ColorMap) => ({
|
||||
inspector: {
|
||||
display: 'flex',
|
||||
'flex-direction': 'column',
|
||||
flexFlow: 'column nowrap',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
'font-family': 'monaco, Consolas, "Lucida Console", monospace',
|
||||
|
@ -60,11 +61,30 @@ const getSheetFromColorMap = (map: ColorMap) => ({
|
|||
|
||||
'background-color': map.BACKGROUND_COLOR,
|
||||
color: map.TEXT_COLOR,
|
||||
|
||||
'&[data-wide-layout="1"]': {
|
||||
flexFlow: 'row nowrap',
|
||||
},
|
||||
},
|
||||
|
||||
querySectionWrapper: {
|
||||
display: 'flex',
|
||||
flex: '0 0 auto',
|
||||
height: '50%',
|
||||
width: '100%',
|
||||
borderColor: map.TAB_BORDER_COLOR,
|
||||
|
||||
'&[data-wide-layout="0"]': {
|
||||
borderBottomWidth: 1,
|
||||
borderStyle: 'solid',
|
||||
},
|
||||
|
||||
'&[data-wide-layout="1"]': {
|
||||
height: '100%',
|
||||
width: '40%',
|
||||
borderRightWidth: 1,
|
||||
borderStyle: 'solid',
|
||||
},
|
||||
flexFlow: 'column nowrap',
|
||||
'& > :first-child': {
|
||||
flex: '0 0 auto',
|
||||
|
@ -230,6 +250,47 @@ const getSheetFromColorMap = (map: ColorMap) => ({
|
|||
color: map.TEXT_PLACEHOLDER_COLOR,
|
||||
},
|
||||
},
|
||||
|
||||
queryPreview: {
|
||||
flex: '1 1 50%',
|
||||
display: 'flex',
|
||||
'flex-direction': 'column',
|
||||
'overflow-y': 'hidden',
|
||||
'& pre': {
|
||||
border: 'inherit',
|
||||
'border-radius': '3px',
|
||||
'line-height': 'inherit',
|
||||
color: 'inherit',
|
||||
},
|
||||
|
||||
'background-color': map.BACKGROUND_COLOR,
|
||||
},
|
||||
|
||||
previewHeader: {
|
||||
flex: '0 0 30px',
|
||||
padding: '5px 10px',
|
||||
'align-items': 'center',
|
||||
'border-bottom-width': '1px',
|
||||
'border-bottom-style': 'solid',
|
||||
|
||||
'background-color': map.HEADER_BACKGROUND_COLOR,
|
||||
'border-bottom-color': map.HEADER_BORDER_COLOR,
|
||||
},
|
||||
|
||||
treeItemPin: {
|
||||
'font-size': '0.7em',
|
||||
'padding-left': '5px',
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
'text-decoration': 'underline',
|
||||
},
|
||||
|
||||
color: map.PIN_COLOR,
|
||||
},
|
||||
|
||||
treeItemKey: {
|
||||
color: map.TEXT_PLACEHOLDER_COLOR,
|
||||
},
|
||||
});
|
||||
|
||||
let themeSheet: StyleSheet;
|
||||
|
@ -256,6 +317,7 @@ export const createStylingFromTheme = createStyling(getDefaultThemeStyling, {
|
|||
export interface StyleUtils {
|
||||
base16Theme: Base16Theme;
|
||||
styling: StylingFunction;
|
||||
invertTheme: boolean;
|
||||
}
|
||||
|
||||
export function createThemeState<S, A extends Action<unknown>>(
|
||||
|
@ -267,10 +329,31 @@ export function createThemeState<S, A extends Action<unknown>>(
|
|||
const theme = props.invertTheme ? invertTheme(props.theme) : props.theme;
|
||||
const styling = createStylingFromTheme(theme);
|
||||
|
||||
return { base16Theme, styling };
|
||||
return { base16Theme, styling, invertTheme: !!props.invertTheme };
|
||||
}
|
||||
|
||||
const mockStyling = (...args: any[]) => ({ className: '', style: {} });
|
||||
|
||||
export const StyleUtilsContext = createContext<StyleUtils>({
|
||||
base16Theme: rtkInspectorTheme,
|
||||
styling: (...args: any[]) => ({ className: '', style: {} }),
|
||||
invertTheme: false,
|
||||
styling: mockStyling,
|
||||
});
|
||||
|
||||
export function getJsonTreeTheme(base16Theme: Base16Theme): StylingConfig {
|
||||
return {
|
||||
extend: base16Theme,
|
||||
nestedNode: ({ style }, keyPath, nodeType, expanded) => ({
|
||||
style: {
|
||||
...style,
|
||||
whiteSpace: expanded ? 'inherit' : 'nowrap',
|
||||
},
|
||||
}),
|
||||
nestedNodeItemString: ({ style }, keyPath, nodeType, expanded) => ({
|
||||
style: {
|
||||
...style,
|
||||
display: expanded ? 'none' : 'inline',
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
import { StylingFunction } from 'react-base16-styling';
|
||||
import { isCollection, isIndexed, isKeyed } from 'immutable';
|
||||
import isIterable from '../utils/isIterable';
|
||||
|
||||
const IS_IMMUTABLE_KEY = '@@__IS_IMMUTABLE__@@';
|
||||
|
||||
function isImmutable(value: any) {
|
||||
return isKeyed(value) || isIndexed(value) || isCollection(value);
|
||||
}
|
||||
|
||||
function getShortTypeString(val: any, diff: boolean | undefined) {
|
||||
if (diff && Array.isArray(val)) {
|
||||
val = val[val.length === 2 ? 1 : 0];
|
||||
}
|
||||
|
||||
if (isIterable(val) && !isImmutable(val)) {
|
||||
return '(…)';
|
||||
} else if (Array.isArray(val)) {
|
||||
return val.length > 0 ? '[…]' : '[]';
|
||||
} else if (val === null) {
|
||||
return 'null';
|
||||
} else if (val === undefined) {
|
||||
return 'undef';
|
||||
} else if (typeof val === 'object') {
|
||||
return Object.keys(val).length > 0 ? '{…}' : '{}';
|
||||
} else if (typeof val === 'function') {
|
||||
return 'fn';
|
||||
} else if (typeof val === 'string') {
|
||||
return `"${val.substr(0, 10) + (val.length > 10 ? '…' : '')}"`;
|
||||
} else if (typeof val === 'symbol') {
|
||||
return 'symbol';
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function getText(
|
||||
type: string,
|
||||
data: any,
|
||||
isWideLayout: boolean,
|
||||
isDiff: boolean | undefined
|
||||
) {
|
||||
if (type === 'Object') {
|
||||
const keys = Object.keys(data);
|
||||
if (!isWideLayout) return keys.length ? '{…}' : '{}';
|
||||
|
||||
const str = keys
|
||||
.slice(0, 3)
|
||||
.map(
|
||||
(key) => `${key}: ${getShortTypeString(data[key], isDiff) as string}`
|
||||
)
|
||||
.concat(keys.length > 3 ? ['…'] : [])
|
||||
.join(', ');
|
||||
|
||||
return `{ ${str} }`;
|
||||
} else if (type === 'Array') {
|
||||
if (!isWideLayout) return data.length ? '[…]' : '[]';
|
||||
|
||||
const str = data
|
||||
.slice(0, 4)
|
||||
.map((val: any) => getShortTypeString(val, isDiff))
|
||||
.concat(data.length > 4 ? ['…'] : [])
|
||||
.join(', ');
|
||||
|
||||
return `[${str as string}]`;
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
export function getItemString(
|
||||
styling: StylingFunction,
|
||||
type: string,
|
||||
data: any,
|
||||
dataTypeKey: string | symbol | undefined,
|
||||
isWideLayout: boolean,
|
||||
isDiff?: boolean
|
||||
): ReactNode {
|
||||
return (
|
||||
<span {...styling('treeItemHint')}>
|
||||
{data[IS_IMMUTABLE_KEY] ? 'Immutable' : ''}
|
||||
{dataTypeKey && data[dataTypeKey]
|
||||
? `${data[dataTypeKey] as string} `
|
||||
: ''}
|
||||
{getText(type, data, isWideLayout, isDiff)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function createTreeItemLabelRenderer(styling: StylingFunction) {
|
||||
return function labelRenderer(
|
||||
[key]: (string | number)[],
|
||||
nodeType: string,
|
||||
expanded: boolean
|
||||
): ReactNode {
|
||||
return (
|
||||
<span>
|
||||
<span {...styling('treeItemKey')}>{key}</span>
|
||||
{!expanded && ': '}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
export default function isIterable(obj: any) {
|
||||
return (
|
||||
obj !== null &&
|
||||
typeof obj === 'object' &&
|
||||
!Array.isArray(obj) &&
|
||||
typeof obj[window.Symbol.iterator] === 'function'
|
||||
);
|
||||
}
|
|
@ -5,6 +5,5 @@
|
|||
"module": "ES2015",
|
||||
"strict": false
|
||||
},
|
||||
"include": ["src"],
|
||||
}
|
||||
|
||||
"include": ["src"]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user