mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-07-26 16:09:50 +03:00
feat(rtk-query): complete initial setup of rtk-query
This commit is contained in:
parent
1449d5a9c2
commit
e9f397bd1e
|
@ -38,6 +38,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@redux-devtools/chart-monitor": "^1.9.0",
|
||||
"@reduxjs/toolkit": "^1.6.0",
|
||||
"@redux-devtools/rtk-query-inspector-monitor": "^1.0.0",
|
||||
"@redux-devtools/core": "^3.9.0",
|
||||
"@redux-devtools/inspector-monitor": "^1.0.0",
|
||||
"@redux-devtools/inspector-monitor-test-tab": "^0.7.2",
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import React, { Component } from 'react';
|
||||
import { connect, ResolveThunks } from 'react-redux';
|
||||
import RtkQueryInspectorMonitor from '@redux-devtools/rtk-query-inspector-monitor';
|
||||
import { selectMonitorWithState } from '../../actions';
|
||||
|
||||
type DispatchProps = ResolveThunks<typeof actionCreators>;
|
||||
type Props = DispatchProps;
|
||||
|
||||
class RtkQueryInspectorMonitorWrapper extends Component<Props> {
|
||||
static update = RtkQueryInspectorMonitor.update;
|
||||
|
||||
render() {
|
||||
return (
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
<RtkQueryInspectorMonitor defaultIsVisible invertTheme {...this.props} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const actionCreators = {
|
||||
selectMonitorWithState: selectMonitorWithState,
|
||||
};
|
||||
|
||||
export default connect(null, actionCreators)(RtkQueryInspectorMonitorWrapper);
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import LogMonitor from '@redux-devtools/log-monitor';
|
||||
import RtkQueryInspectorMonitorWrapper from '../containers/monitors/RtkQueryMonitorWrapper';
|
||||
import ChartMonitorWrapper from '../containers/monitors/ChartMonitorWrapper';
|
||||
import InspectorWrapper from '../containers/monitors/InspectorWrapper';
|
||||
|
||||
|
@ -7,6 +8,7 @@ export const monitors = [
|
|||
{ value: 'InspectorMonitor', name: 'Inspector' },
|
||||
{ value: 'LogMonitor', name: 'Log monitor' },
|
||||
{ value: 'ChartMonitor', name: 'Chart' },
|
||||
{ value: 'RtkQueryMonitor', name: 'RTK Query' },
|
||||
];
|
||||
|
||||
export default function getMonitor({ monitor }: { monitor: string }) {
|
||||
|
@ -17,6 +19,8 @@ export default function getMonitor({ monitor }: { monitor: string }) {
|
|||
);
|
||||
case 'ChartMonitor':
|
||||
return <ChartMonitorWrapper />;
|
||||
case 'RtkQueryMonitor':
|
||||
return <RtkQueryInspectorMonitorWrapper />;
|
||||
default:
|
||||
return <InspectorWrapper />;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"presets": [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react",
|
||||
"@babel/preset-typescript"
|
||||
],
|
||||
"plugins": ["@babel/plugin-proposal-class-properties"]
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
module.exports = {
|
||||
extends: '../../.eslintrc',
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
extends: '../../eslintrc.ts.react.base.json',
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
1
packages/redux-devtools-rtk-query-inspector-monitor/.gitignore
vendored
Normal file
1
packages/redux-devtools-rtk-query-inspector-monitor/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
demo/src/generated-module/
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021 Fabrizio Vitale
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,71 @@
|
|||
# Redux DevTools Chart Monitor
|
||||
|
||||
A chart monitor for [Redux DevTools](https://github.com/gaearon/redux-devtools).
|
||||
|
||||
Created by [@romseguy](https://github.com/romseguy) and merged from [`reduxjs/redux-devtools-chart-monitor`](https://github.com/reduxjs/redux-devtools-chart-monitor).
|
||||
|
||||
It shows a real-time view of the store aka the current state of the app.
|
||||
|
||||
:rocket: Now with immutable-js support.
|
||||
|
||||
[Demo](http://romseguy.github.io/redux-store-visualizer/) [(Source)](https://github.com/romseguy/redux-store-visualizer)
|
||||
|
||||

|
||||
|
||||
### Installation
|
||||
|
||||
```
|
||||
yarn add @redux-devtools/chart-monitor
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
You can use `ChartMonitor` as the only monitor in your app:
|
||||
|
||||
##### `containers/DevTools.js`
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import { createDevTools } from '@redux-devtools/core';
|
||||
import ChartMonitor from '@redux-devtools/chart-monitor';
|
||||
|
||||
export default createDevTools(<ChartMonitor />);
|
||||
```
|
||||
|
||||
Then you can render `<DevTools>` to any place inside app or even into a separate popup window.
|
||||
|
||||
Alternatively, you can use it together with [`DockMonitor`](https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-dock-monitor) to make it dockable.
|
||||
Consult the [`DockMonitor` README](https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-dock-monitor) for details of this approach.
|
||||
|
||||
[Read how to start using Redux DevTools.](https://github.com/reduxjs/redux-devtools)
|
||||
|
||||
### Features
|
||||
|
||||
### Props
|
||||
|
||||
#### ChartMonitor props
|
||||
|
||||
You can read the React component [propTypes](https://github.com/reduxjs/redux-devtools/blob/master/packages/redux-devtools-chart-monitor/src/Chart.js#L11) in addition to the details below:
|
||||
|
||||
| Name | Description |
|
||||
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `defaultIsVisible` | By default, set to `true`. |
|
||||
| `transitionDuration` | By default, set to `750`, in milliseconds. |
|
||||
| `heightBetweenNodesCoeff` | By default, set to `1`. |
|
||||
| `widthBetweenNodesCoeff` | By default, set to `1.3`. |
|
||||
| `isSorted` | By default, set to `false`. |
|
||||
| `style` | {<br> width: '100%', height: '100%', // i.e fullscreen for [`DockMonitor`](https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-dock-monitor)<br> text: {<br> colors: {<br> 'default': `theme.base0D`,<br> hover: `theme.base06`<br> }<br> },<br> node: {<br> colors: {<br> 'default': `theme.base0B`,<br> collapsed: `theme.base0B`,<br> parent: `theme.base0E`<br> },<br> radius: 7<br> }<br>} |
|
||||
| `onClickText` | Function called with a reference to the clicked node as first argument when clicking on the text next to a node. |
|
||||
| `tooltipOptions` | {<br> disabled: false,<br> indentationSize: 2,<br> style: {<br> 'background-color': `theme.base06`,<br> 'opacity': '0.7',<br> 'border-radius': '5px',<br> 'padding': '5px'<br> }<br>}<br>[More info](https://github.com/reduxjs/redux-devtools/tree/master/packages/d3tooltip#api). |
|
||||
|
||||
#### Redux DevTools props
|
||||
|
||||
| Name | Description |
|
||||
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `theme` | Either a string referring to one of the themes provided by [redux-devtools-themes](https://github.com/gaearon/redux-devtools-themes) (feel free to contribute!) or a custom object of the same format. Optional. By default, set to [`'nicinabox'`](https://github.com/gaearon/redux-devtools-themes/blob/master/src/nicinabox.js). |
|
||||
| `invertTheme` | Boolean value that will invert the colors of the selected theme. Optional. By default, set to `false` |
|
||||
| `select` | A function that selects the slice of the state for DevTools to show. For example, `state => state.thePart.iCare.about`. Optional. By default, set to `state => state`. |
|
||||
|
||||
### License
|
||||
|
||||
MIT
|
3
packages/redux-devtools-rtk-query-inspector-monitor/demo/.gitignore
vendored
Normal file
3
packages/redux-devtools-rtk-query-inspector-monitor/demo/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
.snowpack
|
||||
build
|
||||
node_modules
|
|
@ -0,0 +1,25 @@
|
|||
# New Project
|
||||
|
||||
> ✨ Bootstrapped with Create Snowpack App (CSA).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
### npm start
|
||||
|
||||
Runs the app in the development mode.
|
||||
Open http://localhost:8080 to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### npm run build
|
||||
|
||||
Builds a static copy of your site to the `build/` folder.
|
||||
Your app is ready to be deployed!
|
||||
|
||||
**For the best production performance:** Add a build bundler plugin like "@snowpack/plugin-webpack" to your `snowpack.config.mjs` config file.
|
||||
|
||||
### npm test
|
||||
|
||||
Launches the application test runner.
|
||||
Run with the `--watch` flag (`npm test -- --watch`) to run in interactive watch mode.
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "rtk-query-imspector-monitor-demo",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "cross-env SKIP_PREFLIGHT_CHECK=true react-scripts start",
|
||||
"build": "react-scripts build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@redux-devtools/core": "^3.9.0",
|
||||
"@redux-devtools/dock-monitor": "^1.4.0",
|
||||
"@reduxjs/toolkit": "^1.6.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"devui": "^1.0.0-8",
|
||||
"react": "^17.0.2",
|
||||
"react-base16-styling": "^0.8.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-redux": "^7.2.1",
|
||||
"react-scripts": "4.0.2",
|
||||
"redux": "^4.0.5",
|
||||
"redux-devtools-themes": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "17.0.0",
|
||||
"@types/react-dom": "17.0.0",
|
||||
"@types/react-redux": "7.1.9",
|
||||
"typescript": "4.1.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app"
|
||||
]
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all"
|
||||
]
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="description" content="Web site created using create-snowpack-app" />
|
||||
<title>Snowpack App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<script type="module" src="/dist/index.js"></script>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,46 @@
|
|||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
.App code {
|
||||
background: #fff3;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.App p {
|
||||
margin: 0.4rem;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import * as React from 'react';
|
||||
import { Pokemon } from './Pokemon';
|
||||
import { PokemonName, POKEMON_NAMES } from './pokemon.data';
|
||||
import './styles.css';
|
||||
|
||||
const getRandomPokemonName = () =>
|
||||
POKEMON_NAMES[Math.floor(Math.random() * POKEMON_NAMES.length)];
|
||||
|
||||
export default function App() {
|
||||
const [pokemon, setPokemon] = React.useState<PokemonName[]>(['bulbasaur']);
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<div>
|
||||
<button
|
||||
onClick={() =>
|
||||
setPokemon((prev) => [...prev, getRandomPokemonName()])
|
||||
}
|
||||
>
|
||||
Add random pokemon
|
||||
</button>
|
||||
<button onClick={() => setPokemon((prev) => [...prev, 'bulbasaur'])}>
|
||||
Add bulbasaur
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{pokemon.map((name, index) => (
|
||||
<Pokemon key={index} name={name} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import * as React from 'react';
|
||||
import { createDevTools } from '@redux-devtools/core';
|
||||
import DockMonitor from '@redux-devtools/dock-monitor';
|
||||
import RtkQueryInspectorMonitor from './generated-module/RtkQueryInspectorMonitor.js';
|
||||
|
||||
export default createDevTools(
|
||||
<DockMonitor
|
||||
toggleVisibilityKey="ctrl-h"
|
||||
changePositionKey="ctrl-q"
|
||||
changeMonitorKey="ctrl-m"
|
||||
>
|
||||
<RtkQueryInspectorMonitor />
|
||||
</DockMonitor>,
|
||||
);
|
|
@ -0,0 +1,77 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useGetPokemonByNameQuery } from './services/pokemon';
|
||||
import type { PokemonName } from './pokemon.data';
|
||||
|
||||
const intervalOptions = [
|
||||
{ label: 'Off', value: 0 },
|
||||
{ label: '3s', value: 3000 },
|
||||
{ label: '5s', value: 5000 },
|
||||
{ label: '10s', value: 10000 },
|
||||
{ label: '1m', value: 60000 },
|
||||
];
|
||||
|
||||
const getRandomIntervalValue = () =>
|
||||
intervalOptions[Math.floor(Math.random() * intervalOptions.length)].value;
|
||||
|
||||
export function Pokemon({ name }: { name: PokemonName }) {
|
||||
const [pollingInterval, setPollingInterval] = useState(60000);
|
||||
|
||||
const {
|
||||
data,
|
||||
error,
|
||||
isLoading,
|
||||
isFetching,
|
||||
refetch,
|
||||
} = useGetPokemonByNameQuery(name, {
|
||||
pollingInterval,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
float: 'left',
|
||||
textAlign: 'center',
|
||||
...(isFetching ? { background: '#e6ffe8' } : {}),
|
||||
}}
|
||||
>
|
||||
{error ? (
|
||||
<>Oh no, there was an error loading {name}</>
|
||||
) : isLoading ? (
|
||||
<>Loading...</>
|
||||
) : data ? (
|
||||
<>
|
||||
<h3>{data.species.name}</h3>
|
||||
<div style={{ minWidth: 96, minHeight: 96 }}>
|
||||
<img
|
||||
src={data.sprites.front_shiny}
|
||||
alt={data.species.name}
|
||||
style={{ ...(isFetching ? { opacity: 0.3 } : {}) }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ display: 'block' }}>Polling interval</label>
|
||||
<select
|
||||
value={pollingInterval}
|
||||
onChange={({ target: { value } }) =>
|
||||
setPollingInterval(Number(value))
|
||||
}
|
||||
>
|
||||
{intervalOptions.map(({ label, value }) => (
|
||||
<option key={value} value={value}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<button onClick={refetch} disabled={isFetching}>
|
||||
{isFetching ? 'Loading' : 'Manually refetch'}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
'No Data'
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import App from './App';
|
||||
import { store } from './store';
|
||||
import DevTools from './DevTools';
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
<DevTools />
|
||||
</Provider>,
|
||||
rootElement,
|
||||
);
|
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
||||
<g fill="#61DAFB">
|
||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4a43.8 43.8 0 00-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9a487.8 487.8 0 00-41.6-50c32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9a467 467 0 00-63.6 11c-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4a44 44 0 0022.5 5.6c27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7a450.4 450.4 0 01-13.5 39.5 473.3 473.3 0 00-27.5-47.4c14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5a532.7 532.7 0 01-24.1 38.2 520.3 520.3 0 01-90.2.1 551.2 551.2 0 01-45-77.8 521.5 521.5 0 0144.8-78.1 520.3 520.3 0 0190.2-.1 551.2 551.2 0 0145 77.8 560 560 0 01-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8a448.8 448.8 0 01-41.2 8 552.4 552.4 0 0027.4-47.8zM421.2 430a412.3 412.3 0 01-27.8-32 619 619 0 0055.3 0c-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9a451.2 451.2 0 01-41-7.9c3.7-12.9 8.3-26.2 13.5-39.5a473.3 473.3 0 0027.5 47.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32a619 619 0 00-55.3 0c9-11.7 18.3-22.4 27.5-32zm-74 58.9a552.4 552.4 0 00-27.4 47.7c-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9a473.5 473.5 0 00-22.2 60.6c-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9a487.8 487.8 0 0041.6 50c-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9a467 467 0 0063.6-11 280 280 0 015.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9a473.5 473.5 0 0022.2-60.6c9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
|
@ -0,0 +1,155 @@
|
|||
export const POKEMON_NAMES = [
|
||||
'bulbasaur',
|
||||
'ivysaur',
|
||||
'venusaur',
|
||||
'charmander',
|
||||
'charmeleon',
|
||||
'charizard',
|
||||
'squirtle',
|
||||
'wartortle',
|
||||
'blastoise',
|
||||
'caterpie',
|
||||
'metapod',
|
||||
'butterfree',
|
||||
'weedle',
|
||||
'kakuna',
|
||||
'beedrill',
|
||||
'pidgey',
|
||||
'pidgeotto',
|
||||
'pidgeot',
|
||||
'rattata',
|
||||
'raticate',
|
||||
'spearow',
|
||||
'fearow',
|
||||
'ekans',
|
||||
'arbok',
|
||||
'pikachu',
|
||||
'raichu',
|
||||
'sandshrew',
|
||||
'sandslash',
|
||||
'nidoran',
|
||||
'nidorina',
|
||||
'nidoqueen',
|
||||
'nidoran',
|
||||
'nidorino',
|
||||
'nidoking',
|
||||
'clefairy',
|
||||
'clefable',
|
||||
'vulpix',
|
||||
'ninetales',
|
||||
'jigglypuff',
|
||||
'wigglytuff',
|
||||
'zubat',
|
||||
'golbat',
|
||||
'oddish',
|
||||
'gloom',
|
||||
'vileplume',
|
||||
'paras',
|
||||
'parasect',
|
||||
'venonat',
|
||||
'venomoth',
|
||||
'diglett',
|
||||
'dugtrio',
|
||||
'meowth',
|
||||
'persian',
|
||||
'psyduck',
|
||||
'golduck',
|
||||
'mankey',
|
||||
'primeape',
|
||||
'growlithe',
|
||||
'arcanine',
|
||||
'poliwag',
|
||||
'poliwhirl',
|
||||
'poliwrath',
|
||||
'abra',
|
||||
'kadabra',
|
||||
'alakazam',
|
||||
'machop',
|
||||
'machoke',
|
||||
'machamp',
|
||||
'bellsprout',
|
||||
'weepinbell',
|
||||
'victreebel',
|
||||
'tentacool',
|
||||
'tentacruel',
|
||||
'geodude',
|
||||
'graveler',
|
||||
'golem',
|
||||
'ponyta',
|
||||
'rapidash',
|
||||
'slowpoke',
|
||||
'slowbro',
|
||||
'magnemite',
|
||||
'magneton',
|
||||
"farfetch'd",
|
||||
'doduo',
|
||||
'dodrio',
|
||||
'seel',
|
||||
'dewgong',
|
||||
'grimer',
|
||||
'muk',
|
||||
'shellder',
|
||||
'cloyster',
|
||||
'gastly',
|
||||
'haunter',
|
||||
'gengar',
|
||||
'onix',
|
||||
'drowzee',
|
||||
'hypno',
|
||||
'krabby',
|
||||
'kingler',
|
||||
'voltorb',
|
||||
'electrode',
|
||||
'exeggcute',
|
||||
'exeggutor',
|
||||
'cubone',
|
||||
'marowak',
|
||||
'hitmonlee',
|
||||
'hitmonchan',
|
||||
'lickitung',
|
||||
'koffing',
|
||||
'weezing',
|
||||
'rhyhorn',
|
||||
'rhydon',
|
||||
'chansey',
|
||||
'tangela',
|
||||
'kangaskhan',
|
||||
'horsea',
|
||||
'seadra',
|
||||
'goldeen',
|
||||
'seaking',
|
||||
'staryu',
|
||||
'starmie',
|
||||
'mr. mime',
|
||||
'scyther',
|
||||
'jynx',
|
||||
'electabuzz',
|
||||
'magmar',
|
||||
'pinsir',
|
||||
'tauros',
|
||||
'magikarp',
|
||||
'gyarados',
|
||||
'lapras',
|
||||
'ditto',
|
||||
'eevee',
|
||||
'vaporeon',
|
||||
'jolteon',
|
||||
'flareon',
|
||||
'porygon',
|
||||
'omanyte',
|
||||
'omastar',
|
||||
'kabuto',
|
||||
'kabutops',
|
||||
'aerodactyl',
|
||||
'snorlax',
|
||||
'articuno',
|
||||
'zapdos',
|
||||
'moltres',
|
||||
'dratini',
|
||||
'dragonair',
|
||||
'dragonite',
|
||||
'mewtwo',
|
||||
'mew',
|
||||
] as const;
|
||||
|
||||
export type PokemonName = typeof POKEMON_NAMES[number];
|
5
packages/redux-devtools-rtk-query-inspector-monitor/demo/src/react-app-env.d.ts
vendored
Normal file
5
packages/redux-devtools-rtk-query-inspector-monitor/demo/src/react-app-env.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
/// <reference types="react-scripts" />
|
||||
|
||||
declare module '@redux-devtools/app';
|
||||
|
||||
declare module 'remote-redux-devtools';
|
|
@ -0,0 +1,15 @@
|
|||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
||||
import type { PokemonName } from '../pokemon.data';
|
||||
|
||||
export const pokemonApi = createApi({
|
||||
reducerPath: 'pokemonApi',
|
||||
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
|
||||
endpoints: (builder) => ({
|
||||
getPokemonByName: builder.query({
|
||||
query: (name: PokemonName) => `pokemon/${name}`,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
// Export hooks for usage in functional components
|
||||
export const { useGetPokemonByNameQuery } = pokemonApi;
|
|
@ -0,0 +1,13 @@
|
|||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import { pokemonApi } from './services/pokemon';
|
||||
import DevTools from './DevTools';
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
[pokemonApi.reducerPath]: pokemonApi.reducer,
|
||||
},
|
||||
devTools: false,
|
||||
// adding the api middleware enables caching, invalidation, polling and other features of `rtk-query`
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware().concat(pokemonApi.middleware),
|
||||
enhancers: [DevTools.instrument()]
|
||||
});
|
|
@ -0,0 +1,4 @@
|
|||
.App {
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
],
|
||||
"jsx": "react-jsx",
|
||||
"target": "es5",
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
59
packages/redux-devtools-rtk-query-inspector-monitor/demo/types/static.d.ts
vendored
Normal file
59
packages/redux-devtools-rtk-query-inspector-monitor/demo/types/static.d.ts
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
/* Use this file to declare any custom file extensions for importing */
|
||||
/* Use this folder to also add/extend a package d.ts file, if needed. */
|
||||
|
||||
/* CSS MODULES */
|
||||
declare module '*.module.css' {
|
||||
const classes: { [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.module.scss' {
|
||||
const classes: { [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.module.sass' {
|
||||
const classes: { [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.module.less' {
|
||||
const classes: { [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.module.styl' {
|
||||
const classes: { [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
|
||||
/* CSS */
|
||||
declare module '*.css';
|
||||
declare module '*.scss';
|
||||
declare module '*.sass';
|
||||
declare module '*.less';
|
||||
declare module '*.styl';
|
||||
|
||||
/* IMAGES */
|
||||
declare module '*.svg' {
|
||||
const ref: string;
|
||||
export default ref;
|
||||
}
|
||||
declare module '*.bmp' {
|
||||
const ref: string;
|
||||
export default ref;
|
||||
}
|
||||
declare module '*.gif' {
|
||||
const ref: string;
|
||||
export default ref;
|
||||
}
|
||||
declare module '*.jpg' {
|
||||
const ref: string;
|
||||
export default ref;
|
||||
}
|
||||
declare module '*.jpeg' {
|
||||
const ref: string;
|
||||
export default ref;
|
||||
}
|
||||
declare module '*.png' {
|
||||
const ref: string;
|
||||
export default ref;
|
||||
}
|
||||
|
||||
/* CUSTOM: ADD YOUR OWN HERE */
|
11911
packages/redux-devtools-rtk-query-inspector-monitor/demo/yarn.lock
Normal file
11911
packages/redux-devtools-rtk-query-inspector-monitor/demo/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"name": "@redux-devtools/rtk-query-inspector-monitor",
|
||||
"version": "1.0.0",
|
||||
"description": "rtk-query Monitor for Redux DevTools",
|
||||
"keywords": [
|
||||
"redux",
|
||||
"devtools",
|
||||
"flux",
|
||||
"react",
|
||||
"redux-toolkit",
|
||||
"rtk-query"
|
||||
],
|
||||
"homepage": "https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-chart-monitor",
|
||||
"bugs": {
|
||||
"url": "https://github.com/reduxjs/redux-devtools/issues"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "FaberVitale",
|
||||
"files": [
|
||||
"lib",
|
||||
"src"
|
||||
],
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reduxjs/redux-devtools.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run build:types && npm run build:js",
|
||||
"build:types": "tsc --emitDeclarationOnly",
|
||||
"start:dev": "tsc -p ./tsconfig.dev.json --watch",
|
||||
"build:js": "babel src --out-dir lib --extensions \".ts,.tsx\" --source-maps inline",
|
||||
"clean": "rimraf lib",
|
||||
"lint": "eslint . --ext .ts,.tsx",
|
||||
"lint:fix": "eslint . --ext .ts,.tsx --fix",
|
||||
"type-check": "tsc --noEmit",
|
||||
"type-check:watch": "npm run type-check -- --watch",
|
||||
"preversion": "npm run type-check && npm run lint",
|
||||
"prepublishOnly": "npm run clean && npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/prop-types": "^15.7.3",
|
||||
"@types/redux-devtools-themes": "^1.0.0",
|
||||
"@redux-devtools/dock-monitor": "^1.4.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"redux-devtools-themes": "^1.0.0",
|
||||
"devui": "^1.0.0-8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@redux-devtools/core": "^3.9.0",
|
||||
"@types/react": "^16.9.46",
|
||||
"@reduxjs/toolkit": "^1.6.0",
|
||||
"react": "^16.13.1",
|
||||
"redux": "^4.0.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@redux-devtools/core": "^3.7.0",
|
||||
"@reduxjs/toolkit": "^1.6.0",
|
||||
"@types/react": "^16.3.0 || ^17.0.0",
|
||||
"react": "^16.3.0 || ^17.0.0",
|
||||
"redux": "^3.4.0 || ^4.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
import React, { Component, createRef, CSSProperties, ReactNode } from 'react';
|
||||
import { AnyAction, Dispatch, Action } from 'redux';
|
||||
import { LiftedAction, LiftedState } from '@redux-devtools/core';
|
||||
import * as themes from 'redux-devtools-themes';
|
||||
import { Base16Theme } from 'react-base16-styling';
|
||||
import { QueryInfo, RtkQueryInspectorMonitorState } from './types';
|
||||
import { createInspectorSelectors, computeSelectorSource } from './selectors';
|
||||
import { selectQueryKey } from './reducers';
|
||||
import { QueryList } from './components/QueryList';
|
||||
import { StyleUtils } from './styles/createStylingFromTheme';
|
||||
import { QueryForm } from './components/QueryForm';
|
||||
|
||||
const wrapperStyle: CSSProperties = {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
};
|
||||
|
||||
type SelectorsSource<S> = {
|
||||
currentState: S | null;
|
||||
monitorState: RtkQueryInspectorMonitorState;
|
||||
};
|
||||
|
||||
export interface RtkQueryInspectorProps<S, A extends Action<unknown>>
|
||||
extends LiftedState<S, A, RtkQueryInspectorMonitorState> {
|
||||
dispatch: Dispatch<LiftedAction<S, A, RtkQueryInspectorMonitorState>>;
|
||||
theme: keyof typeof themes | Base16Theme;
|
||||
invertTheme: boolean;
|
||||
state: S | null;
|
||||
styleUtils: StyleUtils;
|
||||
}
|
||||
|
||||
type RtkQueryInspectorState<S> = { selectorsSource: SelectorsSource<S> };
|
||||
|
||||
class RtkQueryInspector<S, A extends Action<unknown>> extends Component<
|
||||
RtkQueryInspectorProps<S, A>,
|
||||
RtkQueryInspectorState<S>
|
||||
> {
|
||||
divRef = createRef<HTMLDivElement>();
|
||||
|
||||
constructor(props: RtkQueryInspectorProps<S, A>) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selectorsSource: computeSelectorSource(props, null),
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(
|
||||
props: RtkQueryInspectorProps<unknown, Action<unknown>>,
|
||||
state: RtkQueryInspectorState<unknown>
|
||||
): null | RtkQueryInspectorState<unknown> {
|
||||
const selectorsSource = computeSelectorSource<unknown, Action<unknown>>(
|
||||
props,
|
||||
state.selectorsSource
|
||||
);
|
||||
|
||||
if (selectorsSource !== state.selectorsSource) {
|
||||
return {
|
||||
selectorsSource,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
selectors = createInspectorSelectors<S>();
|
||||
|
||||
handleSelectQuery = (queryInfo: QueryInfo) => {
|
||||
this.props.dispatch(selectQueryKey(queryInfo) as AnyAction);
|
||||
};
|
||||
|
||||
render(): ReactNode {
|
||||
const { selectorsSource } = this.state;
|
||||
const {
|
||||
styleUtils: { styling },
|
||||
} = this.props;
|
||||
const apiStates = this.selectors.selectApiStates(selectorsSource);
|
||||
const allSortedQueries = this.selectors.selectAllSortedQueries(
|
||||
selectorsSource
|
||||
);
|
||||
|
||||
console.log('inspector', { apiStates, allSortedQueries, selectorsSource });
|
||||
|
||||
return (
|
||||
<div style={wrapperStyle} ref={this.divRef}>
|
||||
<div {...styling('querySectionWrapper')}>
|
||||
<QueryForm
|
||||
dispatch={this.props.dispatch}
|
||||
queryComparator={selectorsSource.monitorState.queryComparator}
|
||||
isAscendingQueryComparatorOrder={
|
||||
selectorsSource.monitorState.isAscendingQueryComparatorOrder
|
||||
}
|
||||
/>
|
||||
<QueryList
|
||||
onSelectQuery={this.handleSelectQuery}
|
||||
queryInfos={allSortedQueries}
|
||||
selectedQueryKey={selectorsSource.monitorState.selectedQueryKey}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RtkQueryInspector;
|
|
@ -0,0 +1,116 @@
|
|||
import React, { CSSProperties, PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as themes from 'redux-devtools-themes';
|
||||
import { Action } from 'redux';
|
||||
import { Base16Theme } from 'react-base16-styling';
|
||||
import RtkQueryInspector from './RtkQueryInspector';
|
||||
import reducer from './reducers';
|
||||
import {
|
||||
ExternalProps,
|
||||
RtkQueryInspectorMonitorProps,
|
||||
RtkQueryInspectorMonitorState,
|
||||
} from './types';
|
||||
import {
|
||||
createThemeState,
|
||||
StyleUtils,
|
||||
StyleUtilsContext,
|
||||
} from './styles/createStylingFromTheme';
|
||||
|
||||
const styles: { container: CSSProperties } = {
|
||||
container: {
|
||||
fontFamily: 'monaco, Consolas, Lucida Console, monospace',
|
||||
position: 'relative',
|
||||
overflowY: 'hidden',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
minWidth: 300,
|
||||
},
|
||||
};
|
||||
|
||||
interface DefaultProps<S> {
|
||||
select: (state: unknown) => unknown;
|
||||
theme: keyof typeof themes | Base16Theme;
|
||||
preserveScrollTop: boolean;
|
||||
expandActionRoot: boolean;
|
||||
expandStateRoot: boolean;
|
||||
markStateDiff: boolean;
|
||||
}
|
||||
|
||||
export interface RtkQueryInspectorComponentState {
|
||||
readonly styleUtils: StyleUtils;
|
||||
}
|
||||
|
||||
class RtkQueryInspectorMonitor<
|
||||
S,
|
||||
A extends Action<unknown>
|
||||
> extends PureComponent<
|
||||
RtkQueryInspectorMonitorProps<S, A>,
|
||||
RtkQueryInspectorComponentState
|
||||
> {
|
||||
constructor(props: RtkQueryInspectorMonitorProps<S, A>) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
styleUtils: createThemeState<S, A>(props),
|
||||
};
|
||||
}
|
||||
|
||||
static update = reducer;
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func,
|
||||
computedStates: PropTypes.array,
|
||||
currentStateIndex: PropTypes.number,
|
||||
actionsById: PropTypes.object,
|
||||
stagedActionIds: PropTypes.array,
|
||||
skippedActionIds: PropTypes.array,
|
||||
monitorState: PropTypes.shape({
|
||||
initialScrollTop: PropTypes.number,
|
||||
}),
|
||||
|
||||
preserveScrollTop: PropTypes.bool,
|
||||
select: PropTypes.func.isRequired,
|
||||
theme: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
||||
invertTheme: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps: DefaultProps<unknown> = {
|
||||
select: (state: unknown) => state,
|
||||
theme: 'nicinabox',
|
||||
preserveScrollTop: true,
|
||||
expandActionRoot: true,
|
||||
expandStateRoot: true,
|
||||
markStateDiff: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
styleUtils: { base16Theme, styling },
|
||||
} = this.state;
|
||||
|
||||
const RtkQueryInspectorAsAny = RtkQueryInspector as any;
|
||||
|
||||
return (
|
||||
<StyleUtilsContext.Provider value={this.state.styleUtils}>
|
||||
<div {...styling(['inspector'])}>
|
||||
<RtkQueryInspectorAsAny
|
||||
{...this.props}
|
||||
theme={base16Theme}
|
||||
styleUtils={this.state.styleUtils}
|
||||
/>
|
||||
</div>
|
||||
</StyleUtilsContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default (RtkQueryInspectorMonitor as unknown) as React.ComponentType<
|
||||
ExternalProps<unknown, Action<unknown>>
|
||||
> & {
|
||||
update(
|
||||
monitorProps: ExternalProps<unknown, Action<unknown>>,
|
||||
state: RtkQueryInspectorMonitorState | undefined,
|
||||
action: Action
|
||||
): RtkQueryInspectorMonitorState;
|
||||
defaultProps: DefaultProps<unknown>;
|
||||
};
|
|
@ -0,0 +1,132 @@
|
|||
import React, { ReactNode, FormEvent, MouseEvent } from 'react';
|
||||
import { RtkQueryInspectorMonitorState } from '../types';
|
||||
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
|
||||
import { Select } from 'devui';
|
||||
import { AnyAction } from 'redux';
|
||||
import { sortQueryOptions, QueryComparators } from '../utils/comparators';
|
||||
import {
|
||||
changeIsAscendingQueryComparatorOrder,
|
||||
changeQueryComparator,
|
||||
} from '../reducers';
|
||||
export interface QueryFormProps
|
||||
extends Pick<
|
||||
RtkQueryInspectorMonitorState,
|
||||
'isAscendingQueryComparatorOrder' | 'queryComparator'
|
||||
> {
|
||||
dispatch: (action: AnyAction) => void;
|
||||
}
|
||||
|
||||
const ascId = 'rtk-query-rb-asc';
|
||||
const descId = 'rtk-query-rb-desc';
|
||||
const selectId = 'rtk-query-comp-select';
|
||||
const searchId = 'rtk-query-search-query';
|
||||
|
||||
const searchPlaceholder = 'filter query...';
|
||||
|
||||
export class QueryForm extends React.PureComponent<QueryFormProps> {
|
||||
handleSubmit = (evt: FormEvent<HTMLFormElement>): void => {
|
||||
evt.preventDefault();
|
||||
};
|
||||
|
||||
handleButtonGroupClick = ({ target }: MouseEvent<HTMLElement>): void => {
|
||||
const { isAscendingQueryComparatorOrder: isAsc, dispatch } = this.props;
|
||||
|
||||
const targetId = (target as HTMLElement)?.id ?? null;
|
||||
|
||||
if (targetId === ascId && !isAsc) {
|
||||
dispatch(changeIsAscendingQueryComparatorOrder(true));
|
||||
} else if (targetId === descId && isAsc) {
|
||||
this.props.dispatch(changeIsAscendingQueryComparatorOrder(false));
|
||||
}
|
||||
};
|
||||
|
||||
handleSelectComparator = (option: { value: string }): void => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
if (typeof option?.value === 'string') {
|
||||
dispatch(changeQueryComparator(option.value as QueryComparators));
|
||||
}
|
||||
};
|
||||
|
||||
getSelectedOption = (option: { value: string }): string => option?.value;
|
||||
|
||||
render(): ReactNode {
|
||||
const {
|
||||
isAscendingQueryComparatorOrder: isAsc,
|
||||
queryComparator,
|
||||
} = this.props;
|
||||
|
||||
const isDesc = !isAsc;
|
||||
|
||||
return (
|
||||
<StyleUtilsContext.Consumer>
|
||||
{({ styling, base16Theme }) => {
|
||||
return (
|
||||
<form
|
||||
action="#"
|
||||
onSubmit={this.handleSubmit}
|
||||
{...styling('queryForm')}
|
||||
>
|
||||
<div {...styling('queryListHeader')}>
|
||||
<label htmlFor={searchId} {...styling('srOnly')}>
|
||||
filter query
|
||||
</label>
|
||||
<input
|
||||
type="search"
|
||||
placeholder={searchPlaceholder}
|
||||
{...styling('querySearch')}
|
||||
/>
|
||||
</div>
|
||||
<div {...styling('sortBySection')}>
|
||||
<label htmlFor={selectId}>Sort by</label>
|
||||
<Select
|
||||
id={selectId}
|
||||
isSearchable={false}
|
||||
openOuterUp
|
||||
theme={base16Theme}
|
||||
value={sortQueryOptions.find(
|
||||
(opt) => opt?.value === queryComparator
|
||||
)}
|
||||
options={sortQueryOptions}
|
||||
onChange={this.handleSelectComparator}
|
||||
selectOption={this.getSelectedOption}
|
||||
/>
|
||||
<div
|
||||
tabIndex={0}
|
||||
role="radiogroup"
|
||||
aria-activedescendant={isAsc ? ascId : descId}
|
||||
onClick={this.handleButtonGroupClick}
|
||||
>
|
||||
<button
|
||||
role="radio"
|
||||
type="button"
|
||||
id={ascId}
|
||||
aria-checked={isAsc}
|
||||
{...styling(
|
||||
['selectorButton', isAsc && 'selectorButtonSelected'],
|
||||
isAsc
|
||||
)}
|
||||
>
|
||||
asc
|
||||
</button>
|
||||
<button
|
||||
id={descId}
|
||||
role="radio"
|
||||
type="button"
|
||||
aria-checked={isDesc}
|
||||
{...styling(
|
||||
['selectorButton', isDesc && 'selectorButtonSelected'],
|
||||
isDesc
|
||||
)}
|
||||
>
|
||||
desc
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}}
|
||||
</StyleUtilsContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import React, { PureComponent, ReactNode, MouseEvent } from 'react';
|
||||
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
|
||||
import { QueryInfo, RtkQueryInspectorMonitorState } from '../types';
|
||||
import { isQuerySelected } from '../utils/rtk-query';
|
||||
|
||||
export interface QueryListProps {
|
||||
queryInfos: QueryInfo[];
|
||||
selectedQueryKey: RtkQueryInspectorMonitorState['selectedQueryKey'];
|
||||
onSelectQuery: (query: QueryInfo) => void;
|
||||
}
|
||||
|
||||
export class QueryList extends PureComponent<QueryListProps> {
|
||||
static isItemSelected(
|
||||
selectedQueryKey: QueryListProps['selectedQueryKey'],
|
||||
queryInfo: QueryInfo
|
||||
): boolean {
|
||||
return (
|
||||
!!selectedQueryKey &&
|
||||
selectedQueryKey.queryKey === queryInfo.queryKey &&
|
||||
selectedQueryKey.reducerPath === queryInfo.reducerPath
|
||||
);
|
||||
}
|
||||
|
||||
render(): ReactNode {
|
||||
const { queryInfos, selectedQueryKey, onSelectQuery } = this.props;
|
||||
|
||||
return (
|
||||
<StyleUtilsContext.Consumer>
|
||||
{({ styling }) => (
|
||||
<ul {...styling('queryList')}>
|
||||
{queryInfos.map((queryInfo) => {
|
||||
const isSelected = isQuerySelected(selectedQueryKey, queryInfo);
|
||||
|
||||
return (
|
||||
<li
|
||||
key={queryInfo.queryKey}
|
||||
onClick={() => onSelectQuery(queryInfo)}
|
||||
{...styling(
|
||||
['queryListItem', isSelected && 'queryListItemSelected'],
|
||||
isSelected
|
||||
)}
|
||||
>
|
||||
<p>{queryInfo.queryKey}</p>
|
||||
<p {...styling('queryStatus')}>{queryInfo.query.status}</p>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</StyleUtilsContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from './RtkQueryInspectorMonitor';
|
||||
export { ExternalProps } from './types';
|
|
@ -0,0 +1,52 @@
|
|||
import { Action, AnyAction } from 'redux';
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { RtkQueryInspectorProps } from './RtkQueryInspector';
|
||||
import { QueryInfo, RtkQueryInspectorMonitorState } from './types';
|
||||
import { QueryComparators } from './utils/comparators';
|
||||
|
||||
const initialState: RtkQueryInspectorMonitorState = {
|
||||
queryComparator: QueryComparators.fulfilledTimeStamp,
|
||||
isAscendingQueryComparatorOrder: false,
|
||||
selectedQueryKey: null,
|
||||
};
|
||||
|
||||
const monitorSlice = createSlice({
|
||||
name: 'rtk-query-monitor',
|
||||
initialState,
|
||||
reducers: {
|
||||
changeQueryComparator(state, action: PayloadAction<QueryComparators>) {
|
||||
state.queryComparator = action.payload;
|
||||
},
|
||||
changeIsAscendingQueryComparatorOrder(
|
||||
state,
|
||||
action: PayloadAction<boolean>
|
||||
) {
|
||||
state.isAscendingQueryComparatorOrder = !!action.payload;
|
||||
},
|
||||
selectQueryKey(
|
||||
state,
|
||||
action: PayloadAction<Pick<QueryInfo, 'reducerPath' | 'queryKey'>>
|
||||
) {
|
||||
state.selectedQueryKey = {
|
||||
queryKey: action.payload.queryKey,
|
||||
reducerPath: action.payload.reducerPath,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default function reducer<S, A extends Action<unknown>>(
|
||||
props: RtkQueryInspectorProps<S, A>,
|
||||
state: RtkQueryInspectorMonitorState | undefined = initialState,
|
||||
action: AnyAction
|
||||
): RtkQueryInspectorMonitorState {
|
||||
const output = monitorSlice.reducer(state, action);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
export const {
|
||||
changeIsAscendingQueryComparatorOrder,
|
||||
changeQueryComparator,
|
||||
selectQueryKey,
|
||||
} = monitorSlice.actions;
|
|
@ -0,0 +1,94 @@
|
|||
import { Action, createSelector, Selector } from '@reduxjs/toolkit';
|
||||
import { RtkQueryInspectorProps } from './RtkQueryInspector';
|
||||
import { QueryInfo, RtkQueryInspectorMonitorState } from './types';
|
||||
import { Comparator, queryComparators } from './utils/comparators';
|
||||
import {
|
||||
getApiStatesOf,
|
||||
extractAllApiQueries,
|
||||
flipComparator,
|
||||
} from './utils/rtk-query';
|
||||
|
||||
type SelectorsSource<S> = {
|
||||
currentState: S | null;
|
||||
monitorState: RtkQueryInspectorMonitorState;
|
||||
};
|
||||
|
||||
type InspectorSelector<S, Output> = Selector<SelectorsSource<S>, Output>;
|
||||
|
||||
export function computeSelectorSource<S, A extends Action<unknown>>(
|
||||
props: RtkQueryInspectorProps<S, A>,
|
||||
previous: SelectorsSource<S> | null = null
|
||||
): SelectorsSource<S> {
|
||||
const { computedStates, currentStateIndex, monitorState } = props;
|
||||
|
||||
const currentState =
|
||||
computedStates.length > 0 ? computedStates[currentStateIndex].state : null;
|
||||
|
||||
if (
|
||||
!previous ||
|
||||
previous.currentState !== currentState ||
|
||||
previous.monitorState !== monitorState
|
||||
) {
|
||||
return {
|
||||
currentState,
|
||||
monitorState,
|
||||
};
|
||||
}
|
||||
|
||||
return previous;
|
||||
}
|
||||
|
||||
export interface InspectorSelectors<S> {
|
||||
readonly selectQueryComparator: InspectorSelector<S, Comparator<QueryInfo>>;
|
||||
readonly selectApiStates: InspectorSelector<
|
||||
S,
|
||||
ReturnType<typeof getApiStatesOf>
|
||||
>;
|
||||
readonly selectAllQueries: InspectorSelector<
|
||||
S,
|
||||
ReturnType<typeof extractAllApiQueries>
|
||||
>;
|
||||
readonly selectAllSortedQueries: InspectorSelector<S, QueryInfo[]>;
|
||||
}
|
||||
|
||||
export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
||||
const selectQueryComparator = ({
|
||||
monitorState,
|
||||
}: SelectorsSource<S>): Comparator<QueryInfo> => {
|
||||
return queryComparators[monitorState.queryComparator];
|
||||
};
|
||||
|
||||
const selectApiStates = createSelector(
|
||||
({ currentState }: SelectorsSource<S>) => currentState,
|
||||
getApiStatesOf
|
||||
);
|
||||
const selectAllQueries = createSelector(
|
||||
selectApiStates,
|
||||
extractAllApiQueries
|
||||
);
|
||||
|
||||
const selectAllSortedQueries = createSelector(
|
||||
[
|
||||
selectQueryComparator,
|
||||
selectAllQueries,
|
||||
({ monitorState }: SelectorsSource<S>) =>
|
||||
monitorState.isAscendingQueryComparatorOrder,
|
||||
],
|
||||
(comparator, queryList, isAscending) => {
|
||||
console.log({ comparator, queryList, isAscending });
|
||||
|
||||
const computedComparator = isAscending
|
||||
? comparator
|
||||
: flipComparator(comparator);
|
||||
|
||||
return queryList.slice().sort(computedComparator);
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
selectQueryComparator,
|
||||
selectApiStates,
|
||||
selectAllQueries,
|
||||
selectAllSortedQueries,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,276 @@
|
|||
import jss, { StyleSheet } from 'jss';
|
||||
import preset from 'jss-preset-default';
|
||||
import {
|
||||
createStyling,
|
||||
getBase16Theme,
|
||||
invertTheme,
|
||||
StylingFunction,
|
||||
} from 'react-base16-styling';
|
||||
import rgba from 'hex-rgba';
|
||||
import { Base16Theme } from 'redux-devtools-themes';
|
||||
import { rtkInspectorTheme } from './theme';
|
||||
import * as reduxThemes from 'redux-devtools-themes';
|
||||
import { Action } from 'redux';
|
||||
import { RtkQueryInspectorMonitorProps } from '../types';
|
||||
import { createContext } from 'react';
|
||||
|
||||
jss.setup(preset());
|
||||
|
||||
export const colorMap = (theme: 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,
|
||||
});
|
||||
|
||||
type Color = keyof ReturnType<typeof colorMap>;
|
||||
type ColorMap = {
|
||||
[color in Color]: string;
|
||||
};
|
||||
|
||||
const getSheetFromColorMap = (map: ColorMap) => ({
|
||||
inspector: {
|
||||
display: 'flex',
|
||||
'flex-direction': 'column',
|
||||
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,
|
||||
},
|
||||
|
||||
querySectionWrapper: {
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
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',
|
||||
|
||||
'& > :first-child': {
|
||||
whiteSpace: 'nowrap',
|
||||
overflowX: 'hidden',
|
||||
maxWidth: 'calc(100% - 70px)',
|
||||
textOverflow: 'ellipsis',
|
||||
},
|
||||
'&:last-child': {
|
||||
'border-bottom-width': 0,
|
||||
},
|
||||
|
||||
'border-bottom-color': map.BORDER_COLOR,
|
||||
},
|
||||
|
||||
queryListHeader: {
|
||||
display: 'flex',
|
||||
flex: '0 0 auto',
|
||||
'align-items': 'center',
|
||||
'border-bottom-width': '1px',
|
||||
'border-bottom-style': 'solid',
|
||||
|
||||
'border-color': map.LIST_BORDER_COLOR,
|
||||
},
|
||||
|
||||
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,
|
||||
'background-color': map.ACTION_TIME_BACK_COLOR,
|
||||
color: map.ACTION_TIME_COLOR,
|
||||
},
|
||||
|
||||
queryListItemSelected: {
|
||||
'background-color': map.SELECTED_BACKGROUND_COLOR,
|
||||
},
|
||||
|
||||
tabSelector: {
|
||||
position: 'relative',
|
||||
'z-index': 1,
|
||||
display: 'inline-flex',
|
||||
float: 'right',
|
||||
},
|
||||
|
||||
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',
|
||||
padding: '6.5px 10px',
|
||||
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,
|
||||
},
|
||||
|
||||
selectorButtonSmall: {
|
||||
padding: '0px 8px',
|
||||
'font-size': '0.8em',
|
||||
},
|
||||
|
||||
selectorButtonSelected: {
|
||||
'background-color': map.TAB_BACK_SELECTED_COLOR,
|
||||
},
|
||||
|
||||
queryForm: {
|
||||
display: 'flex',
|
||||
flexFlow: 'column nowrap',
|
||||
},
|
||||
sortBySection: {
|
||||
display: 'flex',
|
||||
padding: '0.4em',
|
||||
'& > [role="radiogroup"]': {
|
||||
flex: '0 0 auto',
|
||||
padding: '0 0 0 0.4em',
|
||||
},
|
||||
'& label': {
|
||||
display: 'flex',
|
||||
flex: '0 0 auto',
|
||||
whiteSpace: 'noWrap',
|
||||
alignItems: 'center',
|
||||
paddingRight: '0.4em',
|
||||
},
|
||||
},
|
||||
querySearch: {
|
||||
outline: 'none',
|
||||
border: 'none',
|
||||
width: '100%',
|
||||
padding: '5px 10px',
|
||||
'font-size': '1em',
|
||||
'font-family': '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,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let themeSheet: StyleSheet;
|
||||
|
||||
const getDefaultThemeStyling = (theme: Base16Theme) => {
|
||||
if (themeSheet) {
|
||||
themeSheet.detach();
|
||||
}
|
||||
|
||||
themeSheet = jss
|
||||
.createStyleSheet(getSheetFromColorMap(colorMap(theme)))
|
||||
.attach();
|
||||
|
||||
return themeSheet.classes;
|
||||
};
|
||||
|
||||
export const base16Themes = { ...reduxThemes };
|
||||
|
||||
export const createStylingFromTheme = createStyling(getDefaultThemeStyling, {
|
||||
defaultBase16: rtkInspectorTheme,
|
||||
base16Themes,
|
||||
});
|
||||
|
||||
export interface StyleUtils {
|
||||
base16Theme: Base16Theme;
|
||||
styling: StylingFunction;
|
||||
}
|
||||
|
||||
export function createThemeState<S, A extends Action<unknown>>(
|
||||
props: RtkQueryInspectorMonitorProps<S, A>
|
||||
): StyleUtils {
|
||||
const base16Theme =
|
||||
getBase16Theme(props.theme, base16Themes) ?? rtkInspectorTheme;
|
||||
|
||||
const theme = props.invertTheme ? invertTheme(props.theme) : props.theme;
|
||||
const styling = createStylingFromTheme(theme);
|
||||
|
||||
return { base16Theme, styling };
|
||||
}
|
||||
|
||||
export const StyleUtilsContext = createContext<StyleUtils>({
|
||||
base16Theme: rtkInspectorTheme,
|
||||
styling: (...args: any[]) => ({ className: '', style: {} }),
|
||||
});
|
|
@ -0,0 +1,25 @@
|
|||
import { Base16Theme } from 'react-base16-styling';
|
||||
|
||||
/**
|
||||
* Lifted from `packages/redux-devtools-inspector-monitor/src/themes/inspector.ts`
|
||||
*/
|
||||
export const rtkInspectorTheme: Base16Theme = {
|
||||
scheme: 'rtk-inspector',
|
||||
author: 'Alexander Kuznetsov (alexkuz@gmail.com)',
|
||||
base00: '#181818',
|
||||
base01: '#282828',
|
||||
base02: '#383838',
|
||||
base03: '#585858',
|
||||
base04: '#b8b8b8',
|
||||
base05: '#d8d8d8',
|
||||
base06: '#e8e8e8',
|
||||
base07: '#FFFFFF',
|
||||
base08: '#E92F28',
|
||||
base09: '#dc9656',
|
||||
base0A: '#f7ca88',
|
||||
base0B: '#65AD00',
|
||||
base0C: '#86c1b9',
|
||||
base0D: '#347BD9',
|
||||
base0E: '#EC31C0',
|
||||
base0F: '#a16946',
|
||||
};
|
|
@ -0,0 +1,65 @@
|
|||
import { LiftedAction, LiftedState } from '@redux-devtools/instrument';
|
||||
import type { createApi } from '@reduxjs/toolkit/query';
|
||||
import { Dispatch } from 'react';
|
||||
import { Base16Theme } from 'react-base16-styling';
|
||||
import { Action } from 'redux';
|
||||
import * as themes from 'redux-devtools-themes';
|
||||
import { QueryComparators } from './utils/comparators';
|
||||
|
||||
export interface RtkQueryInspectorMonitorState {
|
||||
queryComparator: QueryComparators;
|
||||
isAscendingQueryComparatorOrder: boolean;
|
||||
selectedQueryKey: Pick<QueryInfo, 'reducerPath' | 'queryKey'> | null;
|
||||
}
|
||||
|
||||
export interface RtkQueryInspectorMonitorProps<S, A extends Action<unknown>>
|
||||
extends LiftedState<S, A, RtkQueryInspectorMonitorState> {
|
||||
dispatch: Dispatch<
|
||||
Action | LiftedAction<S, A, RtkQueryInspectorMonitorState>
|
||||
>;
|
||||
|
||||
preserveScrollTop: boolean;
|
||||
select: (state: S) => unknown;
|
||||
theme: keyof typeof themes | Base16Theme;
|
||||
expandActionRoot: boolean;
|
||||
expandStateRoot: boolean;
|
||||
markStateDiff: boolean;
|
||||
hideMainButtons?: boolean;
|
||||
invertTheme?: boolean;
|
||||
}
|
||||
|
||||
export type RtkQueryApiState = ReturnType<
|
||||
ReturnType<typeof createApi>['reducer']
|
||||
>;
|
||||
|
||||
export type RtkQueryState = NonNullable<
|
||||
RtkQueryApiState['queries'][keyof RtkQueryApiState]
|
||||
>;
|
||||
|
||||
export interface ExternalProps<S, A extends Action<unknown>> {
|
||||
dispatch: Dispatch<
|
||||
Action | LiftedAction<S, A, RtkQueryInspectorMonitorState>
|
||||
>;
|
||||
|
||||
preserveScrollTop: boolean;
|
||||
select: (state: S) => unknown;
|
||||
theme: keyof typeof themes | Base16Theme;
|
||||
expandActionRoot: boolean;
|
||||
expandStateRoot: boolean;
|
||||
markStateDiff: boolean;
|
||||
hideMainButtons?: boolean;
|
||||
invertTheme: boolean;
|
||||
}
|
||||
|
||||
export type AnyExternalProps = ExternalProps<unknown, any>;
|
||||
|
||||
export interface QueryInfo {
|
||||
query: RtkQueryState;
|
||||
queryKey: string;
|
||||
reducerPath: string;
|
||||
}
|
||||
|
||||
export interface ApiInfo {
|
||||
reducerPath: string;
|
||||
apiState: RtkQueryApiState;
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
import { QueryStatus } from '@reduxjs/toolkit/dist/query';
|
||||
import { QueryInfo } from '../types';
|
||||
|
||||
export interface Comparator<T> {
|
||||
(a: T, b: T): number;
|
||||
}
|
||||
|
||||
export enum QueryComparators {
|
||||
fulfilledTimeStamp = 'timestamp',
|
||||
queryKey = 'key',
|
||||
status = 'status',
|
||||
endpointName = 'endpointName',
|
||||
}
|
||||
|
||||
export const sortQueryOptions: { label: string; value: string }[] = [
|
||||
{ label: 'fulfilledTimeStamp', value: QueryComparators.fulfilledTimeStamp },
|
||||
{ label: 'query key', value: QueryComparators.queryKey },
|
||||
{ label: 'status ', value: QueryComparators.status },
|
||||
{ label: 'endpoint', value: QueryComparators.endpointName },
|
||||
];
|
||||
|
||||
function sortQueryByFulfilled(
|
||||
thisQueryInfo: QueryInfo,
|
||||
thatQueryInfo: QueryInfo
|
||||
): number {
|
||||
const thisFulfilled = thisQueryInfo.query.fulfilledTimeStamp ?? -1;
|
||||
const thatFulfilled = thatQueryInfo.query.fulfilledTimeStamp ?? -1;
|
||||
|
||||
return thisFulfilled - thatFulfilled;
|
||||
}
|
||||
|
||||
const mapStatusToFactor = {
|
||||
[QueryStatus.uninitialized]: 1,
|
||||
[QueryStatus.pending]: 2,
|
||||
[QueryStatus.rejected]: 3,
|
||||
[QueryStatus.fulfilled]: 4,
|
||||
};
|
||||
|
||||
function sortQueryByStatus(
|
||||
thisQueryInfo: QueryInfo,
|
||||
thatQueryInfo: QueryInfo
|
||||
): number {
|
||||
const thisTerm = mapStatusToFactor[thisQueryInfo.query.status] || -1;
|
||||
const thatTerm = mapStatusToFactor[thatQueryInfo.query.status] || -1;
|
||||
|
||||
return thisTerm - thatTerm;
|
||||
}
|
||||
|
||||
function compareStrings(a: string, b: string): number {
|
||||
if (a === b) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return a > b ? 1 : -1;
|
||||
}
|
||||
|
||||
function sortByQueryKey(
|
||||
thisQueryInfo: QueryInfo,
|
||||
thatQueryInfo: QueryInfo
|
||||
): number {
|
||||
return compareStrings(thisQueryInfo.queryKey, thatQueryInfo.queryKey);
|
||||
}
|
||||
|
||||
function sortQueryByEndpointName(
|
||||
thisQueryInfo: QueryInfo,
|
||||
thatQueryInfo: QueryInfo
|
||||
): number {
|
||||
const thisEndpointName = thisQueryInfo.query.endpointName ?? '';
|
||||
const thatEndpointName = thatQueryInfo.query.endpointName ?? '';
|
||||
|
||||
return compareStrings(thisEndpointName, thatEndpointName);
|
||||
}
|
||||
|
||||
export const queryComparators: Readonly<Record<
|
||||
QueryComparators,
|
||||
Comparator<QueryInfo>
|
||||
>> = {
|
||||
[QueryComparators.fulfilledTimeStamp]: sortQueryByFulfilled,
|
||||
[QueryComparators.status]: sortQueryByStatus,
|
||||
[QueryComparators.endpointName]: sortQueryByEndpointName,
|
||||
[QueryComparators.queryKey]: sortByQueryKey,
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
import { freeze } from '@reduxjs/toolkit';
|
||||
|
||||
export const emptyArray = freeze([]);
|
||||
|
||||
export const emptyRecord = freeze({});
|
||||
|
||||
export function identity<T>(val: T): T {
|
||||
return val;
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
import { isPlainObject } from '@reduxjs/toolkit';
|
||||
import type { createApi } from '@reduxjs/toolkit/query';
|
||||
import { QueryInfo, RtkQueryInspectorMonitorState } from '../types';
|
||||
import { Comparator } from './comparators';
|
||||
import { emptyArray } from './object';
|
||||
|
||||
export type RtkQueryApiState = ReturnType<
|
||||
ReturnType<typeof createApi>['reducer']
|
||||
>;
|
||||
|
||||
const rtkqueryApiStateKeys: ReadonlyArray<keyof RtkQueryApiState> = [
|
||||
'queries',
|
||||
'mutations',
|
||||
'config',
|
||||
'provided',
|
||||
'subscriptions',
|
||||
];
|
||||
|
||||
export function isApiSlice(val: unknown): val is RtkQueryApiState {
|
||||
if (!isPlainObject(val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0, len = rtkqueryApiStateKeys.length; i < len; i++) {
|
||||
if (
|
||||
!isPlainObject((val as Record<string, unknown>)[rtkqueryApiStateKeys[i]])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function getApiStatesOf(
|
||||
state: unknown
|
||||
): null | Readonly<Record<string, RtkQueryApiState>> {
|
||||
if (!isPlainObject(state)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const output: null | Record<string, RtkQueryApiState> = {};
|
||||
const keys = Object.keys(state);
|
||||
|
||||
for (let i = 0, len = keys.length; i < len; i++) {
|
||||
const key = keys[i];
|
||||
const value = (state as Record<string, unknown>)[key];
|
||||
|
||||
if (isApiSlice(value)) {
|
||||
output[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(output).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
export function extractAllApiQueries(
|
||||
apiStatesByReducerPath: null | Readonly<Record<string, RtkQueryApiState>>
|
||||
): ReadonlyArray<QueryInfo> {
|
||||
if (!apiStatesByReducerPath) {
|
||||
return emptyArray;
|
||||
}
|
||||
|
||||
const reducerPaths = Object.keys(apiStatesByReducerPath);
|
||||
|
||||
const output: QueryInfo[] = [];
|
||||
|
||||
for (let i = 0, len = reducerPaths.length; i < len; i++) {
|
||||
const reducerPath = reducerPaths[i];
|
||||
const api = apiStatesByReducerPath[reducerPath];
|
||||
const queryKeys = Object.keys(api.queries);
|
||||
|
||||
for (let j = 0, qKeysLen = queryKeys.length; j < qKeysLen; j++) {
|
||||
const queryKey = queryKeys[j];
|
||||
const query = api.queries[queryKey];
|
||||
|
||||
if (query) {
|
||||
output.push({
|
||||
reducerPath,
|
||||
queryKey,
|
||||
query,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
export function flipComparator<T>(comparator: Comparator<T>): Comparator<T> {
|
||||
return function flipped(a: T, b: T) {
|
||||
return comparator(b, a);
|
||||
};
|
||||
}
|
||||
|
||||
export function isQuerySelected(
|
||||
selectedQueryKey: RtkQueryInspectorMonitorState['selectedQueryKey'],
|
||||
queryInfo: QueryInfo
|
||||
): boolean {
|
||||
return (
|
||||
!!selectedQueryKey &&
|
||||
selectedQueryKey.queryKey === queryInfo.queryKey &&
|
||||
selectedQueryKey.reducerPath === queryInfo.reducerPath
|
||||
);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "../../tsconfig.react.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./demo/src/generated-module",
|
||||
"module": "ES2015",
|
||||
"strict": false
|
||||
},
|
||||
"include": ["src"],
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.react.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib"
|
||||
},
|
Loading…
Reference in New Issue
Block a user