Merge branch 'main' into state-filter

This commit is contained in:
Avinash Thakur 2023-03-12 20:00:15 +05:30
commit e1cd49dcc8
No known key found for this signature in database
GPG Key ID: 4877E61DE468FD05
146 changed files with 7202 additions and 7371 deletions

View File

@ -1,5 +1,19 @@
# remotedev-redux-devtools-extension
## 3.0.19
### Patch Changes
- 450cde6e: Fix responsive layout
## 3.0.18
### Patch Changes
- Updated dependencies [81926f32]
- react-json-tree@0.18.0
- @redux-devtools/app@2.2.1
## 3.0.17
### Patch Changes

View File

@ -1,5 +1,5 @@
{
"version": "3.0.17",
"version": "3.0.19",
"name": "Redux DevTools",
"description": "Redux DevTools for debugging application's state changes.",
"homepage_url": "https://github.com/reduxjs/redux-devtools",

View File

@ -1,5 +1,5 @@
{
"version": "3.0.17",
"version": "3.0.19",
"name": "Redux DevTools",
"description": "Redux DevTools for debugging application's state changes.",
"homepage_url": "https://github.com/reduxjs/redux-devtools",

View File

@ -1,5 +1,5 @@
{
"version": "3.0.17",
"version": "3.0.19",
"name": "Redux DevTools",
"manifest_version": 2,
"description": "Redux Developer Tools for debugging application state changes.",

View File

@ -5,5 +5,7 @@ module.exports = {
moduleNameMapper: {
'\\.css$': '<rootDir>/test/__mocks__/styleMock.ts',
},
resolver: '<rootDir>/jestResolver.js',
transformIgnorePatterns: [
'node_modules/(?!.pnpm|d3|dateformat|delaunator|internmap|nanoid|robust-predicates|uuid)',
],
};

View File

@ -1,15 +0,0 @@
module.exports = (path, options) => {
return options.defaultResolver(path, {
...options,
packageFilter: (pkg) => {
if (pkg.name === 'nanoid') {
pkg.exports['.'].browser = pkg.exports['.'].require;
}
if (pkg.name === 'uuid' && pkg.version.startsWith('8.')) {
delete pkg.exports;
delete pkg.module;
}
return pkg;
},
});
};

View File

@ -1,7 +1,7 @@
{
"private": true,
"name": "remotedev-redux-devtools-extension",
"version": "3.0.17",
"version": "3.0.19",
"description": "Redux Developer Tools for debugging application state changes.",
"homepage": "https://github.com/reduxjs/redux-devtools/tree/master/extension",
"license": "MIT",
@ -28,7 +28,7 @@
},
"dependencies": {
"@babel/polyfill": "^7.12.1",
"@redux-devtools/app": "^2.2.0",
"@redux-devtools/app": "^2.2.1",
"@redux-devtools/core": "^3.13.0",
"@redux-devtools/instrument": "^2.1.0",
"@redux-devtools/serialize": "^0.4.1",
@ -43,53 +43,53 @@
"react-dom": "^18.2.0",
"react-icons": "^4.7.1",
"react-is": "^18.2.0",
"react-json-tree": "^0.17.0",
"react-json-tree": "^0.18.0",
"react-redux": "^8.0.5",
"redux": "^4.2.0",
"redux": "^4.2.1",
"redux-persist": "^6.0.0",
"styled-components": "^5.3.6"
"styled-components": "^5.3.8"
},
"devDependencies": {
"@babel/core": "^7.20.5",
"@babel/core": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@babel/register": "^7.18.9",
"@babel/preset-typescript": "^7.21.0",
"@babel/register": "^7.21.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@types/chrome": "^0.0.206",
"@testing-library/react": "^14.0.0",
"@types/chrome": "^0.0.218",
"@types/lodash": "^4.14.191",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/styled-components": "^5.1.26",
"babel-loader": "^9.1.0",
"chromedriver": "^108.0.0",
"babel-loader": "^9.1.2",
"chromedriver": "^110.0.0",
"copy-webpack-plugin": "^11.0.0",
"cpy-cli": "^4.2.0",
"cross-env": "^7.0.3",
"css-loader": "^6.7.3",
"electron": "^22.0.0",
"eslint": "^8.30.0",
"electron": "^23.1.1",
"eslint": "^8.35.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-react": "^7.31.11",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^7.2.14",
"immutable": "^4.1.0",
"jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
"fork-ts-checker-webpack-plugin": "^8.0.0",
"immutable": "^4.2.4",
"jest": "^29.4.3",
"jest-environment-jsdom": "^29.4.3",
"pug-html-loader": "^1.1.5",
"raw-loader": "^4.0.2",
"react-transform-catch-errors": "^1.0.2",
"react-transform-hmr": "^1.0.4",
"rimraf": "^3.0.2",
"selenium-webdriver": "^4.7.1",
"rimraf": "^4.1.3",
"selenium-webdriver": "^4.8.1",
"sinon-chrome": "^3.0.1",
"style-loader": "^3.3.1",
"ts-jest": "^29.0.3",
"typescript": "~4.9.4",
"ts-jest": "^29.0.5",
"typescript": "~4.9.5",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
}

View File

@ -591,12 +591,25 @@ const preEnhancer =
} as any;
};
export type InferComposedStoreExt<StoreEnhancers> = StoreEnhancers extends [
infer HeadStoreEnhancer,
...infer RestStoreEnhancers
]
? HeadStoreEnhancer extends StoreEnhancer<infer StoreExt>
? StoreExt & InferComposedStoreExt<RestStoreEnhancers>
: never
: unknown;
const extensionCompose =
(config: Config) =>
(...funcs: StoreEnhancer[]): StoreEnhancer => {
<StoreEnhancers extends readonly StoreEnhancer<unknown>[]>(
...funcs: StoreEnhancers
): StoreEnhancer<InferComposedStoreExt<StoreEnhancers>> => {
// @ts-ignore FIXME
return (...args) => {
const instanceId = generateId(config.instanceId);
return [preEnhancer(instanceId), ...funcs].reduceRight(
// @ts-ignore FIXME
(composed, f) => f(composed),
__REDUX_DEVTOOLS_EXTENSION__({ ...config, instanceId })(...args)
);
@ -604,8 +617,12 @@ const extensionCompose =
};
interface ReduxDevtoolsExtensionCompose {
(config: Config): (...funcs: StoreEnhancer[]) => StoreEnhancer;
(...funcs: StoreEnhancer[]): StoreEnhancer;
(config: Config): <StoreEnhancers extends readonly StoreEnhancer<unknown>[]>(
...funcs: StoreEnhancers
) => StoreEnhancer<InferComposedStoreExt<StoreEnhancers>>;
<StoreEnhancers extends readonly StoreEnhancer<unknown>[]>(
...funcs: StoreEnhancers
): StoreEnhancer<InferComposedStoreExt<StoreEnhancers>>;
}
declare global {
@ -616,18 +633,24 @@ declare global {
function reduxDevtoolsExtensionCompose(
config: Config
): (...funcs: StoreEnhancer[]) => StoreEnhancer;
): <StoreEnhancers extends readonly StoreEnhancer<unknown>[]>(
...funcs: StoreEnhancers
) => StoreEnhancer<InferComposedStoreExt<StoreEnhancers>>;
function reduxDevtoolsExtensionCompose<
StoreEnhancers extends readonly StoreEnhancer<unknown>[]
>(
...funcs: StoreEnhancers
): StoreEnhancer<InferComposedStoreExt<StoreEnhancers>>;
function reduxDevtoolsExtensionCompose(
...funcs: StoreEnhancer[]
): StoreEnhancer;
function reduxDevtoolsExtensionCompose(...funcs: [Config] | StoreEnhancer[]) {
...funcs: [Config] | StoreEnhancer<unknown>[]
) {
if (funcs.length === 0) {
return __REDUX_DEVTOOLS_EXTENSION__();
}
if (funcs.length === 1 && typeof funcs[0] === 'object') {
return extensionCompose(funcs[0]);
}
return extensionCompose({})(...(funcs as StoreEnhancer[]));
return extensionCompose({})(...(funcs as StoreEnhancer<unknown>[]));
}
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ = reduxDevtoolsExtensionCompose;

View File

@ -7,7 +7,7 @@ style.
overflow: hidden;
height: 100%;
width: 100%;
min-width: 760px;
min-width: 350px;
min-height: 400px;
margin: 0;
padding: 0;
@ -17,7 +17,6 @@ style.
color: #fff;
}
#root {
min-width: 760px;
height: 100%;
}
#root > div {

View File

@ -1,21 +1,21 @@
{
"private": true,
"devDependencies": {
"@babel/core": "^7.20.5",
"@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1",
"@changesets/cli": "^2.26.0",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-jest": "^27.1.7",
"eslint-plugin-react": "^7.31.11",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.3.1",
"prettier": "2.8.1",
"typescript": "~4.9.4",
"nx": "^15.3.3",
"@nrwl/nx-cloud": "^15.0.2"
"jest": "^29.4.3",
"prettier": "2.8.4",
"typescript": "~4.9.5",
"nx": "^15.8.1",
"@nrwl/nx-cloud": "^15.1.1"
},
"scripts": {
"format": "prettier --write .",
@ -39,7 +39,7 @@
"packages/redux-devtools-rtk-query-monitor/demo",
"packages/redux-devtools-slider-monitor/examples/todomvc"
],
"packageManager": "pnpm@7.19.0",
"packageManager": "pnpm@7.28.0",
"pnpm": {
"overrides": {
"@babel/highlight>chalk": "Methuselah96/chalk#v2-without-process"

View File

@ -1,5 +1,23 @@
# Change Log
## 2.0.0
### Major Changes
- b323f77d: Upgrade D3
- Remove UMD build.
- Split `style` option into `chartStyles`, `nodeStyleOptions`, `textStyleOptions`, and `linkStyles`.
- The shape of the argument passed to the `onClickText` option has been updated.
- Rename `InputOptions` to `Options`, `Primitive` to `StyleValue`, and `NodeWithId` to `HierarchyPointNode<Node>`.
### Patch Changes
- Updated dependencies [b323f77d]
- Updated dependencies [b323f77d]
- d3tooltip@3.0.0
- map2tree@3.0.0
## [1.4.0](https://github.com/reduxjs/redux-devtools/compare/d3-state-visualizer@1.3.4...d3-state-visualizer@1.4.0) (2021-03-06)
### Features

View File

@ -35,7 +35,7 @@ const render = tree(document.getElementById('root'), {
isSorted: false,
widthBetweenNodesCoeff: 1.5,
heightBetweenNodesCoeff: 2,
style: { border: '1px solid black' },
chartStyles: { border: '1px solid black' },
tooltipOptions: { offset: { left: 30, top: 10 }, indentationSize: 2 },
});
@ -61,7 +61,7 @@ Other options are listed below and have reasonable default values if you want to
| Option | Type | Default | Description |
| ------------------------- | ------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | String | `'d3svg'` | Sets the identifier of the SVG element —i.e your chart— that will be added to the DOM element you passed as first argument |
| `style` | Object | `{}` | Sets the CSS style of the chart |
| `chartStyles` | Object | `{}` | Sets the CSS style of the chart |
| `size` | Number | `500` | Sets size of the chart in pixels |
| `aspectRatio` | Float | `1.0` | Sets the chart height to `size * aspectRatio` and [viewBox](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox) in order to preserve the aspect ratio of the chart. [Great video](https://www.youtube.com/watch?v=FCOeMy7HrBc) if you want to learn more about how SVG works |
| `widthBetweenNodesCoeff` | Float | `1.0` | Alters the horizontal space between each node |
@ -74,12 +74,6 @@ Other options are listed below and have reasonable default values if you want to
More to come...
## Bindings
### React
[example](https://github.com/reduxjs/redux-devtools/tree/master/packages/d3-state-visualizer/examples/react-tree) implementation.
## Roadmap
- Threshold for large arrays so only a single node is displayed instead of all the children. That single node would be exclude from searching until selected.

View File

@ -0,0 +1,10 @@
# d3-state-visualizer-tree-example
## 0.1.5
### Patch Changes
- Updated dependencies [b323f77d]
- Updated dependencies [b323f77d]
- d3-state-visualizer@2.0.0
- map2tree@3.0.0

View File

@ -1,7 +1,7 @@
{
"private": true,
"name": "d3-state-visualizer-tree-example",
"version": "0.1.4",
"version": "0.1.5",
"description": "Visualize your app state as a tree",
"keywords": [
"d3",
@ -25,24 +25,24 @@
"type-check": "tsc --noEmit"
},
"dependencies": {
"d3-state-visualizer": "^1.6.0",
"map2tree": "^2.1.0"
"d3-state-visualizer": "^2.0.0",
"map2tree": "^3.0.0"
},
"devDependencies": {
"@babel/core": "^7.20.5",
"@babel/core": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.18.6",
"@types/node": "^18.11.17",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"babel-loader": "^9.1.0",
"@babel/preset-typescript": "^7.21.0",
"@types/node": "^18.14.4",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"babel-loader": "^9.1.2",
"cross-env": "^7.0.3",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"fork-ts-checker-webpack-plugin": "^7.2.14",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"fork-ts-checker-webpack-plugin": "^8.0.0",
"html-webpack-plugin": "^5.5.0",
"ts-node": "^10.9.1",
"typescript": "~4.9.4",
"typescript": "~4.9.5",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"

View File

@ -28,7 +28,7 @@ const render = tree(document.getElementById('root')!, {
isSorted: false,
widthBetweenNodesCoeff: 1.5,
heightBetweenNodesCoeff: 2,
style: { border: '1px solid black' },
chartStyles: { border: '1px solid black' },
tooltipOptions: { offset: { left: 30, top: 10 }, indentationSize: 2 },
});

View File

@ -1,6 +1,6 @@
{
"name": "d3-state-visualizer",
"version": "1.6.0",
"version": "2.0.0",
"description": "Visualize your app state with a range of reusable charts",
"keywords": [
"d3",
@ -23,18 +23,16 @@
"main": "lib/cjs/index.js",
"module": "lib/esm/index.js",
"types": "lib/types/index.d.ts",
"unpkg": "dist/d3-state-visualizer.umd.js",
"sideEffects": false,
"repository": {
"type": "git",
"url": "https://github.com/reduxjs/redux-devtools.git"
},
"scripts": {
"build": "pnpm run build:cjs && pnpm run build:esm && pnpm run build:types && pnpm run build:umd",
"build": "pnpm run build:cjs && pnpm run build:esm && pnpm run build:types",
"build:cjs": "babel src --extensions \".ts\" --out-dir lib/cjs",
"build:esm": "babel src --config-file ./babel.config.esm.json --extensions \".ts\" --out-dir lib/esm",
"build:types": "tsc --emitDeclarationOnly",
"build:umd": "rollup -c",
"clean": "rimraf lib",
"lint": "eslint . --ext .ts",
"type-check": "tsc --noEmit",
@ -42,35 +40,26 @@
"prepublish": "pnpm run type-check && pnpm run lint"
},
"dependencies": {
"@babel/runtime": "^7.20.6",
"@types/d3": "^3.5.47",
"d3": "^3.5.17",
"d3tooltip": "^2.1.0",
"deepmerge": "^4.2.2",
"map2tree": "^2.1.0",
"@babel/runtime": "^7.21.0",
"@types/d3": "^7.4.0",
"d3": "^7.8.2",
"d3tooltip": "^3.0.0",
"deepmerge": "^4.3.0",
"map2tree": "^3.0.0",
"ramda": "^0.28.0"
},
"devDependencies": {
"@babel/cli": "^7.19.3",
"@babel/core": "^7.20.5",
"@babel/cli": "^7.21.0",
"@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.18.6",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-commonjs": "^24.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-terser": "^0.2.1",
"@types/node": "^18.11.17",
"@types/ramda": "^0.28.20",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"rimraf": "^3.0.2",
"rollup": "^3.7.5",
"rollup-plugin-typescript2": "^0.34.1",
"tslib": "^2.4.1",
"typescript": "~4.9.4"
"@babel/preset-typescript": "^7.21.0",
"@types/ramda": "^0.28.23",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"rimraf": "^4.1.3",
"typescript": "~4.9.5"
}
}

View File

@ -1,51 +0,0 @@
import typescript from 'rollup-plugin-typescript2';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import terser from '@rollup/plugin-terser';
const config = [
{
input: 'src/index.ts',
output: {
name: 'd3-state-visualizer',
file: 'lib/umd/d3-state-visualizer.js',
format: 'umd',
},
plugins: [
typescript({
tsconfigOverride: { compilerOptions: { declaration: false } },
}),
resolve(),
commonjs(),
babel({
babelHelpers: 'runtime',
extensions: ['.ts'],
plugins: ['@babel/plugin-transform-runtime'],
}),
],
},
{
input: 'src/index.ts',
output: {
name: 'd3-state-visualizer',
file: 'lib/umd/d3-state-visualizer.min.js',
format: 'umd',
},
plugins: [
typescript({
tsconfigOverride: { compilerOptions: { declaration: false } },
}),
resolve(),
commonjs(),
babel({
babelHelpers: 'runtime',
extensions: ['.ts'],
plugins: ['@babel/plugin-transform-runtime'],
}),
terser(),
],
},
];
export default config;

View File

@ -1,2 +1,4 @@
export type { HierarchyPointNode } from 'd3';
export type { StyleValue } from 'd3tooltip';
export { default as tree } from './tree/tree';
export type { InputOptions, NodeWithId, Primitive } from './tree/tree';
export type { Node, Options } from './tree/tree';

View File

@ -1,6 +1,8 @@
import d3, { ZoomEvent, Primitive } from 'd3';
import * as d3 from 'd3';
import type { D3ZoomEvent, HierarchyPointLink, HierarchyPointNode } from 'd3';
import { isEmpty } from 'ramda';
import { map2tree } from 'map2tree';
import type { Node } from 'map2tree';
import deepmerge from 'deepmerge';
import {
getTooltipString,
@ -9,17 +11,33 @@ import {
getNodeGroupByDepthCount,
} from './utils';
import { tooltip } from 'd3tooltip';
import type { StyleValue } from 'd3tooltip';
export interface InputOptions {
export interface Options {
// eslint-disable-next-line @typescript-eslint/ban-types
state?: {} | null;
// eslint-disable-next-line @typescript-eslint/ban-types
tree?: NodeWithId | {};
tree?: Node | {};
rootKeyName: string;
pushMethod: 'push' | 'unshift';
id: string;
style: { [key: string]: Primitive };
chartStyles: { [key: string]: StyleValue };
nodeStyleOptions: {
colors: {
default: string;
collapsed: string;
parent: string;
};
radius: number;
};
textStyleOptions: {
colors: {
default: string;
hover: string;
};
};
linkStyles: { [key: string]: StyleValue };
size: number;
aspectRatio: number;
initialZoom: number;
@ -34,7 +52,7 @@ export interface InputOptions {
widthBetweenNodesCoeff: number;
transitionDuration: number;
blinkDuration: number;
onClickText: (datum: NodeWithId) => void;
onClickText: (datum: HierarchyPointNode<Node>) => void;
tooltipOptions: {
disabled?: boolean;
left?: number | undefined;
@ -43,64 +61,7 @@ export interface InputOptions {
left: number;
top: number;
};
style?: { [key: string]: Primitive } | undefined;
indentationSize?: number;
};
}
interface Options {
// eslint-disable-next-line @typescript-eslint/ban-types
state?: {} | null;
// eslint-disable-next-line @typescript-eslint/ban-types
tree?: NodeWithId | {};
rootKeyName: string;
pushMethod: 'push' | 'unshift';
id: string;
style: {
node: {
colors: {
default: string;
collapsed: string;
parent: string;
};
radius: number;
};
text: {
colors: {
default: string;
hover: string;
};
};
link: {
stroke: string;
fill: string;
};
};
size: number;
aspectRatio: number;
initialZoom: number;
margin: {
top: number;
right: number;
bottom: number;
left: number;
};
isSorted: boolean;
heightBetweenNodesCoeff: number;
widthBetweenNodesCoeff: number;
transitionDuration: number;
blinkDuration: number;
onClickText: () => void;
tooltipOptions: {
disabled: boolean;
left: number | undefined;
top: number | undefined;
offset: {
left: number;
top: number;
};
style: { [key: string]: Primitive } | undefined;
styles?: { [key: string]: StyleValue } | undefined;
indentationSize?: number;
};
}
@ -111,26 +72,25 @@ const defaultOptions: Options = {
pushMethod: 'push',
tree: undefined,
id: 'd3svg',
style: {
node: {
colors: {
default: '#ccc',
collapsed: 'lightsteelblue',
parent: 'white',
},
radius: 7,
chartStyles: {},
nodeStyleOptions: {
colors: {
default: '#ccc',
collapsed: 'lightsteelblue',
parent: 'white',
},
text: {
colors: {
default: 'black',
hover: 'skyblue',
},
},
link: {
stroke: '#000',
fill: 'none',
radius: 7,
},
textStyleOptions: {
colors: {
default: 'black',
hover: 'skyblue',
},
},
linkStyles: {
stroke: '#000',
fill: 'none',
},
size: 500,
aspectRatio: 1.0,
initialZoom: 1,
@ -156,37 +116,29 @@ const defaultOptions: Options = {
left: 0,
top: 0,
},
style: undefined,
styles: undefined,
},
};
} satisfies Options;
export interface NodeWithId {
name: string;
children?: NodeWithId[] | null;
_children?: NodeWithId[] | null;
value?: unknown;
id: string;
parent?: NodeWithId;
depth?: number;
x?: number;
y?: number;
export interface InternalNode extends Node {
_children?: this[] | undefined;
id: string | number;
}
interface NodePosition {
parentId: string | null | undefined;
id: string;
x: number | undefined;
y: number | undefined;
parentId: string | number | null;
id: string | number;
x: number;
y: number;
}
export default function (
DOMNode: HTMLElement,
options: Partial<InputOptions> = {}
) {
export default function (DOMNode: HTMLElement, options: Partial<Options> = {}) {
const {
id,
style,
chartStyles,
nodeStyleOptions,
textStyleOptions,
linkStyles,
size,
aspectRatio,
initialZoom,
@ -202,64 +154,50 @@ export default function (
tree,
tooltipOptions,
onClickText,
} = deepmerge(defaultOptions, options) as Options;
} = deepmerge(defaultOptions, options);
const width = size - margin.left - margin.right;
const height = size * aspectRatio - margin.top - margin.bottom;
const fullWidth = size;
const fullHeight = size * aspectRatio;
const attr: { [key: string]: Primitive } = {
id,
preserveAspectRatio: 'xMinYMin slice',
};
if (!(style as unknown as { [key: string]: Primitive }).width) {
attr.width = fullWidth;
}
if (
!(style as unknown as { [key: string]: Primitive }).width ||
!(style as unknown as { [key: string]: Primitive }).height
) {
attr.viewBox = `0 0 ${fullWidth} ${fullHeight}`;
}
const root = d3.select(DOMNode);
const zoom = d3.behavior.zoom().scaleExtent([0.1, 3]).scale(initialZoom);
const vis = root
const zoom = d3.zoom<SVGSVGElement, unknown>().scaleExtent([0.1, 3]);
const svgElement = root
.append('svg')
.attr(attr)
.style({ cursor: '-webkit-grab', ...style } as unknown as {
[key: string]: Primitive;
})
.attr('id', id)
.attr('preserveAspectRatio', 'xMinYMin slice')
.style('cursor', '-webkit-grab');
if (!chartStyles.width) {
svgElement.attr('width', fullWidth);
}
if (!chartStyles.width || !chartStyles.height) {
svgElement.attr('viewBox', `0 0 ${fullWidth} ${fullHeight}`);
}
for (const [key, value] of Object.entries(chartStyles)) {
svgElement.style(key, value);
}
const vis = svgElement
// eslint-disable-next-line @typescript-eslint/unbound-method
.call(zoom.scaleTo, initialZoom)
.call(
zoom.on('zoom', () => {
const { translate, scale } = d3.event as ZoomEvent;
vis.attr(
'transform',
`translate(${translate.toString()})scale(${scale})`
);
zoom.on('zoom', (event) => {
const { transform } = event as D3ZoomEvent<SVGSVGElement, unknown>;
vis.attr('transform', transform.toString());
})
)
.append('g')
.attr({
transform: `translate(${margin.left + style.node.radius}, ${
.attr(
'transform',
`translate(${margin.left + nodeStyleOptions.radius}, ${
margin.top
}) scale(${initialZoom})`,
});
let layout = d3.layout.tree().size([width, height]);
let data: NodeWithId;
if (isSorted) {
layout.sort((a, b) =>
(b as NodeWithId).name.toLowerCase() <
(a as NodeWithId).name.toLowerCase()
? 1
: -1
}) scale(${initialZoom})`
);
}
// previousNodePositionsById stores node x and y
// as well as hierarchy (id / parentId);
@ -277,8 +215,8 @@ export default function (
// of parent ids; once a parent that matches the given filter is found,
// the parent position gets returned
function findParentNodePosition(
nodePositionsById: { [nodeId: string]: NodePosition },
nodeId: string,
nodePositionsById: { [nodeId: string | number]: NodePosition },
nodeId: string | number,
filter: (nodePosition: NodePosition) => boolean
) {
let currentPosition = nodePositionsById[nodeId];
@ -294,19 +232,18 @@ export default function (
}
return function renderChart(nextState = tree || state) {
data = !tree
? // eslint-disable-next-line @typescript-eslint/ban-types
(map2tree(nextState as {}, {
let data = !tree
? (map2tree(nextState, {
key: rootKeyName,
pushMethod,
}) as NodeWithId)
: (nextState as NodeWithId);
}) as InternalNode)
: (nextState as InternalNode);
if (isEmpty(data) || !data.name) {
data = {
name: 'error',
message: 'Please provide a state map or a tree structure',
} as unknown as NodeWithId;
} as unknown as InternalNode;
}
let nodeIndex = 0;
@ -334,76 +271,94 @@ export default function (
function update() {
// path generator for links
const diagonal = d3.svg
.diagonal<NodePosition>()
.projection((d) => [d.y!, d.x!]);
const linkHorizontal = d3
.linkHorizontal<
{
source: { x: number; y: number };
target: { x: number; y: number };
},
{ x: number; y: number }
>()
.x((d) => d.y)
.y((d) => d.x);
// set tree dimensions and spacing between branches and nodes
const maxNodeCountByLevel = Math.max(...getNodeGroupByDepthCount(data));
layout = layout.size([
maxNodeCountByLevel * 25 * heightBetweenNodesCoeff,
width,
]);
const layout = d3
.tree<InternalNode>()
.size([maxNodeCountByLevel * 25 * heightBetweenNodesCoeff, width]);
const nodes = layout.nodes(data as d3.layout.tree.Node) as NodeWithId[];
const links = layout.links(nodes as d3.layout.tree.Node[]);
const rootNode = d3.hierarchy(data);
if (isSorted) {
rootNode.sort((a, b) =>
b.data.name.toLowerCase() < a.data.name.toLowerCase() ? 1 : -1
);
}
nodes.forEach(
const rootPointNode = layout(rootNode);
const links = rootPointNode.links();
rootPointNode.each(
(node) =>
(node.y = node.depth! * (maxLabelLength * 7 * widthBetweenNodesCoeff))
(node.y = node.depth * (maxLabelLength * 7 * widthBetweenNodesCoeff))
);
const nodes = rootPointNode.descendants();
const nodePositions = nodes.map((n) => ({
parentId: n.parent && n.parent.id,
id: n.id,
parentId: n.parent && n.parent.data.id,
id: n.data.id,
x: n.x,
y: n.y,
}));
const nodePositionsById: { [nodeId: string]: NodePosition } = {};
const nodePositionsById: { [nodeId: string | number]: NodePosition } = {};
nodePositions.forEach((node) => (nodePositionsById[node.id] = node));
// process the node selection
const node = vis
.selectAll('g.node')
.property('__oldData__', (d: NodeWithId) => d)
.data(nodes, (d) => d.id || (d.id = ++nodeIndex as unknown as string));
.selectAll<SVGGElement, HierarchyPointNode<InternalNode>>('g.node')
.property('__oldData__', (d) => d)
.data(nodes, (d) => d.data.id || (d.data.id = ++nodeIndex));
const nodeEnter = node
.enter()
.append('g')
.attr({
class: 'node',
transform: (d) => {
const position = findParentNodePosition(
nodePositionsById,
d.id,
(n) => !!previousNodePositionsById[n.id]
);
const previousPosition =
(position && previousNodePositionsById[position.id]) ||
previousNodePositionsById.root;
return `translate(${previousPosition.y!},${previousPosition.x!})`;
},
.attr('class', 'node')
.attr('transform', (d) => {
const position = findParentNodePosition(
nodePositionsById,
d.data.id,
(n) => !!previousNodePositionsById[n.id]
);
const previousPosition =
(position && previousNodePositionsById[position.id]) ||
previousNodePositionsById.root;
return `translate(${previousPosition.y},${previousPosition.x})`;
})
.style({
fill: style.text.colors.default,
cursor: 'pointer',
.style('fill', textStyleOptions.colors.default)
.style('cursor', 'pointer')
.on('mouseover', function mouseover() {
d3.select(this).style('fill', textStyleOptions.colors.hover);
})
.on('mouseover', function mouseover(this: EventTarget) {
d3.select(this).style({
fill: style.text.colors.hover,
});
})
.on('mouseout', function mouseout(this: EventTarget) {
d3.select(this).style({
fill: style.text.colors.default,
});
.on('mouseout', function mouseout() {
d3.select(this).style('fill', textStyleOptions.colors.default);
});
if (!tooltipOptions.disabled) {
nodeEnter.call(
tooltip<NodeWithId>(d3, 'tooltip', { ...tooltipOptions, root })
.text((d, i) => getTooltipString(d, i, tooltipOptions))
.style(tooltipOptions.style)
tooltip<
SVGGElement,
HierarchyPointNode<InternalNode>,
SVGGElement,
unknown,
HTMLElement,
unknown,
null,
undefined
>('tooltip', {
...tooltipOptions,
root,
text: (d) => getTooltipString(d.data, tooltipOptions),
})
);
}
@ -412,77 +367,81 @@ export default function (
const nodeEnterInnerGroup = nodeEnter.append('g');
nodeEnterInnerGroup
.append('circle')
.attr({
class: 'nodeCircle',
r: 0,
})
.on('click', (clickedNode) => {
if ((d3.event as Event).defaultPrevented) return;
toggleChildren(clickedNode);
.attr('class', 'nodeCircle')
.attr('r', 0)
.on('click', (event, clickedNode) => {
if ((event as Event).defaultPrevented) return;
toggleChildren(clickedNode.data);
update();
});
nodeEnterInnerGroup
.append('text')
.attr({
class: 'nodeText',
'text-anchor': 'middle',
transform: 'translate(0,0)',
dy: '.35em',
})
.style({
'fill-opacity': 0,
})
.text((d) => d.name)
.on('click', onClickText);
// update the text to reflect whether node has children or not
node.select('text').text((d) => d.name);
// change the circle fill depending on whether it has children and is collapsed
node.select('circle').style({
stroke: 'black',
'stroke-width': '1.5px',
fill: (d) =>
d._children
? style.node.colors.collapsed
: d.children
? style.node.colors.parent
: style.node.colors.default,
});
// transition nodes to their new position
const nodeUpdate = node
.transition()
.duration(transitionDuration)
.attr({
transform: (d) => `translate(${d.y!},${d.x!})`,
.attr('class', 'nodeText')
.attr('text-anchor', 'middle')
.attr('transform', 'translate(0,0)')
.attr('dy', '.35em')
.style('fill-opacity', 0)
.text((d) => d.data.name)
.on('click', (_, datum) => {
onClickText(datum as unknown as HierarchyPointNode<Node>);
});
const nodeEnterAndUpdate = nodeEnter.merge(node);
// update the text to reflect whether node has children or not
nodeEnterAndUpdate.select('text').text((d) => d.data.name);
// change the circle fill depending on whether it has children and is collapsed
nodeEnterAndUpdate
.select('circle')
.style('stroke', 'black')
.style('stroke-width', '1.5px')
.style('fill', (d) =>
d.data._children && d.data._children.length > 0
? nodeStyleOptions.colors.collapsed
: d.data.children && d.data.children.length > 0
? nodeStyleOptions.colors.parent
: nodeStyleOptions.colors.default
);
// transition nodes to their new position
const nodeUpdate = nodeEnterAndUpdate
.transition()
.duration(transitionDuration)
.attr('transform', (d) => `translate(${d.y},${d.x})`);
// ensure circle radius is correct
nodeUpdate.select('circle').attr('r', style.node.radius);
nodeUpdate.select('circle').attr('r', nodeStyleOptions.radius);
// fade the text in and align it
nodeUpdate
.select('text')
.select<SVGTextElement>('text')
.style('fill-opacity', 1)
.attr({
transform: function transform(this: SVGGraphicsElement, d) {
const x =
(d.children || d._children ? -1 : 1) *
(this.getBBox().width / 2 + style.node.radius + 5);
return `translate(${x},0)`;
},
.attr('transform', function transform(d) {
const x =
(((d.data.children ?? d.data._children)?.length ?? 0) > 0
? -1
: 1) *
(this.getBBox().width / 2 + nodeStyleOptions.radius + 5);
return `translate(${x},0)`;
});
// blink updated nodes
node
.filter(function flick(this: any, d) {
nodeEnterAndUpdate
.filter(function flick(
this: SVGGElement & {
__oldData__?: HierarchyPointNode<InternalNode>;
},
d
) {
// test whether the relevant properties of d match
// the equivalent property of the oldData
// also test whether the old data exists,
// to catch the entering elements!
return this.__oldData__ && d.value !== this.__oldData__.value;
return (
!!this.__oldData__ && d.data.value !== this.__oldData__.data.value
);
})
.select('g')
.style('opacity', '0.3')
@ -492,21 +451,19 @@ export default function (
// transition exiting nodes to the parent's new position
const nodeExit = node
.exit()
.exit<HierarchyPointNode<InternalNode>>()
.transition()
.duration(transitionDuration)
.attr({
transform: (d) => {
const position = findParentNodePosition(
previousNodePositionsById,
d.id,
(n) => !!nodePositionsById[n.id]
);
const futurePosition =
(position && nodePositionsById[position.id]) ||
nodePositionsById.root;
return `translate(${futurePosition.y!},${futurePosition.x!})`;
},
.attr('transform', (d) => {
const position = findParentNodePosition(
previousNodePositionsById,
d.data.id,
(n) => !!nodePositionsById[n.id]
);
const futurePosition =
(position && nodePositionsById[position.id]) ||
nodePositionsById.root;
return `translate(${futurePosition.y},${futurePosition.x})`;
})
.remove();
@ -516,65 +473,66 @@ export default function (
// update the links
const link = vis
.selectAll('path.link')
.data(links, (d) => (d.target as NodeWithId).id);
.selectAll<SVGPathElement, HierarchyPointLink<InternalNode>>(
'path.link'
)
.data(links, (d) => d.target.data.id);
// enter any new links at the parent's previous position
link
const linkEnter = link
.enter()
.insert('path', 'g')
.attr({
class: 'link',
d: (d) => {
const position = findParentNodePosition(
nodePositionsById,
(d.target as NodeWithId).id,
(n) => !!previousNodePositionsById[n.id]
);
const previousPosition =
(position && previousNodePositionsById[position.id]) ||
previousNodePositionsById.root;
return diagonal({
source: previousPosition,
target: previousPosition,
} as d3.svg.diagonal.Link<NodePosition>);
},
})
.style(style.link);
.attr('class', 'link')
.attr('d', (d) => {
const position = findParentNodePosition(
nodePositionsById,
d.target.data.id,
(n) => !!previousNodePositionsById[n.id]
);
const previousPosition =
(position && previousNodePositionsById[position.id]) ||
previousNodePositionsById.root;
return linkHorizontal({
source: previousPosition,
target: previousPosition,
});
});
for (const [key, value] of Object.entries(linkStyles)) {
linkEnter.style(key, value);
}
const linkEnterAndUpdate = linkEnter.merge(link);
// transition links to their new position
link
linkEnterAndUpdate
.transition()
.duration(transitionDuration)
.attr({
d: diagonal as unknown as Primitive,
});
.attr('d', linkHorizontal);
// transition exiting nodes to the parent's new position
link
.exit()
.exit<HierarchyPointLink<InternalNode>>()
.transition()
.duration(transitionDuration)
.attr({
d: (d) => {
const position = findParentNodePosition(
previousNodePositionsById,
(d.target as NodeWithId).id,
(n) => !!nodePositionsById[n.id]
);
const futurePosition =
(position && nodePositionsById[position.id]) ||
nodePositionsById.root;
return diagonal({
source: futurePosition,
target: futurePosition,
});
},
.attr('d', (d) => {
const position = findParentNodePosition(
previousNodePositionsById,
d.target.data.id,
(n) => !!nodePositionsById[n.id]
);
const futurePosition =
(position && nodePositionsById[position.id]) ||
nodePositionsById.root;
return linkHorizontal({
source: futurePosition,
target: futurePosition,
});
})
.remove();
// delete the old data once it's no longer needed
node.property('__oldData__', null);
nodeEnterAndUpdate.property('__oldData__', null);
// stash the old positions for transition
previousNodePositionsById = nodePositionsById;
@ -582,4 +540,4 @@ export default function (
};
}
export { Primitive };
export type { Node };

View File

@ -1,38 +1,38 @@
import { is, join, pipe, replace } from 'ramda';
import sortAndSerialize from './sortAndSerialize';
import { NodeWithId } from './tree';
import type { InternalNode } from './tree';
export function collapseChildren(node: NodeWithId) {
export function collapseChildren(node: InternalNode) {
if (node.children) {
node._children = node.children;
node._children.forEach(collapseChildren);
node.children = null;
node.children = undefined;
}
}
export function expandChildren(node: NodeWithId) {
export function expandChildren(node: InternalNode) {
if (node._children) {
node.children = node._children;
node.children.forEach(expandChildren);
node._children = null;
node._children = undefined;
}
}
export function toggleChildren(node: NodeWithId) {
export function toggleChildren(node: InternalNode) {
if (node.children) {
node._children = node.children;
node.children = null;
node.children = undefined;
} else if (node._children) {
node.children = node._children;
node._children = null;
node._children = undefined;
}
return node;
}
export function visit(
parent: NodeWithId,
visitFn: (parent: NodeWithId) => void,
childrenFn: (parent: NodeWithId) => NodeWithId[] | null | undefined
parent: InternalNode,
visitFn: (parent: InternalNode) => void,
childrenFn: (parent: InternalNode) => InternalNode[] | null | undefined
) {
if (!parent) {
return;
@ -50,10 +50,10 @@ export function visit(
}
}
export function getNodeGroupByDepthCount(rootNode: NodeWithId) {
export function getNodeGroupByDepthCount(rootNode: InternalNode) {
const nodeGroupByDepthCount = [1];
const traverseFrom = function traverseFrom(node: NodeWithId, depth = 0) {
const traverseFrom = function traverseFrom(node: InternalNode, depth = 0) {
if (!node.children || node.children.length === 0) {
return 0;
}
@ -73,11 +73,7 @@ export function getNodeGroupByDepthCount(rootNode: NodeWithId) {
return nodeGroupByDepthCount;
}
export function getTooltipString(
node: unknown,
i: number | undefined,
{ indentationSize = 4 }
) {
export function getTooltipString(node: InternalNode, { indentationSize = 4 }) {
if (!is(Object, node)) return '';
const spacer = join('&nbsp;&nbsp;');
@ -89,7 +85,6 @@ export function getTooltipString(
if (typeof node.value !== 'undefined') return json2html(node.value);
if (typeof node.object !== 'undefined') return json2html(node.object);
if (children && children.length)
return `childrenCount: ${(children as unknown[]).length}`;
if (children && children.length) return `childrenCount: ${children.length}`;
return 'empty';
}

View File

@ -1,2 +1,2 @@
export { tree } from './charts';
export type { InputOptions, NodeWithId, Primitive } from './charts';
export type { HierarchyPointNode, Node, Options, StyleValue } from './charts';

View File

@ -1,5 +1,18 @@
# Change Log
## 3.0.0
### Major Changes
- b323f77d: Upgrade D3
- Remove UMD build.
- Upgrade d3 peer dependency from v3 to v7.
- Remove `attr` configuration method.
- Rename `style` configuration method to `styles` and move to options.
- Move `text` configuration method to options.
- Remove d3 parameter as first parameter for `tooltip`.
## 2.0.0
- Adds ESM build (https://github.com/reduxjs/redux-devtools/pull/997) and switches the default export to a named export in order to ensure that the CommonJS output and the ESM output are [interchangeable](https://rollupjs.org/guide/en/#outputexports):

View File

@ -10,45 +10,45 @@ It was created by [@romseguy](https://github.com/romseguy) and merged from [`rom
## Quick usage
```javascript
import d3 from 'd3';
import * as d3 from 'd3';
import { tooltip } from 'd3tooltip';
const DOMNode = document.getElementById('chart');
const root = d3.select(DOMNode);
const vis = root.append('svg');
let options = {
offset: {left: 30, top: 10}
const options = {
offset: { left: 30, top: 10 },
styles: { 'min-width': '50px', 'border-radius': '5px' },
};
vis.selectAll('circle').data(someData).enter()
vis
.selectAll('circle')
.data(someData)
.enter()
.append('circle')
.attr('r', 10)
.call(
d3tooltip(d3, 'tooltipClassName', options)
.text((d, i) => toStringOrHtml(d))
.attr({ 'class': 'anotherClassName' })
.style({ 'min-width': '50px', 'border-radius: 5px' })
d3tooltip('tooltipClassName', {
...options,
text: (d) => toStringOrHtml(d),
})
)
.on({
mouseover(d, i) {
d3.select(this).style({
fill: 'skyblue'
});
},
mouseout(d, i) {
d3.select(this).style({
fill: 'black'
});
}
.on('mouseover', function () {
d3.select(this).style('fill', 'skyblue');
})
.on('mouseout', function () {
d3.select(this).style('fill', 'black');
});
```
## API
| Option | Type | Default | Description |
| -------- | ----------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `root` | DOM.Element | `body` | The tooltip will be added as a child of that element. You can also use a D3 [selection](https://github.com/mbostock/d3/wiki/Selections#d3_select) |
| `left` | Number | `undefined` | Sets the tooltip `x` absolute position instead of the mouse `x` position, relative to the `root` element |
| `top` | Number | `undefined` | Sets the tooltip `y` absolute position instead of the mouse `y` position, relative to the `root` element |
| `offset` | Object | `{left: 0, top: 0}` | Sets the distance, starting from the cursor position, until the tooltip is rendered. **Warning**: only applicable if you don't provide a `left` or `top` option |
| Option | Type | Default | Description |
| -------- | ------------------ | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `root` | DOM.Element | `body` | The tooltip will be added as a child of that element. You can also use a D3 [selection](https://github.com/mbostock/d3/wiki/Selections#d3_select). |
| `left` | Number | `undefined` | Sets the tooltip `x` absolute position instead of the mouse `x` position, relative to the `root` element. |
| `top` | Number | `undefined` | Sets the tooltip `y` absolute position instead of the mouse `y` position, relative to the `root` element. |
| `offset` | Object | `{left: 0, top: 0}` | Sets the distance, starting from the cursor position, until the tooltip is rendered. **Warning**: only applicable if you don't provide a `left` or `top` option. |
| `styles` | Object | `{}` | Sets the styles of the tooltip element. |
| `text` | String or Function | `''` | Sets the text of the tooltip. Can be a constant `string` or a function that takes the datum and returns a `string`. |

View File

@ -1,6 +1,6 @@
{
"name": "d3tooltip",
"version": "2.1.0",
"version": "3.0.0",
"description": "A highly configurable tooltip for d3",
"keywords": [
"d3",
@ -19,18 +19,16 @@
"main": "lib/cjs/index.js",
"module": "lib/esm/index.js",
"types": "lib/types/index.d.ts",
"unpkg": "lib/umd/d3tooltip.umd.js",
"sideEffects": false,
"repository": {
"type": "git",
"url": "https://github.com/reduxjs/redux-devtools.git"
},
"scripts": {
"build": "pnpm run build:cjs && pnpm run build:esm && pnpm run build:types && pnpm run build:umd",
"build": "pnpm run build:cjs && pnpm run build:esm && pnpm run build:types",
"build:cjs": "babel src --extensions \".ts\" --out-dir lib/cjs",
"build:esm": "babel src --config-file ./babel.config.esm.json --extensions \".ts\" --out-dir lib/esm",
"build:types": "tsc --emitDeclarationOnly",
"build:umd": "rollup -c",
"clean": "rimraf lib",
"lint": "eslint . --ext .ts",
"type-check": "tsc --noEmit",
@ -38,36 +36,25 @@
"prepublish": "pnpm run type-check && pnpm run lint"
},
"dependencies": {
"@babel/runtime": "^7.20.6",
"ramda": "^0.28.0"
"@babel/runtime": "^7.21.0"
},
"devDependencies": {
"@babel/cli": "^7.19.3",
"@babel/core": "^7.20.5",
"@babel/cli": "^7.21.0",
"@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.18.6",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-commonjs": "^24.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-terser": "^0.2.1",
"@types/d3": "^3.5.47",
"@types/node": "^18.11.17",
"@types/ramda": "^0.28.20",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"d3": "^3.5.17",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"rimraf": "^3.0.2",
"rollup": "^3.7.5",
"rollup-plugin-typescript2": "^0.34.1",
"tslib": "^2.4.1",
"typescript": "~4.9.4"
"@babel/preset-typescript": "^7.21.0",
"@types/d3": "^7.4.0",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"d3": "^7.8.2",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"rimraf": "^4.1.3",
"typescript": "~4.9.5"
},
"peerDependencies": {
"@types/d3": "^3.5.47",
"d3": "^3.5.17"
"@types/d3": "^7.4.0",
"d3": "^7.8.2"
}
}

View File

@ -1,51 +0,0 @@
import typescript from 'rollup-plugin-typescript2';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import terser from '@rollup/plugin-terser';
const config = [
{
input: 'src/index.ts',
output: {
name: 'd3tooltip',
file: 'lib/umd/d3tooltip.js',
format: 'umd',
},
plugins: [
typescript({
tsconfigOverride: { compilerOptions: { declaration: false } },
}),
resolve(),
commonjs(),
babel({
babelHelpers: 'runtime',
extensions: ['.ts'],
plugins: ['@babel/plugin-transform-runtime'],
}),
],
},
{
input: 'src/index.ts',
output: {
name: 'd3tooltip',
file: 'lib/umd/d3tooltip.min.js',
format: 'umd',
},
plugins: [
typescript({
tsconfigOverride: { compilerOptions: { declaration: false } },
}),
resolve(),
commonjs(),
babel({
babelHelpers: 'runtime',
extensions: ['.ts'],
plugins: ['@babel/plugin-transform-runtime'],
}),
terser(),
],
},
];
export default config;

View File

@ -1,161 +1,102 @@
import d3Package, { Primitive, Selection } from 'd3';
import { is } from 'ramda';
import utils from './utils';
const { prependClass, functor } = utils;
import * as d3 from 'd3';
import type { BaseType, Selection } from 'd3';
interface Options<Datum> {
export type StyleValue = string | number | boolean;
interface Options<
Datum,
RootGElement extends BaseType,
RootDatum,
RootPElement extends BaseType,
RootPDatum
> {
left: number | undefined;
top: number | undefined;
offset: {
left: number;
top: number;
};
root: Selection<Datum> | undefined;
root:
| Selection<RootGElement, RootDatum, RootPElement, RootPDatum>
| undefined;
styles: { [key: string]: StyleValue };
text: string | ((datum: Datum) => string);
}
const defaultOptions: Options<unknown> = {
const defaultOptions: Options<unknown, BaseType, unknown, BaseType, unknown> = {
left: undefined, // mouseX
top: undefined, // mouseY
offset: { left: 0, top: 0 },
root: undefined,
styles: {},
text: '',
};
interface Tip<Datum> {
(selection: Selection<Datum>): void;
attr: (
this: this,
d:
| string
| {
[key: string]:
| Primitive
| ((datum: Datum, index: number, outerIndex: number) => Primitive);
}
) => this;
style: (
this: this,
d:
| string
| {
[key: string]:
| Primitive
| ((datum: Datum, index: number, outerIndex: number) => Primitive);
}
| undefined
) => this;
text: (
this: this,
d: string | ((datum: Datum, index?: number, outerIndex?: number) => string)
) => this;
}
export function tooltip<Datum>(
d3: typeof d3Package,
export function tooltip<
GElement extends BaseType,
Datum,
PElement extends BaseType,
PDatum,
RootGElement extends BaseType,
RootDatum,
RootPElement extends BaseType,
RootPDatum
>(
className = 'tooltip',
options: Partial<Options<Datum>> = {}
): Tip<Datum> {
const { left, top, offset, root } = {
options: Partial<
Options<Datum, RootGElement, RootDatum, RootPElement, RootPDatum>
> = {}
) {
const { left, top, offset, root, styles, text } = {
...defaultOptions,
...options,
} as Options<Datum>;
} as Options<Datum, RootGElement, RootDatum, RootPElement, RootPDatum>;
let attrs = { class: className };
let text: (datum: Datum, index?: number, outerIndex?: number) => string = (
node: Datum
) => '';
let styles = {};
let el: Selection<HTMLDivElement, RootDatum, BaseType, unknown>;
const anchor: Selection<
RootGElement,
RootDatum,
RootPElement | HTMLElement,
RootPDatum
> = root || d3.select<RootGElement, RootDatum>('body');
const rootNode = anchor.node()!;
let el: Selection<Datum>;
const anchor = root || d3.select('body');
const rootNode = anchor.node();
function tip(selection: Selection<Datum>) {
selection.on('mouseover.tip', (node) => {
const [mouseX, mouseY] = d3.mouse(rootNode);
const [x, y] = [left || mouseX + offset.left, top || mouseY - offset.top];
return function tip(selection: Selection<GElement, Datum, PElement, PDatum>) {
selection.on('mouseover.tip', (event, datum) => {
const [pointerX, pointerY] = d3.pointer(event, rootNode);
const [x, y] = [
left || pointerX + offset.left,
top || pointerY - offset.top,
];
anchor.selectAll(`div.${className}`).remove();
el = anchor
.append('div')
.attr(prependClass(className)(attrs))
.style({
position: 'absolute',
'z-index': 1001,
left: `${x}px`,
top: `${y}px`,
...styles,
})
.html(() => text(node)) as Selection<Datum>;
.attr('class', className)
.style('position', 'absolute')
.style('z-index', 1001)
.style('left', `${x}px`)
.style('top', `${y}px`)
.html(typeof text === 'function' ? () => text(datum) : () => text);
for (const [key, value] of Object.entries(styles)) {
el.style(key, value);
}
});
selection.on('mousemove.tip', (node) => {
const [mouseX, mouseY] = d3.mouse(rootNode);
const [x, y] = [left || mouseX + offset.left, top || mouseY - offset.top];
selection.on('mousemove.tip', (event, datum) => {
const [pointerX, pointerY] = d3.pointer(event, rootNode);
const [x, y] = [
left || pointerX + offset.left,
top || pointerY - offset.top,
];
el.style({
left: `${x}px`,
top: `${y}px`,
}).html(() => text(node));
el.style('left', `${x}px`)
.style('top', `${y}px`)
.html(typeof text === 'function' ? () => text(datum) : () => text);
});
selection.on('mouseout.tip', () => el.remove());
}
tip.attr = function setAttr(
this: typeof tip,
d:
| string
| {
[key: string]:
| Primitive
| ((datum: Datum, index: number, outerIndex: number) => Primitive);
}
) {
if (is(Object, d)) {
attrs = {
...attrs,
...(d as {
[key: string]:
| Primitive
| ((datum: Datum, index: number, outerIndex: number) => Primitive);
}),
};
}
return this;
};
tip.style = function setStyle(
this: typeof tip,
d:
| string
| {
[key: string]:
| Primitive
| ((datum: Datum, index: number, outerIndex: number) => Primitive);
}
| undefined
) {
if (is(Object, d)) {
styles = {
...styles,
...(d as {
[key: string]:
| Primitive
| ((datum: Datum, index: number, outerIndex: number) => Primitive);
}),
};
}
return this;
};
tip.text = function setText(
this: typeof tip,
d: string | ((datum: Datum, index?: number, outerIndex?: number) => string)
) {
text = functor(d);
return this;
};
return tip;
}

View File

@ -1,20 +0,0 @@
import { is } from 'ramda';
import { Primitive } from 'd3';
export default function functor<Datum>(
v: string | ((datum: Datum, index?: number, outerIndex?: number) => string)
): (datum: Datum, index?: number, outerIndex?: number) => string;
export default function functor<Datum>(
v:
| Primitive
| ((datum: Datum, index: number, outerIndex?: number) => Primitive)
): (datum: Datum, index?: number, outerIndex?: number) => Primitive;
export default function functor<Datum>(
v:
| Primitive
| ((datum: Datum, index: number, outerIndex?: number) => Primitive)
): (datum: Datum, index: number, outerIndex?: number) => Primitive {
return is(Function, v)
? (v as (datum: Datum, index: number, outerIndex?: number) => Primitive)
: () => v;
}

View File

@ -1,7 +0,0 @@
import prependClass from './prependClass';
import functor from './functor';
export default {
prependClass,
functor,
};

View File

@ -1,28 +0,0 @@
import { mapObjIndexed, join } from 'ramda';
import functor from './functor';
import { Primitive } from 'd3';
export default function prependClass<Datum>(className: string) {
return mapObjIndexed(
(
value:
| Primitive
| ((datum: Datum, index: number, outerIndex?: number) => Primitive),
key
) => {
if (key === 'class') {
const fn = functor(value);
return (d: Datum, i: number) => {
const classNames = fn(d, i);
if (classNames !== className) {
return join(' ', [className, classNames]);
}
return classNames;
};
}
return value;
}
);
}

View File

@ -1,5 +1,11 @@
# Change Log
## 3.0.0
### Major Changes
- b323f77d: Remove UMD build.
## 2.0.0
- Adds ESM build (https://github.com/reduxjs/redux-devtools/pull/997) and switches the default export to a named export in order to ensure that the CommonJS output and the ESM output are [interchangeable](https://rollupjs.org/guide/en/#outputexports):

View File

@ -1,6 +1,6 @@
{
"name": "map2tree",
"version": "2.1.0",
"version": "3.0.0",
"description": "Utility for mapping maps to trees",
"keywords": [
"map2tree",
@ -22,18 +22,16 @@
"main": "lib/cjs/index.js",
"module": "lib/esm/index.js",
"types": "lib/types/index.d.ts",
"unpkg": "lib/umd/map2tree.umd.js",
"sideEffects": false,
"repository": {
"type": "git",
"url": "https://github.com/reduxjs/redux-devtools.git"
},
"scripts": {
"build": "pnpm run build:cjs && pnpm run build:esm && pnpm run build:types && pnpm run build:umd",
"build": "pnpm run build:cjs && pnpm run build:esm && pnpm run build:types",
"build:cjs": "babel src --extensions \".ts\" --out-dir lib/cjs",
"build:esm": "babel src --config-file ./babel.config.esm.json --extensions \".ts\" --out-dir lib/esm",
"build:types": "tsc --emitDeclarationOnly",
"build:umd": "rollup -c",
"clean": "rimraf lib",
"test": "jest",
"lint": "eslint . --ext .ts",
@ -42,35 +40,26 @@
"prepublish": "pnpm run type-check && pnpm run lint && pnpm run test"
},
"dependencies": {
"@babel/runtime": "^7.20.6",
"@babel/runtime": "^7.21.0",
"lodash": "^4.17.21"
},
"devDependencies": {
"@babel/cli": "^7.19.3",
"@babel/core": "^7.20.5",
"@babel/cli": "^7.21.0",
"@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.18.6",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-commonjs": "^24.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-terser": "^0.2.1",
"@types/jest": "^29.2.4",
"@babel/preset-typescript": "^7.21.0",
"@types/jest": "^29.4.0",
"@types/lodash": "^4.14.191",
"@types/node": "^18.11.17",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-jest": "^27.1.7",
"immutable": "^4.1.0",
"jest": "^29.3.1",
"rimraf": "^3.0.2",
"rollup": "^3.7.5",
"rollup-plugin-typescript2": "^0.34.1",
"ts-jest": "^29.0.3",
"tslib": "^2.4.1",
"typescript": "~4.9.4"
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.2.1",
"immutable": "^4.2.4",
"jest": "^29.4.3",
"rimraf": "^4.1.3",
"ts-jest": "^29.0.5",
"typescript": "~4.9.5"
}
}

View File

@ -1,51 +0,0 @@
import typescript from 'rollup-plugin-typescript2';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import terser from '@rollup/plugin-terser';
const config = [
{
input: 'src/index.ts',
output: {
name: 'map2tree',
file: 'lib/umd/map2tree.js',
format: 'umd',
},
plugins: [
typescript({
tsconfigOverride: { compilerOptions: { declaration: false } },
}),
resolve(),
commonjs(),
babel({
babelHelpers: 'runtime',
extensions: ['.ts'],
plugins: ['@babel/plugin-transform-runtime'],
}),
],
},
{
input: 'src/index.ts',
output: {
name: 'map2tree',
file: 'lib/umd/map2tree.min.js',
format: 'umd',
},
plugins: [
typescript({
tsconfigOverride: { compilerOptions: { declaration: false } },
}),
resolve(),
commonjs(),
babel({
babelHelpers: 'runtime',
extensions: ['.ts'],
plugins: ['@babel/plugin-transform-runtime'],
}),
terser(),
],
},
];
export default config;

View File

@ -4,7 +4,8 @@ import mapValues from 'lodash/mapValues';
export interface Node {
name: string;
children?: Node[] | null;
children?: this[];
object?: unknown;
value?: unknown;
}
@ -43,7 +44,6 @@ function getNode(tree: Node, key: string): Node | null {
}
export function map2tree(
// eslint-disable-next-line @typescript-eslint/ban-types
root: unknown,
options: { key?: string; pushMethod?: 'push' | 'unshift' } = {},
tree: Node = { name: options.key || 'state', children: [] }

View File

@ -39,7 +39,7 @@
"prepublish": "pnpm run type-check && pnpm run lint && pnpm run test"
},
"dependencies": {
"@babel/runtime": "^7.20.6",
"@babel/runtime": "^7.21.0",
"@types/base16": "^1.0.2",
"@types/lodash": "^4.14.191",
"base16": "^1.0.0",
@ -48,24 +48,24 @@
"lodash.curry": "^4.1.1"
},
"devDependencies": {
"@babel/cli": "^7.19.3",
"@babel/core": "^7.20.5",
"@babel/cli": "^7.21.0",
"@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/plugin-transform-runtime": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@types/color": "^3.0.3",
"@types/jest": "^29.2.4",
"@types/jest": "^29.4.0",
"@types/lodash.curry": "^4.1.7",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-jest": "^27.1.7",
"jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
"rimraf": "^3.0.2",
"ts-jest": "^29.0.3",
"typescript": "~4.9.4"
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.2.1",
"jest": "^29.4.3",
"jest-environment-jsdom": "^29.4.3",
"rimraf": "^4.1.3",
"ts-jest": "^29.0.5",
"typescript": "~4.9.5"
}
}

View File

@ -11,34 +11,34 @@
},
"dependencies": {
"react": "^18.2.0",
"react-bootstrap": "^2.7.0",
"react-bootstrap": "^2.7.2",
"react-dock": "^0.6.0",
"react-dom": "^18.2.0",
"react-icons": "^4.7.1",
"react-is": "^18.2.0",
"styled-components": "^5.3.6"
"styled-components": "^5.3.8"
},
"devDependencies": {
"@babel/core": "^7.20.5",
"@babel/core": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@types/node": "^18.11.17",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@babel/preset-typescript": "^7.21.0",
"@types/node": "^18.14.4",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/styled-components": "^5.1.26",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"babel-loader": "^9.1.0",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"babel-loader": "^9.1.2",
"cross-env": "^7.0.3",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-react": "^7.31.11",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"fork-ts-checker-webpack-plugin": "^7.2.14",
"fork-ts-checker-webpack-plugin": "^8.0.0",
"html-webpack-plugin": "^5.5.0",
"ts-node": "^10.9.1",
"typescript": "~4.9.4",
"typescript": "~4.9.5",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"

View File

@ -39,38 +39,38 @@
"prepublish": "pnpm run type-check && pnpm run lint && pnpm run test"
},
"dependencies": {
"@babel/runtime": "^7.20.6",
"@babel/runtime": "^7.21.0",
"@types/lodash": "^4.14.191",
"@types/prop-types": "^15.7.5",
"lodash.debounce": "^4.0.8",
"prop-types": "^15.8.1"
},
"devDependencies": {
"@babel/cli": "^7.19.3",
"@babel/core": "^7.20.5",
"@babel/cli": "^7.21.0",
"@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/plugin-transform-runtime": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@types/jest": "^29.2.4",
"@babel/preset-typescript": "^7.21.0",
"@types/jest": "^29.4.0",
"@types/lodash.debounce": "^4.0.7",
"@types/react": "^18.0.26",
"@types/react": "^18.0.28",
"@types/react-test-renderer": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-jest": "^27.1.7",
"eslint-plugin-react": "^7.31.11",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
"jest": "^29.4.3",
"jest-environment-jsdom": "^29.4.3",
"react": "^18.2.0",
"react-test-renderer": "^18.2.0",
"rimraf": "^3.0.2",
"ts-jest": "^29.0.3",
"typescript": "~4.9.4"
"rimraf": "^4.1.3",
"ts-jest": "^29.0.5",
"typescript": "~4.9.5"
},
"peerDependencies": {
"@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0",

View File

@ -1,5 +1,15 @@
# Change Log
## 0.18.0
### Major Changes
- 81926f32: Remove UNSAFE method from react-json-tree
- Replace `shouldExpandNode` with `shouldExpandNodeInitially`. This function is now only called when a node in the tree is first rendered, when before it would update the expanded state of the node if the results of calling `shouldExpandNode` changed between renders. There is no way to replicate the old behavior exactly, but the new behavior is the intended behavior for the use cases within Redux DevTools. Please open an issue if you need a way to programatically control the expanded state of nodes.
- Bump the minimum React version from `16.3.0` to `16.8.0` so that `react-json-tree` can use hooks.
- Tightened TypeScript prop types to use `unknown` instead of `any` where possible and make the key path array `readonly`.
## 0.17.0
### Minor Changes

View File

@ -139,7 +139,7 @@ Their full signatures are:
#### More Options
- `shouldExpandNode: function(keyPath, data, level)` - determines if node should be expanded (root is expanded by default)
- `shouldExpandNodeInitially: function(keyPath, data, level)` - determines if node should be expanded when it first renders (root is expanded by default)
- `hideRoot: boolean` - if `true`, the root node is hidden.
- `sortObjectKeys: boolean | function(a, b)` - sorts object keys with compare function (optional). Isn't applied to iterable maps like `Immutable.Map`.
- `postprocessValue: function(value)` - maps `value` to a new `value`

View File

@ -1,5 +1,12 @@
# react-json-tree-example
## 1.1.8
### Patch Changes
- Updated dependencies [81926f32]
- react-json-tree@0.18.0
## 1.1.7
### Patch Changes

View File

@ -1,7 +1,7 @@
{
"private": true,
"name": "react-json-tree-example",
"version": "1.1.7",
"version": "1.1.8",
"description": "React-Json-Tree example",
"homepage": "https://github.com/reduxjs/redux-devtools/tree/master/packages/react-json-tree/examples",
"bugs": {
@ -19,32 +19,32 @@
"type-check": "tsc --noEmit"
},
"dependencies": {
"immutable": "^4.1.0",
"immutable": "^4.2.4",
"react": "^18.2.0",
"react-base16-styling": "^0.9.1",
"react-dom": "^18.2.0",
"react-json-tree": "^0.17.0"
"react-json-tree": "^0.18.0"
},
"devDependencies": {
"@babel/core": "^7.20.5",
"@babel/core": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@types/node": "^18.11.17",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"babel-loader": "^9.1.0",
"@babel/preset-typescript": "^7.21.0",
"@types/node": "^18.14.4",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"babel-loader": "^9.1.2",
"cross-env": "^7.0.3",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-react": "^7.31.11",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"fork-ts-checker-webpack-plugin": "^7.2.14",
"fork-ts-checker-webpack-plugin": "^8.0.0",
"html-webpack-plugin": "^5.5.0",
"ts-node": "^10.9.1",
"typescript": "~4.9.4",
"typescript": "~4.9.5",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"

View File

@ -178,7 +178,7 @@ const App = () => (
<span role="img" aria-label="mellow">
😐
</span>{' '}
{raw}{' '}
{raw as string}{' '}
<span role="img" aria-label="mellow">
😐
</span>
@ -194,7 +194,11 @@ const App = () => (
</div>
<p>Collapsed root node</p>
<div>
<JSONTree data={data} theme={theme} shouldExpandNode={() => false} />
<JSONTree
data={data}
theme={theme}
shouldExpandNodeInitially={() => false}
/>
</div>
</div>
);

View File

@ -1,6 +1,6 @@
{
"name": "react-json-tree",
"version": "0.17.0",
"version": "0.18.0",
"description": "React JSON Viewer Component, Extracted from redux-devtools",
"keywords": [
"react",
@ -45,47 +45,45 @@
"prepublish": "pnpm run type-check && pnpm run lint && pnpm run test"
},
"dependencies": {
"@babel/runtime": "^7.20.6",
"@babel/runtime": "^7.21.0",
"@types/lodash": "^4.14.191",
"@types/prop-types": "^15.7.5",
"prop-types": "^15.8.1",
"react-base16-styling": "^0.9.1"
},
"devDependencies": {
"@babel/cli": "^7.19.3",
"@babel/core": "^7.20.5",
"@babel/cli": "^7.21.0",
"@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/plugin-transform-runtime": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-commonjs": "^24.0.0",
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-terser": "^0.2.1",
"@types/jest": "^29.2.4",
"@types/node": "^18.11.17",
"@types/react": "^18.0.26",
"@rollup/plugin-terser": "^0.4.0",
"@types/jest": "^29.4.0",
"@types/node": "^18.14.4",
"@types/react": "^18.0.28",
"@types/react-test-renderer": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-jest": "^27.1.7",
"eslint-plugin-react": "^7.31.11",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.3.1",
"jest": "^29.4.3",
"react": "^18.2.0",
"react-test-renderer": "^18.2.0",
"rimraf": "^3.0.2",
"rollup": "^3.7.5",
"rimraf": "^4.1.3",
"rollup": "^3.18.0",
"rollup-plugin-typescript2": "^0.34.1",
"ts-jest": "^29.0.3",
"tslib": "^2.4.1",
"typescript": "~4.9.4"
"ts-jest": "^29.0.5",
"tslib": "^2.5.0",
"typescript": "~4.9.5"
},
"peerDependencies": {
"@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0",
"react": "^16.3.0 || ^17.0.0 || ^18.0.0"
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
}

View File

@ -1,59 +1,39 @@
import React from 'react';
import PropTypes from 'prop-types';
import React, { useCallback, useState } from 'react';
import JSONArrow from './JSONArrow';
import { CircularPropsPassedThroughItemRange } from './types';
import type { CircularCache, CommonInternalProps } from './types';
interface Props extends CircularPropsPassedThroughItemRange {
data: any;
interface Props extends CommonInternalProps {
data: unknown;
nodeType: string;
from: number;
to: number;
renderChildNodes: (props: Props, from: number, to: number) => React.ReactNode;
circularCache: CircularCache;
level: number;
}
interface State {
expanded: boolean;
}
export default class ItemRange extends React.Component<Props, State> {
static propTypes = {
styling: PropTypes.func.isRequired,
from: PropTypes.number.isRequired,
to: PropTypes.number.isRequired,
renderChildNodes: PropTypes.func.isRequired,
nodeType: PropTypes.string.isRequired,
};
constructor(props: Props) {
super(props);
this.state = { expanded: false };
}
render() {
const { styling, from, to, renderChildNodes, nodeType } = this.props;
return this.state.expanded ? (
<div {...styling('itemRange', this.state.expanded)}>
{renderChildNodes(this.props, from, to)}
</div>
) : (
<div
{...styling('itemRange', this.state.expanded)}
onClick={this.handleClick}
>
<JSONArrow
nodeType={nodeType}
styling={styling}
expanded={false}
onClick={this.handleClick}
arrowStyle="double"
/>
{`${from} ... ${to}`}
</div>
);
}
handleClick = () => {
this.setState({ expanded: !this.state.expanded });
};
export default function ItemRange(props: Props) {
const { styling, from, to, renderChildNodes, nodeType } = props;
const [expanded, setExpanded] = useState<boolean>(false);
const handleClick = useCallback(() => {
setExpanded(!expanded);
}, [expanded]);
return expanded ? (
<div {...styling('itemRange', expanded)}>
{renderChildNodes(props, from, to)}
</div>
) : (
<div {...styling('itemRange', expanded)} onClick={handleClick}>
<JSONArrow
nodeType={nodeType}
styling={styling}
expanded={false}
onClick={handleClick}
arrowStyle="double"
/>
{`${from} ... ${to}`}
</div>
);
}

View File

@ -1,35 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import JSONNestedNode from './JSONNestedNode';
import { CircularPropsPassedThroughJSONNode } from './types';
import type { CommonInternalProps } from './types';
// Returns the "n Items" string for this node,
// generating and caching it if it hasn't been created yet.
function createItemString(data: any) {
function createItemString(data: unknown) {
return `${(data as unknown[]).length} ${
(data as unknown[]).length !== 1 ? 'items' : 'item'
}`;
}
interface Props extends CircularPropsPassedThroughJSONNode {
data: any;
interface Props extends CommonInternalProps {
data: unknown;
nodeType: string;
}
// Configures <JSONNestedNode> to render an Array
const JSONArrayNode: React.FunctionComponent<Props> = ({ data, ...props }) => (
<JSONNestedNode
{...props}
data={data}
nodeType="Array"
nodeTypeIndicator="[]"
createItemString={createItemString}
expandable={data.length > 0}
/>
);
JSONArrayNode.propTypes = {
data: PropTypes.array,
};
export default JSONArrayNode;
export default function JSONArrayNode({ data, ...props }: Props) {
return (
<JSONNestedNode
{...props}
data={data}
nodeType="Array"
nodeTypeIndicator="[]"
createItemString={createItemString}
expandable={(data as unknown[]).length > 0}
/>
);
}

View File

@ -1,6 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import { StylingFunction } from 'react-base16-styling';
import type { StylingFunction } from 'react-base16-styling';
interface Props {
styling: StylingFunction;
@ -10,33 +9,21 @@ interface Props {
onClick: React.MouseEventHandler<HTMLDivElement>;
}
const JSONArrow: React.FunctionComponent<Props> = ({
export default function JSONArrow({
styling,
arrowStyle,
arrowStyle = 'single',
expanded,
nodeType,
onClick,
}) => (
<div {...styling('arrowContainer', arrowStyle)} onClick={onClick}>
<div {...styling(['arrow', 'arrowSign'], nodeType, expanded, arrowStyle)}>
{'\u25B6'}
{arrowStyle === 'double' && (
<div {...styling(['arrowSign', 'arrowSignInner'])}>{'\u25B6'}</div>
)}
}: Props) {
return (
<div {...styling('arrowContainer', arrowStyle)} onClick={onClick}>
<div {...styling(['arrow', 'arrowSign'], nodeType, expanded, arrowStyle)}>
{'\u25B6'}
{arrowStyle === 'double' && (
<div {...styling(['arrowSign', 'arrowSignInner'])}>{'\u25B6'}</div>
)}
</div>
</div>
</div>
);
JSONArrow.propTypes = {
styling: PropTypes.func.isRequired,
arrowStyle: PropTypes.oneOf(['single', 'double']),
expanded: PropTypes.bool.isRequired,
nodeType: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
};
JSONArrow.defaultProps = {
arrowStyle: 'single',
};
export default JSONArrow;
);
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import JSONNestedNode from './JSONNestedNode';
import { CircularPropsPassedThroughJSONNode } from './types';
import type { CommonInternalProps } from './types';
// Returns the "n Items" string for this node,
// generating and caching it if it hasn't been created yet.
@ -22,21 +22,20 @@ function createItemString(data: any, limit: number) {
return `${hasMore ? '>' : ''}${count} ${count !== 1 ? 'entries' : 'entry'}`;
}
interface Props extends CircularPropsPassedThroughJSONNode {
data: any;
interface Props extends CommonInternalProps {
data: unknown;
nodeType: string;
}
// Configures <JSONNestedNode> to render an iterable
const JSONIterableNode: React.FunctionComponent<Props> = ({ ...props }) => {
export default function JSONIterableNode(props: Props) {
return (
<JSONNestedNode
{...props}
nodeType="Iterable"
nodeTypeIndicator="()"
createItemString={createItemString}
expandable
/>
);
};
export default JSONIterableNode;
}

View File

@ -1,22 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import React, { useCallback, useState } from 'react';
import JSONArrow from './JSONArrow';
import getCollectionEntries from './getCollectionEntries';
import JSONNode from './JSONNode';
import ItemRange from './ItemRange';
import {
CircularPropsPassedThroughJSONNestedNode,
CircularPropsPassedThroughRenderChildNodes,
} from './types';
import type { CircularCache, CommonInternalProps } from './types';
/**
* Renders nested values (eg. objects, arrays, lists, etc.)
*/
export interface RenderChildNodesProps
extends CircularPropsPassedThroughRenderChildNodes {
data: any;
export interface RenderChildNodesProps extends CommonInternalProps {
data: unknown;
nodeType: string;
circularCache: CircularCache;
level: number;
}
interface Range {
@ -26,7 +23,7 @@ interface Range {
interface Entry {
key: string | number;
value: any;
value: unknown;
}
function isRange(rangeOrEntry: Range | Entry): rangeOrEntry is Range {
@ -89,152 +86,92 @@ function renderChildNodes(
return childNodes;
}
interface Props extends CircularPropsPassedThroughJSONNestedNode {
data: any;
interface Props extends CommonInternalProps {
data: unknown;
nodeType: string;
nodeTypeIndicator: string;
createItemString: (data: any, collectionLimit: number) => string;
createItemString: (data: unknown, collectionLimit: number) => string;
expandable: boolean;
}
interface State {
expanded: boolean;
}
export default function JSONNestedNode(props: Props) {
const {
circularCache = [],
collectionLimit,
createItemString,
data,
expandable,
getItemString,
hideRoot,
isCircular,
keyPath,
labelRenderer,
level = 0,
nodeType,
nodeTypeIndicator,
shouldExpandNodeInitially,
styling,
} = props;
function getStateFromProps(props: Props) {
// calculate individual node expansion if necessary
const expanded = !props.isCircular
? props.shouldExpandNode(props.keyPath, props.data, props.level)
: false;
return {
expanded,
};
}
const [expanded, setExpanded] = useState<boolean>(
// calculate individual node expansion if necessary
isCircular ? false : shouldExpandNodeInitially(keyPath, data, level)
);
export default class JSONNestedNode extends React.Component<Props, State> {
static propTypes = {
getItemString: PropTypes.func.isRequired,
nodeTypeIndicator: PropTypes.any,
nodeType: PropTypes.string.isRequired,
data: PropTypes.any,
hideRoot: PropTypes.bool.isRequired,
createItemString: PropTypes.func.isRequired,
styling: PropTypes.func.isRequired,
collectionLimit: PropTypes.number,
keyPath: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
).isRequired,
labelRenderer: PropTypes.func.isRequired,
shouldExpandNode: PropTypes.func,
level: PropTypes.number.isRequired,
sortObjectKeys: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
isCircular: PropTypes.bool,
expandable: PropTypes.bool,
};
const handleClick = useCallback(() => {
if (expandable) setExpanded(!expanded);
}, [expandable, expanded]);
static defaultProps = {
data: [],
circularCache: [],
level: 0,
expandable: true,
};
const renderedChildren =
expanded || (hideRoot && level === 0)
? renderChildNodes({ ...props, circularCache, level: level + 1 })
: null;
constructor(props: Props) {
super(props);
this.state = getStateFromProps(props);
}
const itemType = (
<span {...styling('nestedNodeItemType', expanded)}>
{nodeTypeIndicator}
</span>
);
const renderedItemString = getItemString(
nodeType,
data,
itemType,
createItemString(data, collectionLimit),
keyPath
);
const stylingArgs = [keyPath, nodeType, expanded, expandable] as const;
UNSAFE_componentWillReceiveProps(nextProps: Props) {
const nextState = getStateFromProps(nextProps);
if (getStateFromProps(this.props).expanded !== nextState.expanded) {
this.setState(nextState);
}
}
shouldComponentUpdate(nextProps: Props, nextState: State) {
return (
!!Object.keys(nextProps).find(
(key) =>
key !== 'circularCache' &&
(key === 'keyPath'
? nextProps[key].join('/') !== this.props[key].join('/')
: nextProps[key as keyof Props] !== this.props[key as keyof Props])
) || nextState.expanded !== this.state.expanded
);
}
render() {
const {
getItemString,
nodeTypeIndicator,
nodeType,
data,
hideRoot,
createItemString,
styling,
collectionLimit,
keyPath,
labelRenderer,
expandable,
} = this.props;
const { expanded } = this.state;
const renderedChildren =
expanded || (hideRoot && this.props.level === 0)
? renderChildNodes({ ...this.props, level: this.props.level + 1 })
: null;
const itemType = (
<span {...styling('nestedNodeItemType', expanded)}>
{nodeTypeIndicator}
return hideRoot ? (
<li {...styling('rootNode', ...stylingArgs)}>
<ul {...styling('rootNodeChildren', ...stylingArgs)}>
{renderedChildren}
</ul>
</li>
) : (
<li {...styling('nestedNode', ...stylingArgs)}>
{expandable && (
<JSONArrow
styling={styling}
nodeType={nodeType}
expanded={expanded}
onClick={handleClick}
/>
)}
<label
{...styling(['label', 'nestedNodeLabel'], ...stylingArgs)}
onClick={handleClick}
>
{labelRenderer(...stylingArgs)}
</label>
<span
{...styling('nestedNodeItemString', ...stylingArgs)}
onClick={handleClick}
>
{renderedItemString}
</span>
);
const renderedItemString = getItemString(
nodeType,
data,
itemType,
createItemString(data, collectionLimit),
keyPath
);
const stylingArgs = [keyPath, nodeType, expanded, expandable] as const;
return hideRoot ? (
<li {...styling('rootNode', ...stylingArgs)}>
<ul {...styling('rootNodeChildren', ...stylingArgs)}>
{renderedChildren}
</ul>
</li>
) : (
<li {...styling('nestedNode', ...stylingArgs)}>
{expandable && (
<JSONArrow
styling={styling}
nodeType={nodeType}
expanded={expanded}
onClick={this.handleClick}
/>
)}
<label
{...styling(['label', 'nestedNodeLabel'], ...stylingArgs)}
onClick={this.handleClick}
>
{labelRenderer(...stylingArgs)}
</label>
<span
{...styling('nestedNodeItemString', ...stylingArgs)}
onClick={this.handleClick}
>
{renderedItemString}
</span>
<ul {...styling('nestedNodeChildren', ...stylingArgs)}>
{renderedChildren}
</ul>
</li>
);
}
handleClick = () => {
if (this.props.expandable) {
this.setState({ expanded: !this.state.expanded });
}
};
<ul {...styling('nestedNodeChildren', ...stylingArgs)}>
{renderedChildren}
</ul>
</li>
);
}

View File

@ -1,19 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import objType from './objType';
import JSONObjectNode from './JSONObjectNode';
import JSONArrayNode from './JSONArrayNode';
import JSONIterableNode from './JSONIterableNode';
import JSONValueNode from './JSONValueNode';
import { CircularPropsPassedThroughJSONNode } from './types';
import type { CommonInternalProps } from './types';
interface Props extends CircularPropsPassedThroughJSONNode {
keyPath: (string | number)[];
value: any;
isCustomNode: (value: any) => boolean;
interface Props extends CommonInternalProps {
value: unknown;
}
const JSONNode: React.FunctionComponent<Props> = ({
export default function JSONNode({
getItemString,
keyPath,
labelRenderer,
@ -22,7 +19,7 @@ const JSONNode: React.FunctionComponent<Props> = ({
valueRenderer,
isCustomNode,
...rest
}) => {
}: Props) {
const nodeType = isCustomNode(value) ? 'Custom' : objType(value);
const simpleNodeProps = {
@ -102,18 +99,4 @@ const JSONNode: React.FunctionComponent<Props> = ({
/>
);
}
};
JSONNode.propTypes = {
getItemString: PropTypes.func.isRequired,
keyPath: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired
).isRequired,
labelRenderer: PropTypes.func.isRequired,
styling: PropTypes.func.isRequired,
value: PropTypes.any,
valueRenderer: PropTypes.func.isRequired,
isCustomNode: PropTypes.func.isRequired,
};
export default JSONNode;
}

View File

@ -1,35 +1,29 @@
import React from 'react';
import PropTypes from 'prop-types';
import JSONNestedNode from './JSONNestedNode';
import { CircularPropsPassedThroughJSONNode } from './types';
import type { CommonInternalProps } from './types';
// Returns the "n Items" string for this node,
// generating and caching it if it hasn't been created yet.
function createItemString(data: any) {
function createItemString(data: unknown) {
const len = Object.getOwnPropertyNames(data).length;
return `${len} ${len !== 1 ? 'keys' : 'key'}`;
}
interface Props extends CircularPropsPassedThroughJSONNode {
data: any;
interface Props extends CommonInternalProps {
data: unknown;
nodeType: string;
}
// Configures <JSONNestedNode> to render an Object
const JSONObjectNode: React.FunctionComponent<Props> = ({ data, ...props }) => (
<JSONNestedNode
{...props}
data={data}
nodeType="Object"
nodeTypeIndicator={props.nodeType === 'Error' ? 'Error()' : '{}'}
createItemString={createItemString}
expandable={Object.getOwnPropertyNames(data).length > 0}
/>
);
JSONObjectNode.propTypes = {
data: PropTypes.object,
nodeType: PropTypes.string.isRequired,
};
export default JSONObjectNode;
export default function JSONObjectNode({ data, ...props }: Props) {
return (
<JSONNestedNode
{...props}
data={data}
nodeType="Object"
nodeTypeIndicator={props.nodeType === 'Error' ? 'Error()' : '{}'}
createItemString={createItemString}
expandable={Object.getOwnPropertyNames(data).length > 0}
/>
);
}

View File

@ -1,18 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import { JSONValueNodeCircularPropsProvidedByJSONNode } from './types';
import type {
GetItemString,
Key,
KeyPath,
LabelRenderer,
Styling,
ValueRenderer,
} from './types';
/**
* Renders simple values (eg. strings, numbers, booleans, etc)
*/
interface Props extends JSONValueNodeCircularPropsProvidedByJSONNode {
interface Props {
getItemString: GetItemString;
key: Key;
keyPath: KeyPath;
labelRenderer: LabelRenderer;
nodeType: string;
value: any;
valueGetter?: (value: any) => any;
styling: Styling;
value: unknown;
valueRenderer: ValueRenderer;
valueGetter?: (value: any) => unknown;
}
const JSONValueNode: React.FunctionComponent<Props> = ({
export default function JSONValueNode({
nodeType,
styling,
labelRenderer,
@ -20,27 +32,15 @@ const JSONValueNode: React.FunctionComponent<Props> = ({
valueRenderer,
value,
valueGetter = (value) => value,
}) => (
<li {...styling('value', nodeType, keyPath)}>
<label {...styling(['label', 'valueLabel'], nodeType, keyPath)}>
{labelRenderer(keyPath, nodeType, false, false)}
</label>
<span {...styling('valueText', nodeType, keyPath)}>
{valueRenderer(valueGetter(value), value, ...keyPath)}
</span>
</li>
);
JSONValueNode.propTypes = {
nodeType: PropTypes.string.isRequired,
styling: PropTypes.func.isRequired,
labelRenderer: PropTypes.func.isRequired,
keyPath: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired
).isRequired,
valueRenderer: PropTypes.func.isRequired,
value: PropTypes.any,
valueGetter: PropTypes.func,
};
export default JSONValueNode;
}: Props) {
return (
<li {...styling('value', nodeType, keyPath)}>
<label {...styling(['label', 'valueLabel'], nodeType, keyPath)}>
{labelRenderer(keyPath, nodeType, false, false)}
</label>
<span {...styling('valueText', nodeType, keyPath)}>
{valueRenderer(valueGetter(value), value, ...keyPath)}
</span>
</li>
);
}

View File

@ -1,11 +1,12 @@
import type { CurriedFunction1 } from 'lodash';
import {
import { createStyling } from 'react-base16-styling';
import type {
Base16Theme,
createStyling,
StylingConfig,
StylingFunction,
Theme,
} from 'react-base16-styling';
import solarized from './themes/solarized';
import { StylingFunction, Theme } from 'react-base16-styling/src';
const colorMap = (theme: Base16Theme) => ({
BACKGROUND_COLOR: theme.base00,

View File

@ -1,4 +1,6 @@
function getLength(type: string, collection: any) {
import type { SortObjectKeys } from './types';
function getLength(type: string, collection: unknown) {
if (type === 'Object') {
// eslint-disable-next-line @typescript-eslint/ban-types
return Object.keys(collection as {}).length;
@ -9,17 +11,17 @@ function getLength(type: string, collection: any) {
return Infinity;
}
function isIterableMap(collection: any) {
return typeof (collection as Map<any, any>).set === 'function';
function isIterableMap(collection: unknown) {
return typeof (collection as Map<unknown, unknown>).set === 'function';
}
function getEntries(
type: string,
collection: any,
sortObjectKeys?: ((a: any, b: any) => number) | boolean | undefined,
sortObjectKeys: SortObjectKeys,
from = 0,
to = Infinity
): { entries: { key: string | number; value: any }[]; hasMore?: boolean } {
): { entries: { key: string | number; value: unknown }[]; hasMore?: boolean } {
let res;
if (type === 'Object') {
@ -95,8 +97,8 @@ function getRanges(from: number, to: number, limit: number) {
export default function getCollectionEntries(
type: string,
collection: any,
sortObjectKeys: ((a: any, b: any) => number) | boolean | undefined,
collection: unknown,
sortObjectKeys: SortObjectKeys,
limit: number,
from = 0,
to = Infinity

View File

@ -3,177 +3,88 @@
// Dave Vedder <veddermatic@gmail.com> http://www.eskimospy.com/
// port by Daniele Zannotti http://www.github.com/dzannotti <dzannotti@me.com>
import React from 'react';
import PropTypes from 'prop-types';
import React, { useMemo } from 'react';
import JSONNode from './JSONNode';
import createStylingFromTheme from './createStylingFromTheme';
import { invertTheme } from 'react-base16-styling';
import type { StylingValue, Theme } from 'react-base16-styling';
import type {
StylingConfig,
StylingFunction,
StylingValue,
Theme,
} from 'react-base16-styling';
import { CircularPropsPassedThroughJSONTree } from './types';
CommonExternalProps,
GetItemString,
IsCustomNode,
LabelRenderer,
ShouldExpandNodeInitially,
} from './types';
interface Props extends CircularPropsPassedThroughJSONTree {
data: any;
interface Props extends Partial<CommonExternalProps> {
data: unknown;
theme?: Theme;
invertTheme: boolean;
}
interface State {
styling: StylingFunction;
invertTheme?: boolean;
}
const identity = (value: any) => value;
const expandRootNode = (
keyPath: (string | number)[],
data: any,
level: number
) => level === 0;
const defaultItemString = (
type: string,
data: any,
itemType: React.ReactNode,
itemString: string
) => (
const expandRootNode: ShouldExpandNodeInitially = (keyPath, data, level) =>
level === 0;
const defaultItemString: GetItemString = (type, data, itemType, itemString) => (
<span>
{itemType} {itemString}
</span>
);
const defaultLabelRenderer = ([label]: (string | number)[]) => (
<span>{label}:</span>
);
const noCustomNode = () => false;
const defaultLabelRenderer: LabelRenderer = ([label]) => <span>{label}:</span>;
const noCustomNode: IsCustomNode = () => false;
function checkLegacyTheming(theme: Theme | undefined, props: Props) {
const deprecatedStylingMethodsMap = {
getArrowStyle: 'arrow',
getListStyle: 'nestedNodeChildren',
getItemStringStyle: 'nestedNodeItemString',
getLabelStyle: 'label',
getValueStyle: 'valueText',
};
export function JSONTree({
data: value,
theme,
invertTheme: shouldInvertTheme,
keyPath = ['root'],
labelRenderer = defaultLabelRenderer,
valueRenderer = identity,
shouldExpandNodeInitially = expandRootNode,
hideRoot = false,
getItemString = defaultItemString,
postprocessValue = identity,
isCustomNode = noCustomNode,
collectionLimit = 50,
sortObjectKeys = false,
}: Props) {
const styling = useMemo(
() =>
createStylingFromTheme(shouldInvertTheme ? invertTheme(theme) : theme),
[theme, shouldInvertTheme]
);
const deprecatedStylingMethods = Object.keys(
deprecatedStylingMethodsMap
).filter((name) => props[name as keyof Props]);
if (deprecatedStylingMethods.length > 0) {
if (typeof theme === 'string') {
theme = {
extend: theme,
};
} else {
theme = { ...theme };
}
deprecatedStylingMethods.forEach((name) => {
// eslint-disable-next-line no-console
console.error(
`Styling method "${name}" is deprecated, use "theme" property instead`
);
(theme as StylingConfig)[
deprecatedStylingMethodsMap[
name as keyof typeof deprecatedStylingMethodsMap
]
] = ({ style }, ...args) => ({
style: {
...style,
...props[name as keyof Props](...args),
},
});
});
}
return theme;
return (
<ul {...styling('tree')}>
<JSONNode
keyPath={hideRoot ? [] : keyPath}
value={postprocessValue(value)}
isCustomNode={isCustomNode}
styling={styling}
labelRenderer={labelRenderer}
valueRenderer={valueRenderer}
shouldExpandNodeInitially={shouldExpandNodeInitially}
hideRoot={hideRoot}
getItemString={getItemString}
postprocessValue={postprocessValue}
collectionLimit={collectionLimit}
sortObjectKeys={sortObjectKeys}
/>
</ul>
);
}
function getStateFromProps(props: Props) {
let theme = checkLegacyTheming(props.theme, props);
if (props.invertTheme) {
theme = invertTheme(theme);
}
return {
styling: createStylingFromTheme(theme),
};
}
export class JSONTree extends React.Component<Props, State> {
static propTypes = {
data: PropTypes.any,
hideRoot: PropTypes.bool,
theme: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
invertTheme: PropTypes.bool,
keyPath: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
),
postprocessValue: PropTypes.func,
sortObjectKeys: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
};
static defaultProps = {
shouldExpandNode: expandRootNode,
hideRoot: false,
keyPath: ['root'],
getItemString: defaultItemString,
labelRenderer: defaultLabelRenderer,
valueRenderer: identity,
postprocessValue: identity,
isCustomNode: noCustomNode,
collectionLimit: 50,
invertTheme: true,
};
constructor(props: Props) {
super(props);
this.state = getStateFromProps(props);
}
UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (
['theme', 'invertTheme'].find(
(k) => nextProps[k as keyof Props] !== this.props[k as keyof Props]
)
) {
this.setState(getStateFromProps(nextProps));
}
}
shouldComponentUpdate(nextProps: Props) {
return !!Object.keys(nextProps).find((k) =>
k === 'keyPath'
? nextProps[k].join('/') !== this.props[k].join('/')
: nextProps[k as keyof Props] !== this.props[k as keyof Props]
);
}
render() {
const {
data: value,
keyPath,
postprocessValue,
hideRoot,
theme, // eslint-disable-line no-unused-vars
invertTheme: _, // eslint-disable-line no-unused-vars
...rest
} = this.props;
const { styling } = this.state;
return (
<ul {...styling('tree')}>
<JSONNode
{...{ postprocessValue, hideRoot, styling, ...rest }}
keyPath={hideRoot ? [] : keyPath}
value={postprocessValue(value)}
/>
</ul>
);
}
}
export { StylingValue };
export type {
Key,
KeyPath,
GetItemString,
LabelRenderer,
ValueRenderer,
ShouldExpandNodeInitially,
PostprocessValue,
IsCustomNode,
SortObjectKeys,
Styling,
CommonExternalProps,
} from './types';
export type { StylingValue };

View File

@ -1,81 +1,63 @@
import React from 'react';
import { StylingFunction } from 'react-base16-styling';
interface SharedCircularPropsPassedThroughJSONTree {
keyPath: (string | number)[];
labelRenderer: (
keyPath: (string | number)[],
nodeType: string,
expanded: boolean,
expandable: boolean
) => React.ReactNode;
}
interface SharedCircularPropsProvidedByJSONTree
extends SharedCircularPropsPassedThroughJSONTree {
styling: StylingFunction;
}
interface JSONValueNodeCircularPropsPassedThroughJSONTree {
valueRenderer: (
valueAsString: any,
value: any,
...keyPath: (string | number)[]
) => React.ReactNode;
}
export type JSONValueNodeCircularPropsProvidedByJSONNode =
SharedCircularPropsProvidedByJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree;
export type Key = string | number;
interface JSONNestedNodeCircularPropsPassedThroughJSONTree {
shouldExpandNode: (
keyPath: (string | number)[],
data: any,
level: number
) => boolean;
export type KeyPath = readonly (string | number)[];
export type GetItemString = (
nodeType: string,
data: unknown,
itemType: React.ReactNode,
itemString: string,
keyPath: KeyPath
) => React.ReactNode;
export type LabelRenderer = (
keyPath: KeyPath,
nodeType: string,
expanded: boolean,
expandable: boolean
) => React.ReactNode;
export type ValueRenderer = (
valueAsString: unknown,
value: unknown,
...keyPath: KeyPath
) => React.ReactNode;
export type ShouldExpandNodeInitially = (
keyPath: KeyPath,
data: unknown,
level: number
) => boolean;
export type PostprocessValue = (value: unknown) => unknown;
export type IsCustomNode = (value: unknown) => boolean;
export type SortObjectKeys = ((a: unknown, b: unknown) => number) | boolean;
export type Styling = StylingFunction;
export type CircularCache = unknown[];
export interface CommonExternalProps {
keyPath: KeyPath;
labelRenderer: LabelRenderer;
valueRenderer: ValueRenderer;
shouldExpandNodeInitially: ShouldExpandNodeInitially;
hideRoot: boolean;
getItemString: (
nodeType: string,
data: any,
itemType: React.ReactNode,
itemString: string,
keyPath: (string | number)[]
) => React.ReactNode;
postprocessValue: (value: any) => any;
isCustomNode: (value: any) => boolean;
getItemString: GetItemString;
postprocessValue: PostprocessValue;
isCustomNode: IsCustomNode;
collectionLimit: number;
sortObjectKeys?: ((a: any, b: any) => number) | boolean;
sortObjectKeys: SortObjectKeys;
}
export type CircularPropsPassedThroughJSONTree =
SharedCircularPropsPassedThroughJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONTree;
interface JSONNestedNodeCircularPropsPassedThroughJSONNode
extends JSONNestedNodeCircularPropsPassedThroughJSONTree {
circularCache?: any[];
isCircular?: boolean;
export interface CommonInternalProps extends CommonExternalProps {
styling: StylingFunction;
circularCache?: CircularCache;
level?: number;
isCircular?: boolean;
}
export type CircularPropsPassedThroughJSONNode =
SharedCircularPropsProvidedByJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONNode;
export interface JSONNestedNodeCircularPropsPassedThroughJSONNestedNode
extends JSONNestedNodeCircularPropsPassedThroughJSONNode {
circularCache: any[];
level: number;
}
export type CircularPropsPassedThroughJSONNestedNode =
SharedCircularPropsProvidedByJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONNestedNode;
export type CircularPropsPassedThroughRenderChildNodes =
SharedCircularPropsProvidedByJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONNestedNode;
export type CircularPropsPassedThroughItemRange =
SharedCircularPropsProvidedByJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONNestedNode;

View File

@ -1,5 +1,17 @@
# Change Log
## 2.2.1
### Patch Changes
- Updated dependencies [b323f77d]
- Updated dependencies [b323f77d]
- d3-state-visualizer@2.0.0
- @redux-devtools/chart-monitor@4.0.0
- @redux-devtools/inspector-monitor@3.0.2
- @redux-devtools/log-monitor@4.0.2
- @redux-devtools/rtk-query-monitor@3.1.1
## 2.2.0
### Minor Changes

View File

@ -6,7 +6,10 @@ module.exports = {
'\\.css$': '<rootDir>/test/__mocks__/styleMock.ts',
},
transform: {
'^.+\\.jsx?$': 'babel-jest',
'^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.test.json' }],
},
resolver: '<rootDir>/jestResolver.js',
transformIgnorePatterns: [
'node_modules/(?!.pnpm|d3|dateformat|delaunator|internmap|nanoid|robust-predicates|uuid)',
],
};

View File

@ -1,15 +0,0 @@
module.exports = (path, options) => {
return options.defaultResolver(path, {
...options,
packageFilter: (pkg) => {
if (pkg.name === 'nanoid') {
pkg.exports['.'].browser = pkg.exports['.'].require;
}
if (pkg.name === 'uuid' && pkg.version.startsWith('8.')) {
delete pkg.exports;
delete pkg.module;
}
return pkg;
},
});
};

View File

@ -1,6 +1,6 @@
{
"name": "@redux-devtools/app",
"version": "2.2.0",
"version": "2.2.1",
"description": "Redux DevTools app",
"homepage": "https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-app",
"bugs": {
@ -40,19 +40,19 @@
"prepublish": "pnpm run type-check && pnpm run lint && pnpm run test"
},
"dependencies": {
"@babel/runtime": "^7.20.6",
"@redux-devtools/chart-monitor": "^3.0.0",
"@babel/runtime": "^7.21.0",
"@redux-devtools/chart-monitor": "^4.0.0",
"@redux-devtools/core": "^3.13.0",
"@redux-devtools/inspector-monitor": "^3.0.0",
"@redux-devtools/inspector-monitor": "^3.0.2",
"@redux-devtools/inspector-monitor-test-tab": "^1.0.0",
"@redux-devtools/inspector-monitor-trace-tab": "^1.0.0",
"@redux-devtools/log-monitor": "^4.0.0",
"@redux-devtools/rtk-query-monitor": "^3.0.0",
"@redux-devtools/log-monitor": "^4.0.2",
"@redux-devtools/rtk-query-monitor": "^3.1.1",
"@redux-devtools/slider-monitor": "^4.0.0",
"@redux-devtools/ui": "^1.3.0",
"@reduxjs/toolkit": "^1.9.1",
"@reduxjs/toolkit": "^1.9.3",
"@types/prop-types": "^15.7.5",
"d3-state-visualizer": "^1.6.0",
"d3-state-visualizer": "^2.0.0",
"javascript-stringify": "^2.1.0",
"jsan": "^3.1.14",
"jsondiffpatch": "^0.4.1",
@ -63,55 +63,55 @@
"react-icons": "^4.7.1",
"react-is": "^18.2.0",
"react-redux": "^8.0.5",
"redux": "^4.2.0",
"redux": "^4.2.1",
"redux-persist": "^6.0.0",
"socketcluster-client": "^17.1.0"
"socketcluster-client": "^17.1.1"
},
"devDependencies": {
"@babel/cli": "^7.19.3",
"@babel/core": "^7.20.5",
"@babel/cli": "^7.21.0",
"@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/plugin-transform-runtime": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@rjsf/core": "^4.2.3",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@types/jest": "^29.2.4",
"@testing-library/react": "^14.0.0",
"@types/jest": "^29.4.0",
"@types/jsan": "^3.1.2",
"@types/json-schema": "^7.0.11",
"@types/lodash": "^4.14.191",
"@types/node": "^18.11.17",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@types/node": "^18.14.4",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/socketcluster-client": "^16.0.0",
"@types/styled-components": "^5.1.26",
"@types/testing-library__jest-dom": "^5.14.5",
"@types/webpack-env": "^1.18.0",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"babel-loader": "^9.1.0",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"babel-loader": "^9.1.2",
"cross-env": "^7.0.3",
"css-loader": "^6.7.3",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-jest": "^27.1.7",
"eslint-plugin-react": "^7.31.11",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"fork-ts-checker-webpack-plugin": "^7.2.14",
"fork-ts-checker-webpack-plugin": "^8.0.0",
"html-loader": "^4.2.0",
"html-webpack-plugin": "^5.5.0",
"jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
"jest": "^29.4.3",
"jest-environment-jsdom": "^29.4.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rimraf": "^3.0.2",
"rimraf": "^4.1.3",
"style-loader": "^3.3.1",
"styled-components": "^5.3.6",
"ts-jest": "^29.0.3",
"styled-components": "^5.3.8",
"ts-jest": "^29.0.5",
"ts-node": "^10.9.1",
"typescript": "~4.9.4",
"typescript": "~4.9.5",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"
@ -120,6 +120,6 @@
"@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0",
"@types/styled-components": "^5.1.26",
"react": "^16.3.0 || ^17.0.0 || ^18.0.0",
"styled-components": "^5.3.6"
"styled-components": "^5.3.8"
}
}

View File

@ -1,14 +1,17 @@
import React, { Component } from 'react';
import { connect, ResolveThunks } from 'react-redux';
import { ChartMonitor } from '@redux-devtools/chart-monitor';
import { NodeWithId } from 'd3-state-visualizer';
import type { HierarchyPointNode, Node } from 'd3-state-visualizer';
import { selectMonitorWithState } from '../../actions';
export function getPath(obj: NodeWithId, inspectedStatePath: string[]) {
export function getPath(
obj: HierarchyPointNode<Node>,
inspectedStatePath: string[]
) {
const parent = obj.parent;
if (!parent) return;
getPath(parent, inspectedStatePath);
let name = obj.name;
let name = obj.data.name;
const item = /.+\[(\d+)]/.exec(name);
if (item) name = item[1];
inspectedStatePath.push(name);
@ -20,7 +23,7 @@ type Props = DispatchProps;
class ChartMonitorWrapper extends Component<Props> {
static update = ChartMonitor.update;
onClickText = (data: NodeWithId) => {
onClickText = (data: HierarchyPointNode<Node>) => {
const inspectedStatePath: string[] = [];
getPath(data, inspectedStatePath);
this.props.selectMonitorWithState('InspectorMonitor', {

View File

@ -1,7 +1,8 @@
import React, { Component, RefCallback } from 'react';
import { connect, ResolveThunks } from 'react-redux';
import { withTheme } from 'styled-components';
import { InputOptions, NodeWithId, tree } from 'd3-state-visualizer';
import { tree } from 'd3-state-visualizer';
import type { HierarchyPointNode, Node, Options } from 'd3-state-visualizer';
import { getPath } from '../ChartMonitorWrapper';
import { updateMonitorState } from '../../../actions';
import { ThemeFromProvider } from '@redux-devtools/ui';
@ -54,12 +55,12 @@ class ChartTab extends Component<Props> {
this.renderChart(props.data as {} | null | undefined);
}
getChartTheme(theme: ThemeFromProvider): Partial<InputOptions> {
getChartTheme(theme: ThemeFromProvider): Partial<Options> {
return {
heightBetweenNodesCoeff: 1,
widthBetweenNodesCoeff: 1.3,
tooltipOptions: {
style: {
styles: {
color: theme.base06,
'background-color': theme.base01,
opacity: '0.9',
@ -69,29 +70,29 @@ class ChartTab extends Component<Props> {
offset: { left: 30, top: 10 },
indentationSize: 2,
},
style: {
chartStyles: {
width: '100%',
height: '100%',
node: {
colors: {
default: theme.base0B,
collapsed: theme.base0B,
parent: theme.base0E,
},
radius: 7,
} as unknown as string,
text: {
colors: {
default: theme.base0D,
hover: theme.base06,
},
} as unknown as string,
},
nodeStyleOptions: {
colors: {
default: theme.base0B,
collapsed: theme.base0B,
parent: theme.base0E,
},
radius: 7,
},
textStyleOptions: {
colors: {
default: theme.base0D,
hover: theme.base06,
},
},
onClickText: this.onClickText,
};
}
onClickText = (data: NodeWithId) => {
onClickText = (data: HierarchyPointNode<Node>) => {
const inspectedStatePath: string[] = [];
getPath(data, inspectedStatePath);
this.props.updateMonitorState({

View File

@ -1,5 +1,19 @@
# Change Log
## 4.0.0
### Major Changes
- b323f77d: Upgrade D3
- Split `style` option into `chartStyles`, `nodeStyleOptions`, `textStyleOptions`, and `linkStyles`.
- The shape of the argument passed to the `onClickText` option has been updated.
### Patch Changes
- Updated dependencies [b323f77d]
- d3-state-visualizer@2.0.0
## 3.0.1
### Patch Changes

View File

@ -47,16 +47,18 @@ Consult the [`DockMonitor` README](https://github.com/reduxjs/redux-devtools/tre
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>&nbsp;&nbsp;width: '100%', height: '100%', // i.e fullscreen for [`DockMonitor`](https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-dock-monitor)<br>&nbsp;&nbsp;text: {<br>&nbsp;&nbsp;&nbsp;&nbsp;colors: {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'default': `theme.base0D`,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hover: `theme.base06`<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;},<br>&nbsp;&nbsp;node: {<br>&nbsp;&nbsp;&nbsp;&nbsp;colors: {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'default': `theme.base0B`,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;collapsed: `theme.base0B`,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;parent: `theme.base0E`<br>&nbsp;&nbsp;&nbsp;&nbsp;},<br>&nbsp;&nbsp;&nbsp;&nbsp;radius: 7<br>&nbsp;&nbsp;}<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>&nbsp;&nbsp;disabled: false,<br>&nbsp;&nbsp;indentationSize: 2,<br>&nbsp;&nbsp;style: {<br>&nbsp;&nbsp;&nbsp;&nbsp;'background-color': `theme.base06`,<br>&nbsp;&nbsp;&nbsp;&nbsp;'opacity': '0.7',<br>&nbsp;&nbsp;&nbsp;&nbsp;'border-radius': '5px',<br>&nbsp;&nbsp;&nbsp;&nbsp;'padding': '5px'<br>&nbsp;&nbsp;}<br>}<br>[More info](https://github.com/reduxjs/redux-devtools/tree/master/packages/d3tooltip#api). |
| 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`. |
| `chartStyles` | {<br>&nbsp;&nbsp;width: '100%', height: '100%', // i.e fullscreen for [`DockMonitor`](https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-dock-monitor)<br>} |
| `textStyleOptions` | {<br>&nbsp;&nbsp;colors: {<br>&nbsp;&nbsp;&nbsp;&nbsp;default: `theme.base0D`,<br>&nbsp;&nbsp;&nbsp;&nbsp;hover: `theme.base06`,<br>&nbsp;&nbsp;},<br>} |
| `nodeStyleOptions` | {<br>&nbsp;&nbsp;colors: {<br>&nbsp;&nbsp;&nbsp;&nbsp;default: `theme.base0B`,<br>&nbsp;&nbsp;&nbsp;&nbsp;collapsed: `theme.base0B`,<br>&nbsp;&nbsp;&nbsp;&nbsp;parent: `theme.base0E`,<br>&nbsp;&nbsp;},<br>&nbsp;&nbsp;radius: 7,<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>&nbsp;&nbsp;disabled: false,<br>&nbsp;&nbsp;indentationSize: 2,<br>&nbsp;&nbsp;styles: {<br>&nbsp;&nbsp;&nbsp;&nbsp;'background-color': `theme.base06`,<br>&nbsp;&nbsp;&nbsp;&nbsp;'opacity': '0.7',<br>&nbsp;&nbsp;&nbsp;&nbsp;'border-radius': '5px',<br>&nbsp;&nbsp;&nbsp;&nbsp;'padding': '5px',<br>&nbsp;&nbsp;},<br>}<br>[More info](https://github.com/reduxjs/redux-devtools/tree/master/packages/d3tooltip#api). |
#### Redux DevTools props

View File

@ -1,6 +1,6 @@
{
"name": "@redux-devtools/chart-monitor",
"version": "3.0.1",
"version": "4.0.0",
"description": "Chart monitor for Redux DevTools",
"keywords": [
"redux",
@ -39,34 +39,32 @@
"prepublish": "pnpm run type-check && pnpm run lint"
},
"dependencies": {
"@babel/runtime": "^7.20.6",
"@types/prop-types": "^15.7.5",
"@babel/runtime": "^7.21.0",
"@types/redux-devtools-themes": "^1.0.0",
"d3-state-visualizer": "^1.6.0",
"deepmerge": "^4.2.2",
"prop-types": "^15.8.1",
"d3-state-visualizer": "^2.0.0",
"deepmerge": "^4.3.0",
"redux-devtools-themes": "^1.0.0"
},
"devDependencies": {
"@babel/cli": "^7.19.3",
"@babel/core": "^7.20.5",
"@babel/cli": "^7.21.0",
"@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/plugin-transform-runtime": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@redux-devtools/core": "^3.13.1",
"@types/react": "^18.0.26",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-react": "^7.31.11",
"@types/react": "^18.0.28",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"react": "^18.2.0",
"redux": "^4.2.0",
"rimraf": "^3.0.2",
"typescript": "~4.9.4"
"redux": "^4.2.1",
"rimraf": "^4.1.3",
"typescript": "~4.9.5"
},
"peerDependencies": {
"@redux-devtools/core": "^3.13.1",

View File

@ -1,6 +1,6 @@
import React, { Component, createRef } from 'react';
import PropTypes from 'prop-types';
import { tree, NodeWithId, Primitive } from 'd3-state-visualizer';
import { tree } from 'd3-state-visualizer';
import type { Options } from 'd3-state-visualizer';
import { Action, Dispatch } from 'redux';
import { LiftedAction, LiftedState } from '@redux-devtools/core';
import * as themes from 'redux-devtools-themes';
@ -12,7 +12,8 @@ const wrapperStyle = {
};
export interface Props<S, A extends Action<unknown>>
extends LiftedState<S, A, ChartMonitorState> {
extends LiftedState<S, A, ChartMonitorState>,
Options {
dispatch: Dispatch<LiftedAction<S, A, ChartMonitorState>>;
preserveScrollTop: boolean;
select: (state: S) => unknown;
@ -20,76 +21,10 @@ export interface Props<S, A extends Action<unknown>>
invertTheme: boolean;
state: S | null;
isSorted: boolean;
heightBetweenNodesCoeff: number;
widthBetweenNodesCoeff: number;
onClickText: (datum: NodeWithId) => void;
tooltipOptions: {
disabled: boolean;
offset: {
left: number;
top: number;
};
indentationSize: number;
style: { [key: string]: Primitive } | undefined;
};
style: { [key: string]: Primitive } | undefined;
defaultIsVisible?: boolean;
}
class Chart<S, A extends Action<unknown>> extends Component<Props<S, A>> {
static propTypes = {
state: PropTypes.object,
rootKeyName: PropTypes.string,
pushMethod: PropTypes.oneOf(['push', 'unshift']),
tree: PropTypes.shape({
name: PropTypes.string,
children: PropTypes.array,
}),
id: PropTypes.string,
style: PropTypes.shape({
node: PropTypes.shape({
colors: PropTypes.shape({
default: PropTypes.string,
parent: PropTypes.string,
collapsed: PropTypes.string,
}),
radius: PropTypes.number,
}),
text: PropTypes.shape({
colors: PropTypes.shape({
default: PropTypes.string,
hover: PropTypes.string,
}),
}),
link: PropTypes.object,
}),
size: PropTypes.number,
aspectRatio: PropTypes.number,
margin: PropTypes.shape({
top: PropTypes.number,
right: PropTypes.number,
bottom: PropTypes.number,
left: PropTypes.number,
}),
isSorted: PropTypes.bool,
heightBetweenNodesCoeff: PropTypes.number,
widthBetweenNodesCoeff: PropTypes.number,
transitionDuration: PropTypes.number,
onClickText: PropTypes.func,
tooltipOptions: PropTypes.shape({
disabled: PropTypes.bool,
left: PropTypes.number,
top: PropTypes.number,
offset: PropTypes.shape({
left: PropTypes.number,
top: PropTypes.number,
}),
indentationSize: PropTypes.number,
style: PropTypes.object,
}),
};
divRef = createRef<HTMLDivElement>();
// eslint-disable-next-line @typescript-eslint/ban-types
renderChart?: (state?: {} | null | undefined) => void;

View File

@ -1,5 +1,4 @@
import React, { CSSProperties, PureComponent } from 'react';
import PropTypes from 'prop-types';
import * as themes from 'redux-devtools-themes';
import {
ActionCreators,
@ -8,7 +7,7 @@ import {
} from '@redux-devtools/core';
import deepmerge from 'deepmerge';
import { Action, Dispatch } from 'redux';
import { NodeWithId, Primitive } from 'd3-state-visualizer';
import type { Options } from 'd3-state-visualizer';
import reducer, { ChartMonitorState } from './reducers';
import Chart, { Props } from './Chart';
@ -41,37 +40,14 @@ function invertColors(theme: themes.Base16Theme) {
}
export interface ChartMonitorProps<S, A extends Action<unknown>>
extends LiftedState<S, A, ChartMonitorState> {
extends LiftedState<S, A, ChartMonitorState>,
Options {
dispatch: Dispatch<LiftedAction<S, A, ChartMonitorState>>;
preserveScrollTop: boolean;
select: (state: S) => unknown;
theme: keyof typeof themes | themes.Base16Theme;
invertTheme: boolean;
state: S | null;
isSorted: boolean;
heightBetweenNodesCoeff: number;
widthBetweenNodesCoeff: number;
onClickText: (datum: NodeWithId) => void;
tooltipOptions: unknown;
style: {
width: number;
height: number;
node: {
colors: {
default: string;
collapsed: string;
parent: string;
};
radius: number;
};
text: {
colors: {
default: string;
hover: string;
};
};
};
defaultIsVisible?: boolean;
}
@ -80,23 +56,6 @@ class ChartMonitor<S, A extends Action<unknown>> extends PureComponent<
> {
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 = {
select: (state: unknown) => state,
theme: 'nicinabox',
@ -140,45 +99,10 @@ class ChartMonitor<S, A extends Action<unknown>> extends PureComponent<
return invertTheme ? invertColors(themes.nicinabox) : themes.nicinabox;
}
getChartStyle() {
const theme = this.getTheme();
return {
width: '100%',
height: '100%',
node: {
colors: {
default: theme.base0B,
collapsed: theme.base0B,
parent: theme.base0E,
},
radius: 7,
},
text: {
colors: {
default: theme.base0D,
hover: theme.base06,
},
},
};
}
getChartOptions(props = this.props): Props<S, A> {
const { computedStates } = props;
const theme = this.getTheme();
const tooltipOptions = {
disabled: false,
offset: { left: 30, top: 10 },
indentationSize: 2,
style: {
'background-color': theme.base06,
opacity: '0.7',
'border-radius': '5px',
padding: '5px',
},
};
const defaultOptions = {
state: computedStates.length
? computedStates[props.currentStateIndex].state
@ -186,10 +110,35 @@ class ChartMonitor<S, A extends Action<unknown>> extends PureComponent<
isSorted: false,
heightBetweenNodesCoeff: 1,
widthBetweenNodesCoeff: 1.3,
tooltipOptions,
style: this.getChartStyle() as unknown as
| { [key: string]: Primitive }
| undefined,
tooltipOptions: {
disabled: false,
offset: { left: 30, top: 10 },
indentationSize: 2,
styles: {
'background-color': theme.base06,
opacity: '0.7',
'border-radius': '5px',
padding: '5px',
},
},
chartStyles: {
width: '100%',
height: '100%',
},
nodeStyleOptions: {
colors: {
default: theme.base0B,
collapsed: theme.base0B,
parent: theme.base0E,
},
radius: 7,
},
textStyleOptions: {
colors: {
default: theme.base0D,
hover: theme.base06,
},
},
};
return deepmerge(defaultOptions, props);
@ -198,10 +147,9 @@ class ChartMonitor<S, A extends Action<unknown>> extends PureComponent<
render() {
const theme = this.getTheme();
const ChartAsAny = Chart as any;
return (
<div style={{ ...styles.container, backgroundColor: theme.base07 }}>
<ChartAsAny {...this.getChartOptions()} />
<Chart {...this.getChartOptions()} />
</div>
);
}

View File

@ -1,5 +1,16 @@
# Change Log
## 2.0.0
### Major Changes
- 7e129988: Convert @redux-devtools/cli to ESM. Please [read this](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c) for more info about ESM.
Update supported Node versions from `>=14.15.0` to `^14.13.1 || ^16.13.0 || >=18.12.0`.
### Patch Changes
- a7729dae: Updates `--open` flag to respect protocol and host when provided
## 1.0.7
### Patch Changes

View File

@ -9,7 +9,11 @@ function createWindow() {
height: 600,
});
mainWindow.loadURL('http://localhost:' + (argv.port ? argv.port : 8000));
const port = argv.port ? argv.port : 8000;
const host = argv.host ? argv.host : 'localhost';
const protocol = argv.protocol ? argv.protocol : 'http';
mainWindow.loadURL(protocol + '://' + host + ':' + port);
}
app.whenReady().then(() => {

View File

@ -1,3 +1,3 @@
#! /usr/bin/env node
require('../dist/bin/redux-devtools.js');
import '../dist/bin/redux-devtools.js';

View File

@ -1,4 +1,4 @@
module.exports = {
export default {
preset: 'ts-jest',
transform: {
'^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.test.json' }],

View File

@ -1,6 +1,6 @@
{
"name": "@redux-devtools/cli",
"version": "1.0.7",
"version": "2.0.0",
"description": "CLI for remote debugging with Redux DevTools.",
"homepage": "https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-cli",
"bugs": {
@ -16,6 +16,7 @@
"index.js",
"defaultDbOptions.json"
],
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"bin": {
@ -37,61 +38,61 @@
"prepublish": "pnpm run type-check && pnpm run lint && pnpm run test"
},
"engines": {
"node": ">=14.15.0"
"node": "^14.13.1 || ^16.13.0 || >= 18.12.0"
},
"dependencies": {
"@apollo/server": "^4.4.0",
"@redux-devtools/app": "^2.1.3",
"@types/react": "^18.0.26",
"apollo-server-express": "^3.11.1",
"body-parser": "^1.20.1",
"chalk": "^4.1.2",
"@types/react": "^18.0.28",
"body-parser": "^1.20.2",
"chalk": "^5.2.0",
"cors": "^2.8.5",
"cross-spawn": "^7.0.3",
"electron": "^22.0.0",
"electron": "^23.1.1",
"express": "^4.18.2",
"getport": "^0.1.0",
"get-port": "^6.1.2",
"graphql": "^16.6.0",
"knex": "^2.3.0",
"lodash": "^4.17.21",
"minimist": "^1.2.7",
"knex": "^2.4.2",
"lodash-es": "^4.17.21",
"minimist": "^1.2.8",
"morgan": "^1.10.0",
"open": "^8.4.0",
"open": "^8.4.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-is": "^18.2.0",
"semver": "^7.3.8",
"socketcluster-server": "^17.2.0",
"socketcluster-server": "^17.3.1",
"sqlite3": "^5.1.4",
"styled-components": "^5.3.6",
"styled-components": "^5.3.8",
"uuid": "^9.0.0"
},
"devDependencies": {
"@types/body-parser": "^1.19.2",
"@types/cors": "^2.8.13",
"@types/cross-spawn": "^6.0.2",
"@types/express": "^4.17.15",
"@types/jest": "^29.2.4",
"@types/lodash": "^4.14.191",
"@types/express": "^4.17.17",
"@types/jest": "^29.4.0",
"@types/lodash-es": "^4.17.6",
"@types/minimist": "^1.2.2",
"@types/morgan": "^1.9.3",
"@types/node": "^18.11.17",
"@types/morgan": "^1.9.4",
"@types/node": "^18.14.4",
"@types/semver": "^7.3.13",
"@types/socketcluster-client": "^16.0.0",
"@types/socketcluster-server": "^16.1.0",
"@types/socketcluster-server": "^17.3.0",
"@types/styled-components": "^5.1.26",
"@types/supertest": "^2.0.12",
"@types/uuid": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-jest": "^27.1.7",
"jest": "^29.3.1",
"@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.2.1",
"jest": "^29.4.3",
"ncp": "^2.0.0",
"rimraf": "^3.0.2",
"socketcluster-client": "^17.1.0",
"rimraf": "^4.1.3",
"socketcluster-client": "^17.1.1",
"supertest": "^6.3.3",
"ts-jest": "^29.0.3",
"typescript": "~4.9.4"
"ts-jest": "^29.0.5",
"typescript": "~4.9.5"
}
}

View File

@ -1,9 +1,10 @@
import fs from 'fs';
import { Store } from '../store';
import type { Store } from '../store.js';
export const schema = fs
.readFileSync(require.resolve('./schema_def.graphql'))
.toString();
export const schema = fs.readFileSync(
new URL('./schema_def.graphql', import.meta.url),
'utf8'
);
export const resolvers = {
Query: {

View File

@ -1,7 +1,7 @@
import fs from 'fs';
import path from 'path';
import semver from 'semver';
import { Options } from '../options';
import type { Options } from '../options.js';
const name = '@redux-devtools/cli';
const startFlag = '/* ' + name + ' start */';
@ -56,7 +56,7 @@ export function inject(
startFlag,
' require("' + name + '")(' + JSON.stringify(options) + ')',
' .then(_remotedev =>',
' _remotedev.on("ready", () => {',
' _remotedev.ready.then(() => {',
' if (!_remotedev.portAlreadyUsed) console.log("-".repeat(80));',
' ' + serverFlag,
' })',

View File

@ -1,16 +1,30 @@
import open from 'open';
import path from 'path';
import { fileURLToPath } from 'url';
import { createRequire } from 'module';
import spawn from 'cross-spawn';
import { Options } from '../options';
import type { Options } from '../options.js';
const require = createRequire(import.meta.url);
export default async function openApp(app: true | string, options: Options) {
if (app === true || app === 'electron') {
try {
const port = options.port ? `--port=${options.port}` : '';
const host = options.host ? `--host=${options.host}` : '';
const protocol = options.protocol ? `--protocol=${options.protocol}` : '';
// eslint-disable-next-line @typescript-eslint/no-var-requires
spawn.sync(require('electron') as string, [
path.join(__dirname, '..', '..', 'app'),
spawn(require('electron') as string, [
path.join(
path.dirname(fileURLToPath(import.meta.url)),
'..',
'..',
'app'
),
port,
host,
protocol,
]);
} catch (error) {
/* eslint-disable no-console */
@ -33,7 +47,7 @@ export default async function openApp(app: true | string, options: Options) {
}
await open(
`http://localhost:${options.port}/`,
`${options.protocol}://${options.host ?? 'localhost'}:${options.port}/`,
app !== 'browser' ? { app: { name: app } } : undefined
);
}

View File

@ -3,10 +3,10 @@ import fs from 'fs';
import path from 'path';
import parseArgs from 'minimist';
import chalk from 'chalk';
import * as injectServer from './injectServer';
import getOptions from '../options';
import server from '../index';
import openApp from './openApp';
import * as injectServer from './injectServer.js';
import getOptions from '../options.js';
import server from '../index.js';
import openApp from './openApp.js';
const argv = parseArgs(process.argv.slice(2));
@ -87,10 +87,8 @@ if (argv.injectserver) {
);
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
server(argv).then(async function (r) {
if (argv.open && argv.open !== 'false') {
await r.listener('ready').once();
await openApp(argv.open as string, options);
}
});
const response = await server(argv);
if (argv.open && argv.open !== 'false') {
await response.ready;
await openApp(argv.open as string, options);
}

View File

@ -1,23 +1,37 @@
import path from 'path';
import knexModule, { Knex } from 'knex';
import { fileURLToPath } from 'url';
import knex from 'knex';
import type { Knex } from 'knex';
import { AGServer } from 'socketcluster-server';
// eslint-disable-next-line @typescript-eslint/ban-types
type KnexFunction = <TRecord extends {} = any, TResult = unknown[]>(
config: Knex.Config | string
) => Knex<TRecord, TResult>;
export default function connector(options: AGServer.AGServerOptions) {
const dbOptions = options.dbOptions as Knex.Config;
dbOptions.useNullAsDefault = true;
if (!(dbOptions as any).migrate) {
return knexModule(dbOptions);
return (knex as unknown as KnexFunction)(dbOptions);
}
dbOptions.migrations = { directory: path.resolve(__dirname, 'migrations') };
dbOptions.seeds = { directory: path.resolve(__dirname, 'seeds') };
const knex = knexModule(dbOptions);
dbOptions.migrations = {
directory: path.join(
path.dirname(fileURLToPath(import.meta.url)),
'migrations'
),
};
dbOptions.seeds = {
directory: path.join(path.dirname(fileURLToPath(import.meta.url)), 'seeds'),
};
const knexInstance = (knex as unknown as KnexFunction)(dbOptions);
/* eslint-disable no-console */
knex.migrate
knexInstance.migrate
.latest({ loadExtensions: ['.js'] })
.then(function () {
return knex.seed.run({ loadExtensions: ['.js'] });
return knexInstance.seed.run({ loadExtensions: ['.js'] });
})
.then(function () {
console.log(' \x1b[0;32m[Done]\x1b[0m Migrations are finished\n');
@ -27,5 +41,5 @@ export default function connector(options: AGServer.AGServerOptions) {
});
/* eslint-enable no-console */
return knex;
return knexInstance;
}

View File

@ -1,6 +0,0 @@
declare module 'getport' {
export default function getport(
start: number,
callback: (e: Error | undefined, port: number) => void
): void;
}

View File

@ -1,23 +1,19 @@
import express from 'express';
import http from 'http';
import getPort from 'getport';
import getPort from 'get-port';
import socketClusterServer from 'socketcluster-server';
import getOptions, { Options } from './options';
import routes from './routes';
import createStore from './store';
import getOptions from './options.js';
import routes from './routes.js';
import createStore from './store.js';
// var LOG_LEVEL_NONE = 0;
const LOG_LEVEL_ERROR = 1;
// const LOG_LEVEL_NONE = 0;
// const LOG_LEVEL_ERROR = 1;
const LOG_LEVEL_WARN = 2;
const LOG_LEVEL_INFO = 3;
export interface ExtendedOptions extends Options {
allowClientPublish: boolean;
}
export default function (argv: { [arg: string]: any }): Promise<{
export default async function (argv: { [arg: string]: any }): Promise<{
portAlreadyUsed?: boolean;
listener: (eventName: 'ready') => { once(): Promise<void> };
ready: Promise<void>;
}> {
const options = Object.assign(getOptions(argv), {
allowClientPublish: false,
@ -25,131 +21,117 @@ export default function (argv: { [arg: string]: any }): Promise<{
const port = options.port;
const logLevel =
options.logLevel === undefined ? LOG_LEVEL_INFO : options.logLevel;
return new Promise(function (resolve) {
// Check port already used
getPort(port, function (err, p) {
/* eslint-disable no-console */
if (err) {
if (logLevel >= LOG_LEVEL_ERROR) {
console.error(err);
}
return;
}
if (port !== p) {
if (logLevel >= LOG_LEVEL_WARN) {
console.log(`[ReduxDevTools] Server port ${port} is already used.`);
}
resolve({
portAlreadyUsed: true,
listener: function (eventName: 'ready') {
return {
once() {
return Promise.resolve();
},
};
},
});
} else {
if (logLevel >= LOG_LEVEL_INFO) {
console.log('[ReduxDevTools] Start server...');
console.log('-'.repeat(80) + '\n');
}
const httpServer = http.createServer();
const agServer = socketClusterServer.attach(httpServer, options);
// Check port already used
const p = await getPort({ port });
if (port !== p) {
if (logLevel >= LOG_LEVEL_WARN) {
console.log(`[ReduxDevTools] Server port ${port} is already used.`);
}
return {
portAlreadyUsed: true,
ready: Promise.resolve(),
};
}
const app = express();
httpServer.on('request', app);
const store = createStore(options);
app.use(routes(options, store, agServer));
if (logLevel >= LOG_LEVEL_INFO) {
console.log('[ReduxDevTools] Start server...');
console.log('-'.repeat(80) + '\n');
}
const httpServer = http.createServer();
const agServer = socketClusterServer.attach(httpServer, options);
agServer.setMiddleware(
agServer.MIDDLEWARE_INBOUND,
// eslint-disable-next-line @typescript-eslint/no-misused-promises
async (middlewareStream) => {
for await (const action of middlewareStream) {
if (action.type === action.TRANSMIT) {
const channel = action.receiver;
const data = action.data;
if (
channel.substring(0, 3) === 'sc-' ||
channel === 'respond' ||
channel === 'log'
) {
void agServer.exchange.transmitPublish(channel, data);
} else if (channel === 'log-noid') {
void agServer.exchange.transmitPublish('log', {
id: action.socket.id,
data: data,
});
}
} else if (action.type === action.SUBSCRIBE) {
if (action.channel === 'report') {
store
.list()
.then(function (data) {
void agServer.exchange.transmitPublish('report', {
type: 'list',
data: data,
});
})
.catch(function (error) {
console.error(error); // eslint-disable-line no-console
});
}
}
action.allow();
}
const app = express();
httpServer.on('request', app);
const store = createStore(options);
app.use(routes(options, store, agServer));
agServer.setMiddleware(
agServer.MIDDLEWARE_INBOUND,
// eslint-disable-next-line @typescript-eslint/no-misused-promises
async (middlewareStream) => {
for await (const action of middlewareStream) {
if (action.type === action.TRANSMIT) {
const channel = action.receiver;
const data = action.data;
if (
channel.substring(0, 3) === 'sc-' ||
channel === 'respond' ||
channel === 'log'
) {
void agServer.exchange.transmitPublish(channel, data);
} else if (channel === 'log-noid') {
void agServer.exchange.transmitPublish('log', {
id: action.socket.id,
data: data,
});
}
);
void (async () => {
for await (const { socket } of agServer.listener('connection')) {
let channelToWatch: string, channelToEmit: string;
void (async () => {
for await (const request of socket.procedure('login')) {
const credentials = request.data;
if (credentials === 'master') {
channelToWatch = 'respond';
channelToEmit = 'log';
} else {
channelToWatch = 'log';
channelToEmit = 'respond';
}
request.end(channelToWatch);
}
})();
void (async () => {
for await (const request of socket.procedure('getReport')) {
const id = request.data as string;
store
.get(id)
.then(function (data) {
request.end(data);
})
.catch(function (error) {
console.error(error); // eslint-disable-line no-console
});
}
})();
void (async () => {
for await (const data of socket.listener('disconnect')) {
const channel = agServer.exchange.channel('sc-' + socket.id);
channel.unsubscribe();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
void agServer.exchange.transmitPublish(channelToEmit!, {
id: socket.id,
type: 'DISCONNECTED',
} else if (action.type === action.SUBSCRIBE) {
if (action.channel === 'report') {
store
.list()
.then(function (data) {
void agServer.exchange.transmitPublish('report', {
type: 'list',
data: data,
});
}
})();
})
.catch(function (error) {
console.error(error); // eslint-disable-line no-console
});
}
})();
httpServer.listen(options.port);
// @ts-expect-error Shouldn't there be a 'ready' event?
resolve(agServer);
}
action.allow();
}
/* eslint-enable no-console */
});
});
}
);
void (async () => {
for await (const { socket } of agServer.listener('connection')) {
let channelToWatch: string, channelToEmit: string;
void (async () => {
for await (const request of socket.procedure('login')) {
const credentials = request.data;
if (credentials === 'master') {
channelToWatch = 'respond';
channelToEmit = 'log';
} else {
channelToWatch = 'log';
channelToEmit = 'respond';
}
request.end(channelToWatch);
}
})();
void (async () => {
for await (const request of socket.procedure('getReport')) {
const id = request.data as string;
store
.get(id)
.then(function (data) {
request.end(data);
})
.catch(function (error) {
console.error(error); // eslint-disable-line no-console
});
}
})();
void (async () => {
for await (const data of socket.listener('disconnect')) {
const channel = agServer.exchange.channel('sc-' + socket.id);
channel.unsubscribe();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
void agServer.exchange.transmitPublish(channelToEmit!, {
id: socket.id,
type: 'DISCONNECTED',
});
}
})();
}
})();
httpServer.listen(options.port);
return {
ready: (async () => {
await agServer.listener('ready' as 'error').once();
})(),
};
}

View File

@ -1,13 +0,0 @@
import { ApolloServer } from 'apollo-server-express';
import { schema, resolvers } from '../api/schema';
import { Store } from '../store';
export default function (store: Store) {
return new ApolloServer({
typeDefs: schema,
resolvers,
context: {
store: store,
},
});
}

View File

@ -1,4 +1,4 @@
import path from 'path';
import fs from 'fs';
interface ProtocolOptions {
key: string | undefined;
@ -31,9 +31,14 @@ export interface Options {
export default function getOptions(argv: { [arg: string]: any }): Options {
let dbOptions = argv.dbOptions;
if (typeof dbOptions === 'string') {
dbOptions = require(path.resolve(process.cwd(), argv.dbOptions as string));
dbOptions = JSON.parse(fs.readFileSync(dbOptions, 'utf8'));
} else if (typeof dbOptions === 'undefined') {
dbOptions = require('../defaultDbOptions.json');
dbOptions = JSON.parse(
fs.readFileSync(
new URL('../defaultDbOptions.json', import.meta.url),
'utf8'
)
);
}
return {

View File

@ -1,4 +1,6 @@
import path from 'path';
import { createRequire } from 'module';
import { fileURLToPath } from 'url';
import express from 'express';
import type { Router } from 'express';
import morgan from 'morgan';
@ -6,12 +8,15 @@ import * as http from 'http';
import bodyParser from 'body-parser';
import cors from 'cors';
import { AGServer } from 'socketcluster-server';
import { ApolloServer } from 'apollo-server-express';
import { AddData, ReportBaseFields, Store } from './store';
import { resolvers, schema } from './api/schema';
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import type { AddData, ReportBaseFields, Store } from './store.js';
import { resolvers, schema } from './api/schema.js';
const app = express.Router();
const require = createRequire(import.meta.url);
function serveUmdModule(name: string) {
app.use(
express.static(
@ -20,6 +25,10 @@ function serveUmdModule(name: string) {
);
}
interface Context {
store?: Store;
}
function routes(
options: AGServer.AGServerOptions,
store: Store,
@ -42,19 +51,19 @@ function routes(
else app.use(morgan('combined'));
}
const server = new ApolloServer({
const server = new ApolloServer<Context>({
typeDefs: schema,
resolvers,
context: {
store: store,
},
});
server
.start()
.then(() => {
server.applyMiddleware({ app } as {
app: express.Application;
});
app.use(
'/graphql',
cors<cors.CorsRequest>(),
bodyParser.json(),
expressMiddleware(server, { context: () => Promise.resolve({ store }) })
);
})
.catch((error) => {
console.error(error); // eslint-disable-line no-console
@ -69,7 +78,12 @@ function routes(
res.send(`reduxDevToolsPort = ${options.port}`);
});
app.get('*', function (req, res) {
res.sendFile(path.join(__dirname, '../app/index.html'));
res.sendFile(
path.join(
path.dirname(fileURLToPath(import.meta.url)),
'../app/index.html'
)
);
});
app.use(cors({ methods: 'POST' }));

View File

@ -1,8 +1,8 @@
import { v4 as uuidV4 } from 'uuid';
import pick from 'lodash/pick';
import { pick } from 'lodash-es';
import { AGServer } from 'socketcluster-server';
import { Knex } from 'knex';
import connector from './db/connector';
import connector from './db/connector.js';
const reports = 'remotedev_reports';
// var payloads = 'remotedev_payloads';

View File

@ -1,7 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "CommonJS",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist"
},
"include": ["src"]

View File

@ -41,33 +41,33 @@
"prepublish": "pnpm run type-check && pnpm run lint"
},
"dependencies": {
"@babel/runtime": "^7.20.6",
"@babel/runtime": "^7.21.0",
"@types/prop-types": "^15.7.5",
"parse-key": "^0.2.1",
"prop-types": "^15.8.1",
"react-dock": "^0.6.0"
},
"devDependencies": {
"@babel/cli": "^7.19.3",
"@babel/core": "^7.20.5",
"@babel/cli": "^7.21.0",
"@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/plugin-transform-runtime": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@redux-devtools/core": "^3.13.1",
"@types/parse-key": "^0.2.0",
"@types/react": "^18.0.26",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-react": "^7.31.11",
"@types/react": "^18.0.28",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"react": "^18.2.0",
"redux": "^4.2.0",
"rimraf": "^3.0.2",
"typescript": "~4.9.4"
"redux": "^4.2.1",
"rimraf": "^4.1.3",
"typescript": "~4.9.5"
},
"peerDependencies": {
"@redux-devtools/core": "^3.13.1",

View File

@ -1,5 +1,17 @@
# Change Log
## 3.2.5
### Patch Changes
- a0716740: Fix types for other exports from `@redux-devtools/extension`.
## 3.2.4
### Patch Changes
- 07456db4: Propagate store enhancer generic type when using composeWithDevTools
## 3.2.3
### Patch Changes

View File

@ -1,6 +1,6 @@
{
"name": "@redux-devtools/extension",
"version": "3.2.3",
"version": "3.2.5",
"description": "Wrappers for Redux DevTools Extension.",
"homepage": "https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-extension",
"license": "MIT",
@ -29,23 +29,23 @@
"prepublish": "pnpm run type-check && pnpm run lint"
},
"dependencies": {
"@babel/runtime": "^7.20.6",
"immutable": "^4.1.0"
"@babel/runtime": "^7.21.0",
"immutable": "^4.2.4"
},
"devDependencies": {
"@babel/cli": "^7.19.3",
"@babel/core": "^7.20.5",
"@babel/cli": "^7.21.0",
"@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/plugin-transform-runtime": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.18.6",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"redux": "^4.2.0",
"rimraf": "^3.0.2",
"typescript": "~4.9.4"
"@babel/preset-typescript": "^7.21.0",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"redux": "^4.2.1",
"rimraf": "^4.1.3",
"typescript": "~4.9.5"
},
"peerDependencies": {
"redux": "^3.1.0 || ^4.0.0"

View File

@ -1,5 +1,11 @@
import { compose, StoreEnhancer } from 'redux';
import { Config, EnhancerOptions } from './index';
import { compose } from 'redux';
import type { StoreEnhancer } from 'redux';
import type {
Config,
EnhancerOptions,
InferComposedStoreExt,
ReduxDevtoolsExtensionCompose,
} from './index';
declare const process: {
env: {
@ -9,15 +15,21 @@ declare const process: {
function extensionComposeStub(
config: Config
): (...funcs: StoreEnhancer[]) => StoreEnhancer;
function extensionComposeStub(...funcs: StoreEnhancer[]): StoreEnhancer;
function extensionComposeStub(...funcs: [Config] | StoreEnhancer[]) {
): <StoreEnhancers extends readonly StoreEnhancer<unknown>[]>(
...funcs: StoreEnhancers
) => StoreEnhancer<InferComposedStoreExt<StoreEnhancers>>;
function extensionComposeStub<
StoreEnhancers extends readonly StoreEnhancer<unknown>[]
>(
...funcs: StoreEnhancers
): StoreEnhancer<InferComposedStoreExt<StoreEnhancers>>;
function extensionComposeStub(...funcs: [Config] | StoreEnhancer<unknown>[]) {
if (funcs.length === 0) return undefined;
if (typeof funcs[0] === 'object') return compose;
return compose(...(funcs as StoreEnhancer[]));
return compose(...(funcs as StoreEnhancer<unknown>[]));
}
export const composeWithDevTools =
export const composeWithDevTools: ReduxDevtoolsExtensionCompose =
process.env.NODE_ENV !== 'production' &&
typeof window !== 'undefined' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__

View File

@ -1,5 +1,6 @@
import Immutable from 'immutable';
import { Action, ActionCreator, compose, StoreEnhancer } from 'redux';
import type Immutable from 'immutable';
import { compose } from 'redux';
import type { Action, ActionCreator, StoreEnhancer } from 'redux';
export interface EnhancerOptions {
/**
@ -227,9 +228,22 @@ interface ReduxDevtoolsExtension {
connect: (preConfig: Config) => ConnectResponse;
}
export type InferComposedStoreExt<StoreEnhancers> = StoreEnhancers extends [
infer HeadStoreEnhancer,
...infer RestStoreEnhancers
]
? HeadStoreEnhancer extends StoreEnhancer<infer StoreExt>
? StoreExt & InferComposedStoreExt<RestStoreEnhancers>
: never
: unknown;
export interface ReduxDevtoolsExtensionCompose {
(config: Config): (...funcs: StoreEnhancer[]) => StoreEnhancer;
(...funcs: StoreEnhancer[]): StoreEnhancer;
(config: Config): <StoreEnhancers extends readonly StoreEnhancer<unknown>[]>(
...funcs: StoreEnhancers
) => StoreEnhancer<InferComposedStoreExt<StoreEnhancers>>;
<StoreEnhancers extends readonly StoreEnhancer<unknown>[]>(
...funcs: StoreEnhancers
): StoreEnhancer<InferComposedStoreExt<StoreEnhancers>>;
}
declare global {
@ -241,12 +255,18 @@ declare global {
function extensionComposeStub(
config: Config
): (...funcs: StoreEnhancer[]) => StoreEnhancer;
function extensionComposeStub(...funcs: StoreEnhancer[]): StoreEnhancer;
function extensionComposeStub(...funcs: [Config] | StoreEnhancer[]) {
): <StoreEnhancers extends readonly StoreEnhancer<unknown>[]>(
...funcs: StoreEnhancers
) => StoreEnhancer<InferComposedStoreExt<StoreEnhancers>>;
function extensionComposeStub<
StoreEnhancers extends readonly StoreEnhancer<unknown>[]
>(
...funcs: StoreEnhancers
): StoreEnhancer<InferComposedStoreExt<StoreEnhancers>>;
function extensionComposeStub(...funcs: [Config] | StoreEnhancer<unknown>[]) {
if (funcs.length === 0) return undefined;
if (typeof funcs[0] === 'object') return compose;
return compose(...(funcs as StoreEnhancer[]));
return compose(...(funcs as StoreEnhancer<unknown>[]));
}
export const composeWithDevTools: ReduxDevtoolsExtensionCompose =

View File

@ -1,13 +1,13 @@
import assign from './utils/assign';
import {
import { compose } from 'redux';
import type {
Action,
compose,
Dispatch,
PreloadedState,
Reducer,
StoreEnhancer,
} from 'redux';
import { Config, EnhancerOptions } from './index';
import type { Config, EnhancerOptions, InferComposedStoreExt } from './index';
function enhancer(options?: EnhancerOptions): StoreEnhancer {
const config: Config = options || {};
@ -40,20 +40,28 @@ function enhancer(options?: EnhancerOptions): StoreEnhancer {
}
function composeWithEnhancer(config?: EnhancerOptions) {
return function (...funcs: StoreEnhancer[]) {
return function (...funcs: StoreEnhancer<unknown>[]) {
return compose(compose(...funcs), enhancer(config));
};
}
export function composeWithDevTools(
config: Config
): (...funcs: StoreEnhancer[]) => StoreEnhancer;
export function composeWithDevTools(...funcs: StoreEnhancer[]): StoreEnhancer;
export function composeWithDevTools(...funcs: [Config] | StoreEnhancer[]) {
): <StoreEnhancers extends readonly StoreEnhancer<unknown>[]>(
...funcs: StoreEnhancers
) => StoreEnhancer<InferComposedStoreExt<StoreEnhancers>>;
export function composeWithDevTools<
StoreEnhancers extends readonly StoreEnhancer<unknown>[]
>(
...funcs: StoreEnhancers
): StoreEnhancer<InferComposedStoreExt<StoreEnhancers>>;
export function composeWithDevTools(
...funcs: [Config] | StoreEnhancer<unknown>[]
) {
if (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__) {
if (funcs.length === 0) return enhancer();
if (typeof funcs[0] === 'object') return composeWithEnhancer(funcs[0]);
return composeWithEnhancer()(...(funcs as StoreEnhancer[]));
return composeWithEnhancer()(...(funcs as StoreEnhancer<unknown>[]));
}
if (funcs.length === 0) return undefined;

View File

@ -1,6 +1,12 @@
import { compose, StoreEnhancer } from 'redux';
import { compose } from 'redux';
import type { StoreEnhancer } from 'redux';
import * as logOnly from './logOnly';
import { Config, EnhancerOptions } from './index';
import type {
Config,
EnhancerOptions,
InferComposedStoreExt,
ReduxDevtoolsExtensionCompose,
} from './index';
declare const process: {
env: {
@ -10,15 +16,21 @@ declare const process: {
function extensionComposeStub(
config: Config
): (...funcs: StoreEnhancer[]) => StoreEnhancer;
function extensionComposeStub(...funcs: StoreEnhancer[]): StoreEnhancer;
function extensionComposeStub(...funcs: [Config] | StoreEnhancer[]) {
): <StoreEnhancers extends readonly StoreEnhancer<unknown>[]>(
...funcs: StoreEnhancers
) => StoreEnhancer<InferComposedStoreExt<StoreEnhancers>>;
function extensionComposeStub<
StoreEnhancers extends readonly StoreEnhancer<unknown>[]
>(
...funcs: StoreEnhancers
): StoreEnhancer<InferComposedStoreExt<StoreEnhancers>>;
function extensionComposeStub(...funcs: [Config] | StoreEnhancer<unknown>[]) {
if (funcs.length === 0) return undefined;
if (typeof funcs[0] === 'object') return compose;
return compose(...(funcs as StoreEnhancer[]));
return compose(...(funcs as StoreEnhancer<unknown>[]));
}
export const composeWithDevTools =
export const composeWithDevTools: ReduxDevtoolsExtensionCompose =
process.env.NODE_ENV === 'production'
? logOnly.composeWithDevTools
: typeof window !== 'undefined' &&

View File

@ -15,43 +15,43 @@
"@redux-devtools/inspector-monitor": "^3.0.0",
"@redux-devtools/inspector-monitor-test-tab": "^1.0.0",
"@redux-devtools/ui": "^1.3.0",
"immutable": "^4.1.0",
"immutable": "^4.2.4",
"lodash.shuffle": "^4.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-is": "^18.2.0",
"react-redux": "^8.0.5",
"react-router-dom": "^6.6.0",
"redux": "^4.2.0",
"react-router-dom": "^6.8.2",
"redux": "^4.2.1",
"redux-logger": "^3.0.6",
"styled-components": "^5.3.6"
"styled-components": "^5.3.8"
},
"devDependencies": {
"@babel/core": "^7.20.5",
"@babel/core": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@types/lodash.shuffle": "^4.2.7",
"@types/node": "^18.11.17",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@types/node": "^18.14.4",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/redux-logger": "^3.0.9",
"@types/styled-components": "^5.1.26",
"@types/webpack-env": "^1.18.0",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"babel-loader": "^9.1.0",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"babel-loader": "^9.1.2",
"cross-env": "^7.0.3",
"css-loader": "^6.7.3",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-react": "^7.31.11",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"fork-ts-checker-webpack-plugin": "^7.2.14",
"fork-ts-checker-webpack-plugin": "^8.0.0",
"html-webpack-plugin": "^5.5.0",
"style-loader": "^3.3.1",
"ts-node": "^10.9.1",
"typescript": "~4.9.4",
"typescript": "~4.9.5",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"

View File

@ -5,7 +5,8 @@ module.exports = {
'\\.css$': '<rootDir>/test/__mocks__/styleMock.ts',
},
transform: {
'^.+\\.jsx?$': 'babel-jest',
'^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.test.json' }],
},
resolver: '<rootDir>/jestResolver.js',
transformIgnorePatterns: ['node_modules/(?!.pnpm|nanoid)'],
};

View File

@ -1,11 +0,0 @@
module.exports = (path, options) => {
return options.defaultResolver(path, {
...options,
packageFilter: (pkg) => {
if (pkg.name === 'nanoid') {
pkg.exports['.'].browser = pkg.exports['.'].require;
}
return pkg;
},
});
};

View File

@ -43,7 +43,7 @@
"prepublish": "pnpm run type-check && pnpm run lint && pnpm run test"
},
"dependencies": {
"@babel/runtime": "^7.20.6",
"@babel/runtime": "^7.21.0",
"@redux-devtools/ui": "^1.3.0",
"@types/prop-types": "^15.7.5",
"es6template": "^1.0.5",
@ -55,37 +55,37 @@
"simple-diff": "^1.6.0"
},
"devDependencies": {
"@babel/cli": "^7.19.3",
"@babel/core": "^7.20.5",
"@babel/cli": "^7.21.0",
"@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/plugin-transform-runtime": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@redux-devtools/core": "^3.13.0",
"@redux-devtools/inspector-monitor": "^3.0.0",
"@testing-library/react": "^13.4.0",
"@types/es6template": "^1.0.0",
"@types/jest": "^29.2.4",
"@testing-library/react": "^14.0.0",
"@types/es6template": "^1.0.1",
"@types/jest": "^29.4.0",
"@types/jsan": "^3.1.2",
"@types/object-path": "^0.11.1",
"@types/react": "^18.0.26",
"@types/react": "^18.0.28",
"@types/simple-diff": "^1.6.1",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-jest": "^27.1.7",
"eslint-plugin-react": "^7.31.11",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
"jest": "^29.4.3",
"jest-environment-jsdom": "^29.4.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"redux": "^4.2.0",
"rimraf": "^3.0.2",
"ts-jest": "^29.0.3",
"typescript": "~4.9.4"
"redux": "^4.2.1",
"rimraf": "^4.1.3",
"ts-jest": "^29.0.5",
"typescript": "~4.9.5"
},
"peerDependencies": {
"@redux-devtools/inspector-monitor": "^3.0.0",
@ -93,6 +93,6 @@
"@types/styled-components": "^5.1.26",
"react": "^16.3.0 || ^17.0.0 || ^18.0.0",
"redux": "^3.4.0 || ^4.0.0",
"styled-components": "^5.3.6"
"styled-components": "^5.3.8"
}
}

View File

@ -31,8 +31,8 @@
},
"dependencies": {
"@babel/code-frame": "^7.18.6",
"@babel/runtime": "^7.20.6",
"@types/chrome": "^0.0.206",
"@babel/runtime": "^7.21.0",
"@types/chrome": "^0.0.218",
"anser": "^2.1.1",
"html-entities": "^2.3.3",
"path-browserify": "^1.0.1",
@ -40,40 +40,40 @@
"source-map": "^0.5.7"
},
"devDependencies": {
"@babel/cli": "^7.19.3",
"@babel/core": "^7.20.5",
"@babel/cli": "^7.21.0",
"@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/plugin-transform-runtime": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@redux-devtools/core": "^3.13.0",
"@redux-devtools/inspector-monitor": "^3.0.0",
"@testing-library/react": "^13.4.0",
"@testing-library/react": "^14.0.0",
"@types/babel__code-frame": "^7.0.3",
"@types/html-entities": "^1.3.4",
"@types/jest": "^29.2.4",
"@types/node": "^18.11.17",
"@types/jest": "^29.4.0",
"@types/node": "^18.14.4",
"@types/path-browserify": "^1.0.0",
"@types/react": "^18.0.26",
"@types/react": "^18.0.28",
"@types/redux-devtools-themes": "^1.0.0",
"@types/source-map": "0.5.2",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-jest": "^27.1.7",
"eslint-plugin-react": "^7.31.11",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
"jest": "^29.4.3",
"jest-environment-jsdom": "^29.4.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-test-renderer": "^18.2.0",
"redux": "^4.2.0",
"rimraf": "^3.0.2",
"ts-jest": "^29.0.3",
"typescript": "~4.9.4"
"redux": "^4.2.1",
"rimraf": "^4.1.3",
"ts-jest": "^29.0.5",
"typescript": "~4.9.5"
},
"peerDependencies": {
"@redux-devtools/inspector-monitor": "^3.0.0",

View File

@ -1,39 +0,0 @@
import typescript from 'rollup-plugin-typescript2';
import babel from '@rollup/plugin-babel';
import nodePolyfills from 'rollup-plugin-node-polyfills';
const config = [
{
input: 'src/StackTraceTab.tsx',
output: [
{
file: 'dist/redux-devtools-inspector-monitor-trace-tab.cjs.js',
format: 'cjs',
},
{
file: 'dist/redux-devtools-inspector-monitor-trace-tab.esm.js',
format: 'esm',
},
],
plugins: [
typescript(),
babel({
babelHelpers: 'runtime',
extensions: ['.ts', '.tsx'],
plugins: ['@babel/plugin-transform-runtime'],
}),
nodePolyfills(),
],
external: [
/@babel\/runtime/,
'react',
'redux-devtools-themes',
'source-map',
'@babel/code-frame',
'anser',
'html-entities',
],
},
];
export default config;

View File

@ -1,5 +1,12 @@
# Change Log
## 3.0.2
### Patch Changes
- Updated dependencies [81926f32]
- react-json-tree@0.18.0
## 3.0.1
### Patch Changes

View File

@ -14,40 +14,40 @@
"@redux-devtools/dock-monitor": "^3.0.0",
"@redux-devtools/inspector-monitor": "^3.0.0",
"base16": "^1.0.0",
"immutable": "^4.1.0",
"immutable": "^4.2.4",
"lodash.shuffle": "^4.2.0",
"react": "^18.2.0",
"react-bootstrap": "^2.7.0",
"react-bootstrap": "^2.7.2",
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
"react-router-dom": "^6.6.0",
"redux": "^4.2.0",
"react-router-dom": "^6.8.2",
"redux": "^4.2.1",
"redux-logger": "^3.0.6"
},
"devDependencies": {
"@babel/core": "^7.20.5",
"@babel/core": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@types/base16": "^1.0.2",
"@types/lodash.shuffle": "^4.2.7",
"@types/node": "^18.11.17",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@types/node": "^18.14.4",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/redux-logger": "^3.0.9",
"@types/webpack-env": "^1.18.0",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"babel-loader": "^9.1.0",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"babel-loader": "^9.1.2",
"cross-env": "^7.0.3",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-react": "^7.31.11",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"fork-ts-checker-webpack-plugin": "^7.2.14",
"fork-ts-checker-webpack-plugin": "^8.0.0",
"html-webpack-plugin": "^5.5.0",
"ts-node": "^10.9.1",
"typescript": "~4.9.4",
"typescript": "~4.9.5",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"

View File

@ -1,6 +1,6 @@
{
"name": "@redux-devtools/inspector-monitor",
"version": "3.0.1",
"version": "3.0.2",
"description": "Redux DevTools Diff Monitor",
"homepage": "https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-inspector-monitor",
"bugs": {
@ -35,50 +35,50 @@
"prepublish": "pnpm run type-check && pnpm run lint"
},
"dependencies": {
"@babel/runtime": "^7.20.6",
"@babel/runtime": "^7.21.0",
"@types/dragula": "^3.7.1",
"@types/lodash": "^4.14.191",
"@types/prop-types": "^15.7.5",
"dateformat": "^4.6.3",
"dateformat": "^5.0.3",
"hex-rgba": "^1.0.2",
"immutable": "^4.1.0",
"immutable": "^4.2.4",
"javascript-stringify": "^2.1.0",
"jsondiffpatch": "^0.4.1",
"jss": "^10.9.2",
"jss-preset-default": "^10.9.2",
"jss": "^10.10.0",
"jss-preset-default": "^10.10.0",
"lodash.debounce": "^4.0.8",
"prop-types": "^15.8.1",
"react-base16-styling": "^0.9.1",
"react-dragula": "^1.1.17",
"react-json-tree": "^0.17.0",
"react-json-tree": "^0.18.0",
"redux-devtools-themes": "^1.0.0"
},
"devDependencies": {
"@babel/cli": "^7.19.3",
"@babel/core": "^7.20.5",
"@babel/cli": "^7.21.0",
"@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/plugin-transform-runtime": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@redux-devtools/core": "^3.13.1",
"@types/dateformat": "^3.0.1",
"@types/dateformat": "^5.0.0",
"@types/hex-rgba": "^1.0.1",
"@types/history": "^4.7.11",
"@types/lodash.debounce": "^4.0.7",
"@types/react": "^18.0.26",
"@types/react": "^18.0.28",
"@types/react-dragula": "^1.1.0",
"@types/redux-devtools-themes": "^1.0.0",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-react": "^7.31.11",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"react": "^18.2.0",
"redux": "^4.2.0",
"rimraf": "^3.0.2",
"typescript": "~4.9.4"
"redux": "^4.2.1",
"rimraf": "^4.1.3",
"typescript": "~4.9.5"
},
"peerDependencies": {
"@redux-devtools/core": "^3.13.1",

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react';
import { Base16Theme } from 'redux-devtools-themes';
import { Action } from 'redux';
import type { StylingFunction } from 'react-base16-styling';
import type { LabelRenderer } from 'react-json-tree';
import { PerformAction } from '@redux-devtools/core';
import { Delta } from 'jsondiffpatch';
import { DEFAULT_STATE, DevtoolsInspectorState } from './redux';
@ -11,12 +12,7 @@ import StateTab from './tabs/StateTab';
import ActionTab from './tabs/ActionTab';
export interface TabComponentProps<S, A extends Action<unknown>> {
labelRenderer: (
keyPath: (string | number)[],
nodeType: string,
expanded: boolean,
expandable: boolean
) => React.ReactNode;
labelRenderer: LabelRenderer;
styling: StylingFunction;
computedStates: { state: S; error?: string }[];
actions: { [actionId: number]: PerformAction<A> };
@ -152,11 +148,7 @@ class ActionPreview<S, A extends Action<unknown>> extends Component<
);
}
labelRenderer = (
[key, ...rest]: (string | number)[],
nodeType: string,
expanded: boolean
) => {
labelRenderer: LabelRenderer = ([key, ...rest], nodeType, expanded) => {
const { styling, onInspectPath, inspectedPath } = this.props;
return (

Some files were not shown because too many files have changed in this diff Show More