+
+
> extends Component<
selectedQueryKey={selectorsSource.monitorState.selectedQueryKey}
/>
+
);
}
diff --git a/packages/redux-devtools-rtk-query-inspector-monitor/src/RtkQueryInspectorMonitor.tsx b/packages/redux-devtools-rtk-query-inspector-monitor/src/RtkQueryInspectorMonitor.tsx
index 47639055..6cb714c8 100644
--- a/packages/redux-devtools-rtk-query-inspector-monitor/src/RtkQueryInspectorMonitor.tsx
+++ b/packages/redux-devtools-rtk-query-inspector-monitor/src/RtkQueryInspectorMonitor.tsx
@@ -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
{
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 (
-
-
-
+
);
}
diff --git a/packages/redux-devtools-rtk-query-inspector-monitor/src/components/QueryPreview.tsx b/packages/redux-devtools-rtk-query-inspector-monitor/src/components/QueryPreview.tsx
new file mode 100644
index 00000000..7b0e0245
--- /dev/null
+++ b/packages/redux-devtools-rtk-query-inspector-monitor/src/components/QueryPreview.tsx
@@ -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 {
+ readonly labelRenderer: ReturnType;
+
+ constructor(props: QueryPreviewProps) {
+ super(props);
+
+ this.labelRenderer = createTreeItemLabelRenderer(this.props.styling);
+ }
+
+ render(): ReactNode {
+ const { selectedQueryInfo, isWideLayout } = this.props;
+
+ if (!selectedQueryInfo) {
+ return (
+
+ {({ styling }) => }
+
+ );
+ }
+
+ const {
+ query: {
+ endpointName,
+ fulfilledTimeStamp,
+ status,
+ startedTimeStamp,
+ data,
+ },
+ reducerPath,
+ } = selectedQueryInfo;
+
+ const startedAt = startedTimeStamp
+ ? new Date(startedTimeStamp).toISOString()
+ : '-';
+
+ const latestFetch = fulfilledTimeStamp
+ ? new Date(fulfilledTimeStamp).toISOString()
+ : '-';
+
+ return (
+
+ {({ styling, base16Theme, invertTheme }) => {
+ return (
+
+
+
+
+ - {`reducerPath: ${reducerPath ?? '-'}`}
+ - {`endpointName: ${endpointName ?? '-'}`}
+ - {`status: ${status}`}
+ - {`loaded at: ${latestFetch}`}
+ - {`requested at: ${startedAt}`}
+
+
+
+ getItemString(
+ styling,
+ type,
+ data,
+ DATA_TYPE_KEY,
+ isWideLayout
+ )
+ }
+ hideRoot
+ />
+
+
+
+ );
+ }}
+
+ );
+ }
+}
diff --git a/packages/redux-devtools-rtk-query-inspector-monitor/src/monitor-config.ts b/packages/redux-devtools-rtk-query-inspector-monitor/src/monitor-config.ts
new file mode 100644
index 00000000..803e1698
--- /dev/null
+++ b/packages/redux-devtools-rtk-query-inspector-monitor/src/monitor-config.ts
@@ -0,0 +1 @@
+export const DATA_TYPE_KEY = Symbol.for('__serializedType__');
diff --git a/packages/redux-devtools-rtk-query-inspector-monitor/src/selectors.ts b/packages/redux-devtools-rtk-query-inspector-monitor/src/selectors.ts
index 62b49247..63152f2b 100644
--- a/packages/redux-devtools-rtk-query-inspector-monitor/src/selectors.ts
+++ b/packages/redux-devtools-rtk-query-inspector-monitor/src/selectors.ts
@@ -49,6 +49,7 @@ export interface InspectorSelectors {
ReturnType
>;
readonly selectAllSortedQueries: InspectorSelector;
+ readonly selectorCurrentQueryInfo: InspectorSelector;
}
export function createInspectorSelectors(): InspectorSelectors {
@@ -85,10 +86,30 @@ export function createInspectorSelectors(): InspectorSelectors {
}
);
+ const selectorCurrentQueryInfo = createSelector(
+ selectAllQueries,
+ ({ monitorState }: SelectorsSource) => 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,
};
}
diff --git a/packages/redux-devtools-rtk-query-inspector-monitor/src/styles/createStylingFromTheme.ts b/packages/redux-devtools-rtk-query-inspector-monitor/src/styles/createStylingFromTheme.ts
index 8f420872..7ffb0ac0 100644
--- a/packages/redux-devtools-rtk-query-inspector-monitor/src/styles/createStylingFromTheme.ts
+++ b/packages/redux-devtools-rtk-query-inspector-monitor/src/styles/createStylingFromTheme.ts
@@ -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',
- 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',
'& > :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>(
@@ -267,10 +329,31 @@ export function createThemeState>(
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({
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',
+ },
+ }),
+ };
+}
diff --git a/packages/redux-devtools-rtk-query-inspector-monitor/src/styles/tree.tsx b/packages/redux-devtools-rtk-query-inspector-monitor/src/styles/tree.tsx
new file mode 100644
index 00000000..08fafb41
--- /dev/null
+++ b/packages/redux-devtools-rtk-query-inspector-monitor/src/styles/tree.tsx
@@ -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 (
+
+ {data[IS_IMMUTABLE_KEY] ? 'Immutable' : ''}
+ {dataTypeKey && data[dataTypeKey]
+ ? `${data[dataTypeKey] as string} `
+ : ''}
+ {getText(type, data, isWideLayout, isDiff)}
+
+ );
+}
+
+export function createTreeItemLabelRenderer(styling: StylingFunction) {
+ return function labelRenderer(
+ [key]: (string | number)[],
+ nodeType: string,
+ expanded: boolean
+ ): ReactNode {
+ return (
+
+ {key}
+ {!expanded && ': '}
+
+ );
+ };
+}
diff --git a/packages/redux-devtools-rtk-query-inspector-monitor/src/utils/isIterable.ts b/packages/redux-devtools-rtk-query-inspector-monitor/src/utils/isIterable.ts
new file mode 100644
index 00000000..a7226ad0
--- /dev/null
+++ b/packages/redux-devtools-rtk-query-inspector-monitor/src/utils/isIterable.ts
@@ -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'
+ );
+}
diff --git a/packages/redux-devtools-rtk-query-inspector-monitor/tsconfig.dev.json b/packages/redux-devtools-rtk-query-inspector-monitor/tsconfig.dev.json
index e057814a..852044b7 100644
--- a/packages/redux-devtools-rtk-query-inspector-monitor/tsconfig.dev.json
+++ b/packages/redux-devtools-rtk-query-inspector-monitor/tsconfig.dev.json
@@ -1,10 +1,9 @@
{
- "extends": "../../tsconfig.react.base.json",
- "compilerOptions": {
- "outDir": "./demo/src/generated-module",
- "module": "ES2015",
- "strict": false
- },
- "include": ["src"],
- }
-
\ No newline at end of file
+ "extends": "../../tsconfig.react.base.json",
+ "compilerOptions": {
+ "outDir": "./demo/src/generated-module",
+ "module": "ES2015",
+ "strict": false
+ },
+ "include": ["src"]
+}