mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-07-27 00:19:55 +03:00
refactor(rtk-query): reduce selector computations
Other changes: * simplify TreeView props
This commit is contained in:
parent
ad02c0ab26
commit
71b483bf02
|
@ -2,51 +2,101 @@ import React, { ReactNode } from 'react';
|
||||||
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
|
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
|
||||||
import { createTreeItemLabelRenderer } from '../styles/tree';
|
import { createTreeItemLabelRenderer } from '../styles/tree';
|
||||||
import {
|
import {
|
||||||
QueryPreviewTabOption,
|
|
||||||
QueryPreviewTabs,
|
QueryPreviewTabs,
|
||||||
QueryPreviewTabProps,
|
QueryInfo,
|
||||||
|
SelectorsSource,
|
||||||
|
TabOption,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { QueryPreviewHeader } from './QueryPreviewHeader';
|
import { QueryPreviewHeader } from './QueryPreviewHeader';
|
||||||
import { QueryPreviewInfo } from './QueryPreviewInfo';
|
import { QueryPreviewInfo, QueryPreviewInfoProps } from './QueryPreviewInfo';
|
||||||
import { QueryPreviewApi } from './QueryPreviewApi';
|
import { QueryPreviewApi, QueryPreviewApiProps } from './QueryPreviewApi';
|
||||||
import { QueryPreviewSubscriptions } from './QueryPreviewSubscriptions';
|
import {
|
||||||
import { QueryPreviewTags } from './QueryPreviewTags';
|
QueryPreviewSubscriptions,
|
||||||
|
QueryPreviewSubscriptionsProps,
|
||||||
|
} from './QueryPreviewSubscriptions';
|
||||||
|
import { QueryPreviewTags, QueryPreviewTagsProps } from './QueryPreviewTags';
|
||||||
import { NoRtkQueryApi } from './NoRtkQueryApi';
|
import { NoRtkQueryApi } from './NoRtkQueryApi';
|
||||||
|
import { InspectorSelectors } from '../selectors';
|
||||||
|
import { StylingFunction } from 'react-base16-styling';
|
||||||
|
import { mapProps } from '../containers/mapProps';
|
||||||
|
|
||||||
export interface QueryPreviewProps
|
export interface QueryPreviewProps<S = unknown> {
|
||||||
extends Omit<QueryPreviewTabProps, 'base16Theme' | 'invertTheme'> {
|
readonly selectedTab: QueryPreviewTabs;
|
||||||
selectedTab: QueryPreviewTabs;
|
readonly hasNoApis: boolean;
|
||||||
hasNoApis: boolean;
|
readonly onTabChange: (tab: QueryPreviewTabs) => void;
|
||||||
onTabChange: (tab: QueryPreviewTabs) => void;
|
readonly queryInfo: QueryInfo | null;
|
||||||
|
readonly styling: StylingFunction;
|
||||||
|
readonly isWideLayout: boolean;
|
||||||
|
readonly selectorsSource: SelectorsSource<S>;
|
||||||
|
readonly selectors: InspectorSelectors<S>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs: ReadonlyArray<QueryPreviewTabOption> = [
|
/**
|
||||||
|
* Tab content is not rendered if there's no selected query.
|
||||||
|
*/
|
||||||
|
type QueryPreviewTabProps = Omit<QueryPreviewProps<unknown>, 'queryInfo'> & {
|
||||||
|
queryInfo: QueryInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MappedQueryPreviewTags = mapProps<
|
||||||
|
QueryPreviewTabProps,
|
||||||
|
QueryPreviewTagsProps
|
||||||
|
>(({ selectors, selectorsSource, isWideLayout, queryInfo }) => ({
|
||||||
|
queryInfo,
|
||||||
|
tags: selectors.selectCurrentQueryTags(selectorsSource),
|
||||||
|
isWideLayout,
|
||||||
|
}))(QueryPreviewTags);
|
||||||
|
|
||||||
|
const MappedQueryPreviewInfo = mapProps<
|
||||||
|
QueryPreviewTabProps,
|
||||||
|
QueryPreviewInfoProps
|
||||||
|
>(({ queryInfo, isWideLayout }) => ({ queryInfo, isWideLayout }))(
|
||||||
|
QueryPreviewInfo
|
||||||
|
);
|
||||||
|
|
||||||
|
const MappedQuerySubscriptipns = mapProps<
|
||||||
|
QueryPreviewTabProps,
|
||||||
|
QueryPreviewSubscriptionsProps
|
||||||
|
>(({ selectors, selectorsSource, isWideLayout }) => ({
|
||||||
|
isWideLayout,
|
||||||
|
subscriptions: selectors.selectSubscriptionsOfCurrentQuery(selectorsSource),
|
||||||
|
}))(QueryPreviewSubscriptions);
|
||||||
|
|
||||||
|
const MappedApiPreview = mapProps<QueryPreviewTabProps, QueryPreviewApiProps>(
|
||||||
|
({ isWideLayout, selectors, selectorsSource }) => ({
|
||||||
|
isWideLayout,
|
||||||
|
apiState: selectors.selectApiOfCurrentQuery(selectorsSource),
|
||||||
|
apiStats: selectors.selectApiStatsOfCurrentQuery(selectorsSource),
|
||||||
|
})
|
||||||
|
)(QueryPreviewApi);
|
||||||
|
|
||||||
|
const tabs: ReadonlyArray<TabOption<QueryPreviewTabs, QueryPreviewTabProps>> = [
|
||||||
{
|
{
|
||||||
label: 'query',
|
label: 'query',
|
||||||
value: QueryPreviewTabs.queryinfo,
|
value: QueryPreviewTabs.queryinfo,
|
||||||
component: QueryPreviewInfo,
|
component: MappedQueryPreviewInfo,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'tags',
|
label: 'tags',
|
||||||
value: QueryPreviewTabs.queryTags,
|
value: QueryPreviewTabs.queryTags,
|
||||||
component: QueryPreviewTags,
|
component: MappedQueryPreviewTags,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'subs',
|
label: 'subs',
|
||||||
value: QueryPreviewTabs.querySubscriptions,
|
value: QueryPreviewTabs.querySubscriptions,
|
||||||
component: QueryPreviewSubscriptions,
|
component: MappedQuerySubscriptipns,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'api',
|
label: 'api',
|
||||||
value: QueryPreviewTabs.apiConfig,
|
value: QueryPreviewTabs.apiConfig,
|
||||||
component: QueryPreviewApi,
|
component: MappedApiPreview,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export class QueryPreview extends React.PureComponent<QueryPreviewProps> {
|
export class QueryPreview<S> extends React.PureComponent<QueryPreviewProps<S>> {
|
||||||
readonly labelRenderer: ReturnType<typeof createTreeItemLabelRenderer>;
|
readonly labelRenderer: ReturnType<typeof createTreeItemLabelRenderer>;
|
||||||
|
|
||||||
constructor(props: QueryPreviewProps) {
|
constructor(props: QueryPreviewProps<S>) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.labelRenderer = createTreeItemLabelRenderer(this.props.styling);
|
this.labelRenderer = createTreeItemLabelRenderer(this.props.styling);
|
||||||
|
@ -65,40 +115,19 @@ export class QueryPreview extends React.PureComponent<QueryPreviewProps> {
|
||||||
return `${label} (${counterAsString})`;
|
return `${label} (${counterAsString})`;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderTabLabel = (tab: QueryPreviewTabOption): ReactNode => {
|
renderTabLabel = (tab: TabOption<QueryPreviewTabs, unknown>): ReactNode => {
|
||||||
const { queryInfo, tags, querySubscriptions } = this.props;
|
const { selectors, selectorsSource } = this.props;
|
||||||
if (queryInfo) {
|
const tabCount = selectors.selectTabCounters(selectorsSource)[tab.value];
|
||||||
if (tab.value === QueryPreviewTabs.queryTags && tags.length > 0) {
|
|
||||||
return this.renderLabelWithCounter(tab.label, tags.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (tabCount > 0) {
|
||||||
tab.value === QueryPreviewTabs.querySubscriptions &&
|
return this.renderLabelWithCounter(tab.label, tabCount);
|
||||||
querySubscriptions
|
|
||||||
) {
|
|
||||||
const subsCount = Object.keys(querySubscriptions).length;
|
|
||||||
|
|
||||||
if (subsCount > 0) {
|
|
||||||
return this.renderLabelWithCounter(tab.label, subsCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tab.label;
|
return tab.label;
|
||||||
};
|
};
|
||||||
|
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
const {
|
const { queryInfo, selectedTab, onTabChange, hasNoApis } = this.props;
|
||||||
queryInfo,
|
|
||||||
isWideLayout,
|
|
||||||
selectedTab,
|
|
||||||
apiState,
|
|
||||||
onTabChange,
|
|
||||||
querySubscriptions,
|
|
||||||
tags,
|
|
||||||
apiStats,
|
|
||||||
hasNoApis,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const { component: TabComponent } =
|
const { component: TabComponent } =
|
||||||
tabs.find((tab) => tab.value === selectedTab) || tabs[0];
|
tabs.find((tab) => tab.value === selectedTab) || tabs[0];
|
||||||
|
@ -111,7 +140,9 @@ export class QueryPreview extends React.PureComponent<QueryPreviewProps> {
|
||||||
<QueryPreviewHeader
|
<QueryPreviewHeader
|
||||||
selectedTab={selectedTab}
|
selectedTab={selectedTab}
|
||||||
onTabChange={onTabChange}
|
onTabChange={onTabChange}
|
||||||
tabs={tabs}
|
tabs={
|
||||||
|
tabs as ReadonlyArray<TabOption<QueryPreviewTabs, unknown>>
|
||||||
|
}
|
||||||
renderTabLabel={this.renderTabLabel}
|
renderTabLabel={this.renderTabLabel}
|
||||||
/>
|
/>
|
||||||
{hasNoApis && <NoRtkQueryApi />}
|
{hasNoApis && <NoRtkQueryApi />}
|
||||||
|
@ -123,26 +154,18 @@ export class QueryPreview extends React.PureComponent<QueryPreviewProps> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyleUtilsContext.Consumer>
|
<StyleUtilsContext.Consumer>
|
||||||
{({ styling, base16Theme, invertTheme }) => {
|
{({ styling }) => {
|
||||||
return (
|
return (
|
||||||
<div {...styling('queryPreview')}>
|
<div {...styling('queryPreview')}>
|
||||||
<QueryPreviewHeader
|
<QueryPreviewHeader
|
||||||
selectedTab={selectedTab}
|
selectedTab={selectedTab}
|
||||||
onTabChange={onTabChange}
|
onTabChange={onTabChange}
|
||||||
tabs={tabs}
|
tabs={
|
||||||
|
tabs as ReadonlyArray<TabOption<QueryPreviewTabs, unknown>>
|
||||||
|
}
|
||||||
renderTabLabel={this.renderTabLabel}
|
renderTabLabel={this.renderTabLabel}
|
||||||
/>
|
/>
|
||||||
<TabComponent
|
<TabComponent {...(this.props as QueryPreviewTabProps)} />
|
||||||
styling={styling}
|
|
||||||
base16Theme={base16Theme}
|
|
||||||
invertTheme={invertTheme}
|
|
||||||
querySubscriptions={querySubscriptions}
|
|
||||||
queryInfo={queryInfo}
|
|
||||||
tags={tags}
|
|
||||||
apiState={apiState}
|
|
||||||
isWideLayout={isWideLayout}
|
|
||||||
apiStats={apiStats}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import React, { ReactNode, PureComponent } from 'react';
|
import React, { ReactNode, PureComponent } from 'react';
|
||||||
import { QueryPreviewTabProps } from '../types';
|
import { ApiStats, RtkQueryApiState } from '../types';
|
||||||
import { TreeView } from './TreeView';
|
import { TreeView } from './TreeView';
|
||||||
|
|
||||||
interface TreeDisplayed {
|
export interface QueryPreviewApiProps {
|
||||||
reducerPath: string;
|
apiStats: ApiStats | null;
|
||||||
api: QueryPreviewTabProps['apiState'];
|
apiState: RtkQueryApiState | null;
|
||||||
stats: QueryPreviewTabProps['apiStats'];
|
isWideLayout: boolean;
|
||||||
}
|
}
|
||||||
export class QueryPreviewApi extends PureComponent<QueryPreviewTabProps> {
|
|
||||||
|
export class QueryPreviewApi extends PureComponent<QueryPreviewApiProps> {
|
||||||
selectData = createSelector(
|
selectData = createSelector(
|
||||||
[
|
[
|
||||||
({ apiState }: QueryPreviewTabProps) => apiState,
|
({ apiState }: QueryPreviewApiProps) => apiState,
|
||||||
({ apiStats }: QueryPreviewTabProps) => apiStats,
|
({ apiStats }: QueryPreviewApiProps) => apiStats,
|
||||||
],
|
],
|
||||||
(apiState, apiStats) => ({
|
(apiState, apiStats) => ({
|
||||||
reducerPath: apiState?.config?.reducerPath ?? null,
|
reducerPath: apiState?.config?.reducerPath ?? null,
|
||||||
|
@ -22,20 +23,10 @@ export class QueryPreviewApi extends PureComponent<QueryPreviewTabProps> {
|
||||||
);
|
);
|
||||||
|
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
const { queryInfo, isWideLayout, base16Theme, styling, invertTheme } =
|
|
||||||
this.props;
|
|
||||||
|
|
||||||
if (!queryInfo) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TreeView
|
<TreeView
|
||||||
data={this.selectData(this.props)}
|
data={this.selectData(this.props)}
|
||||||
isWideLayout={isWideLayout}
|
isWideLayout={this.props.isWideLayout}
|
||||||
base16Theme={base16Theme}
|
|
||||||
styling={styling}
|
|
||||||
invertTheme={invertTheme}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import React, { ReactNode } from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
|
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
|
||||||
import { QueryPreviewTabOption, QueryPreviewTabs } from '../types';
|
import { QueryPreviewTabs, TabOption } from '../types';
|
||||||
import { emptyArray } from '../utils/object';
|
import { emptyArray } from '../utils/object';
|
||||||
|
|
||||||
export interface QueryPreviewHeaderProps {
|
export interface QueryPreviewHeaderProps {
|
||||||
tabs: ReadonlyArray<QueryPreviewTabOption>;
|
tabs: ReadonlyArray<TabOption<QueryPreviewTabs, unknown>>;
|
||||||
onTabChange: (tab: QueryPreviewTabs) => void;
|
onTabChange: (tab: QueryPreviewTabs) => void;
|
||||||
selectedTab: QueryPreviewTabs;
|
selectedTab: QueryPreviewTabs;
|
||||||
renderTabLabel?: (tab: QueryPreviewTabOption) => ReactNode;
|
renderTabLabel?: (tab: QueryPreviewHeaderProps['tabs'][number]) => ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QueryPreviewHeader extends React.Component<QueryPreviewHeaderProps> {
|
export class QueryPreviewHeader extends React.Component<QueryPreviewHeaderProps> {
|
||||||
handleTabClick = (tab: QueryPreviewTabOption): void => {
|
handleTabClick = (tab: QueryPreviewHeaderProps['tabs'][number]): void => {
|
||||||
if (this.props.selectedTab !== tab.value) {
|
if (this.props.selectedTab !== tab.value) {
|
||||||
this.props.onTabChange(tab.value);
|
this.props.onTabChange(tab.value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
import { createSelector, Selector } from '@reduxjs/toolkit';
|
import { createSelector, Selector } from '@reduxjs/toolkit';
|
||||||
import React, { ReactNode, PureComponent } from 'react';
|
import React, { ReactNode, PureComponent } from 'react';
|
||||||
import {
|
import { QueryInfo, RtkQueryState, RTKStatusFlags } from '../types';
|
||||||
QueryInfo,
|
|
||||||
QueryPreviewTabProps,
|
|
||||||
RtkQueryState,
|
|
||||||
RTKStatusFlags,
|
|
||||||
} from '../types';
|
|
||||||
import { identity } from '../utils/object';
|
import { identity } from '../utils/object';
|
||||||
import { getQueryStatusFlags } from '../utils/rtk-query';
|
import { getQueryStatusFlags } from '../utils/rtk-query';
|
||||||
import { TreeView } from './TreeView';
|
import { TreeView } from './TreeView';
|
||||||
|
@ -22,17 +17,14 @@ interface FormattedQuery extends ComputedQueryInfo {
|
||||||
query: RtkQueryState;
|
query: RtkQueryState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QueryPreviewInfo extends PureComponent<QueryPreviewTabProps> {
|
export interface QueryPreviewInfoProps {
|
||||||
selectFormattedQuery: Selector<
|
queryInfo: QueryInfo;
|
||||||
QueryPreviewTabProps['queryInfo'],
|
isWideLayout: boolean;
|
||||||
FormattedQuery | null
|
}
|
||||||
> = createSelector(
|
export class QueryPreviewInfo extends PureComponent<QueryPreviewInfoProps> {
|
||||||
|
selectFormattedQuery: Selector<QueryInfo, FormattedQuery> = createSelector(
|
||||||
identity,
|
identity,
|
||||||
(queryInfo: QueryInfo | null): FormattedQuery | null => {
|
(queryInfo: QueryInfo): FormattedQuery => {
|
||||||
if (!queryInfo) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { query, queryKey, reducerPath } = queryInfo;
|
const { query, queryKey, reducerPath } = queryInfo;
|
||||||
|
|
||||||
const startedAt = query.startedTimeStamp
|
const startedAt = query.startedTimeStamp
|
||||||
|
@ -57,23 +49,9 @@ export class QueryPreviewInfo extends PureComponent<QueryPreviewTabProps> {
|
||||||
);
|
);
|
||||||
|
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
const { queryInfo, isWideLayout, base16Theme, styling, invertTheme } =
|
const { queryInfo, isWideLayout } = this.props;
|
||||||
this.props;
|
|
||||||
|
|
||||||
const formattedQuery = this.selectFormattedQuery(queryInfo);
|
const formattedQuery = this.selectFormattedQuery(queryInfo);
|
||||||
|
|
||||||
if (!formattedQuery) {
|
return <TreeView data={formattedQuery} isWideLayout={isWideLayout} />;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TreeView
|
|
||||||
data={formattedQuery}
|
|
||||||
isWideLayout={isWideLayout}
|
|
||||||
base16Theme={base16Theme}
|
|
||||||
styling={styling}
|
|
||||||
invertTheme={invertTheme}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,60 +1,18 @@
|
||||||
import React, { ReactNode, PureComponent } from 'react';
|
import React, { ReactNode, PureComponent } from 'react';
|
||||||
import { QueryPreviewTabProps } from '../types';
|
import { RtkQueryApiState } from '../types';
|
||||||
import { TreeView } from './TreeView';
|
import { TreeView } from './TreeView';
|
||||||
|
|
||||||
export interface QueryPreviewSubscriptionsState {
|
export interface QueryPreviewSubscriptionsProps {
|
||||||
data: { subscriptions: QueryPreviewTabProps['querySubscriptions'] };
|
subscriptions: RtkQueryApiState['subscriptions'][keyof RtkQueryApiState['subscriptions']];
|
||||||
|
isWideLayout: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QueryPreviewSubscriptions extends PureComponent<
|
export class QueryPreviewSubscriptions extends PureComponent<QueryPreviewSubscriptionsProps> {
|
||||||
QueryPreviewTabProps,
|
|
||||||
QueryPreviewSubscriptionsState
|
|
||||||
> {
|
|
||||||
static getDerivedStateFromProps(
|
|
||||||
props: QueryPreviewTabProps,
|
|
||||||
state: QueryPreviewSubscriptionsState
|
|
||||||
): Partial<QueryPreviewSubscriptionsState> | null {
|
|
||||||
if (props.querySubscriptions !== state.data.subscriptions) {
|
|
||||||
return {
|
|
||||||
data: { subscriptions: props.querySubscriptions },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props: QueryPreviewTabProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
data: { subscriptions: props.querySubscriptions },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
const {
|
const { subscriptions } = this.props;
|
||||||
queryInfo,
|
|
||||||
isWideLayout,
|
|
||||||
base16Theme,
|
|
||||||
styling,
|
|
||||||
invertTheme,
|
|
||||||
querySubscriptions,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
if (!querySubscriptions || !queryInfo) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<TreeView data={subscriptions} isWideLayout={this.props.isWideLayout} />
|
||||||
<TreeView
|
|
||||||
data={this.state.data}
|
|
||||||
isWideLayout={isWideLayout}
|
|
||||||
base16Theme={base16Theme}
|
|
||||||
styling={styling}
|
|
||||||
invertTheme={invertTheme}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,21 @@
|
||||||
import React, { ReactNode, PureComponent } from 'react';
|
import React, { ReactNode, PureComponent } from 'react';
|
||||||
import { QueryPreviewTabProps } from '../types';
|
import { RtkQueryTag } from '../types';
|
||||||
import { TreeView } from './TreeView';
|
import { TreeView } from './TreeView';
|
||||||
|
|
||||||
interface QueryPreviewTagsState {
|
interface QueryPreviewTagsState {
|
||||||
data: { tags: QueryPreviewTabProps['tags'] };
|
data: { tags: RtkQueryTag[] };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueryPreviewTagsProps {
|
||||||
|
tags: RtkQueryTag[];
|
||||||
|
isWideLayout: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QueryPreviewTags extends PureComponent<
|
export class QueryPreviewTags extends PureComponent<
|
||||||
QueryPreviewTabProps,
|
QueryPreviewTagsProps,
|
||||||
QueryPreviewTagsState
|
QueryPreviewTagsState
|
||||||
> {
|
> {
|
||||||
static getDerivedStateFromProps(
|
constructor(props: QueryPreviewTagsProps) {
|
||||||
{ tags }: QueryPreviewTabProps,
|
|
||||||
state: QueryPreviewTagsState
|
|
||||||
): QueryPreviewTagsState | null {
|
|
||||||
if (tags !== state.data.tags) {
|
|
||||||
return {
|
|
||||||
data: { tags },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props: QueryPreviewTabProps) {
|
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -32,21 +24,8 @@ export class QueryPreviewTags extends PureComponent<
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
const { queryInfo, isWideLayout, base16Theme, styling, invertTheme } =
|
const { isWideLayout, tags } = this.props;
|
||||||
this.props;
|
|
||||||
|
|
||||||
if (!queryInfo) {
|
return <TreeView data={tags} isWideLayout={isWideLayout} />;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TreeView
|
|
||||||
data={this.state.data}
|
|
||||||
isWideLayout={isWideLayout}
|
|
||||||
base16Theme={base16Theme}
|
|
||||||
styling={styling}
|
|
||||||
invertTheme={invertTheme}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import React, { ComponentProps, ReactNode } from 'react';
|
import React, { ComponentProps, ReactNode } from 'react';
|
||||||
import JSONTree from 'react-json-tree';
|
import JSONTree from 'react-json-tree';
|
||||||
|
import { StylingFunction } from 'react-base16-styling';
|
||||||
import { DATA_TYPE_KEY } from '../monitor-config';
|
import { DATA_TYPE_KEY } from '../monitor-config';
|
||||||
import { getJsonTreeTheme } from '../styles/createStylingFromTheme';
|
import {
|
||||||
|
getJsonTreeTheme,
|
||||||
|
StyleUtilsContext,
|
||||||
|
} from '../styles/createStylingFromTheme';
|
||||||
import { createTreeItemLabelRenderer, getItemString } from '../styles/tree';
|
import { createTreeItemLabelRenderer, getItemString } from '../styles/tree';
|
||||||
import { StyleUtils } from '../types';
|
import { identity } from '../utils/object';
|
||||||
|
|
||||||
export interface TreeViewProps extends StyleUtils {
|
export interface TreeViewProps {
|
||||||
data: unknown;
|
data: unknown;
|
||||||
isWideLayout: boolean;
|
isWideLayout: boolean;
|
||||||
before?: ReactNode;
|
before?: ReactNode;
|
||||||
|
@ -15,43 +20,48 @@ export interface TreeViewProps extends StyleUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TreeView extends React.PureComponent<TreeViewProps> {
|
export class TreeView extends React.PureComponent<TreeViewProps> {
|
||||||
readonly labelRenderer: ReturnType<typeof createTreeItemLabelRenderer>;
|
readonly selectLabelRenderer = createSelector<
|
||||||
|
StylingFunction,
|
||||||
|
StylingFunction,
|
||||||
|
ReturnType<typeof createTreeItemLabelRenderer>
|
||||||
|
>(identity, createTreeItemLabelRenderer);
|
||||||
|
|
||||||
constructor(props: TreeViewProps) {
|
constructor(props: TreeViewProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.labelRenderer = createTreeItemLabelRenderer(this.props.styling);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
const {
|
const { isWideLayout, data, before, after, children, keyPath } = this.props;
|
||||||
styling,
|
|
||||||
base16Theme,
|
|
||||||
invertTheme,
|
|
||||||
isWideLayout,
|
|
||||||
data,
|
|
||||||
before,
|
|
||||||
after,
|
|
||||||
children,
|
|
||||||
keyPath,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div {...styling('treeWrapper')}>
|
<StyleUtilsContext.Consumer>
|
||||||
{before}
|
{({ styling, invertTheme, base16Theme }) => {
|
||||||
<JSONTree
|
return (
|
||||||
keyPath={keyPath}
|
<div {...styling('treeWrapper')}>
|
||||||
data={data}
|
{before}
|
||||||
labelRenderer={this.labelRenderer}
|
<JSONTree
|
||||||
theme={getJsonTreeTheme(base16Theme)}
|
keyPath={keyPath}
|
||||||
invertTheme={invertTheme}
|
data={data}
|
||||||
getItemString={(type, data) =>
|
labelRenderer={this.selectLabelRenderer(styling)}
|
||||||
getItemString(styling, type, data, DATA_TYPE_KEY, isWideLayout)
|
theme={getJsonTreeTheme(base16Theme)}
|
||||||
}
|
invertTheme={invertTheme}
|
||||||
hideRoot
|
getItemString={(type, data) =>
|
||||||
/>
|
getItemString(
|
||||||
{after}
|
styling,
|
||||||
{children}
|
type,
|
||||||
</div>
|
data,
|
||||||
|
DATA_TYPE_KEY,
|
||||||
|
isWideLayout
|
||||||
|
)
|
||||||
|
}
|
||||||
|
hideRoot
|
||||||
|
/>
|
||||||
|
{after}
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</StyleUtilsContext.Consumer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ import {
|
||||||
import { QueryList } from '../components/QueryList';
|
import { QueryList } from '../components/QueryList';
|
||||||
import { QueryForm } from '../components/QueryForm';
|
import { QueryForm } from '../components/QueryForm';
|
||||||
import { QueryPreview } from '../components/QueryPreview';
|
import { QueryPreview } from '../components/QueryPreview';
|
||||||
import { getApiStateOf, getQuerySubscriptionsOf } from '../utils/rtk-query';
|
|
||||||
|
|
||||||
type ForwardedMonitorProps<S, A extends Action<unknown>> = Pick<
|
type ForwardedMonitorProps<S, A extends Action<unknown>> = Pick<
|
||||||
LiftedState<S, A, RtkQueryMonitorState>,
|
LiftedState<S, A, RtkQueryMonitorState>,
|
||||||
|
@ -115,26 +114,15 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends PureComponent<
|
||||||
const {
|
const {
|
||||||
styleUtils: { styling },
|
styleUtils: { styling },
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const apiStates = this.selectors.selectApiStates(selectorsSource);
|
|
||||||
const allVisibleQueries =
|
const allVisibleQueries =
|
||||||
this.selectors.selectAllVisbileQueries(selectorsSource);
|
this.selectors.selectAllVisbileQueries(selectorsSource);
|
||||||
|
|
||||||
const currentQueryInfo =
|
const currentQueryInfo =
|
||||||
this.selectors.selectCurrentQueryInfo(selectorsSource);
|
this.selectors.selectCurrentQueryInfo(selectorsSource);
|
||||||
|
|
||||||
const currentRtkApi =
|
const apiStates = this.selectors.selectApiStates(selectorsSource);
|
||||||
this.selectors.selectApiOfCurrentQuery(selectorsSource);
|
|
||||||
const currentQuerySubscriptions = getQuerySubscriptionsOf(
|
|
||||||
currentQueryInfo,
|
|
||||||
apiStates
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentTags = this.selectors.selectCurrentQueryTags(selectorsSource);
|
const hasNoApi = apiStates == null;
|
||||||
|
|
||||||
const currentApiStats =
|
|
||||||
this.selectors.selectApiStatsOfCurrentQuery(selectorsSource);
|
|
||||||
|
|
||||||
const hasNoApis = apiStates == null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -156,17 +144,15 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends PureComponent<
|
||||||
selectedQueryKey={selectorsSource.monitorState.selectedQueryKey}
|
selectedQueryKey={selectorsSource.monitorState.selectedQueryKey}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<QueryPreview
|
<QueryPreview<S>
|
||||||
|
selectorsSource={this.state.selectorsSource}
|
||||||
|
selectors={this.selectors}
|
||||||
queryInfo={currentQueryInfo}
|
queryInfo={currentQueryInfo}
|
||||||
selectedTab={selectorsSource.monitorState.selectedPreviewTab}
|
selectedTab={selectorsSource.monitorState.selectedPreviewTab}
|
||||||
onTabChange={this.handleTabChange}
|
onTabChange={this.handleTabChange}
|
||||||
styling={styling}
|
styling={styling}
|
||||||
tags={currentTags}
|
|
||||||
querySubscriptions={currentQuerySubscriptions}
|
|
||||||
apiState={currentRtkApi}
|
|
||||||
isWideLayout={isWideLayout}
|
isWideLayout={isWideLayout}
|
||||||
apiStats={currentApiStats}
|
hasNoApis={hasNoApi}
|
||||||
hasNoApis={hasNoApis}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import React, { ComponentType, ReactNode, Component } from 'react';
|
||||||
|
|
||||||
|
interface Mapper<In, Out> {
|
||||||
|
(inProps: In): Out;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MapPropsOutput<In, Out> {
|
||||||
|
(comp: ComponentType<Out>): ComponentType<In>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapProps<In, Out>(
|
||||||
|
mapper: Mapper<In, Out>
|
||||||
|
): MapPropsOutput<In, Out> {
|
||||||
|
return function mapPropsHoc(Comp) {
|
||||||
|
class MapPropsHoc extends Component<In> {
|
||||||
|
render(): ReactNode {
|
||||||
|
const mappedProps = mapper(this.props);
|
||||||
|
|
||||||
|
return <Comp {...mappedProps} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
static displayName = `mapProps(${
|
||||||
|
Comp.displayName || Comp.name || 'Component'
|
||||||
|
})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MapPropsHoc;
|
||||||
|
};
|
||||||
|
}
|
|
@ -7,9 +7,11 @@ import {
|
||||||
RtkQueryTag,
|
RtkQueryTag,
|
||||||
SelectorsSource,
|
SelectorsSource,
|
||||||
RtkQueryProvided,
|
RtkQueryProvided,
|
||||||
|
QueryPreviewTabs,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { Comparator, queryComparators } from './utils/comparators';
|
import { Comparator, queryComparators } from './utils/comparators';
|
||||||
import { FilterList, queryListFilters } from './utils/filters';
|
import { FilterList, queryListFilters } from './utils/filters';
|
||||||
|
import { emptyRecord } from './utils/object';
|
||||||
import { escapeRegExpSpecialCharacter } from './utils/regexp';
|
import { escapeRegExpSpecialCharacter } from './utils/regexp';
|
||||||
import {
|
import {
|
||||||
getApiStatesOf,
|
getApiStatesOf,
|
||||||
|
@ -63,6 +65,14 @@ export interface InspectorSelectors<S> {
|
||||||
S,
|
S,
|
||||||
RtkQueryApiState | null
|
RtkQueryApiState | null
|
||||||
>;
|
>;
|
||||||
|
readonly selectTabCounters: InspectorSelector<
|
||||||
|
S,
|
||||||
|
Record<QueryPreviewTabs, number>
|
||||||
|
>;
|
||||||
|
readonly selectSubscriptionsOfCurrentQuery: InspectorSelector<
|
||||||
|
S,
|
||||||
|
RtkQueryApiState['subscriptions'][string]
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
||||||
|
@ -161,6 +171,17 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
||||||
return selectApiOfCurrentQuery(selectorsSource)?.provided ?? null;
|
return selectApiOfCurrentQuery(selectorsSource)?.provided ?? null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectSubscriptionsOfCurrentQuery = createSelector(
|
||||||
|
[selectApiOfCurrentQuery, selectCurrentQueryInfo],
|
||||||
|
(apiState, queryInfo) => {
|
||||||
|
if (!queryInfo || !apiState) {
|
||||||
|
return emptyRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiState.subscriptions[queryInfo.queryKey];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const selectCurrentQueryTags = createSelector(
|
const selectCurrentQueryTags = createSelector(
|
||||||
[selectCurrentQueryInfo, selectProvidedOfCurrentQuery],
|
[selectCurrentQueryInfo, selectProvidedOfCurrentQuery],
|
||||||
getQueryTagsOf
|
getQueryTagsOf
|
||||||
|
@ -171,6 +192,21 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
||||||
generateApiStatsOfCurrentQuery
|
generateApiStatsOfCurrentQuery
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const selectTabCounters = (
|
||||||
|
selectorsSource: SelectorsSource<S>
|
||||||
|
): Record<QueryPreviewTabs, number> => {
|
||||||
|
const subscriptions = selectSubscriptionsOfCurrentQuery(selectorsSource);
|
||||||
|
const subsLen = Object.keys(subscriptions ?? {}).length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
[QueryPreviewTabs.queryTags]:
|
||||||
|
selectCurrentQueryTags(selectorsSource).length,
|
||||||
|
[QueryPreviewTabs.querySubscriptions]: subsLen,
|
||||||
|
[QueryPreviewTabs.apiConfig]: 0,
|
||||||
|
[QueryPreviewTabs.queryinfo]: 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectQueryComparator,
|
selectQueryComparator,
|
||||||
selectApiStates,
|
selectApiStates,
|
||||||
|
@ -180,6 +216,8 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
||||||
selectCurrentQueryInfo,
|
selectCurrentQueryInfo,
|
||||||
selectCurrentQueryTags,
|
selectCurrentQueryTags,
|
||||||
selectApiStatsOfCurrentQuery,
|
selectApiStatsOfCurrentQuery,
|
||||||
|
selectSubscriptionsOfCurrentQuery,
|
||||||
selectApiOfCurrentQuery,
|
selectApiOfCurrentQuery,
|
||||||
|
selectTabCounters,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,24 +132,10 @@ export interface ApiStats {
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryPreviewTabProps extends StyleUtils {
|
|
||||||
queryInfo: QueryInfo | null;
|
|
||||||
apiState: RtkQueryApiState | null;
|
|
||||||
querySubscriptions: RTKQuerySubscribers | null;
|
|
||||||
isWideLayout: boolean;
|
|
||||||
tags: RtkQueryTag[];
|
|
||||||
apiStats: ApiStats | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TabOption<S, P> extends SelectOption<S> {
|
export interface TabOption<S, P> extends SelectOption<S> {
|
||||||
component: ComponentType<P>;
|
component: ComponentType<P>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type QueryPreviewTabOption = TabOption<
|
|
||||||
QueryPreviewTabs,
|
|
||||||
QueryPreviewTabProps
|
|
||||||
>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It is Omit<RequestStatusFlags, 'status'> & { isFetching: boolean; }
|
* It is Omit<RequestStatusFlags, 'status'> & { isFetching: boolean; }
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue
Block a user