[rtk-query-monitor] Replace jss with Emotion (#1568)

* Add Emotion to RTK Query monitor

* Add other Emotion setup

* Start transition

* Convert more styling to Emotion

* Convert more styling to Emotion

* Finish convert styling to Emotion

* import type

* Fix test

* Remove unused styling

* Remove more unused styling

* Remove more unused styling

* Remove jss

* Cleanup

* Create perfect-otters-help.md

* Update perfect-otters-help.md
This commit is contained in:
Nathan Bierema 2023-12-13 19:45:53 -05:00 committed by GitHub
parent 178002de65
commit 6954eb9580
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 730 additions and 1025 deletions

View File

@ -0,0 +1,5 @@
---
'@redux-devtools/rtk-query-monitor': major
---
Replace jss with Emotion in inspector-monitor. `@emotion/react` is now a required peer dependency.

View File

@ -8,6 +8,9 @@ module.exports = {
tsconfigRootDir: __dirname, tsconfigRootDir: __dirname,
project: true, project: true,
}, },
rules: {
'react/no-unknown-property': ['error', { ignore: ['css'] }],
},
}, },
{ {
files: ['test/**/*.ts', 'test/**/*.tsx'], files: ['test/**/*.ts', 'test/**/*.tsx'],

View File

@ -2,7 +2,8 @@
"presets": [ "presets": [
["@babel/preset-env", { "targets": "defaults", "modules": false }], ["@babel/preset-env", { "targets": "defaults", "modules": false }],
"@babel/preset-react", "@babel/preset-react",
"@babel/preset-typescript" "@babel/preset-typescript",
"@emotion/babel-preset-css-prop"
], ],
"plugins": ["@babel/plugin-transform-runtime"] "plugins": ["@babel/plugin-transform-runtime"]
} }

View File

@ -2,7 +2,8 @@
"presets": [ "presets": [
["@babel/preset-env", { "targets": "defaults" }], ["@babel/preset-env", { "targets": "defaults" }],
"@babel/preset-react", "@babel/preset-react",
"@babel/preset-typescript" "@babel/preset-typescript",
"@emotion/babel-preset-css-prop"
], ],
"plugins": ["@babel/plugin-transform-runtime"] "plugins": ["@babel/plugin-transform-runtime"]
} }

View File

@ -3,5 +3,5 @@
"compilerOptions": { "compilerOptions": {
"resolveJsonModule": true "resolveJsonModule": true
}, },
"include": ["../src", "src"] "include": ["src"]
} }

View File

@ -50,8 +50,6 @@
"@types/redux-devtools-themes": "^1.0.3", "@types/redux-devtools-themes": "^1.0.3",
"hex-rgba": "^1.0.2", "hex-rgba": "^1.0.2",
"immutable": "^4.3.4", "immutable": "^4.3.4",
"jss": "^10.10.0",
"jss-preset-default": "^10.10.0",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"react-base16-styling": "^0.9.1", "react-base16-styling": "^0.9.1",
"react-json-tree": "^0.18.0", "react-json-tree": "^0.18.0",
@ -65,6 +63,8 @@
"@babel/preset-env": "^7.23.5", "@babel/preset-env": "^7.23.5",
"@babel/preset-react": "^7.23.3", "@babel/preset-react": "^7.23.3",
"@babel/preset-typescript": "^7.23.3", "@babel/preset-typescript": "^7.23.3",
"@emotion/babel-preset-css-prop": "^11.11.0",
"@emotion/react": "^11.11.1",
"@redux-devtools/core": "^3.14.0", "@redux-devtools/core": "^3.14.0",
"@reduxjs/toolkit": "^1.9.7", "@reduxjs/toolkit": "^1.9.7",
"@testing-library/jest-dom": "^6.1.5", "@testing-library/jest-dom": "^6.1.5",
@ -91,6 +91,7 @@
"typescript": "~5.3.3" "typescript": "~5.3.3"
}, },
"peerDependencies": { "peerDependencies": {
"@emotion/react": "^11.0.0",
"@redux-devtools/core": "^3.0.0", "@redux-devtools/core": "^3.0.0",
"@reduxjs/toolkit": "^1.0.0 || ^2.0.0", "@reduxjs/toolkit": "^1.0.0 || ^2.0.0",
"@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0", "@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0",

View File

@ -1,11 +1,20 @@
import React from 'react'; import React from 'react';
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
export function NoRtkQueryApi(): JSX.Element { export function NoRtkQueryApi(): JSX.Element {
return ( return (
<StyleUtilsContext.Consumer> <div
{({ styling }) => ( css={(theme) => ({
<div {...styling('noApiFound')}> width: '100%',
textAlign: 'center',
color: theme.TEXT_COLOR,
padding: '1.4em',
'& a': {
fontSize: 'inherit',
color: theme.TEXT_COLOR,
textDecoration: 'underline',
},
})}
>
No rtk-query api found. No rtk-query api found.
<br /> <br />
Make sure to follow{' '} Make sure to follow{' '}
@ -18,7 +27,5 @@ export function NoRtkQueryApi(): JSX.Element {
</a> </a>
. .
</div> </div>
)}
</StyleUtilsContext.Consumer>
); );
} }

View File

@ -1,8 +1,9 @@
import React, { ReactNode, FormEvent, MouseEvent, ChangeEvent } from 'react'; import React, { ReactNode, FormEvent, MouseEvent, ChangeEvent } from 'react';
import type { DebouncedFunc } from 'lodash'; import type { DebouncedFunc } from 'lodash';
import { css } from '@emotion/react';
import { Select } from '@redux-devtools/ui'; import { Select } from '@redux-devtools/ui';
import { QueryFormValues } from '../types'; import { QueryFormValues } from '../types';
import { StyleUtilsContext } from '../styles/createStylingFromTheme'; import { StyleUtilsContext } from '../styles/themes';
import { SelectOption } from '../types'; import { SelectOption } from '../types';
import debounce from 'lodash.debounce'; import debounce from 'lodash.debounce';
import { sortQueryOptions, QueryComparators } from '../utils/comparators'; import { sortQueryOptions, QueryComparators } from '../utils/comparators';
@ -10,6 +11,17 @@ import { QueryFilters, filterQueryOptions } from '../utils/filters';
import { SortOrderButton } from './SortOrderButton'; import { SortOrderButton } from './SortOrderButton';
import { RegexIcon } from './RegexIcon'; import { RegexIcon } from './RegexIcon';
const srOnlyCss = css({
position: 'absolute',
width: 1,
height: 1,
padding: 0,
margin: '-1px',
overflow: 'hidden',
clip: 'rect(0,0,0,0)',
border: 0,
});
export interface QueryFormProps { export interface QueryFormProps {
values: QueryFormValues; values: QueryFormValues;
searchQueryRegex: RegExp | null; searchQueryRegex: RegExp | null;
@ -125,19 +137,68 @@ export class QueryForm extends React.PureComponent<
return ( return (
<StyleUtilsContext.Consumer> <StyleUtilsContext.Consumer>
{({ styling, base16Theme }) => { {({ base16Theme }) => {
return ( return (
<form <form
id="rtk-query-monitor-query-selection-form" id="rtk-query-monitor-query-selection-form"
action="#" action="#"
onSubmit={this.handleSubmit} onSubmit={this.handleSubmit}
{...styling('queryForm')} css={{
display: 'flex',
flexFlow: 'column nowrap',
}}
> >
<div {...styling('queryListHeader')}> <div
<label htmlFor={searchId} {...styling('srOnly')}> css={(theme) => ({
display: 'flex',
padding: 4,
flex: '0 0 auto',
alignItems: 'center',
borderBottomWidth: '1px',
borderBottomStyle: 'solid',
borderColor: theme.LIST_BORDER_COLOR,
})}
>
<label htmlFor={searchId} css={srOnlyCss}>
filter query filter query
</label> </label>
<div {...styling('querySearch')}> <div
css={(theme) => ({
maxWidth: '65%',
backgroundColor: theme.BACKGROUND_COLOR,
display: 'flex',
alignItems: 'center',
flexFlow: 'row nowrap',
flex: '1 1 auto',
paddingRight: 6,
'& input': {
outline: 'none',
border: 'none',
width: '100%',
flex: '1 1 auto',
padding: '5px 10px',
fontSize: '1em',
position: 'relative',
fontFamily:
'monaco, Consolas, "Lucida Console", monospace',
backgroundColor: theme.BACKGROUND_COLOR,
color: theme.TEXT_COLOR,
'&::-webkit-input-placeholder': {
color: theme.TEXT_PLACEHOLDER_COLOR,
},
'&::-moz-placeholder': {
color: theme.TEXT_PLACEHOLDER_COLOR,
},
'&::-webkit-search-cancel-button': {
WebkitAppearance: 'none',
},
},
})}
>
<input <input
ref={this.inputSearchRef} ref={this.inputSearchRef}
type="search" type="search"
@ -152,7 +213,32 @@ export class QueryForm extends React.PureComponent<
+(this.state.searchValue.length === 0) || undefined +(this.state.searchValue.length === 0) || undefined
} }
onClick={this.handleClearSearchClick} onClick={this.handleClearSearchClick}
{...styling('closeButton')} css={(theme) => ({
WebkitAppearance: 'none',
border: 'none',
outline: 'none',
boxShadow: 'none',
display: 'block',
flex: '0 0 auto',
cursor: 'pointer',
background: 'transparent',
position: 'relative',
fontSize: 'inherit',
'&[data-invisible="1"]': {
visibility: 'hidden !important' as 'hidden',
},
'&::after': {
content: '"\u00d7"',
display: 'block',
padding: 4,
fontSize: '1.2em',
color: theme.TEXT_PLACEHOLDER_COLOR,
background: 'transparent',
},
'&:hover::after': {
color: theme.TEXT_COLOR,
},
})}
/> />
<button <button
type="button" type="button"
@ -161,12 +247,41 @@ export class QueryForm extends React.PureComponent<
data-type={regexToggleType} data-type={regexToggleType}
aria-pressed={isRegexSearch} aria-pressed={isRegexSearch}
onClick={this.handleRegexSearchClick} onClick={this.handleRegexSearchClick}
{...styling('toggleButton')} css={(theme) => ({
width: '24px',
height: '24px',
display: 'inline-block',
flex: '0 0 auto',
color: theme.TEXT_PLACEHOLDER_COLOR,
cursor: 'pointer',
padding: 0,
fontSize: '0.7em',
letterSpacing: '-0.7px',
outline: 'none',
boxShadow: 'none',
fontWeight: '700',
border: 'none',
'&:hover': {
color: theme.TEXT_COLOR,
},
backgroundColor: 'transparent',
'&[aria-pressed="true"]': {
color: theme.BACKGROUND_COLOR,
backgroundColor: theme.TEXT_COLOR,
},
'&[data-type="error"]': {
color: theme.TEXT_COLOR,
backgroundColor: theme.TOGGLE_BUTTON_ERROR,
},
})}
> >
<RegexIcon /> <RegexIcon />
</button> </button>
</div> </div>
<label htmlFor={selectId} {...styling('srOnly')}> <label htmlFor={selectId} css={srOnlyCss}>
filter by filter by
</label> </label>
<Select<SelectOption<QueryFilters>> <Select<SelectOption<QueryFilters>>
@ -180,7 +295,24 @@ export class QueryForm extends React.PureComponent<
onChange={this.handleSelectFilterChange} onChange={this.handleSelectFilterChange}
/> />
</div> </div>
<div {...styling('sortBySection')}> <div
css={{
display: 'flex',
padding: '0.4em',
'& label': {
display: 'flex',
flex: '0 0 auto',
whiteSpace: 'nowrap',
alignItems: 'center',
paddingRight: '0.4em',
},
'& > :last-child': {
flex: '0 0 auto',
marginLeft: '0.4em',
},
}}
>
<label htmlFor={selectId}>Sort by</label> <label htmlFor={selectId}>Sort by</label>
<Select<SelectOption<QueryComparators>> <Select<SelectOption<QueryComparators>>
id={selectId} id={selectId}

View File

@ -1,8 +1,23 @@
import React, { PureComponent, ReactNode } from 'react'; import React, { PureComponent, ReactNode } from 'react';
import { StyleUtilsContext } from '../styles/createStylingFromTheme'; import type { Interpolation, Theme } from '@emotion/react';
import { RtkResourceInfo, RtkQueryMonitorState } from '../types'; import { RtkResourceInfo, RtkQueryMonitorState } from '../types';
import { isQuerySelected } from '../utils/rtk-query'; import { isQuerySelected } from '../utils/rtk-query';
const queryStatusCss: Interpolation<Theme> = (theme) => ({
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
height: 22,
padding: '0 6px',
borderRadius: '3px',
fontSize: '0.7em',
lineHeight: '1em',
flexShrink: 0,
fontWeight: 700,
backgroundColor: theme.ACTION_TIME_BACK_COLOR,
color: theme.ACTION_TIME_COLOR,
});
export interface QueryListProps { export interface QueryListProps {
resInfos: RtkResourceInfo[]; resInfos: RtkResourceInfo[];
selectedQueryKey: RtkQueryMonitorState['selectedQueryKey']; selectedQueryKey: RtkQueryMonitorState['selectedQueryKey'];
@ -34,9 +49,7 @@ export class QueryList extends PureComponent<QueryListProps> {
const { resInfos, selectedQueryKey, onSelectQuery } = this.props; const { resInfos, selectedQueryKey, onSelectQuery } = this.props;
return ( return (
<StyleUtilsContext.Consumer> <ul css={{ listStyle: 'none', margin: '0', padding: '0' }}>
{({ styling }) => (
<ul {...styling('queryList')}>
{resInfos.map((resInfo) => { {resInfos.map((resInfo) => {
const isSelected = isQuerySelected(selectedQueryKey, resInfo); const isSelected = isQuerySelected(selectedQueryKey, resInfo);
@ -44,26 +57,63 @@ export class QueryList extends PureComponent<QueryListProps> {
<li <li
key={resInfo.queryKey} key={resInfo.queryKey}
onClick={() => onSelectQuery(resInfo)} onClick={() => onSelectQuery(resInfo)}
{...styling( css={[
['queryListItem', isSelected && 'queryListItemSelected'], (theme) => ({
isSelected, borderBottomWidth: '1px',
)} borderBottomStyle: 'solid',
display: 'flex',
justifyContent: 'space-between',
padding: '5px 10px',
cursor: 'pointer',
userSelect: 'none',
'&:last-child': {
borderBottomWidth: 0,
},
overflow: 'hidden',
maxHeight: 47,
borderBottomColor: theme.BORDER_COLOR,
}),
isSelected &&
((theme) => ({
backgroundColor: theme.SELECTED_BACKGROUND_COLOR,
})),
]}
>
<p
css={{
display: '-webkit-box',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: 2,
whiteSpace: 'normal',
overflow: 'hidden',
width: '100%',
maxWidth: 'calc(100% - 70px)',
wordBreak: 'break-all',
margin: 0,
}}
> >
<p {...styling('queryListItemKey')}>
{QueryList.formatQuery(resInfo)} {QueryList.formatQuery(resInfo)}
</p> </p>
<div {...styling('queryStatusWrapper')}> <div
<strong {...styling(['queryStatus', 'queryType'])}> css={{
display: 'flex',
width: 'auto',
justifyContent: 'center',
alignItems: 'center',
margin: 0,
flex: '0 0 auto',
overflow: 'hidden',
}}
>
<strong css={[queryStatusCss, { marginRight: 4 }]}>
{resInfo.type === 'query' ? 'Q' : 'M'} {resInfo.type === 'query' ? 'Q' : 'M'}
</strong> </strong>
<p {...styling('queryStatus')}>{resInfo.state.status}</p> <p css={queryStatusCss}>{resInfo.state.status}</p>
</div> </div>
</li> </li>
); );
})} })}
</ul> </ul>
)}
</StyleUtilsContext.Consumer>
); );
} }
} }

View File

@ -1,7 +1,6 @@
import React, { ReactNode, PureComponent } from 'react'; import React, { ReactNode, PureComponent } from 'react';
import type { ShouldExpandNodeInitially } from 'react-json-tree'; import type { ShouldExpandNodeInitially } from 'react-json-tree';
import { ApiStats, QueryPreviewTabs, RtkQueryApiState } from '../types'; import { ApiStats, QueryPreviewTabs, RtkQueryApiState } from '../types';
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
import { TreeView, TreeViewProps } from './TreeView'; import { TreeView, TreeViewProps } from './TreeView';
import { renderTabPanelId, renderTabPanelButtonId } from '../utils/a11y'; import { renderTabPanelId, renderTabPanelButtonId } from '../utils/a11y';
@ -39,9 +38,23 @@ export class QueryPreviewApi extends PureComponent<QueryPreviewApiProps> {
const hasQueries = Object.keys(apiState.queries).length > 0; const hasQueries = Object.keys(apiState.queries).length > 0;
return ( return (
<StyleUtilsContext.Consumer> <article
{({ styling }) => ( {...rootProps}
<article {...rootProps} {...styling('tabContent')}> css={(theme) => ({
display: 'block',
overflowY: 'auto',
padding: '0.5em 0',
color: theme.TAB_CONTENT_COLOR,
'& h2': {
color: theme.ULIST_STRONG_COLOR,
padding: '0.5em 1em',
fontWeight: 700,
},
'& h3': {
color: theme.ULIST_STRONG_COLOR,
},
})}
>
<h2>{apiState.config.reducerPath}</h2> <h2>{apiState.config.reducerPath}</h2>
<TreeView <TreeView
before={<h3>State</h3>} before={<h3>State</h3>}
@ -73,8 +86,6 @@ export class QueryPreviewApi extends PureComponent<QueryPreviewApiProps> {
</> </>
)} )}
</article> </article>
)}
</StyleUtilsContext.Consumer>
); );
} }
} }

View File

@ -1,5 +1,4 @@
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
import { QueryPreviewTabs, TabOption } from '../types'; import { QueryPreviewTabs, TabOption } from '../types';
import { renderTabPanelButtonId } from '../utils/a11y'; import { renderTabPanelButtonId } from '../utils/a11y';
import { emptyArray } from '../utils/object'; import { emptyArray } from '../utils/object';
@ -24,10 +23,29 @@ export class QueryPreviewHeader extends React.Component<QueryPreviewHeaderProps>
const { tabs, selectedTab, renderTabLabel } = this.props; const { tabs, selectedTab, renderTabLabel } = this.props;
return ( return (
<StyleUtilsContext.Consumer> <div
{({ styling }) => ( css={(theme) => ({
<div {...styling('previewHeader')}> flex: '0 0 30px',
<div {...styling('tabSelector')}> padding: '5px 4px',
alignItems: 'center',
borderBottomWidth: '1px',
borderBottomStyle: 'solid',
backgroundColor: theme.HEADER_BACKGROUND_COLOR,
borderBottomColor: theme.HEADER_BORDER_COLOR,
})}
>
<div
css={{
display: 'flex',
width: '100%',
justifyContent: 'flex-end',
overflow: 'hidden',
'& > *': {
flex: '0 1 auto',
},
}}
>
{tabs.map((tab) => ( {tabs.map((tab) => (
<button <button
type="button" type="button"
@ -36,23 +54,61 @@ export class QueryPreviewHeader extends React.Component<QueryPreviewHeaderProps>
role={'tab'} role={'tab'}
onClick={() => this.handleTabClick(tab)} onClick={() => this.handleTabClick(tab)}
key={tab.value} key={tab.value}
{...styling( css={[
[ (theme) => ({
'selectorButton', cursor: 'pointer',
tab.value === selectedTab && 'selectorButtonSelected', position: 'relative',
], height: '33px',
tab.value === selectedTab, padding: '0 8px',
)} display: 'inline-flex',
alignItems: 'center',
boxShadow: 'none',
outline: 'none',
color: theme.TEXT_COLOR,
borderStyle: 'solid',
borderWidth: '1px',
borderLeftWidth: 0,
'&:first-of-type': {
borderLeftWidth: '1px',
borderTopLeftRadius: '3px',
borderBottomLeftRadius: '3px',
},
'&:last-of-type': {
borderTopRightRadius: '3px',
borderBottomRightRadius: '3px',
},
backgroundColor: theme.TAB_BACK_COLOR,
'&:hover': {
backgroundColor: theme.TAB_BACK_HOVER_COLOR,
},
borderColor: theme.TAB_BORDER_COLOR,
'& > *': {
display: '-webkit-box',
boxOrient: 'vertical',
WebkitLineClamp: 1,
overflow: 'hidden',
wordBreak: 'break-all',
WebkitBoxPack: 'end',
paddingBottom: 0,
},
}),
tab.value === selectedTab &&
((theme) => ({
backgroundColor: theme.TAB_BACK_SELECTED_COLOR,
})),
]}
> >
<span> <span>{renderTabLabel ? renderTabLabel(tab) : tab.label}</span>
{renderTabLabel ? renderTabLabel(tab) : tab.label}
</span>
</button> </button>
))} ))}
</div> </div>
</div> </div>
)}
</StyleUtilsContext.Consumer>
); );
} }

View File

@ -1,6 +1,5 @@
import React, { CSSProperties } from 'react'; import React, { CSSProperties } from 'react';
import { ArrowUpIcon } from './ArrowUpIcon'; import { ArrowUpIcon } from './ArrowUpIcon';
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
export interface SortOrderButtonProps { export interface SortOrderButtonProps {
readonly isAsc?: boolean; readonly isAsc?: boolean;
@ -28,19 +27,36 @@ export function SortOrderButton({
}; };
return ( return (
<StyleUtilsContext.Consumer>
{({ styling }) => (
<button <button
type="button" type="button"
id={id} id={id}
onClick={handleButtonClick} onClick={handleButtonClick}
aria-pressed={isAsc} aria-pressed={isAsc}
{...styling(['sortButton'])} css={(theme) => ({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
flexFlow: 'row nowrap',
cursor: 'pointer',
position: 'relative',
padding: '0 8px',
color: theme.TEXT_COLOR,
borderStyle: 'solid',
borderWidth: '1px',
borderRadius: '3px',
backgroundColor: theme.TAB_BACK_COLOR,
borderColor: theme.TAB_BORDER_COLOR,
height: 30,
fontSize: 12,
width: 64,
'&:active': {
backgroundColor: theme.TAB_BACK_SELECTED_COLOR,
},
})}
> >
<ArrowUpIcon style={arrowStyles} /> <ArrowUpIcon style={arrowStyles} />
{buttonLabel} {buttonLabel}
</button> </button>
)}
</StyleUtilsContext.Consumer>
); );
} }

View File

@ -1,13 +1,9 @@
import { createSelector, Selector } from '@reduxjs/toolkit'; import { createSelector, Selector } 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 { Base16Theme, StylingFunction } from 'react-base16-styling'; import { Base16Theme } from 'react-base16-styling';
import { DATA_TYPE_KEY } from '../monitor-config'; import { getJsonTreeTheme, StyleUtilsContext } from '../styles/themes';
import { import { getItemString, labelRenderer } from '../styles/tree';
getJsonTreeTheme,
StyleUtilsContext,
} from '../styles/createStylingFromTheme';
import { createTreeItemLabelRenderer, getItemString } from '../styles/tree';
import { identity } from '../utils/object'; import { identity } from '../utils/object';
export interface TreeViewProps export interface TreeViewProps
@ -39,28 +35,6 @@ export class TreeView extends React.PureComponent<TreeViewProps> {
}, },
}; };
readonly selectLabelRenderer: Selector<
StylingFunction,
ReturnType<typeof createTreeItemLabelRenderer>,
never
> = createSelector<
[(stylingFunction: StylingFunction) => StylingFunction],
ReturnType<typeof createTreeItemLabelRenderer>
>(identity, createTreeItemLabelRenderer);
readonly selectGetItemString: Selector<
StylingFunction,
(type: string, data: unknown) => ReactNode,
never
> = createSelector<
[(stylingFunction: StylingFunction) => StylingFunction],
(type: string, data: unknown) => ReactNode
>(
identity,
(styling) => (type, data) =>
getItemString(styling, type, data, DATA_TYPE_KEY, false),
);
readonly selectTheme: Selector< readonly selectTheme: Selector<
Base16Theme, Base16Theme,
ReturnType<typeof getJsonTreeTheme>, ReturnType<typeof getJsonTreeTheme>,
@ -88,18 +62,25 @@ export class TreeView extends React.PureComponent<TreeViewProps> {
return ( return (
<StyleUtilsContext.Consumer> <StyleUtilsContext.Consumer>
{({ styling, invertTheme, base16Theme }) => { {({ invertTheme, base16Theme }) => {
return ( return (
<div {...rootProps} {...styling('treeWrapper')}> <div
{...rootProps}
css={{
overflowX: 'auto',
overflowY: 'auto',
padding: '0.5em 1em',
}}
>
{before} {before}
<JSONTree <JSONTree
keyPath={keyPath} keyPath={keyPath}
shouldExpandNodeInitially={shouldExpandNodeInitially} shouldExpandNodeInitially={shouldExpandNodeInitially}
data={data} data={data}
labelRenderer={this.selectLabelRenderer(styling)} labelRenderer={labelRenderer}
theme={this.selectTheme(base16Theme)} theme={this.selectTheme(base16Theme)}
invertTheme={invertTheme} invertTheme={invertTheme}
getItemString={this.selectGetItemString(styling)} getItemString={getItemString}
hideRoot={hideRoot} hideRoot={hideRoot}
/> />
{after} {after}

View File

@ -1,12 +1,30 @@
import * as React from 'react'; import * as React from 'react';
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
export type UListProps = React.HTMLAttributes<HTMLUListElement>; export type UListProps = React.HTMLAttributes<HTMLUListElement>;
export function UList(props: UListProps): JSX.Element { export function UList(props: UListProps): JSX.Element {
return ( return (
<StyleUtilsContext.Consumer> <ul
{({ styling }) => <ul {...props} {...styling('uList')} />} {...props}
</StyleUtilsContext.Consumer> css={(theme) => ({
listStyle: 'none',
padding: '0 0 0 1em',
color: theme.ULIST_COLOR,
'& > li': {
listStyle: 'none',
},
'& > li::before': {
content: '"\\2022"',
display: 'inline-block',
paddingRight: '0.5em',
color: theme.ULIST_DISC_COLOR,
fontSize: '0.8em',
},
'& strong': {
color: theme.ULIST_STRONG_COLOR,
},
})}
/>
); );
} }

View File

@ -1,6 +1,5 @@
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { StyleUtilsContext } from '../styles/createStylingFromTheme'; import type { Interpolation, Theme } from '@emotion/react';
import { createTreeItemLabelRenderer } from '../styles/tree';
import { import {
QueryPreviewTabs, QueryPreviewTabs,
RtkResourceInfo, RtkResourceInfo,
@ -26,7 +25,6 @@ import {
} from '../components/QueryPreviewTags'; } from '../components/QueryPreviewTags';
import { NoRtkQueryApi } from '../components/NoRtkQueryApi'; import { NoRtkQueryApi } from '../components/NoRtkQueryApi';
import { InspectorSelectors } from '../selectors'; import { InspectorSelectors } from '../selectors';
import { StylingFunction } from 'react-base16-styling';
import { mapProps } from './mapProps'; import { mapProps } from './mapProps';
import { import {
QueryPreviewActions, QueryPreviewActions,
@ -38,12 +36,28 @@ import {
QueryPreviewDataProps, QueryPreviewDataProps,
} from '../components/QueryPreviewData'; } from '../components/QueryPreviewData';
const queryPreviewCss: Interpolation<Theme> = (theme) => ({
flex: '1 1 50%',
overflowX: 'hidden',
oveflowY: 'auto',
display: 'flex',
flexDirection: 'column',
overflowY: 'hidden',
'& pre': {
border: 'inherit',
borderRadius: '3px',
lineHeight: 'inherit',
color: 'inherit',
},
backgroundColor: theme.BACKGROUND_COLOR,
});
export interface QueryPreviewProps<S = unknown> { export interface QueryPreviewProps<S = unknown> {
readonly selectedTab: QueryPreviewTabs; readonly selectedTab: QueryPreviewTabs;
readonly hasNoApis: boolean; readonly hasNoApis: boolean;
readonly onTabChange: (tab: QueryPreviewTabs) => void; readonly onTabChange: (tab: QueryPreviewTabs) => void;
readonly resInfo: RtkResourceInfo | null; readonly resInfo: RtkResourceInfo | null;
readonly styling: StylingFunction;
readonly isWideLayout: boolean; readonly isWideLayout: boolean;
readonly selectorsSource: SelectorsSource<S>; readonly selectorsSource: SelectorsSource<S>;
readonly selectors: InspectorSelectors<S>; readonly selectors: InspectorSelectors<S>;
@ -163,14 +177,6 @@ const tabs: ReadonlyArray<
]; ];
export class QueryPreview<S> extends React.PureComponent<QueryPreviewProps<S>> { export class QueryPreview<S> extends React.PureComponent<QueryPreviewProps<S>> {
readonly labelRenderer: ReturnType<typeof createTreeItemLabelRenderer>;
constructor(props: QueryPreviewProps<S>) {
super(props);
this.labelRenderer = createTreeItemLabelRenderer(this.props.styling);
}
renderLabelWithCounter = ( renderLabelWithCounter = (
label: React.ReactText, label: React.ReactText,
counter: number, counter: number,
@ -211,9 +217,7 @@ export class QueryPreview<S> extends React.PureComponent<QueryPreviewProps<S>> {
if (!resInfo) { if (!resInfo) {
return ( return (
<StyleUtilsContext.Consumer> <div css={queryPreviewCss}>
{({ styling }) => (
<div {...styling('queryPreview')}>
<QueryPreviewHeader <QueryPreviewHeader
selectedTab={selectedTab} selectedTab={selectedTab}
onTabChange={onTabChange} onTabChange={onTabChange}
@ -221,27 +225,18 @@ export class QueryPreview<S> extends React.PureComponent<QueryPreviewProps<S>> {
tabs.filter((tab) => tabs.filter((tab) =>
isTabVisible(tab, 'default'), isTabVisible(tab, 'default'),
) as ReadonlyArray< ) as ReadonlyArray<
TabOption< TabOption<QueryPreviewTabs, unknown, RtkResourceInfo['type']>
QueryPreviewTabs,
unknown,
RtkResourceInfo['type']
>
> >
} }
renderTabLabel={this.renderTabLabel} renderTabLabel={this.renderTabLabel}
/> />
{hasNoApis && <NoRtkQueryApi />} {hasNoApis && <NoRtkQueryApi />}
</div> </div>
)}
</StyleUtilsContext.Consumer>
); );
} }
return ( return (
<StyleUtilsContext.Consumer> <div css={queryPreviewCss}>
{({ styling }) => {
return (
<div {...styling('queryPreview')}>
<QueryPreviewHeader <QueryPreviewHeader
selectedTab={selectedTab} selectedTab={selectedTab}
onTabChange={onTabChange} onTabChange={onTabChange}
@ -249,11 +244,7 @@ export class QueryPreview<S> extends React.PureComponent<QueryPreviewProps<S>> {
tabs.filter((tab) => tabs.filter((tab) =>
isTabVisible(tab, resInfo.type), isTabVisible(tab, resInfo.type),
) as ReadonlyArray< ) as ReadonlyArray<
TabOption< TabOption<QueryPreviewTabs, unknown, RtkResourceInfo['type']>
QueryPreviewTabs,
unknown,
RtkResourceInfo['type']
>
> >
} }
renderTabLabel={this.renderTabLabel} renderTabLabel={this.renderTabLabel}
@ -261,8 +252,5 @@ export class QueryPreview<S> extends React.PureComponent<QueryPreviewProps<S>> {
<TabComponent {...(this.props as QueryPreviewTabProps)} /> <TabComponent {...(this.props as QueryPreviewTabProps)} />
</div> </div>
); );
}}
</StyleUtilsContext.Consumer>
);
} }
} }

View File

@ -5,7 +5,6 @@ import {
QueryFormValues, QueryFormValues,
QueryPreviewTabs, QueryPreviewTabs,
RtkQueryMonitorState, RtkQueryMonitorState,
StyleUtils,
SelectorsSource, SelectorsSource,
RtkResourceInfo, RtkResourceInfo,
} from '../types'; } from '../types';
@ -27,7 +26,6 @@ type ForwardedMonitorProps<S, A extends Action<string>> = Pick<
export interface RtkQueryInspectorProps<S, A extends Action<string>> export interface RtkQueryInspectorProps<S, A extends Action<string>>
extends ForwardedMonitorProps<S, A> { extends ForwardedMonitorProps<S, A> {
dispatch: Dispatch<LiftedAction<S, A, RtkQueryMonitorState>>; dispatch: Dispatch<LiftedAction<S, A, RtkQueryMonitorState>>;
styleUtils: StyleUtils;
} }
type RtkQueryInspectorState<S> = { type RtkQueryInspectorState<S> = {
@ -111,9 +109,6 @@ class RtkQueryInspector<S, A extends Action<string>> extends PureComponent<
render(): ReactNode { render(): ReactNode {
const { selectorsSource, isWideLayout } = this.state; const { selectorsSource, isWideLayout } = this.state;
const {
styleUtils: { styling },
} = this.props;
const allVisibleRtkResourceInfos = const allVisibleRtkResourceInfos =
this.selectors.selectAllVisbileQueries(selectorsSource); this.selectors.selectAllVisbileQueries(selectorsSource);
@ -131,10 +126,58 @@ class RtkQueryInspector<S, A extends Action<string>> extends PureComponent<
<div <div
ref={this.inspectorRef} ref={this.inspectorRef}
data-wide-layout={+this.state.isWideLayout} data-wide-layout={+this.state.isWideLayout}
{...styling('inspector')} css={(theme) => ({
display: 'flex',
flexFlow: 'column nowrap',
overflow: 'hidden',
width: '100%',
height: '100%',
fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
fontSize: '12px',
WebkitFontSmoothing: 'antialiased',
lineHeight: '1.5em',
backgroundColor: theme.BACKGROUND_COLOR,
color: theme.TEXT_COLOR,
'&[data-wide-layout="1"]': {
flexFlow: 'row nowrap',
},
})}
> >
<div <div
{...styling('querySectionWrapper')} css={(theme) => ({
display: 'flex',
flex: '0 0 auto',
height: '50%',
width: '100%',
borderColor: theme.TAB_BORDER_COLOR,
'&[data-wide-layout="0"]': {
borderBottomWidth: 1,
borderStyle: 'solid',
},
'&[data-wide-layout="1"]': {
height: '100%',
width: '44%',
borderRightWidth: 1,
borderStyle: 'solid',
},
flexFlow: 'column nowrap',
'& > form': {
flex: '0 0 auto',
borderBottomWidth: '1px',
borderBottomStyle: 'solid',
borderColor: theme.LIST_BORDER_COLOR,
},
'& > ul': {
flex: '1 1 auto',
overflowX: 'hidden',
overflowY: 'auto',
maxHeight: 'calc(100% - 70px)',
},
})}
data-wide-layout={+this.state.isWideLayout} data-wide-layout={+this.state.isWideLayout}
> >
<QueryForm <QueryForm
@ -154,7 +197,6 @@ class RtkQueryInspector<S, A extends Action<string>> extends PureComponent<
resInfo={currentResInfo} resInfo={currentResInfo}
selectedTab={selectorsSource.monitorState.selectedPreviewTab} selectedTab={selectorsSource.monitorState.selectedPreviewTab}
onTabChange={this.handleTabChange} onTabChange={this.handleTabChange}
styling={styling}
isWideLayout={isWideLayout} isWideLayout={isWideLayout}
hasNoApis={hasNoApi} hasNoApis={hasNoApi}
/> />

View File

@ -1,30 +1,26 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Action, AnyAction } from 'redux'; import { Action, AnyAction } from 'redux';
import { ThemeProvider } from '@emotion/react';
import RtkQueryInspector from './RtkQueryInspector'; import RtkQueryInspector from './RtkQueryInspector';
import { reducer } from '../reducers'; import { reducer } from '../reducers';
import { import {
ExternalProps, ExternalProps,
RtkQueryMonitorProps, RtkQueryMonitorProps,
RtkQueryMonitorState, RtkQueryMonitorState,
StyleUtils,
} from '../types'; } from '../types';
import { import {
createThemeState, createRtkQueryMonitorThemeFromBase16Theme,
resolveBase16Theme,
StyleUtilsContext, StyleUtilsContext,
} from '../styles/createStylingFromTheme'; } from '../styles/themes';
interface DefaultProps { interface DefaultProps {
theme: string; theme: string;
invertTheme: boolean; invertTheme: boolean;
} }
export interface RtkQueryComponentState {
readonly styleUtils: StyleUtils;
}
class RtkQueryMonitor<S, A extends Action<string>> extends Component< class RtkQueryMonitor<S, A extends Action<string>> extends Component<
RtkQueryMonitorProps<S, A>, RtkQueryMonitorProps<S, A>
RtkQueryComponentState
> { > {
static update = reducer; static update = reducer;
@ -33,14 +29,6 @@ class RtkQueryMonitor<S, A extends Action<string>> extends Component<
invertTheme: false, invertTheme: false,
}; };
constructor(props: RtkQueryMonitorProps<S, A>) {
super(props);
this.state = {
styleUtils: createThemeState<S, A>(props),
};
}
render() { render() {
const { const {
currentStateIndex, currentStateIndex,
@ -48,18 +36,28 @@ class RtkQueryMonitor<S, A extends Action<string>> extends Component<
monitorState, monitorState,
dispatch, dispatch,
actionsById, actionsById,
theme,
invertTheme,
} = this.props; } = this.props;
const base16Theme = resolveBase16Theme(theme);
const styleUtils = { base16Theme, invertTheme };
const rtkQueryMonitorTheme = createRtkQueryMonitorThemeFromBase16Theme(
base16Theme,
invertTheme,
);
return ( return (
<StyleUtilsContext.Provider value={this.state.styleUtils}> <StyleUtilsContext.Provider value={styleUtils}>
<ThemeProvider theme={rtkQueryMonitorTheme}>
<RtkQueryInspector<S, AnyAction> <RtkQueryInspector<S, AnyAction>
computedStates={computedStates} computedStates={computedStates}
currentStateIndex={currentStateIndex} currentStateIndex={currentStateIndex}
monitorState={monitorState} monitorState={monitorState}
dispatch={dispatch} dispatch={dispatch}
styleUtils={this.state.styleUtils}
actionsById={actionsById} actionsById={actionsById}
/> />
</ThemeProvider>
</StyleUtilsContext.Provider> </StyleUtilsContext.Provider>
); );
} }

View File

@ -1,567 +0,0 @@
import jss, { StyleSheet } from 'jss';
import preset from 'jss-preset-default';
import {
createStyling,
getBase16Theme,
invertTheme,
StylingConfig,
StylingFunction,
Theme,
} from 'react-base16-styling';
import rgba from 'hex-rgba';
import * as reduxThemes from 'redux-devtools-themes';
import { Action } from 'redux';
import { createContext } from 'react';
import type { CurriedFunction1 } from 'lodash';
import { RtkQueryMonitorProps, StyleUtils } from '../types';
jss.setup(preset());
export const colorMap = (theme: reduxThemes.Base16Theme) =>
({
TEXT_COLOR: theme.base06,
TEXT_PLACEHOLDER_COLOR: rgba(theme.base06, 60),
BACKGROUND_COLOR: theme.base00,
SELECTED_BACKGROUND_COLOR: rgba(theme.base03, 20),
SKIPPED_BACKGROUND_COLOR: rgba(theme.base03, 10),
HEADER_BACKGROUND_COLOR: rgba(theme.base03, 30),
HEADER_BORDER_COLOR: rgba(theme.base03, 20),
BORDER_COLOR: rgba(theme.base03, 50),
LIST_BORDER_COLOR: rgba(theme.base03, 50),
ACTION_TIME_BACK_COLOR: rgba(theme.base03, 20),
ACTION_TIME_COLOR: theme.base04,
PIN_COLOR: theme.base04,
ITEM_HINT_COLOR: rgba(theme.base0F, 90),
TAB_BACK_SELECTED_COLOR: rgba(theme.base03, 20),
TAB_BACK_COLOR: rgba(theme.base00, 70),
TAB_BACK_HOVER_COLOR: rgba(theme.base03, 40),
TAB_BORDER_COLOR: rgba(theme.base03, 50),
DIFF_ADD_COLOR: rgba(theme.base0B, 40),
DIFF_REMOVE_COLOR: rgba(theme.base08, 40),
DIFF_ARROW_COLOR: theme.base0E,
LINK_COLOR: rgba(theme.base0E, 90),
LINK_HOVER_COLOR: theme.base0E,
ERROR_COLOR: theme.base08,
ULIST_DISC_COLOR: theme.base0D,
ULIST_COLOR: rgba(theme.base06, 60),
ULIST_STRONG_COLOR: theme.base0B,
TAB_CONTENT_COLOR: rgba(theme.base06, 60),
TOGGLE_BUTTON_BACKGROUND: rgba(theme.base00, 70),
TOGGLE_BUTTON_SELECTED_BACKGROUND: theme.base04,
TOGGLE_BUTTON_ERROR: rgba(theme.base08, 40),
}) as const;
type Color = keyof ReturnType<typeof colorMap>;
type ColorMap = {
[color in Color]: string;
};
const getSheetFromColorMap = (map: ColorMap) => {
const appearanceNone = {
'-webkit-appearance': 'none',
};
return {
inspector: {
display: 'flex',
flexFlow: 'column nowrap',
overflow: 'hidden',
width: '100%',
height: '100%',
'font-family': 'monaco, Consolas, "Lucida Console", monospace',
'font-size': '12px',
'font-smoothing': 'antialiased',
'line-height': '1.5em',
'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: '44%',
borderRightWidth: 1,
borderStyle: 'solid',
},
flexFlow: 'column nowrap',
'& > :first-child': {
flex: '0 0 auto',
'border-bottom-width': '1px',
'border-bottom-style': 'solid',
'border-color': map.LIST_BORDER_COLOR,
},
'& > :nth-child(n + 2)': {
flex: '1 1 auto',
overflowX: 'hidden',
overflowY: 'auto',
maxHeight: 'calc(100% - 70px)',
},
},
queryList: {
listStyle: 'none',
margin: '0',
padding: '0',
},
queryListItem: {
'border-bottom-width': '1px',
'border-bottom-style': 'solid',
display: 'flex',
'justify-content': 'space-between',
padding: '5px 10px',
cursor: 'pointer',
'user-select': 'none',
'&:last-child': {
'border-bottom-width': 0,
},
overflow: 'hidden',
maxHeight: 47,
'border-bottom-color': map.BORDER_COLOR,
},
queryListItemKey: {
display: '-webkit-box',
boxOrient: 'vertical',
'-webkit-line-clamp': 2,
whiteSpace: 'normal',
overflow: 'hidden',
width: '100%',
maxWidth: 'calc(100% - 70px)',
wordBreak: 'break-all',
margin: 0,
},
queryListHeader: {
display: 'flex',
padding: 4,
flex: '0 0 auto',
'align-items': 'center',
'border-bottom-width': '1px',
'border-bottom-style': 'solid',
'border-color': map.LIST_BORDER_COLOR,
},
queryStatusWrapper: {
display: 'flex',
width: 'auto',
justifyContent: 'center',
alignItems: 'center',
margin: 0,
flex: '0 0 auto',
overflow: 'hidden',
},
queryType: {
marginRight: 4,
},
queryStatus: {
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
height: 22,
padding: '0 6px',
'border-radius': '3px',
'font-size': '0.7em',
'line-height': '1em',
'flex-shrink': 0,
fontWeight: 700,
'background-color': map.ACTION_TIME_BACK_COLOR,
color: map.ACTION_TIME_COLOR,
},
queryListItemSelected: {
'background-color': map.SELECTED_BACKGROUND_COLOR,
},
tabSelector: {
display: 'flex',
width: '100%',
justifyContent: 'flex-end',
overflow: 'hidden',
'& > *': {
flex: '0 1 auto',
},
},
srOnly: {
position: 'absolute',
width: 1,
height: 1,
padding: 0,
margin: '-1px',
overflow: 'hidden',
clip: 'rect(0,0,0,0)',
border: 0,
},
selectorButton: {
cursor: 'pointer',
position: 'relative',
height: '33px',
padding: '0 8px',
display: 'inline-flex',
alignItems: 'center',
boxShadow: 'none',
outline: 'none',
color: map.TEXT_COLOR,
'border-style': 'solid',
'border-width': '1px',
'border-left-width': 0,
'&:first-child': {
'border-left-width': '1px',
'border-top-left-radius': '3px',
'border-bottom-left-radius': '3px',
},
'&:last-child': {
'border-top-right-radius': '3px',
'border-bottom-right-radius': '3px',
},
'background-color': map.TAB_BACK_COLOR,
'&:hover': {
'background-color': map.TAB_BACK_HOVER_COLOR,
},
'border-color': map.TAB_BORDER_COLOR,
'& > *': {
display: '-webkit-box',
boxOrient: 'vertical',
'-webkit-line-clamp': 1,
overflow: 'hidden',
wordBreak: 'break-all',
'-webkit-box-pack': 'end',
paddingBottom: 0,
},
},
selectorButtonSmall: {
padding: '0px 8px',
'font-size': '0.8em',
},
selectorButtonSelected: {
'background-color': map.TAB_BACK_SELECTED_COLOR,
},
sortButton: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
flexFlow: 'row nowrap',
cursor: 'pointer',
position: 'relative',
padding: '0 8px',
color: map.TEXT_COLOR,
borderStyle: 'solid',
borderWidth: '1px',
borderRadius: '3px',
backgroundColor: map.TAB_BACK_COLOR,
borderColor: map.TAB_BORDER_COLOR,
height: 30,
fontSize: 12,
width: 64,
'&:active': {
backgroundColor: map.TAB_BACK_SELECTED_COLOR,
},
},
toggleButton: {
width: '24px',
height: '24px',
display: 'inline-block',
flex: '0 0 auto',
color: map.TEXT_PLACEHOLDER_COLOR,
cursor: 'pointer',
padding: 0,
fontSize: '0.7em',
letterSpacing: '-0.7px',
outline: 'none',
boxShadow: 'none',
fontWeight: '700',
border: 'none',
'&:hover': {
color: map.TEXT_COLOR,
},
backgroundColor: 'transparent',
'&[aria-pressed="true"]': {
color: map.BACKGROUND_COLOR,
backgroundColor: map.TEXT_COLOR,
},
'&[data-type="error"]': {
color: map.TEXT_COLOR,
backgroundColor: map.TOGGLE_BUTTON_ERROR,
},
},
queryForm: {
display: 'flex',
flexFlow: 'column nowrap',
},
sortBySection: {
display: 'flex',
padding: '0.4em',
'& label': {
display: 'flex',
flex: '0 0 auto',
whiteSpace: 'noWrap',
alignItems: 'center',
paddingRight: '0.4em',
},
'& > :last-child': {
flex: '0 0 auto',
marginLeft: '0.4em',
},
},
querySearch: {
maxWidth: '65%',
'background-color': map.BACKGROUND_COLOR,
display: 'flex',
alignItems: 'center',
flexFlow: 'row nowrap',
flex: '1 1 auto',
paddingRight: 6,
'& input': {
outline: 'none',
border: 'none',
width: '100%',
flex: '1 1 auto',
padding: '5px 10px',
'font-size': '1em',
position: 'relative',
fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
'background-color': map.BACKGROUND_COLOR,
color: map.TEXT_COLOR,
'&::-webkit-input-placeholder': {
color: map.TEXT_PLACEHOLDER_COLOR,
},
'&::-moz-placeholder': {
color: map.TEXT_PLACEHOLDER_COLOR,
},
'&::-webkit-search-cancel-button': appearanceNone,
},
},
closeButton: {
...appearanceNone,
border: 'none',
outline: 'none',
boxShadow: 'none',
display: 'block',
flex: '0 0 auto',
cursor: 'pointer',
background: 'transparent',
position: 'relative',
fontSize: 'inherit',
'&[data-invisible="1"]': {
visibility: 'hidden !important',
},
'&::after': {
content: '"\u00d7"',
display: 'block',
padding: 4,
fontSize: '1.2em',
color: map.TEXT_PLACEHOLDER_COLOR,
background: 'transparent',
},
'&:hover::after': {
color: map.TEXT_COLOR,
},
},
noApiFound: {
width: '100%',
textAlign: 'center',
color: map.TEXT_COLOR,
padding: '1.4em',
'& a': {
fontSize: 'inherit',
color: map.TEXT_COLOR,
textDecoration: 'underline',
},
},
searchSelectLabel: {
display: 'inline-block',
padding: 4,
borderLeft: '1px solid currentColor',
},
queryPreview: {
flex: '1 1 50%',
overflowX: 'hidden',
oveflowY: 'auto',
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 4px',
'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,
},
treeWrapper: {
overflowX: 'auto',
overflowY: 'auto',
padding: '0.5em 1em',
},
tabContent: {
display: 'block',
overflowY: 'auto',
padding: '0.5em 0',
color: map.TAB_CONTENT_COLOR,
'& h2': {
color: map.ULIST_STRONG_COLOR,
padding: '0.5em 1em',
fontWeight: 700,
},
'& h3': {
color: map.ULIST_STRONG_COLOR,
},
},
uList: {
listStyle: 'none',
padding: '0 0 0 1em',
color: map.ULIST_COLOR,
'& > li': {
listStyle: 'none',
},
'& > li::before': {
content: '"\\2022"',
display: 'inline-block',
paddingRight: '0.5em',
color: map.ULIST_DISC_COLOR,
fontSize: '0.8em',
},
'& strong': {
color: map.ULIST_STRONG_COLOR,
},
},
};
};
let themeSheet: StyleSheet;
const getDefaultThemeStyling = (theme: reduxThemes.Base16Theme) => {
if (themeSheet) {
themeSheet.detach();
}
themeSheet = jss
.createStyleSheet(getSheetFromColorMap(colorMap(theme)))
.attach();
return themeSheet.classes;
};
export const createStylingFromTheme: CurriedFunction1<
Theme | undefined,
StylingFunction
> = createStyling(getDefaultThemeStyling, {
defaultBase16: reduxThemes.nicinabox,
base16Themes: { ...reduxThemes },
});
export function createThemeState<S, A extends Action<string>>(
props: RtkQueryMonitorProps<S, A>,
): StyleUtils {
const base16Theme =
getBase16Theme(props.theme, { ...reduxThemes }) ?? reduxThemes.nicinabox;
const theme = props.invertTheme ? invertTheme(props.theme) : props.theme;
const styling = createStylingFromTheme(theme);
return { base16Theme, styling, invertTheme: !!props.invertTheme };
}
const mockStyling = () => ({ className: '', style: {} });
export const StyleUtilsContext = createContext<StyleUtils>({
base16Theme: reduxThemes.nicinabox,
invertTheme: false,
styling: mockStyling,
});
export function getJsonTreeTheme(
base16Theme: reduxThemes.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',
},
}),
};
}

View File

@ -0,0 +1,116 @@
import { getBase16Theme, invertBase16Theme } from 'react-base16-styling';
import type { Base16Theme, StylingConfig } from 'react-base16-styling';
import rgba from 'hex-rgba';
import * as reduxThemes from 'redux-devtools-themes';
import { createContext } from 'react';
import { StyleUtils } from '../types';
export function resolveBase16Theme(
theme: keyof typeof reduxThemes | Base16Theme,
) {
return getBase16Theme(theme, reduxThemes) ?? reduxThemes.nicinabox;
}
declare module '@emotion/react' {
export interface Theme {
TEXT_COLOR: string;
TEXT_PLACEHOLDER_COLOR: string;
BACKGROUND_COLOR: string;
SELECTED_BACKGROUND_COLOR: string;
SKIPPED_BACKGROUND_COLOR: string;
HEADER_BACKGROUND_COLOR: string;
HEADER_BORDER_COLOR: string;
BORDER_COLOR: string;
LIST_BORDER_COLOR: string;
ACTION_TIME_BACK_COLOR: string;
ACTION_TIME_COLOR: string;
PIN_COLOR: string;
ITEM_HINT_COLOR: string;
TAB_BACK_SELECTED_COLOR: string;
TAB_BACK_COLOR: string;
TAB_BACK_HOVER_COLOR: string;
TAB_BORDER_COLOR: string;
DIFF_ADD_COLOR: string;
DIFF_REMOVE_COLOR: string;
DIFF_ARROW_COLOR: string;
LINK_COLOR: string;
LINK_HOVER_COLOR: string;
ERROR_COLOR: string;
ULIST_DISC_COLOR: string;
ULIST_COLOR: string;
ULIST_STRONG_COLOR: string;
TAB_CONTENT_COLOR: string;
TOGGLE_BUTTON_BACKGROUND: string;
TOGGLE_BUTTON_SELECTED_BACKGROUND: string;
TOGGLE_BUTTON_ERROR: string;
}
}
export const colorMap = (theme: reduxThemes.Base16Theme) =>
({
TEXT_COLOR: theme.base06,
TEXT_PLACEHOLDER_COLOR: rgba(theme.base06, 60),
BACKGROUND_COLOR: theme.base00,
SELECTED_BACKGROUND_COLOR: rgba(theme.base03, 20),
SKIPPED_BACKGROUND_COLOR: rgba(theme.base03, 10),
HEADER_BACKGROUND_COLOR: rgba(theme.base03, 30),
HEADER_BORDER_COLOR: rgba(theme.base03, 20),
BORDER_COLOR: rgba(theme.base03, 50),
LIST_BORDER_COLOR: rgba(theme.base03, 50),
ACTION_TIME_BACK_COLOR: rgba(theme.base03, 20),
ACTION_TIME_COLOR: theme.base04,
PIN_COLOR: theme.base04,
ITEM_HINT_COLOR: rgba(theme.base0F, 90),
TAB_BACK_SELECTED_COLOR: rgba(theme.base03, 20),
TAB_BACK_COLOR: rgba(theme.base00, 70),
TAB_BACK_HOVER_COLOR: rgba(theme.base03, 40),
TAB_BORDER_COLOR: rgba(theme.base03, 50),
DIFF_ADD_COLOR: rgba(theme.base0B, 40),
DIFF_REMOVE_COLOR: rgba(theme.base08, 40),
DIFF_ARROW_COLOR: theme.base0E,
LINK_COLOR: rgba(theme.base0E, 90),
LINK_HOVER_COLOR: theme.base0E,
ERROR_COLOR: theme.base08,
ULIST_DISC_COLOR: theme.base0D,
ULIST_COLOR: rgba(theme.base06, 60),
ULIST_STRONG_COLOR: theme.base0B,
TAB_CONTENT_COLOR: rgba(theme.base06, 60),
TOGGLE_BUTTON_BACKGROUND: rgba(theme.base00, 70),
TOGGLE_BUTTON_SELECTED_BACKGROUND: theme.base04,
TOGGLE_BUTTON_ERROR: rgba(theme.base08, 40),
}) as const;
export function createRtkQueryMonitorThemeFromBase16Theme(
base16Theme: Base16Theme,
invertTheme: boolean,
) {
const finalBase16Theme = invertTheme
? invertBase16Theme(base16Theme)
: base16Theme;
return colorMap(finalBase16Theme);
}
export const StyleUtilsContext = createContext<StyleUtils>({
base16Theme: reduxThemes.nicinabox,
invertTheme: false,
});
export function getJsonTreeTheme(
base16Theme: reduxThemes.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',
},
}),
};
}

View File

@ -1,8 +1,8 @@
import React, { ReactNode } from 'react'; import React from 'react';
import { StylingFunction } from 'react-base16-styling'; import type { GetItemString, LabelRenderer } from 'react-json-tree';
import type { LabelRenderer } from 'react-json-tree';
import { isCollection, isIndexed, isKeyed } from 'immutable'; import { isCollection, isIndexed, isKeyed } from 'immutable';
import isIterable from '../utils/isIterable'; import isIterable from '../utils/isIterable';
import { DATA_TYPE_KEY } from '../monitor-config';
const IS_IMMUTABLE_KEY = '@@__IS_IMMUTABLE__@@'; const IS_IMMUTABLE_KEY = '@@__IS_IMMUTABLE__@@';
@ -73,34 +73,21 @@ function getText(
} }
} }
export function getItemString( export const getItemString: GetItemString = (type: string, data: any) => (
styling: StylingFunction, <span>
type: string,
data: any,
dataTypeKey: string | symbol | undefined,
previewContent: boolean,
isDiff?: boolean,
): ReactNode {
return (
<span {...styling('treeItemHint')}>
{data[IS_IMMUTABLE_KEY] ? 'Immutable' : ''} {data[IS_IMMUTABLE_KEY] ? 'Immutable' : ''}
{dataTypeKey && data[dataTypeKey] {DATA_TYPE_KEY && data[DATA_TYPE_KEY]
? `${data[dataTypeKey] as string} ` ? `${data[DATA_TYPE_KEY] as string} `
: ''} : ''}
{getText(type, data, previewContent, isDiff)} {getText(type, data, false, undefined)}
</span> </span>
); );
}
export function createTreeItemLabelRenderer( export const labelRenderer: LabelRenderer = ([key], nodeType, expanded) => (
styling: StylingFunction,
): LabelRenderer {
return function labelRenderer([key], nodeType, expanded) {
return (
<span> <span>
<span {...styling('treeItemKey')}>{key}</span> <span css={(theme) => ({ color: theme.TEXT_PLACEHOLDER_COLOR })}>
{key}
</span>
{!expanded && ': '} {!expanded && ': '}
</span> </span>
); );
};
}

View File

@ -35,7 +35,7 @@ export interface RtkQueryMonitorProps<S, A extends Action<string>>
extends LiftedState<S, A, RtkQueryMonitorState> { extends LiftedState<S, A, RtkQueryMonitorState> {
dispatch: Dispatch<Action | LiftedAction<S, A, RtkQueryMonitorState>>; dispatch: Dispatch<Action | LiftedAction<S, A, RtkQueryMonitorState>>;
theme: keyof typeof themes | Base16Theme; theme: keyof typeof themes | Base16Theme;
invertTheme?: boolean; invertTheme: boolean;
} }
export type RtkQueryApiState = ReturnType< export type RtkQueryApiState = ReturnType<
@ -100,7 +100,6 @@ export interface SelectorsSource<S> {
export interface StyleUtils { export interface StyleUtils {
readonly base16Theme: Base16Theme; readonly base16Theme: Base16Theme;
readonly styling: StylingFunction;
readonly invertTheme: boolean; readonly invertTheme: boolean;
} }

View File

@ -2,7 +2,9 @@
"extends": "../../tsconfig.react.base.json", "extends": "../../tsconfig.react.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "lib/types", "outDir": "lib/types",
"resolveJsonModule": true "resolveJsonModule": true,
"jsx": "react-jsx",
"jsxImportSource": "@emotion/react"
}, },
"include": ["src"] "include": ["src"]
} }

View File

@ -1,7 +1,9 @@
{ {
"extends": "../../tsconfig.react.base.json", "extends": "../../tsconfig.react.base.json",
"compilerOptions": { "compilerOptions": {
"types": ["jest"] "types": ["jest"],
"jsx": "react-jsx",
"jsxImportSource": "@emotion/react"
}, },
"include": ["src", "test"], "include": ["src", "test"],
"exclude": ["dist"] "exclude": ["dist"]

View File

@ -2474,12 +2474,6 @@ importers:
immutable: immutable:
specifier: ^4.3.4 specifier: ^4.3.4
version: 4.3.4 version: 4.3.4
jss:
specifier: ^10.10.0
version: 10.10.0
jss-preset-default:
specifier: ^10.10.0
version: 10.10.0
lodash.debounce: lodash.debounce:
specifier: ^4.0.8 specifier: ^4.0.8
version: 4.0.8 version: 4.0.8
@ -2517,6 +2511,12 @@ importers:
'@babel/preset-typescript': '@babel/preset-typescript':
specifier: ^7.23.3 specifier: ^7.23.3
version: 7.23.3(@babel/core@7.23.5) version: 7.23.3(@babel/core@7.23.5)
'@emotion/babel-preset-css-prop':
specifier: ^11.11.0
version: 11.11.0(@babel/core@7.23.5)
'@emotion/react':
specifier: ^11.11.1
version: 11.11.1(@types/react@18.2.43)(react@18.2.0)
'@redux-devtools/core': '@redux-devtools/core':
specifier: ^3.14.0 specifier: ^3.14.0
version: link:../redux-devtools version: link:../redux-devtools
@ -12612,13 +12612,6 @@ packages:
source-map-js: 1.0.2 source-map-js: 1.0.2
dev: true dev: true
/css-vendor@2.0.8:
resolution: {integrity: sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==}
dependencies:
'@babel/runtime': 7.23.5
is-in-browser: 1.1.3
dev: false
/css-what@6.1.0: /css-what@6.1.0:
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@ -15220,10 +15213,6 @@ packages:
dev: false dev: false
optional: true optional: true
/hyphenate-style-name@1.0.4:
resolution: {integrity: sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==}
dev: false
/iconv-lite@0.4.24: /iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -15529,10 +15518,6 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/is-in-browser@1.1.3:
resolution: {integrity: sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==}
dev: false
/is-inside-container@1.0.0: /is-inside-container@1.0.0:
resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
engines: {node: '>=14.16'} engines: {node: '>=14.16'}
@ -16506,126 +16491,6 @@ packages:
semver: 7.5.4 semver: 7.5.4
dev: false dev: false
/jss-plugin-camel-case@10.10.0:
resolution: {integrity: sha512-z+HETfj5IYgFxh1wJnUAU8jByI48ED+v0fuTuhKrPR+pRBYS2EDwbusU8aFOpCdYhtRc9zhN+PJ7iNE8pAWyPw==}
dependencies:
'@babel/runtime': 7.23.5
hyphenate-style-name: 1.0.4
jss: 10.10.0
dev: false
/jss-plugin-compose@10.10.0:
resolution: {integrity: sha512-F5kgtWpI2XfZ3Z8eP78tZEYFdgTIbpA/TMuX3a8vwrNolYtN1N4qJR/Ob0LAsqIwCMLojtxN7c7Oo/+Vz6THow==}
dependencies:
'@babel/runtime': 7.23.5
jss: 10.10.0
tiny-warning: 1.0.3
dev: false
/jss-plugin-default-unit@10.10.0:
resolution: {integrity: sha512-SvpajxIECi4JDUbGLefvNckmI+c2VWmP43qnEy/0eiwzRUsafg5DVSIWSzZe4d2vFX1u9nRDP46WCFV/PXVBGQ==}
dependencies:
'@babel/runtime': 7.23.5
jss: 10.10.0
dev: false
/jss-plugin-expand@10.10.0:
resolution: {integrity: sha512-ymT62W2OyDxBxr7A6JR87vVX9vTq2ep5jZLIdUSusfBIEENLdkkc0lL/Xaq8W9s3opUq7R0sZQpzRWELrfVYzA==}
dependencies:
'@babel/runtime': 7.23.5
jss: 10.10.0
dev: false
/jss-plugin-extend@10.10.0:
resolution: {integrity: sha512-sKYrcMfr4xxigmIwqTjxNcHwXJIfvhvjTNxF+Tbc1NmNdyspGW47Ey6sGH8BcQ4FFQhLXctpWCQSpDwdNmXSwg==}
dependencies:
'@babel/runtime': 7.23.5
jss: 10.10.0
tiny-warning: 1.0.3
dev: false
/jss-plugin-global@10.10.0:
resolution: {integrity: sha512-icXEYbMufiNuWfuazLeN+BNJO16Ge88OcXU5ZDC2vLqElmMybA31Wi7lZ3lf+vgufRocvPj8443irhYRgWxP+A==}
dependencies:
'@babel/runtime': 7.23.5
jss: 10.10.0
dev: false
/jss-plugin-nested@10.10.0:
resolution: {integrity: sha512-9R4JHxxGgiZhurDo3q7LdIiDEgtA1bTGzAbhSPyIOWb7ZubrjQe8acwhEQ6OEKydzpl8XHMtTnEwHXCARLYqYA==}
dependencies:
'@babel/runtime': 7.23.5
jss: 10.10.0
tiny-warning: 1.0.3
dev: false
/jss-plugin-props-sort@10.10.0:
resolution: {integrity: sha512-5VNJvQJbnq/vRfje6uZLe/FyaOpzP/IH1LP+0fr88QamVrGJa0hpRRyAa0ea4U/3LcorJfBFVyC4yN2QC73lJg==}
dependencies:
'@babel/runtime': 7.23.5
jss: 10.10.0
dev: false
/jss-plugin-rule-value-function@10.10.0:
resolution: {integrity: sha512-uEFJFgaCtkXeIPgki8ICw3Y7VMkL9GEan6SqmT9tqpwM+/t+hxfMUdU4wQ0MtOiMNWhwnckBV0IebrKcZM9C0g==}
dependencies:
'@babel/runtime': 7.23.5
jss: 10.10.0
tiny-warning: 1.0.3
dev: false
/jss-plugin-rule-value-observable@10.10.0:
resolution: {integrity: sha512-ZLMaYrR3QE+vD7nl3oNXuj79VZl9Kp8/u6A1IbTPDcuOu8b56cFdWRZNZ0vNr8jHewooEeq2doy8Oxtymr2ZPA==}
dependencies:
'@babel/runtime': 7.23.5
jss: 10.10.0
symbol-observable: 1.2.0
dev: false
/jss-plugin-template@10.10.0:
resolution: {integrity: sha512-ocXZBIOJOA+jISPdsgkTs8wwpK6UbsvtZK5JI7VUggTD6LWKbtoxUzadd2TpfF+lEtlhUmMsCkTRNkITdPKa6w==}
dependencies:
'@babel/runtime': 7.23.5
jss: 10.10.0
tiny-warning: 1.0.3
dev: false
/jss-plugin-vendor-prefixer@10.10.0:
resolution: {integrity: sha512-UY/41WumgjW8r1qMCO8l1ARg7NHnfRVWRhZ2E2m0DMYsr2DD91qIXLyNhiX83hHswR7Wm4D+oDYNC1zWCJWtqg==}
dependencies:
'@babel/runtime': 7.23.5
css-vendor: 2.0.8
jss: 10.10.0
dev: false
/jss-preset-default@10.10.0:
resolution: {integrity: sha512-GL175Wt2FGhjE+f+Y3aWh+JioL06/QWFgZp53CbNNq6ZkVU0TDplD8Bxm9KnkotAYn3FlplNqoW5CjyLXcoJ7Q==}
dependencies:
'@babel/runtime': 7.23.5
jss: 10.10.0
jss-plugin-camel-case: 10.10.0
jss-plugin-compose: 10.10.0
jss-plugin-default-unit: 10.10.0
jss-plugin-expand: 10.10.0
jss-plugin-extend: 10.10.0
jss-plugin-global: 10.10.0
jss-plugin-nested: 10.10.0
jss-plugin-props-sort: 10.10.0
jss-plugin-rule-value-function: 10.10.0
jss-plugin-rule-value-observable: 10.10.0
jss-plugin-template: 10.10.0
jss-plugin-vendor-prefixer: 10.10.0
dev: false
/jss@10.10.0:
resolution: {integrity: sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw==}
dependencies:
'@babel/runtime': 7.23.5
csstype: 3.1.2
is-in-browser: 1.1.3
tiny-warning: 1.0.3
dev: false
/jstransformer@1.0.0: /jstransformer@1.0.0:
resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==} resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==}
dependencies: dependencies:
@ -20394,11 +20259,6 @@ packages:
webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20) webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)
dev: true dev: true
/symbol-observable@1.2.0:
resolution: {integrity: sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==}
engines: {node: '>=0.10.0'}
dev: false
/symbol-tree@3.2.4: /symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
dev: true dev: true
@ -20634,10 +20494,6 @@ packages:
/tiny-invariant@1.3.1: /tiny-invariant@1.3.1:
resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==} resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==}
/tiny-warning@1.0.3:
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
dev: false
/titleize@3.0.0: /titleize@3.0.0:
resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==}
engines: {node: '>=12'} engines: {node: '>=12'}