mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-07-27 00:19:55 +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-redux": "^7.2.1",
|
||||||
"react-scripts": "4.0.2",
|
"react-scripts": "4.0.2",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
"redux-devtools-themes": "^1.0.0"
|
"redux-devtools-themes": "^1.0.0",
|
||||||
|
"react-json-tree": "^0.15.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "17.0.0",
|
"@types/react": "17.0.0",
|
||||||
|
|
|
@ -4,7 +4,10 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<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>
|
<title>Snowpack App</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -10,5 +10,5 @@ export default createDevTools(
|
||||||
changeMonitorKey="ctrl-m"
|
changeMonitorKey="ctrl-m"
|
||||||
>
|
>
|
||||||
<RtkQueryInspectorMonitor />
|
<RtkQueryInspectorMonitor />
|
||||||
</DockMonitor>,
|
</DockMonitor>
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,5 +12,5 @@ ReactDOM.render(
|
||||||
<App />
|
<App />
|
||||||
<DevTools />
|
<DevTools />
|
||||||
</Provider>,
|
</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`
|
// adding the api middleware enables caching, invalidation, polling and other features of `rtk-query`
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
getDefaultMiddleware().concat(pokemonApi.middleware),
|
getDefaultMiddleware().concat(pokemonApi.middleware),
|
||||||
enhancers: [DevTools.instrument()]
|
enhancers: [DevTools.instrument()],
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
{
|
{
|
||||||
"include": [
|
"include": ["./src/**/*"],
|
||||||
"./src/**/*"
|
|
||||||
],
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"lib": [
|
"lib": ["dom", "es2015"],
|
||||||
"dom",
|
|
||||||
"es2015"
|
|
||||||
],
|
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"allowJs": true,
|
"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"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
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:
|
react-redux@^7.2.1:
|
||||||
version "7.2.4"
|
version "7.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225"
|
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",
|
"@redux-devtools/dock-monitor": "^1.4.0",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"redux-devtools-themes": "^1.0.0",
|
"redux-devtools-themes": "^1.0.0",
|
||||||
"devui": "^1.0.0-8"
|
"devui": "^1.0.0-8",
|
||||||
|
"react-json-tree": "^0.15.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@redux-devtools/core": "^3.9.0",
|
"@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 { AnyAction, Dispatch, Action } from 'redux';
|
||||||
import { LiftedAction, LiftedState } from '@redux-devtools/core';
|
import { LiftedAction, LiftedState } from '@redux-devtools/core';
|
||||||
import * as themes from 'redux-devtools-themes';
|
import * as themes from 'redux-devtools-themes';
|
||||||
|
@ -9,11 +9,7 @@ import { selectQueryKey } from './reducers';
|
||||||
import { QueryList } from './components/QueryList';
|
import { QueryList } from './components/QueryList';
|
||||||
import { StyleUtils } from './styles/createStylingFromTheme';
|
import { StyleUtils } from './styles/createStylingFromTheme';
|
||||||
import { QueryForm } from './components/QueryForm';
|
import { QueryForm } from './components/QueryForm';
|
||||||
|
import { QueryPreview } from './components/QueryPreview';
|
||||||
const wrapperStyle: CSSProperties = {
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
};
|
|
||||||
|
|
||||||
type SelectorsSource<S> = {
|
type SelectorsSource<S> = {
|
||||||
currentState: S | null;
|
currentState: S | null;
|
||||||
|
@ -29,26 +25,34 @@ export interface RtkQueryInspectorProps<S, A extends Action<unknown>>
|
||||||
styleUtils: StyleUtils;
|
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<
|
class RtkQueryInspector<S, A extends Action<unknown>> extends Component<
|
||||||
RtkQueryInspectorProps<S, A>,
|
RtkQueryInspectorProps<S, A>,
|
||||||
RtkQueryInspectorState<S>
|
RtkQueryInspectorState<S>
|
||||||
> {
|
> {
|
||||||
divRef = createRef<HTMLDivElement>();
|
inspectorRef = createRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
isWideIntervalRef: number | NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
constructor(props: RtkQueryInspectorProps<S, A>) {
|
constructor(props: RtkQueryInspectorProps<S, A>) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
isWideLayout: true,
|
||||||
selectorsSource: computeSelectorSource(props, null),
|
selectorsSource: computeSelectorSource(props, null),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static wideLayout = 500;
|
||||||
|
|
||||||
static getDerivedStateFromProps(
|
static getDerivedStateFromProps(
|
||||||
props: RtkQueryInspectorProps<unknown, Action<unknown>>,
|
props: RtkQueryInspectorProps<unknown, Action<unknown>>,
|
||||||
state: RtkQueryInspectorState<unknown>
|
state: RtkQueryInspectorState<unknown>
|
||||||
): null | RtkQueryInspectorState<unknown> {
|
): null | Partial<RtkQueryInspectorState<unknown>> {
|
||||||
const selectorsSource = computeSelectorSource<unknown, Action<unknown>>(
|
const selectorsSource = computeSelectorSource<unknown, Action<unknown>>(
|
||||||
props,
|
props,
|
||||||
state.selectorsSource
|
state.selectorsSource
|
||||||
|
@ -65,12 +69,35 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends Component<
|
||||||
|
|
||||||
selectors = createInspectorSelectors<S>();
|
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);
|
this.props.dispatch(selectQueryKey(queryInfo) as AnyAction);
|
||||||
};
|
};
|
||||||
|
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
const { selectorsSource } = this.state;
|
const { selectorsSource, isWideLayout } = this.state;
|
||||||
const {
|
const {
|
||||||
styleUtils: { styling },
|
styleUtils: { styling },
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -79,11 +106,27 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends Component<
|
||||||
selectorsSource
|
selectorsSource
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('inspector', { apiStates, allSortedQueries, selectorsSource });
|
const currentQueryInfo = this.selectors.selectorCurrentQueryInfo(
|
||||||
|
selectorsSource
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('inspector', {
|
||||||
|
apiStates,
|
||||||
|
allSortedQueries,
|
||||||
|
selectorsSource,
|
||||||
|
currentQueryInfo,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={wrapperStyle} ref={this.divRef}>
|
<div
|
||||||
<div {...styling('querySectionWrapper')}>
|
ref={this.inspectorRef}
|
||||||
|
data-wide-layout={+this.state.isWideLayout}
|
||||||
|
{...styling('inspector')}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
{...styling('querySectionWrapper')}
|
||||||
|
data-wide-layout={+this.state.isWideLayout}
|
||||||
|
>
|
||||||
<QueryForm
|
<QueryForm
|
||||||
dispatch={this.props.dispatch}
|
dispatch={this.props.dispatch}
|
||||||
queryComparator={selectorsSource.monitorState.queryComparator}
|
queryComparator={selectorsSource.monitorState.queryComparator}
|
||||||
|
@ -97,6 +140,11 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends Component<
|
||||||
selectedQueryKey={selectorsSource.monitorState.selectedQueryKey}
|
selectedQueryKey={selectorsSource.monitorState.selectedQueryKey}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<QueryPreview
|
||||||
|
selectedQueryInfo={currentQueryInfo}
|
||||||
|
styling={styling}
|
||||||
|
isWideLayout={isWideLayout}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { CSSProperties, PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as themes from 'redux-devtools-themes';
|
import * as themes from 'redux-devtools-themes';
|
||||||
import { Action } from 'redux';
|
import { Action } from 'redux';
|
||||||
|
@ -15,18 +15,6 @@ import {
|
||||||
StyleUtils,
|
StyleUtils,
|
||||||
StyleUtilsContext,
|
StyleUtilsContext,
|
||||||
} from './styles/createStylingFromTheme';
|
} 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> {
|
interface DefaultProps<S> {
|
||||||
select: (state: unknown) => unknown;
|
select: (state: unknown) => unknown;
|
||||||
theme: keyof typeof themes | Base16Theme;
|
theme: keyof typeof themes | Base16Theme;
|
||||||
|
@ -85,20 +73,18 @@ class RtkQueryInspectorMonitor<
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
styleUtils: { base16Theme, styling },
|
styleUtils: { base16Theme },
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const RtkQueryInspectorAsAny = RtkQueryInspector as any;
|
const RtkQueryInspectorAsAny = RtkQueryInspector as any;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyleUtilsContext.Provider value={this.state.styleUtils}>
|
<StyleUtilsContext.Provider value={this.state.styleUtils}>
|
||||||
<div {...styling(['inspector'])}>
|
<RtkQueryInspectorAsAny
|
||||||
<RtkQueryInspectorAsAny
|
{...this.props}
|
||||||
{...this.props}
|
theme={base16Theme}
|
||||||
theme={base16Theme}
|
styleUtils={this.state.styleUtils}
|
||||||
styleUtils={this.state.styleUtils}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</StyleUtilsContext.Provider>
|
</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>
|
ReturnType<typeof extractAllApiQueries>
|
||||||
>;
|
>;
|
||||||
readonly selectAllSortedQueries: InspectorSelector<S, QueryInfo[]>;
|
readonly selectAllSortedQueries: InspectorSelector<S, QueryInfo[]>;
|
||||||
|
readonly selectorCurrentQueryInfo: InspectorSelector<S, QueryInfo | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
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 {
|
return {
|
||||||
selectQueryComparator,
|
selectQueryComparator,
|
||||||
selectApiStates,
|
selectApiStates,
|
||||||
selectAllQueries,
|
selectAllQueries,
|
||||||
selectAllSortedQueries,
|
selectAllSortedQueries,
|
||||||
|
selectorCurrentQueryInfo,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import jss, { StyleSheet } from 'jss';
|
import jss, { StyleSheet } from 'jss';
|
||||||
import preset from 'jss-preset-default';
|
import preset from 'jss-preset-default';
|
||||||
|
import { StylingConfig } from 'react-base16-styling';
|
||||||
import {
|
import {
|
||||||
createStyling,
|
createStyling,
|
||||||
getBase16Theme,
|
getBase16Theme,
|
||||||
|
@ -50,7 +51,7 @@ type ColorMap = {
|
||||||
const getSheetFromColorMap = (map: ColorMap) => ({
|
const getSheetFromColorMap = (map: ColorMap) => ({
|
||||||
inspector: {
|
inspector: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
'flex-direction': 'column',
|
flexFlow: 'column nowrap',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
'font-family': 'monaco, Consolas, "Lucida Console", monospace',
|
'font-family': 'monaco, Consolas, "Lucida Console", monospace',
|
||||||
|
@ -60,11 +61,30 @@ const getSheetFromColorMap = (map: ColorMap) => ({
|
||||||
|
|
||||||
'background-color': map.BACKGROUND_COLOR,
|
'background-color': map.BACKGROUND_COLOR,
|
||||||
color: map.TEXT_COLOR,
|
color: map.TEXT_COLOR,
|
||||||
|
|
||||||
|
'&[data-wide-layout="1"]': {
|
||||||
|
flexFlow: 'row nowrap',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
querySectionWrapper: {
|
querySectionWrapper: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
height: '100%',
|
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',
|
flexFlow: 'column nowrap',
|
||||||
'& > :first-child': {
|
'& > :first-child': {
|
||||||
flex: '0 0 auto',
|
flex: '0 0 auto',
|
||||||
|
@ -230,6 +250,47 @@ const getSheetFromColorMap = (map: ColorMap) => ({
|
||||||
color: map.TEXT_PLACEHOLDER_COLOR,
|
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;
|
let themeSheet: StyleSheet;
|
||||||
|
@ -256,6 +317,7 @@ export const createStylingFromTheme = createStyling(getDefaultThemeStyling, {
|
||||||
export interface StyleUtils {
|
export interface StyleUtils {
|
||||||
base16Theme: Base16Theme;
|
base16Theme: Base16Theme;
|
||||||
styling: StylingFunction;
|
styling: StylingFunction;
|
||||||
|
invertTheme: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createThemeState<S, A extends Action<unknown>>(
|
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 theme = props.invertTheme ? invertTheme(props.theme) : props.theme;
|
||||||
const styling = createStylingFromTheme(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>({
|
export const StyleUtilsContext = createContext<StyleUtils>({
|
||||||
base16Theme: rtkInspectorTheme,
|
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'
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,10 +1,9 @@
|
||||||
{
|
{
|
||||||
"extends": "../../tsconfig.react.base.json",
|
"extends": "../../tsconfig.react.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./demo/src/generated-module",
|
"outDir": "./demo/src/generated-module",
|
||||||
"module": "ES2015",
|
"module": "ES2015",
|
||||||
"strict": false
|
"strict": false
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user