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 # 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 ## 3.0.17
### Patch Changes ### Patch Changes

View File

@ -1,5 +1,5 @@
{ {
"version": "3.0.17", "version": "3.0.19",
"name": "Redux DevTools", "name": "Redux DevTools",
"description": "Redux DevTools for debugging application's state changes.", "description": "Redux DevTools for debugging application's state changes.",
"homepage_url": "https://github.com/reduxjs/redux-devtools", "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", "name": "Redux DevTools",
"description": "Redux DevTools for debugging application's state changes.", "description": "Redux DevTools for debugging application's state changes.",
"homepage_url": "https://github.com/reduxjs/redux-devtools", "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", "name": "Redux DevTools",
"manifest_version": 2, "manifest_version": 2,
"description": "Redux Developer Tools for debugging application state changes.", "description": "Redux Developer Tools for debugging application state changes.",

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,23 @@
# Change Log # 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) ## [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 ### Features

View File

@ -35,7 +35,7 @@ const render = tree(document.getElementById('root'), {
isSorted: false, isSorted: false,
widthBetweenNodesCoeff: 1.5, widthBetweenNodesCoeff: 1.5,
heightBetweenNodesCoeff: 2, heightBetweenNodesCoeff: 2,
style: { border: '1px solid black' }, chartStyles: { border: '1px solid black' },
tooltipOptions: { offset: { left: 30, top: 10 }, indentationSize: 2 }, 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 | | 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 | | `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 | | `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 | | `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 | | `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... More to come...
## Bindings
### React
[example](https://github.com/reduxjs/redux-devtools/tree/master/packages/d3-state-visualizer/examples/react-tree) implementation.
## Roadmap ## 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. - 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, "private": true,
"name": "d3-state-visualizer-tree-example", "name": "d3-state-visualizer-tree-example",
"version": "0.1.4", "version": "0.1.5",
"description": "Visualize your app state as a tree", "description": "Visualize your app state as a tree",
"keywords": [ "keywords": [
"d3", "d3",
@ -25,24 +25,24 @@
"type-check": "tsc --noEmit" "type-check": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"d3-state-visualizer": "^1.6.0", "d3-state-visualizer": "^2.0.0",
"map2tree": "^2.1.0" "map2tree": "^3.0.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.5", "@babel/core": "^7.21.0",
"@babel/preset-env": "^7.20.2", "@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.18.6", "@babel/preset-typescript": "^7.21.0",
"@types/node": "^18.11.17", "@types/node": "^18.14.4",
"@typescript-eslint/eslint-plugin": "^5.47.0", "@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.47.0", "@typescript-eslint/parser": "^5.54.0",
"babel-loader": "^9.1.0", "babel-loader": "^9.1.2",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.30.0", "eslint": "^8.35.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.6.0",
"fork-ts-checker-webpack-plugin": "^7.2.14", "fork-ts-checker-webpack-plugin": "^8.0.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "~4.9.4", "typescript": "~4.9.5",
"webpack": "^5.75.0", "webpack": "^5.75.0",
"webpack-cli": "^5.0.1", "webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1" "webpack-dev-server": "^4.11.1"

View File

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

View File

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

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

View File

@ -1,2 +1,2 @@
export { tree } from './charts'; 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 # 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 ## 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): - 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 ## Quick usage
```javascript ```javascript
import d3 from 'd3'; import * as d3 from 'd3';
import { tooltip } from 'd3tooltip'; import { tooltip } from 'd3tooltip';
const DOMNode = document.getElementById('chart'); const DOMNode = document.getElementById('chart');
const root = d3.select(DOMNode); const root = d3.select(DOMNode);
const vis = root.append('svg'); const vis = root.append('svg');
let options = { const options = {
offset: {left: 30, top: 10} 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') .append('circle')
.attr('r', 10) .attr('r', 10)
.call( .call(
d3tooltip(d3, 'tooltipClassName', options) d3tooltip('tooltipClassName', {
.text((d, i) => toStringOrHtml(d)) ...options,
.attr({ 'class': 'anotherClassName' }) text: (d) => toStringOrHtml(d),
.style({ 'min-width': '50px', 'border-radius: 5px' }) })
) )
.on({ .on('mouseover', function () {
mouseover(d, i) { d3.select(this).style('fill', 'skyblue');
d3.select(this).style({ })
fill: 'skyblue' .on('mouseout', function () {
}); d3.select(this).style('fill', 'black');
},
mouseout(d, i) {
d3.select(this).style({
fill: 'black'
});
}
}); });
``` ```
## API ## API
| Option | Type | Default | Description | | 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) | | `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 | | `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 | | `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 | | `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", "name": "d3tooltip",
"version": "2.1.0", "version": "3.0.0",
"description": "A highly configurable tooltip for d3", "description": "A highly configurable tooltip for d3",
"keywords": [ "keywords": [
"d3", "d3",
@ -19,18 +19,16 @@
"main": "lib/cjs/index.js", "main": "lib/cjs/index.js",
"module": "lib/esm/index.js", "module": "lib/esm/index.js",
"types": "lib/types/index.d.ts", "types": "lib/types/index.d.ts",
"unpkg": "lib/umd/d3tooltip.umd.js",
"sideEffects": false, "sideEffects": false,
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/reduxjs/redux-devtools.git" "url": "https://github.com/reduxjs/redux-devtools.git"
}, },
"scripts": { "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: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:esm": "babel src --config-file ./babel.config.esm.json --extensions \".ts\" --out-dir lib/esm",
"build:types": "tsc --emitDeclarationOnly", "build:types": "tsc --emitDeclarationOnly",
"build:umd": "rollup -c",
"clean": "rimraf lib", "clean": "rimraf lib",
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
"type-check": "tsc --noEmit", "type-check": "tsc --noEmit",
@ -38,36 +36,25 @@
"prepublish": "pnpm run type-check && pnpm run lint" "prepublish": "pnpm run type-check && pnpm run lint"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.6", "@babel/runtime": "^7.21.0"
"ramda": "^0.28.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.19.3", "@babel/cli": "^7.21.0",
"@babel/core": "^7.20.5", "@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1", "@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/preset-env": "^7.20.2", "@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.18.6", "@babel/preset-typescript": "^7.21.0",
"@rollup/plugin-babel": "^6.0.3", "@types/d3": "^7.4.0",
"@rollup/plugin-commonjs": "^24.0.0", "@typescript-eslint/eslint-plugin": "^5.54.0",
"@rollup/plugin-node-resolve": "^15.0.1", "@typescript-eslint/parser": "^5.54.0",
"@rollup/plugin-terser": "^0.2.1", "d3": "^7.8.2",
"@types/d3": "^3.5.47", "eslint": "^8.35.0",
"@types/node": "^18.11.17", "eslint-config-prettier": "^8.6.0",
"@types/ramda": "^0.28.20", "rimraf": "^4.1.3",
"@typescript-eslint/eslint-plugin": "^5.47.0", "typescript": "~4.9.5"
"@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"
}, },
"peerDependencies": { "peerDependencies": {
"@types/d3": "^3.5.47", "@types/d3": "^7.4.0",
"d3": "^3.5.17" "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 * as d3 from 'd3';
import { is } from 'ramda'; import type { BaseType, Selection } from 'd3';
import utils from './utils';
const { prependClass, functor } = utils;
interface Options<Datum> { export type StyleValue = string | number | boolean;
interface Options<
Datum,
RootGElement extends BaseType,
RootDatum,
RootPElement extends BaseType,
RootPDatum
> {
left: number | undefined; left: number | undefined;
top: number | undefined; top: number | undefined;
offset: { offset: {
left: number; left: number;
top: 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 left: undefined, // mouseX
top: undefined, // mouseY top: undefined, // mouseY
offset: { left: 0, top: 0 }, offset: { left: 0, top: 0 },
root: undefined, root: undefined,
styles: {},
text: '',
}; };
interface Tip<Datum> { export function tooltip<
(selection: Selection<Datum>): void; GElement extends BaseType,
attr: ( Datum,
this: this, PElement extends BaseType,
d: PDatum,
| string RootGElement extends BaseType,
| { RootDatum,
[key: string]: RootPElement extends BaseType,
| Primitive RootPDatum
| ((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,
className = 'tooltip', className = 'tooltip',
options: Partial<Options<Datum>> = {} options: Partial<
): Tip<Datum> { Options<Datum, RootGElement, RootDatum, RootPElement, RootPDatum>
const { left, top, offset, root } = { > = {}
) {
const { left, top, offset, root, styles, text } = {
...defaultOptions, ...defaultOptions,
...options, ...options,
} as Options<Datum>; } as Options<Datum, RootGElement, RootDatum, RootPElement, RootPDatum>;
let attrs = { class: className }; let el: Selection<HTMLDivElement, RootDatum, BaseType, unknown>;
let text: (datum: Datum, index?: number, outerIndex?: number) => string = ( const anchor: Selection<
node: Datum RootGElement,
) => ''; RootDatum,
let styles = {}; RootPElement | HTMLElement,
RootPDatum
> = root || d3.select<RootGElement, RootDatum>('body');
const rootNode = anchor.node()!;
let el: Selection<Datum>; return function tip(selection: Selection<GElement, Datum, PElement, PDatum>) {
const anchor = root || d3.select('body'); selection.on('mouseover.tip', (event, datum) => {
const rootNode = anchor.node(); const [pointerX, pointerY] = d3.pointer(event, rootNode);
const [x, y] = [
function tip(selection: Selection<Datum>) { left || pointerX + offset.left,
selection.on('mouseover.tip', (node) => { top || pointerY - offset.top,
const [mouseX, mouseY] = d3.mouse(rootNode); ];
const [x, y] = [left || mouseX + offset.left, top || mouseY - offset.top];
anchor.selectAll(`div.${className}`).remove(); anchor.selectAll(`div.${className}`).remove();
el = anchor el = anchor
.append('div') .append('div')
.attr(prependClass(className)(attrs)) .attr('class', className)
.style({ .style('position', 'absolute')
position: 'absolute', .style('z-index', 1001)
'z-index': 1001, .style('left', `${x}px`)
left: `${x}px`, .style('top', `${y}px`)
top: `${y}px`, .html(typeof text === 'function' ? () => text(datum) : () => text);
...styles,
}) for (const [key, value] of Object.entries(styles)) {
.html(() => text(node)) as Selection<Datum>; el.style(key, value);
}
}); });
selection.on('mousemove.tip', (node) => { selection.on('mousemove.tip', (event, datum) => {
const [mouseX, mouseY] = d3.mouse(rootNode); const [pointerX, pointerY] = d3.pointer(event, rootNode);
const [x, y] = [left || mouseX + offset.left, top || mouseY - offset.top]; const [x, y] = [
left || pointerX + offset.left,
top || pointerY - offset.top,
];
el.style({ el.style('left', `${x}px`)
left: `${x}px`, .style('top', `${y}px`)
top: `${y}px`, .html(typeof text === 'function' ? () => text(datum) : () => text);
}).html(() => text(node));
}); });
selection.on('mouseout.tip', () => el.remove()); 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 # Change Log
## 3.0.0
### Major Changes
- b323f77d: Remove UMD build.
## 2.0.0 ## 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): - 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", "name": "map2tree",
"version": "2.1.0", "version": "3.0.0",
"description": "Utility for mapping maps to trees", "description": "Utility for mapping maps to trees",
"keywords": [ "keywords": [
"map2tree", "map2tree",
@ -22,18 +22,16 @@
"main": "lib/cjs/index.js", "main": "lib/cjs/index.js",
"module": "lib/esm/index.js", "module": "lib/esm/index.js",
"types": "lib/types/index.d.ts", "types": "lib/types/index.d.ts",
"unpkg": "lib/umd/map2tree.umd.js",
"sideEffects": false, "sideEffects": false,
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/reduxjs/redux-devtools.git" "url": "https://github.com/reduxjs/redux-devtools.git"
}, },
"scripts": { "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: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:esm": "babel src --config-file ./babel.config.esm.json --extensions \".ts\" --out-dir lib/esm",
"build:types": "tsc --emitDeclarationOnly", "build:types": "tsc --emitDeclarationOnly",
"build:umd": "rollup -c",
"clean": "rimraf lib", "clean": "rimraf lib",
"test": "jest", "test": "jest",
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
@ -42,35 +40,26 @@
"prepublish": "pnpm run type-check && pnpm run lint && pnpm run test" "prepublish": "pnpm run type-check && pnpm run lint && pnpm run test"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.6", "@babel/runtime": "^7.21.0",
"lodash": "^4.17.21" "lodash": "^4.17.21"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.19.3", "@babel/cli": "^7.21.0",
"@babel/core": "^7.20.5", "@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1", "@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/preset-env": "^7.20.2", "@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.18.6", "@babel/preset-typescript": "^7.21.0",
"@rollup/plugin-babel": "^6.0.3", "@types/jest": "^29.4.0",
"@rollup/plugin-commonjs": "^24.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-terser": "^0.2.1",
"@types/jest": "^29.2.4",
"@types/lodash": "^4.14.191", "@types/lodash": "^4.14.191",
"@types/node": "^18.11.17", "@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/eslint-plugin": "^5.47.0", "@typescript-eslint/parser": "^5.54.0",
"@typescript-eslint/parser": "^5.47.0", "eslint": "^8.35.0",
"eslint": "^8.30.0", "eslint-config-prettier": "^8.6.0",
"eslint-config-prettier": "^8.5.0", "eslint-plugin-jest": "^27.2.1",
"eslint-plugin-jest": "^27.1.7", "immutable": "^4.2.4",
"immutable": "^4.1.0", "jest": "^29.4.3",
"jest": "^29.3.1", "rimraf": "^4.1.3",
"rimraf": "^3.0.2", "ts-jest": "^29.0.5",
"rollup": "^3.7.5", "typescript": "~4.9.5"
"rollup-plugin-typescript2": "^0.34.1",
"ts-jest": "^29.0.3",
"tslib": "^2.4.1",
"typescript": "~4.9.4"
} }
} }

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,15 @@
# Change Log # 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 ## 0.17.0
### Minor Changes ### Minor Changes

View File

@ -139,7 +139,7 @@ Their full signatures are:
#### More Options #### 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. - `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`. - `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` - `postprocessValue: function(value)` - maps `value` to a new `value`

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "react-json-tree", "name": "react-json-tree",
"version": "0.17.0", "version": "0.18.0",
"description": "React JSON Viewer Component, Extracted from redux-devtools", "description": "React JSON Viewer Component, Extracted from redux-devtools",
"keywords": [ "keywords": [
"react", "react",
@ -45,47 +45,45 @@
"prepublish": "pnpm run type-check && pnpm run lint && pnpm run test" "prepublish": "pnpm run type-check && pnpm run lint && pnpm run test"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.6", "@babel/runtime": "^7.21.0",
"@types/lodash": "^4.14.191", "@types/lodash": "^4.14.191",
"@types/prop-types": "^15.7.5",
"prop-types": "^15.8.1",
"react-base16-styling": "^0.9.1" "react-base16-styling": "^0.9.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.19.3", "@babel/cli": "^7.21.0",
"@babel/core": "^7.20.5", "@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1", "@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-env": "^7.20.2",
"@babel/preset-react": "^7.18.6", "@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-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-node-resolve": "^15.0.1",
"@rollup/plugin-terser": "^0.2.1", "@rollup/plugin-terser": "^0.4.0",
"@types/jest": "^29.2.4", "@types/jest": "^29.4.0",
"@types/node": "^18.11.17", "@types/node": "^18.14.4",
"@types/react": "^18.0.26", "@types/react": "^18.0.28",
"@types/react-test-renderer": "^18.0.0", "@types/react-test-renderer": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.47.0", "@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.47.0", "@typescript-eslint/parser": "^5.54.0",
"eslint": "^8.30.0", "eslint": "^8.35.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.1.7", "eslint-plugin-jest": "^27.2.1",
"eslint-plugin-react": "^7.31.11", "eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.3.1", "jest": "^29.4.3",
"react": "^18.2.0", "react": "^18.2.0",
"react-test-renderer": "^18.2.0", "react-test-renderer": "^18.2.0",
"rimraf": "^3.0.2", "rimraf": "^4.1.3",
"rollup": "^3.7.5", "rollup": "^3.18.0",
"rollup-plugin-typescript2": "^0.34.1", "rollup-plugin-typescript2": "^0.34.1",
"ts-jest": "^29.0.3", "ts-jest": "^29.0.5",
"tslib": "^2.4.1", "tslib": "^2.5.0",
"typescript": "~4.9.4" "typescript": "~4.9.5"
}, },
"peerDependencies": { "peerDependencies": {
"@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0", "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react": "^16.3.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 React, { useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import JSONArrow from './JSONArrow'; import JSONArrow from './JSONArrow';
import { CircularPropsPassedThroughItemRange } from './types'; import type { CircularCache, CommonInternalProps } from './types';
interface Props extends CircularPropsPassedThroughItemRange { interface Props extends CommonInternalProps {
data: any; data: unknown;
nodeType: string; nodeType: string;
from: number; from: number;
to: number; to: number;
renderChildNodes: (props: Props, from: number, to: number) => React.ReactNode; renderChildNodes: (props: Props, from: number, to: number) => React.ReactNode;
circularCache: CircularCache;
level: number;
} }
interface State { export default function ItemRange(props: Props) {
expanded: boolean; const { styling, from, to, renderChildNodes, nodeType } = props;
}
const [expanded, setExpanded] = useState<boolean>(false);
export default class ItemRange extends React.Component<Props, State> { const handleClick = useCallback(() => {
static propTypes = { setExpanded(!expanded);
styling: PropTypes.func.isRequired, }, [expanded]);
from: PropTypes.number.isRequired,
to: PropTypes.number.isRequired, return expanded ? (
renderChildNodes: PropTypes.func.isRequired, <div {...styling('itemRange', expanded)}>
nodeType: PropTypes.string.isRequired, {renderChildNodes(props, from, to)}
}; </div>
) : (
constructor(props: Props) { <div {...styling('itemRange', expanded)} onClick={handleClick}>
super(props); <JSONArrow
this.state = { expanded: false }; nodeType={nodeType}
} styling={styling}
expanded={false}
render() { onClick={handleClick}
const { styling, from, to, renderChildNodes, nodeType } = this.props; arrowStyle="double"
/>
return this.state.expanded ? ( {`${from} ... ${to}`}
<div {...styling('itemRange', this.state.expanded)}> </div>
{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 });
};
} }

View File

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

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import type { StylingFunction } from 'react-base16-styling';
import { StylingFunction } from 'react-base16-styling';
interface Props { interface Props {
styling: StylingFunction; styling: StylingFunction;
@ -10,33 +9,21 @@ interface Props {
onClick: React.MouseEventHandler<HTMLDivElement>; onClick: React.MouseEventHandler<HTMLDivElement>;
} }
const JSONArrow: React.FunctionComponent<Props> = ({ export default function JSONArrow({
styling, styling,
arrowStyle, arrowStyle = 'single',
expanded, expanded,
nodeType, nodeType,
onClick, onClick,
}) => ( }: Props) {
<div {...styling('arrowContainer', arrowStyle)} onClick={onClick}> return (
<div {...styling(['arrow', 'arrowSign'], nodeType, expanded, arrowStyle)}> <div {...styling('arrowContainer', arrowStyle)} onClick={onClick}>
{'\u25B6'} <div {...styling(['arrow', 'arrowSign'], nodeType, expanded, arrowStyle)}>
{arrowStyle === 'double' && ( {'\u25B6'}
<div {...styling(['arrowSign', 'arrowSignInner'])}>{'\u25B6'}</div> {arrowStyle === 'double' && (
)} <div {...styling(['arrowSign', 'arrowSignInner'])}>{'\u25B6'}</div>
)}
</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 React from 'react';
import JSONNestedNode from './JSONNestedNode'; import JSONNestedNode from './JSONNestedNode';
import { CircularPropsPassedThroughJSONNode } from './types'; import type { CommonInternalProps } from './types';
// Returns the "n Items" string for this node, // Returns the "n Items" string for this node,
// generating and caching it if it hasn't been created yet. // 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'}`; return `${hasMore ? '>' : ''}${count} ${count !== 1 ? 'entries' : 'entry'}`;
} }
interface Props extends CircularPropsPassedThroughJSONNode { interface Props extends CommonInternalProps {
data: any; data: unknown;
nodeType: string; nodeType: string;
} }
// Configures <JSONNestedNode> to render an iterable // Configures <JSONNestedNode> to render an iterable
const JSONIterableNode: React.FunctionComponent<Props> = ({ ...props }) => { export default function JSONIterableNode(props: Props) {
return ( return (
<JSONNestedNode <JSONNestedNode
{...props} {...props}
nodeType="Iterable" nodeType="Iterable"
nodeTypeIndicator="()" nodeTypeIndicator="()"
createItemString={createItemString} createItemString={createItemString}
expandable
/> />
); );
}; }
export default JSONIterableNode;

View File

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

View File

@ -1,19 +1,16 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import objType from './objType'; import objType from './objType';
import JSONObjectNode from './JSONObjectNode'; import JSONObjectNode from './JSONObjectNode';
import JSONArrayNode from './JSONArrayNode'; import JSONArrayNode from './JSONArrayNode';
import JSONIterableNode from './JSONIterableNode'; import JSONIterableNode from './JSONIterableNode';
import JSONValueNode from './JSONValueNode'; import JSONValueNode from './JSONValueNode';
import { CircularPropsPassedThroughJSONNode } from './types'; import type { CommonInternalProps } from './types';
interface Props extends CircularPropsPassedThroughJSONNode { interface Props extends CommonInternalProps {
keyPath: (string | number)[]; value: unknown;
value: any;
isCustomNode: (value: any) => boolean;
} }
const JSONNode: React.FunctionComponent<Props> = ({ export default function JSONNode({
getItemString, getItemString,
keyPath, keyPath,
labelRenderer, labelRenderer,
@ -22,7 +19,7 @@ const JSONNode: React.FunctionComponent<Props> = ({
valueRenderer, valueRenderer,
isCustomNode, isCustomNode,
...rest ...rest
}) => { }: Props) {
const nodeType = isCustomNode(value) ? 'Custom' : objType(value); const nodeType = isCustomNode(value) ? 'Custom' : objType(value);
const simpleNodeProps = { 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 React from 'react';
import PropTypes from 'prop-types';
import JSONNestedNode from './JSONNestedNode'; import JSONNestedNode from './JSONNestedNode';
import { CircularPropsPassedThroughJSONNode } from './types'; import type { CommonInternalProps } from './types';
// Returns the "n Items" string for this node, // Returns the "n Items" string for this node,
// generating and caching it if it hasn't been created yet. // 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; const len = Object.getOwnPropertyNames(data).length;
return `${len} ${len !== 1 ? 'keys' : 'key'}`; return `${len} ${len !== 1 ? 'keys' : 'key'}`;
} }
interface Props extends CircularPropsPassedThroughJSONNode { interface Props extends CommonInternalProps {
data: any; data: unknown;
nodeType: string; nodeType: string;
} }
// Configures <JSONNestedNode> to render an Object // Configures <JSONNestedNode> to render an Object
const JSONObjectNode: React.FunctionComponent<Props> = ({ data, ...props }) => ( export default function JSONObjectNode({ data, ...props }: Props) {
<JSONNestedNode return (
{...props} <JSONNestedNode
data={data} {...props}
nodeType="Object" data={data}
nodeTypeIndicator={props.nodeType === 'Error' ? 'Error()' : '{}'} nodeType="Object"
createItemString={createItemString} nodeTypeIndicator={props.nodeType === 'Error' ? 'Error()' : '{}'}
expandable={Object.getOwnPropertyNames(data).length > 0} createItemString={createItemString}
/> expandable={Object.getOwnPropertyNames(data).length > 0}
); />
);
JSONObjectNode.propTypes = { }
data: PropTypes.object,
nodeType: PropTypes.string.isRequired,
};
export default JSONObjectNode;

View File

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

View File

@ -1,11 +1,12 @@
import type { CurriedFunction1 } from 'lodash'; import type { CurriedFunction1 } from 'lodash';
import { import { createStyling } from 'react-base16-styling';
import type {
Base16Theme, Base16Theme,
createStyling,
StylingConfig, StylingConfig,
StylingFunction,
Theme,
} from 'react-base16-styling'; } from 'react-base16-styling';
import solarized from './themes/solarized'; import solarized from './themes/solarized';
import { StylingFunction, Theme } from 'react-base16-styling/src';
const colorMap = (theme: Base16Theme) => ({ const colorMap = (theme: Base16Theme) => ({
BACKGROUND_COLOR: theme.base00, 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') { if (type === 'Object') {
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
return Object.keys(collection as {}).length; return Object.keys(collection as {}).length;
@ -9,17 +11,17 @@ function getLength(type: string, collection: any) {
return Infinity; return Infinity;
} }
function isIterableMap(collection: any) { function isIterableMap(collection: unknown) {
return typeof (collection as Map<any, any>).set === 'function'; return typeof (collection as Map<unknown, unknown>).set === 'function';
} }
function getEntries( function getEntries(
type: string, type: string,
collection: any, collection: any,
sortObjectKeys?: ((a: any, b: any) => number) | boolean | undefined, sortObjectKeys: SortObjectKeys,
from = 0, from = 0,
to = Infinity to = Infinity
): { entries: { key: string | number; value: any }[]; hasMore?: boolean } { ): { entries: { key: string | number; value: unknown }[]; hasMore?: boolean } {
let res; let res;
if (type === 'Object') { if (type === 'Object') {
@ -95,8 +97,8 @@ function getRanges(from: number, to: number, limit: number) {
export default function getCollectionEntries( export default function getCollectionEntries(
type: string, type: string,
collection: any, collection: unknown,
sortObjectKeys: ((a: any, b: any) => number) | boolean | undefined, sortObjectKeys: SortObjectKeys,
limit: number, limit: number,
from = 0, from = 0,
to = Infinity to = Infinity

View File

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

View File

@ -1,81 +1,63 @@
import React from 'react'; import React from 'react';
import { StylingFunction } from 'react-base16-styling'; import { StylingFunction } from 'react-base16-styling';
interface SharedCircularPropsPassedThroughJSONTree { export type Key = string | number;
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;
interface JSONNestedNodeCircularPropsPassedThroughJSONTree { export type KeyPath = readonly (string | number)[];
shouldExpandNode: (
keyPath: (string | number)[], export type GetItemString = (
data: any, nodeType: string,
level: number data: unknown,
) => boolean; 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; hideRoot: boolean;
getItemString: ( getItemString: GetItemString;
nodeType: string, postprocessValue: PostprocessValue;
data: any, isCustomNode: IsCustomNode;
itemType: React.ReactNode,
itemString: string,
keyPath: (string | number)[]
) => React.ReactNode;
postprocessValue: (value: any) => any;
isCustomNode: (value: any) => boolean;
collectionLimit: number; collectionLimit: number;
sortObjectKeys?: ((a: any, b: any) => number) | boolean; sortObjectKeys: SortObjectKeys;
} }
export type CircularPropsPassedThroughJSONTree =
SharedCircularPropsPassedThroughJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONTree;
interface JSONNestedNodeCircularPropsPassedThroughJSONNode export interface CommonInternalProps extends CommonExternalProps {
extends JSONNestedNodeCircularPropsPassedThroughJSONTree { styling: StylingFunction;
circularCache?: any[]; circularCache?: CircularCache;
isCircular?: boolean;
level?: number; 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 # 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 ## 2.2.0
### Minor Changes ### Minor Changes

View File

@ -6,7 +6,10 @@ module.exports = {
'\\.css$': '<rootDir>/test/__mocks__/styleMock.ts', '\\.css$': '<rootDir>/test/__mocks__/styleMock.ts',
}, },
transform: { transform: {
'^.+\\.jsx?$': 'babel-jest',
'^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.test.json' }], '^.+\\.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", "name": "@redux-devtools/app",
"version": "2.2.0", "version": "2.2.1",
"description": "Redux DevTools app", "description": "Redux DevTools app",
"homepage": "https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-app", "homepage": "https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-app",
"bugs": { "bugs": {
@ -40,19 +40,19 @@
"prepublish": "pnpm run type-check && pnpm run lint && pnpm run test" "prepublish": "pnpm run type-check && pnpm run lint && pnpm run test"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.6", "@babel/runtime": "^7.21.0",
"@redux-devtools/chart-monitor": "^3.0.0", "@redux-devtools/chart-monitor": "^4.0.0",
"@redux-devtools/core": "^3.13.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-test-tab": "^1.0.0",
"@redux-devtools/inspector-monitor-trace-tab": "^1.0.0", "@redux-devtools/inspector-monitor-trace-tab": "^1.0.0",
"@redux-devtools/log-monitor": "^4.0.0", "@redux-devtools/log-monitor": "^4.0.2",
"@redux-devtools/rtk-query-monitor": "^3.0.0", "@redux-devtools/rtk-query-monitor": "^3.1.1",
"@redux-devtools/slider-monitor": "^4.0.0", "@redux-devtools/slider-monitor": "^4.0.0",
"@redux-devtools/ui": "^1.3.0", "@redux-devtools/ui": "^1.3.0",
"@reduxjs/toolkit": "^1.9.1", "@reduxjs/toolkit": "^1.9.3",
"@types/prop-types": "^15.7.5", "@types/prop-types": "^15.7.5",
"d3-state-visualizer": "^1.6.0", "d3-state-visualizer": "^2.0.0",
"javascript-stringify": "^2.1.0", "javascript-stringify": "^2.1.0",
"jsan": "^3.1.14", "jsan": "^3.1.14",
"jsondiffpatch": "^0.4.1", "jsondiffpatch": "^0.4.1",
@ -63,55 +63,55 @@
"react-icons": "^4.7.1", "react-icons": "^4.7.1",
"react-is": "^18.2.0", "react-is": "^18.2.0",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
"redux": "^4.2.0", "redux": "^4.2.1",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"socketcluster-client": "^17.1.0" "socketcluster-client": "^17.1.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.19.3", "@babel/cli": "^7.21.0",
"@babel/core": "^7.20.5", "@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1", "@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-env": "^7.20.2",
"@babel/preset-react": "^7.18.6", "@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6", "@babel/preset-typescript": "^7.21.0",
"@rjsf/core": "^4.2.3", "@rjsf/core": "^4.2.3",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^14.0.0",
"@types/jest": "^29.2.4", "@types/jest": "^29.4.0",
"@types/jsan": "^3.1.2", "@types/jsan": "^3.1.2",
"@types/json-schema": "^7.0.11", "@types/json-schema": "^7.0.11",
"@types/lodash": "^4.14.191", "@types/lodash": "^4.14.191",
"@types/node": "^18.11.17", "@types/node": "^18.14.4",
"@types/react": "^18.0.26", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.9", "@types/react-dom": "^18.0.11",
"@types/socketcluster-client": "^16.0.0", "@types/socketcluster-client": "^16.0.0",
"@types/styled-components": "^5.1.26", "@types/styled-components": "^5.1.26",
"@types/testing-library__jest-dom": "^5.14.5", "@types/testing-library__jest-dom": "^5.14.5",
"@types/webpack-env": "^1.18.0", "@types/webpack-env": "^1.18.0",
"@typescript-eslint/eslint-plugin": "^5.47.0", "@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.47.0", "@typescript-eslint/parser": "^5.54.0",
"babel-loader": "^9.1.0", "babel-loader": "^9.1.2",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"css-loader": "^6.7.3", "css-loader": "^6.7.3",
"eslint": "^8.30.0", "eslint": "^8.35.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.1.7", "eslint-plugin-jest": "^27.2.1",
"eslint-plugin-react": "^7.31.11", "eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0", "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-loader": "^4.2.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"jest": "^29.3.1", "jest": "^29.4.3",
"jest-environment-jsdom": "^29.3.1", "jest-environment-jsdom": "^29.4.3",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"rimraf": "^3.0.2", "rimraf": "^4.1.3",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"styled-components": "^5.3.6", "styled-components": "^5.3.8",
"ts-jest": "^29.0.3", "ts-jest": "^29.0.5",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "~4.9.4", "typescript": "~4.9.5",
"webpack": "^5.75.0", "webpack": "^5.75.0",
"webpack-cli": "^5.0.1", "webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1" "webpack-dev-server": "^4.11.1"
@ -120,6 +120,6 @@
"@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0", "@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0",
"@types/styled-components": "^5.1.26", "@types/styled-components": "^5.1.26",
"react": "^16.3.0 || ^17.0.0 || ^18.0.0", "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 React, { Component } from 'react';
import { connect, ResolveThunks } from 'react-redux'; import { connect, ResolveThunks } from 'react-redux';
import { ChartMonitor } from '@redux-devtools/chart-monitor'; 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'; import { selectMonitorWithState } from '../../actions';
export function getPath(obj: NodeWithId, inspectedStatePath: string[]) { export function getPath(
obj: HierarchyPointNode<Node>,
inspectedStatePath: string[]
) {
const parent = obj.parent; const parent = obj.parent;
if (!parent) return; if (!parent) return;
getPath(parent, inspectedStatePath); getPath(parent, inspectedStatePath);
let name = obj.name; let name = obj.data.name;
const item = /.+\[(\d+)]/.exec(name); const item = /.+\[(\d+)]/.exec(name);
if (item) name = item[1]; if (item) name = item[1];
inspectedStatePath.push(name); inspectedStatePath.push(name);
@ -20,7 +23,7 @@ type Props = DispatchProps;
class ChartMonitorWrapper extends Component<Props> { class ChartMonitorWrapper extends Component<Props> {
static update = ChartMonitor.update; static update = ChartMonitor.update;
onClickText = (data: NodeWithId) => { onClickText = (data: HierarchyPointNode<Node>) => {
const inspectedStatePath: string[] = []; const inspectedStatePath: string[] = [];
getPath(data, inspectedStatePath); getPath(data, inspectedStatePath);
this.props.selectMonitorWithState('InspectorMonitor', { this.props.selectMonitorWithState('InspectorMonitor', {

View File

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

View File

@ -1,5 +1,19 @@
# Change Log # 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 ## 3.0.1
### Patch Changes ### 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: 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 | | Name | Description |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `defaultIsVisible` | By default, set to `true`. | | `defaultIsVisible` | By default, set to `true`. |
| `transitionDuration` | By default, set to `750`, in milliseconds. | | `transitionDuration` | By default, set to `750`, in milliseconds. |
| `heightBetweenNodesCoeff` | By default, set to `1`. | | `heightBetweenNodesCoeff` | By default, set to `1`. |
| `widthBetweenNodesCoeff` | By default, set to `1.3`. | | `widthBetweenNodesCoeff` | By default, set to `1.3`. |
| `isSorted` | By default, set to `false`. | | `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>} | | `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>} |
| `onClickText` | Function called with a reference to the clicked node as first argument when clicking on the text next to a node. | | `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>} |
| `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). | | `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 #### Redux DevTools props

View File

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

View File

@ -1,6 +1,6 @@
import React, { Component, createRef } from 'react'; import React, { Component, createRef } from 'react';
import PropTypes from 'prop-types'; import { tree } from 'd3-state-visualizer';
import { tree, NodeWithId, Primitive } from 'd3-state-visualizer'; import type { Options } from 'd3-state-visualizer';
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { LiftedAction, LiftedState } from '@redux-devtools/core'; import { LiftedAction, LiftedState } from '@redux-devtools/core';
import * as themes from 'redux-devtools-themes'; import * as themes from 'redux-devtools-themes';
@ -12,7 +12,8 @@ const wrapperStyle = {
}; };
export interface Props<S, A extends Action<unknown>> 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>>; dispatch: Dispatch<LiftedAction<S, A, ChartMonitorState>>;
preserveScrollTop: boolean; preserveScrollTop: boolean;
select: (state: S) => unknown; select: (state: S) => unknown;
@ -20,76 +21,10 @@ export interface Props<S, A extends Action<unknown>>
invertTheme: boolean; invertTheme: boolean;
state: S | null; 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; defaultIsVisible?: boolean;
} }
class Chart<S, A extends Action<unknown>> extends Component<Props<S, A>> { 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>(); divRef = createRef<HTMLDivElement>();
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
renderChart?: (state?: {} | null | undefined) => void; renderChart?: (state?: {} | null | undefined) => void;

View File

@ -1,5 +1,4 @@
import React, { CSSProperties, PureComponent } from 'react'; import React, { CSSProperties, PureComponent } from 'react';
import PropTypes from 'prop-types';
import * as themes from 'redux-devtools-themes'; import * as themes from 'redux-devtools-themes';
import { import {
ActionCreators, ActionCreators,
@ -8,7 +7,7 @@ import {
} from '@redux-devtools/core'; } from '@redux-devtools/core';
import deepmerge from 'deepmerge'; import deepmerge from 'deepmerge';
import { Action, Dispatch } from 'redux'; 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 reducer, { ChartMonitorState } from './reducers';
import Chart, { Props } from './Chart'; import Chart, { Props } from './Chart';
@ -41,37 +40,14 @@ function invertColors(theme: themes.Base16Theme) {
} }
export interface ChartMonitorProps<S, A extends Action<unknown>> 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>>; dispatch: Dispatch<LiftedAction<S, A, ChartMonitorState>>;
preserveScrollTop: boolean; preserveScrollTop: boolean;
select: (state: S) => unknown; select: (state: S) => unknown;
theme: keyof typeof themes | themes.Base16Theme; theme: keyof typeof themes | themes.Base16Theme;
invertTheme: boolean; 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; defaultIsVisible?: boolean;
} }
@ -80,23 +56,6 @@ class ChartMonitor<S, A extends Action<unknown>> extends PureComponent<
> { > {
static update = reducer; 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 = { static defaultProps = {
select: (state: unknown) => state, select: (state: unknown) => state,
theme: 'nicinabox', theme: 'nicinabox',
@ -140,45 +99,10 @@ class ChartMonitor<S, A extends Action<unknown>> extends PureComponent<
return invertTheme ? invertColors(themes.nicinabox) : themes.nicinabox; 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> { getChartOptions(props = this.props): Props<S, A> {
const { computedStates } = props; const { computedStates } = props;
const theme = this.getTheme(); 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 = { const defaultOptions = {
state: computedStates.length state: computedStates.length
? computedStates[props.currentStateIndex].state ? computedStates[props.currentStateIndex].state
@ -186,10 +110,35 @@ class ChartMonitor<S, A extends Action<unknown>> extends PureComponent<
isSorted: false, isSorted: false,
heightBetweenNodesCoeff: 1, heightBetweenNodesCoeff: 1,
widthBetweenNodesCoeff: 1.3, widthBetweenNodesCoeff: 1.3,
tooltipOptions, tooltipOptions: {
style: this.getChartStyle() as unknown as disabled: false,
| { [key: string]: Primitive } offset: { left: 30, top: 10 },
| undefined, 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); return deepmerge(defaultOptions, props);
@ -198,10 +147,9 @@ class ChartMonitor<S, A extends Action<unknown>> extends PureComponent<
render() { render() {
const theme = this.getTheme(); const theme = this.getTheme();
const ChartAsAny = Chart as any;
return ( return (
<div style={{ ...styles.container, backgroundColor: theme.base07 }}> <div style={{ ...styles.container, backgroundColor: theme.base07 }}>
<ChartAsAny {...this.getChartOptions()} /> <Chart {...this.getChartOptions()} />
</div> </div>
); );
} }

View File

@ -1,5 +1,16 @@
# Change Log # 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 ## 1.0.7
### Patch Changes ### Patch Changes

View File

@ -9,7 +9,11 @@ function createWindow() {
height: 600, 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(() => { app.whenReady().then(() => {

View File

@ -1,3 +1,3 @@
#! /usr/bin/env node #! /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', preset: 'ts-jest',
transform: { transform: {
'^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.test.json' }], '^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.test.json' }],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,23 +1,37 @@
import path from 'path'; 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'; 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) { export default function connector(options: AGServer.AGServerOptions) {
const dbOptions = options.dbOptions as Knex.Config; const dbOptions = options.dbOptions as Knex.Config;
dbOptions.useNullAsDefault = true; dbOptions.useNullAsDefault = true;
if (!(dbOptions as any).migrate) { if (!(dbOptions as any).migrate) {
return knexModule(dbOptions); return (knex as unknown as KnexFunction)(dbOptions);
} }
dbOptions.migrations = { directory: path.resolve(__dirname, 'migrations') }; dbOptions.migrations = {
dbOptions.seeds = { directory: path.resolve(__dirname, 'seeds') }; directory: path.join(
const knex = knexModule(dbOptions); 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 */ /* eslint-disable no-console */
knex.migrate knexInstance.migrate
.latest({ loadExtensions: ['.js'] }) .latest({ loadExtensions: ['.js'] })
.then(function () { .then(function () {
return knex.seed.run({ loadExtensions: ['.js'] }); return knexInstance.seed.run({ loadExtensions: ['.js'] });
}) })
.then(function () { .then(function () {
console.log(' \x1b[0;32m[Done]\x1b[0m Migrations are finished\n'); 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 */ /* 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 express from 'express';
import http from 'http'; import http from 'http';
import getPort from 'getport'; import getPort from 'get-port';
import socketClusterServer from 'socketcluster-server'; import socketClusterServer from 'socketcluster-server';
import getOptions, { Options } from './options'; import getOptions from './options.js';
import routes from './routes'; import routes from './routes.js';
import createStore from './store'; import createStore from './store.js';
// var LOG_LEVEL_NONE = 0; // const LOG_LEVEL_NONE = 0;
const LOG_LEVEL_ERROR = 1; // const LOG_LEVEL_ERROR = 1;
const LOG_LEVEL_WARN = 2; const LOG_LEVEL_WARN = 2;
const LOG_LEVEL_INFO = 3; const LOG_LEVEL_INFO = 3;
export interface ExtendedOptions extends Options { export default async function (argv: { [arg: string]: any }): Promise<{
allowClientPublish: boolean;
}
export default function (argv: { [arg: string]: any }): Promise<{
portAlreadyUsed?: boolean; portAlreadyUsed?: boolean;
listener: (eventName: 'ready') => { once(): Promise<void> }; ready: Promise<void>;
}> { }> {
const options = Object.assign(getOptions(argv), { const options = Object.assign(getOptions(argv), {
allowClientPublish: false, allowClientPublish: false,
@ -25,131 +21,117 @@ export default function (argv: { [arg: string]: any }): Promise<{
const port = options.port; const port = options.port;
const logLevel = const logLevel =
options.logLevel === undefined ? LOG_LEVEL_INFO : options.logLevel; options.logLevel === undefined ? LOG_LEVEL_INFO : options.logLevel;
return new Promise(function (resolve) { // Check port already used
// Check port already used const p = await getPort({ port });
getPort(port, function (err, p) { if (port !== p) {
/* eslint-disable no-console */ if (logLevel >= LOG_LEVEL_WARN) {
if (err) { console.log(`[ReduxDevTools] Server port ${port} is already used.`);
if (logLevel >= LOG_LEVEL_ERROR) { }
console.error(err); return {
} portAlreadyUsed: true,
return; ready: Promise.resolve(),
} };
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);
const app = express(); if (logLevel >= LOG_LEVEL_INFO) {
httpServer.on('request', app); console.log('[ReduxDevTools] Start server...');
const store = createStore(options); console.log('-'.repeat(80) + '\n');
app.use(routes(options, store, agServer)); }
const httpServer = http.createServer();
const agServer = socketClusterServer.attach(httpServer, options);
agServer.setMiddleware( const app = express();
agServer.MIDDLEWARE_INBOUND, httpServer.on('request', app);
// eslint-disable-next-line @typescript-eslint/no-misused-promises const store = createStore(options);
async (middlewareStream) => { app.use(routes(options, store, agServer));
for await (const action of middlewareStream) {
if (action.type === action.TRANSMIT) { agServer.setMiddleware(
const channel = action.receiver; agServer.MIDDLEWARE_INBOUND,
const data = action.data; // eslint-disable-next-line @typescript-eslint/no-misused-promises
if ( async (middlewareStream) => {
channel.substring(0, 3) === 'sc-' || for await (const action of middlewareStream) {
channel === 'respond' || if (action.type === action.TRANSMIT) {
channel === 'log' const channel = action.receiver;
) { const data = action.data;
void agServer.exchange.transmitPublish(channel, data); if (
} else if (channel === 'log-noid') { channel.substring(0, 3) === 'sc-' ||
void agServer.exchange.transmitPublish('log', { channel === 'respond' ||
id: action.socket.id, channel === 'log'
data: data, ) {
}); void agServer.exchange.transmitPublish(channel, data);
} } else if (channel === 'log-noid') {
} else if (action.type === action.SUBSCRIBE) { void agServer.exchange.transmitPublish('log', {
if (action.channel === 'report') { id: action.socket.id,
store data: data,
.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();
}
} }
); } else if (action.type === action.SUBSCRIBE) {
if (action.channel === 'report') {
void (async () => { store
for await (const { socket } of agServer.listener('connection')) { .list()
let channelToWatch: string, channelToEmit: string; .then(function (data) {
void (async () => { void agServer.exchange.transmitPublish('report', {
for await (const request of socket.procedure('login')) { type: 'list',
const credentials = request.data; data: 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',
}); });
} })
})(); .catch(function (error) {
console.error(error); // eslint-disable-line no-console
});
} }
})(); }
action.allow();
httpServer.listen(options.port);
// @ts-expect-error Shouldn't there be a 'ready' event?
resolve(agServer);
} }
/* 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 { interface ProtocolOptions {
key: string | undefined; key: string | undefined;
@ -31,9 +31,14 @@ export interface Options {
export default function getOptions(argv: { [arg: string]: any }): Options { export default function getOptions(argv: { [arg: string]: any }): Options {
let dbOptions = argv.dbOptions; let dbOptions = argv.dbOptions;
if (typeof dbOptions === 'string') { 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') { } else if (typeof dbOptions === 'undefined') {
dbOptions = require('../defaultDbOptions.json'); dbOptions = JSON.parse(
fs.readFileSync(
new URL('../defaultDbOptions.json', import.meta.url),
'utf8'
)
);
} }
return { return {

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,17 @@
# Change Log # 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 ## 3.2.3
### Patch Changes ### Patch Changes

View File

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

View File

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

View File

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

View File

@ -1,13 +1,13 @@
import assign from './utils/assign'; import assign from './utils/assign';
import { import { compose } from 'redux';
import type {
Action, Action,
compose,
Dispatch, Dispatch,
PreloadedState, PreloadedState,
Reducer, Reducer,
StoreEnhancer, StoreEnhancer,
} from 'redux'; } from 'redux';
import { Config, EnhancerOptions } from './index'; import type { Config, EnhancerOptions, InferComposedStoreExt } from './index';
function enhancer(options?: EnhancerOptions): StoreEnhancer { function enhancer(options?: EnhancerOptions): StoreEnhancer {
const config: Config = options || {}; const config: Config = options || {};
@ -40,20 +40,28 @@ function enhancer(options?: EnhancerOptions): StoreEnhancer {
} }
function composeWithEnhancer(config?: EnhancerOptions) { function composeWithEnhancer(config?: EnhancerOptions) {
return function (...funcs: StoreEnhancer[]) { return function (...funcs: StoreEnhancer<unknown>[]) {
return compose(compose(...funcs), enhancer(config)); return compose(compose(...funcs), enhancer(config));
}; };
} }
export function composeWithDevTools( export function composeWithDevTools(
config: Config config: Config
): (...funcs: StoreEnhancer[]) => StoreEnhancer; ): <StoreEnhancers extends readonly StoreEnhancer<unknown>[]>(
export function composeWithDevTools(...funcs: StoreEnhancer[]): StoreEnhancer; ...funcs: StoreEnhancers
export function composeWithDevTools(...funcs: [Config] | StoreEnhancer[]) { ) => 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 (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__) {
if (funcs.length === 0) return enhancer(); if (funcs.length === 0) return enhancer();
if (typeof funcs[0] === 'object') return composeWithEnhancer(funcs[0]); 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; 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 * as logOnly from './logOnly';
import { Config, EnhancerOptions } from './index'; import type {
Config,
EnhancerOptions,
InferComposedStoreExt,
ReduxDevtoolsExtensionCompose,
} from './index';
declare const process: { declare const process: {
env: { env: {
@ -10,15 +16,21 @@ declare const process: {
function extensionComposeStub( function extensionComposeStub(
config: Config config: Config
): (...funcs: StoreEnhancer[]) => StoreEnhancer; ): <StoreEnhancers extends readonly StoreEnhancer<unknown>[]>(
function extensionComposeStub(...funcs: StoreEnhancer[]): StoreEnhancer; ...funcs: StoreEnhancers
function extensionComposeStub(...funcs: [Config] | StoreEnhancer[]) { ) => 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 (funcs.length === 0) return undefined;
if (typeof funcs[0] === 'object') return compose; 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' process.env.NODE_ENV === 'production'
? logOnly.composeWithDevTools ? logOnly.composeWithDevTools
: typeof window !== 'undefined' && : typeof window !== 'undefined' &&

View File

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

View File

@ -5,7 +5,8 @@ module.exports = {
'\\.css$': '<rootDir>/test/__mocks__/styleMock.ts', '\\.css$': '<rootDir>/test/__mocks__/styleMock.ts',
}, },
transform: { transform: {
'^.+\\.jsx?$': 'babel-jest',
'^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.test.json' }], '^.+\\.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" "prepublish": "pnpm run type-check && pnpm run lint && pnpm run test"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.6", "@babel/runtime": "^7.21.0",
"@redux-devtools/ui": "^1.3.0", "@redux-devtools/ui": "^1.3.0",
"@types/prop-types": "^15.7.5", "@types/prop-types": "^15.7.5",
"es6template": "^1.0.5", "es6template": "^1.0.5",
@ -55,37 +55,37 @@
"simple-diff": "^1.6.0" "simple-diff": "^1.6.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.19.3", "@babel/cli": "^7.21.0",
"@babel/core": "^7.20.5", "@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1", "@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-env": "^7.20.2",
"@babel/preset-react": "^7.18.6", "@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/core": "^3.13.0",
"@redux-devtools/inspector-monitor": "^3.0.0", "@redux-devtools/inspector-monitor": "^3.0.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^14.0.0",
"@types/es6template": "^1.0.0", "@types/es6template": "^1.0.1",
"@types/jest": "^29.2.4", "@types/jest": "^29.4.0",
"@types/jsan": "^3.1.2", "@types/jsan": "^3.1.2",
"@types/object-path": "^0.11.1", "@types/object-path": "^0.11.1",
"@types/react": "^18.0.26", "@types/react": "^18.0.28",
"@types/simple-diff": "^1.6.1", "@types/simple-diff": "^1.6.1",
"@typescript-eslint/eslint-plugin": "^5.47.0", "@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.47.0", "@typescript-eslint/parser": "^5.54.0",
"eslint": "^8.30.0", "eslint": "^8.35.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.1.7", "eslint-plugin-jest": "^27.2.1",
"eslint-plugin-react": "^7.31.11", "eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.3.1", "jest": "^29.4.3",
"jest-environment-jsdom": "^29.3.1", "jest-environment-jsdom": "^29.4.3",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"redux": "^4.2.0", "redux": "^4.2.1",
"rimraf": "^3.0.2", "rimraf": "^4.1.3",
"ts-jest": "^29.0.3", "ts-jest": "^29.0.5",
"typescript": "~4.9.4" "typescript": "~4.9.5"
}, },
"peerDependencies": { "peerDependencies": {
"@redux-devtools/inspector-monitor": "^3.0.0", "@redux-devtools/inspector-monitor": "^3.0.0",
@ -93,6 +93,6 @@
"@types/styled-components": "^5.1.26", "@types/styled-components": "^5.1.26",
"react": "^16.3.0 || ^17.0.0 || ^18.0.0", "react": "^16.3.0 || ^17.0.0 || ^18.0.0",
"redux": "^3.4.0 || ^4.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": { "dependencies": {
"@babel/code-frame": "^7.18.6", "@babel/code-frame": "^7.18.6",
"@babel/runtime": "^7.20.6", "@babel/runtime": "^7.21.0",
"@types/chrome": "^0.0.206", "@types/chrome": "^0.0.218",
"anser": "^2.1.1", "anser": "^2.1.1",
"html-entities": "^2.3.3", "html-entities": "^2.3.3",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
@ -40,40 +40,40 @@
"source-map": "^0.5.7" "source-map": "^0.5.7"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.19.3", "@babel/cli": "^7.21.0",
"@babel/core": "^7.20.5", "@babel/core": "^7.21.0",
"@babel/eslint-parser": "^7.19.1", "@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-env": "^7.20.2",
"@babel/preset-react": "^7.18.6", "@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/core": "^3.13.0",
"@redux-devtools/inspector-monitor": "^3.0.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/babel__code-frame": "^7.0.3",
"@types/html-entities": "^1.3.4", "@types/html-entities": "^1.3.4",
"@types/jest": "^29.2.4", "@types/jest": "^29.4.0",
"@types/node": "^18.11.17", "@types/node": "^18.14.4",
"@types/path-browserify": "^1.0.0", "@types/path-browserify": "^1.0.0",
"@types/react": "^18.0.26", "@types/react": "^18.0.28",
"@types/redux-devtools-themes": "^1.0.0", "@types/redux-devtools-themes": "^1.0.0",
"@types/source-map": "0.5.2", "@types/source-map": "0.5.2",
"@typescript-eslint/eslint-plugin": "^5.47.0", "@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.47.0", "@typescript-eslint/parser": "^5.54.0",
"eslint": "^8.30.0", "eslint": "^8.35.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.1.7", "eslint-plugin-jest": "^27.2.1",
"eslint-plugin-react": "^7.31.11", "eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.3.1", "jest": "^29.4.3",
"jest-environment-jsdom": "^29.3.1", "jest-environment-jsdom": "^29.4.3",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-test-renderer": "^18.2.0", "react-test-renderer": "^18.2.0",
"redux": "^4.2.0", "redux": "^4.2.1",
"rimraf": "^3.0.2", "rimraf": "^4.1.3",
"ts-jest": "^29.0.3", "ts-jest": "^29.0.5",
"typescript": "~4.9.4" "typescript": "~4.9.5"
}, },
"peerDependencies": { "peerDependencies": {
"@redux-devtools/inspector-monitor": "^3.0.0", "@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 # Change Log
## 3.0.2
### Patch Changes
- Updated dependencies [81926f32]
- react-json-tree@0.18.0
## 3.0.1 ## 3.0.1
### Patch Changes ### Patch Changes

View File

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

View File

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

View File

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

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