mirror of
				https://github.com/reduxjs/redux-devtools.git
				synced 2025-10-30 23:47:35 +03:00 
			
		
		
		
	feat(d3-state-visualizer): convert to TypeScript (#640)
* feat(d3-state-visualizer): convert to TypeScript * dep * Odd
This commit is contained in:
		
							parent
							
								
									3b580dad4c
								
							
						
					
					
						commit
						0c78a5a9a7
					
				|  | @ -1,7 +1,4 @@ | ||||||
| { | { | ||||||
|   "presets": ["@babel/preset-env"], |   "presets": ["@babel/preset-env", "@babel/preset-typescript"], | ||||||
|   "plugins": [ |   "plugins": ["@babel/plugin-proposal-class-properties"] | ||||||
|     "@babel/plugin-proposal-class-properties", |  | ||||||
|     "@babel/plugin-proposal-export-default-from" |  | ||||||
|   ] |  | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										3
									
								
								packages/d3-state-visualizer/.eslintignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								packages/d3-state-visualizer/.eslintignore
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | examples | ||||||
|  | lib | ||||||
|  | dist | ||||||
							
								
								
									
										21
									
								
								packages/d3-state-visualizer/.eslintrc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								packages/d3-state-visualizer/.eslintrc.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | module.exports = { | ||||||
|  |   extends: '../../.eslintrc', | ||||||
|  |   overrides: [ | ||||||
|  |     { | ||||||
|  |       files: ['*.ts'], | ||||||
|  |       extends: '../../eslintrc.ts.base.json', | ||||||
|  |       parserOptions: { | ||||||
|  |         tsconfigRootDir: __dirname, | ||||||
|  |         project: ['./tsconfig.json'], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       files: ['webpack.config.umd.ts'], | ||||||
|  |       extends: '../../eslintrc.ts.base.json', | ||||||
|  |       parserOptions: { | ||||||
|  |         tsconfigRootDir: __dirname, | ||||||
|  |         project: ['./tsconfig.webpack.json'], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  | }; | ||||||
|  | @ -2,24 +2,6 @@ | ||||||
|   "name": "d3-state-visualizer", |   "name": "d3-state-visualizer", | ||||||
|   "version": "1.3.4", |   "version": "1.3.4", | ||||||
|   "description": "Visualize your app state with a range of reusable charts", |   "description": "Visualize your app state with a range of reusable charts", | ||||||
|   "main": "lib/index.js", |  | ||||||
|   "files": [ |  | ||||||
|     "dist", |  | ||||||
|     "lib", |  | ||||||
|     "src" |  | ||||||
|   ], |  | ||||||
|   "scripts": { |  | ||||||
|     "clean": "rimraf lib dist", |  | ||||||
|     "build": "babel src --out-dir lib", |  | ||||||
|     "build:umd": "webpack src/index.js -o dist/d3-state-visualizer.js --config webpack.config.development.js", |  | ||||||
|     "build:umd:min": "webpack src/index.js -o dist/d3-state-visualizer.min.js --config webpack.config.production.js", |  | ||||||
|     "prepare": "npm run build", |  | ||||||
|     "prepublishOnly": "npm run clean && npm run build && npm run build:umd && npm run build:umd:min" |  | ||||||
|   }, |  | ||||||
|   "repository": { |  | ||||||
|     "type": "git", |  | ||||||
|     "url": "https://github.com/reduxjs/redux-devtools.git" |  | ||||||
|   }, |  | ||||||
|   "keywords": [ |   "keywords": [ | ||||||
|     "d3", |     "d3", | ||||||
|     "state", |     "state", | ||||||
|  | @ -27,27 +9,46 @@ | ||||||
|     "tree", |     "tree", | ||||||
|     "visualization" |     "visualization" | ||||||
|   ], |   ], | ||||||
|   "author": "romseguy", |   "homepage": "https://github.com/reduxjs/redux-devtools/tree/master/packages/d3-state-visualizer", | ||||||
|   "license": "MIT", |  | ||||||
|   "bugs": { |   "bugs": { | ||||||
|     "url": "https://github.com/reduxjs/redux-devtools/issues" |     "url": "https://github.com/reduxjs/redux-devtools/issues" | ||||||
|   }, |   }, | ||||||
|   "homepage": "https://github.com/reduxjs/redux-devtools", |   "license": "MIT", | ||||||
|   "devDependencies": { |   "author": "romseguy", | ||||||
|     "@babel/cli": "^7.10.5", |   "files": [ | ||||||
|     "@babel/core": "^7.11.1", |     "dist", | ||||||
|     "@babel/plugin-proposal-class-properties": "^7.10.4", |     "lib", | ||||||
|     "@babel/plugin-proposal-export-default-from": "^7.10.4", |     "src" | ||||||
|     "@babel/preset-env": "^7.11.0", |   ], | ||||||
|     "babel-loader": "^8.1.0", |   "main": "lib/index.js", | ||||||
|     "rimraf": "^3.0.2", |   "types": "lib/index.d.ts", | ||||||
|     "webpack": "^4.44.1" |   "repository": { | ||||||
|  |     "type": "git", | ||||||
|  |     "url": "https://github.com/reduxjs/redux-devtools.git" | ||||||
|  |   }, | ||||||
|  |   "scripts": { | ||||||
|  |     "build": "npm run build:types && npm run build:js && npm run build:umd && npm run build:umd:min", | ||||||
|  |     "build:types": "tsc --emitDeclarationOnly", | ||||||
|  |     "build:js": "babel src --out-dir lib --extensions \".ts\" --source-maps inline", | ||||||
|  |     "build:umd": "webpack --env.production --progress --config webpack.config.umd.ts", | ||||||
|  |     "build:umd:min": "webpack --env.production --progress --config webpack.config.umd.ts", | ||||||
|  |     "clean": "rimraf lib dist", | ||||||
|  |     "lint": "eslint . --ext .ts", | ||||||
|  |     "lint:fix": "eslint . --ext .ts --fix", | ||||||
|  |     "type-check": "tsc --noEmit", | ||||||
|  |     "type-check:watch": "npm run type-check -- --watch", | ||||||
|  |     "preversion": "npm run type-check && npm run lint", | ||||||
|  |     "prepublishOnly": "npm run clean && npm run build" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|  |     "@types/d3": "^3.5.43", | ||||||
|     "d3": "^3.5.17", |     "d3": "^3.5.17", | ||||||
|     "d3tooltip": "^1.2.3", |     "d3tooltip": "^1.2.3", | ||||||
|     "deepmerge": "^4.2.2", |     "deepmerge": "^4.2.2", | ||||||
|     "map2tree": "^1.4.2", |     "map2tree": "^1.4.2", | ||||||
|     "ramda": "^0.27.1" |     "ramda": "^0.27.1" | ||||||
|  |   }, | ||||||
|  |   "devDependencies": { | ||||||
|  |     "@types/ramda": "^0.27.17" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1 +0,0 @@ | ||||||
| export tree from './tree/tree'; |  | ||||||
							
								
								
									
										1
									
								
								packages/d3-state-visualizer/src/charts/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								packages/d3-state-visualizer/src/charts/index.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | export { default as tree } from './tree/tree'; | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| function sortObject(obj, strict) { | function sortObject(obj: unknown, strict?: boolean) { | ||||||
|   if (obj instanceof Array) { |   if (obj instanceof Array) { | ||||||
|     let ary; |     let ary; | ||||||
|     if (strict) { |     if (strict) { | ||||||
|  | @ -10,16 +10,16 @@ function sortObject(obj, strict) { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   if (obj && typeof obj === 'object') { |   if (obj && typeof obj === 'object') { | ||||||
|     const tObj = {}; |     const tObj: { [key: string]: unknown } = {}; | ||||||
|     Object.keys(obj) |     Object.keys(obj) | ||||||
|       .sort() |       .sort() | ||||||
|       .forEach((key) => (tObj[key] = sortObject(obj[key]))); |       .forEach((key) => (tObj[key] = sortObject(obj[key as keyof typeof obj]))); | ||||||
|     return tObj; |     return tObj; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return obj; |   return obj; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default function sortAndSerialize(obj) { | export default function sortAndSerialize(obj: unknown) { | ||||||
|   return JSON.stringify(sortObject(obj, true), undefined, 2); |   return JSON.stringify(sortObject(obj, true), undefined, 2); | ||||||
| } | } | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import d3 from 'd3'; | import d3, { ZoomEvent, Primitive } from 'd3'; | ||||||
| import { isEmpty } from 'ramda'; | import { isEmpty } from 'ramda'; | ||||||
| import map2tree from 'map2tree'; | import map2tree from 'map2tree'; | ||||||
| import deepmerge from 'deepmerge'; | import deepmerge from 'deepmerge'; | ||||||
|  | @ -10,7 +10,63 @@ import { | ||||||
| } from './utils'; | } from './utils'; | ||||||
| import d3tooltip from 'd3tooltip'; | import d3tooltip from 'd3tooltip'; | ||||||
| 
 | 
 | ||||||
| const defaultOptions = { | interface Options { | ||||||
|  |   // eslint-disable-next-line @typescript-eslint/ban-types
 | ||||||
|  |   state?: {}; | ||||||
|  |   // 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; | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const defaultOptions: Options = { | ||||||
|   state: undefined, |   state: undefined, | ||||||
|   rootKeyName: 'state', |   rootKeyName: 'state', | ||||||
|   pushMethod: 'push', |   pushMethod: 'push', | ||||||
|  | @ -50,11 +106,13 @@ const defaultOptions = { | ||||||
|   widthBetweenNodesCoeff: 1, |   widthBetweenNodesCoeff: 1, | ||||||
|   transitionDuration: 750, |   transitionDuration: 750, | ||||||
|   blinkDuration: 100, |   blinkDuration: 100, | ||||||
|   onClickText: () => {}, |   onClickText: () => { | ||||||
|  |     // noop
 | ||||||
|  |   }, | ||||||
|   tooltipOptions: { |   tooltipOptions: { | ||||||
|     disabled: false, |     disabled: false, | ||||||
|     left: undefined, |     left: undefined, | ||||||
|     right: undefined, |     top: undefined, | ||||||
|     offset: { |     offset: { | ||||||
|       left: 0, |       left: 0, | ||||||
|       top: 0, |       top: 0, | ||||||
|  | @ -63,7 +121,27 @@ const defaultOptions = { | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default function (DOMNode, options = {}) { | export interface NodeWithId { | ||||||
|  |   name: string; | ||||||
|  |   children?: NodeWithId[] | null; | ||||||
|  |   _children?: NodeWithId[] | null; | ||||||
|  |   value?: unknown; | ||||||
|  |   id: string; | ||||||
|  | 
 | ||||||
|  |   parent?: NodeWithId; | ||||||
|  |   depth?: number; | ||||||
|  |   x?: number; | ||||||
|  |   y?: number; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface NodePosition { | ||||||
|  |   parentId: string | null | undefined; | ||||||
|  |   id: string; | ||||||
|  |   x: number | undefined; | ||||||
|  |   y: number | undefined; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default function (DOMNode: HTMLElement, options: Partial<Options> = {}) { | ||||||
|   const { |   const { | ||||||
|     id, |     id, | ||||||
|     style, |     style, | ||||||
|  | @ -89,16 +167,19 @@ export default function (DOMNode, options = {}) { | ||||||
|   const fullWidth = size; |   const fullWidth = size; | ||||||
|   const fullHeight = size * aspectRatio; |   const fullHeight = size * aspectRatio; | ||||||
| 
 | 
 | ||||||
|   const attr = { |   const attr: { [key: string]: Primitive } = { | ||||||
|     id, |     id, | ||||||
|     preserveAspectRatio: 'xMinYMin slice', |     preserveAspectRatio: 'xMinYMin slice', | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   if (!style.width) { |   if (!((style as unknown) as { [key: string]: Primitive }).width) { | ||||||
|     attr.width = fullWidth; |     attr.width = fullWidth; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   if (!style.width || !style.height) { |   if ( | ||||||
|  |     !((style as unknown) as { [key: string]: Primitive }).width || | ||||||
|  |     !((style as unknown) as { [key: string]: Primitive }).height | ||||||
|  |   ) { | ||||||
|     attr.viewBox = `0 0 ${fullWidth} ${fullHeight}`; |     attr.viewBox = `0 0 ${fullWidth} ${fullHeight}`; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -107,11 +188,16 @@ export default function (DOMNode, options = {}) { | ||||||
|   const vis = root |   const vis = root | ||||||
|     .append('svg') |     .append('svg') | ||||||
|     .attr(attr) |     .attr(attr) | ||||||
|     .style({ cursor: '-webkit-grab', ...style }) |     .style(({ cursor: '-webkit-grab', ...style } as unknown) as { | ||||||
|  |       [key: string]: Primitive; | ||||||
|  |     }) | ||||||
|     .call( |     .call( | ||||||
|       zoom.on('zoom', () => { |       zoom.on('zoom', () => { | ||||||
|         const { translate, scale } = d3.event; |         const { translate, scale } = d3.event as ZoomEvent; | ||||||
|         vis.attr('transform', `translate(${translate})scale(${scale})`); |         vis.attr( | ||||||
|  |           'transform', | ||||||
|  |           `translate(${translate.toString()})scale(${scale})` | ||||||
|  |         ); | ||||||
|       }) |       }) | ||||||
|     ) |     ) | ||||||
|     .append('g') |     .append('g') | ||||||
|  | @ -122,18 +208,21 @@ export default function (DOMNode, options = {}) { | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|   let layout = d3.layout.tree().size([width, height]); |   let layout = d3.layout.tree().size([width, height]); | ||||||
|   let data; |   let data: NodeWithId; | ||||||
| 
 | 
 | ||||||
|   if (isSorted) { |   if (isSorted) { | ||||||
|     layout.sort((a, b) => |     layout.sort((a, b) => | ||||||
|       b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1 |       (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);
 | ||||||
|   // helps animating transitions
 |   // helps animating transitions
 | ||||||
|   let previousNodePositionsById = { |   let previousNodePositionsById: { [nodeId: string]: NodePosition } = { | ||||||
|     root: { |     root: { | ||||||
|       id: 'root', |       id: 'root', | ||||||
|       parentId: null, |       parentId: null, | ||||||
|  | @ -145,10 +234,14 @@ export default function (DOMNode, options = {}) { | ||||||
|   // traverses a map with node positions by going through the chain
 |   // traverses a map with node positions by going through the chain
 | ||||||
|   // 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(nodePositionsById, nodeId, filter) { |   function findParentNodePosition( | ||||||
|  |     nodePositionsById: { [nodeId: string]: NodePosition }, | ||||||
|  |     nodeId: string, | ||||||
|  |     filter: (nodePosition: NodePosition) => boolean | ||||||
|  |   ) { | ||||||
|     let currentPosition = nodePositionsById[nodeId]; |     let currentPosition = nodePositionsById[nodeId]; | ||||||
|     while (currentPosition) { |     while (currentPosition) { | ||||||
|       currentPosition = nodePositionsById[currentPosition.parentId]; |       currentPosition = nodePositionsById[currentPosition.parentId!]; | ||||||
|       if (!currentPosition) { |       if (!currentPosition) { | ||||||
|         return null; |         return null; | ||||||
|       } |       } | ||||||
|  | @ -160,14 +253,18 @@ export default function (DOMNode, options = {}) { | ||||||
| 
 | 
 | ||||||
|   return function renderChart(nextState = tree || state) { |   return function renderChart(nextState = tree || state) { | ||||||
|     data = !tree |     data = !tree | ||||||
|       ? map2tree(nextState, { key: rootKeyName, pushMethod }) |       ? // eslint-disable-next-line @typescript-eslint/ban-types
 | ||||||
|       : nextState; |         (map2tree(nextState as {}, { | ||||||
|  |           key: rootKeyName, | ||||||
|  |           pushMethod, | ||||||
|  |         }) as NodeWithId) | ||||||
|  |       : (nextState as NodeWithId); | ||||||
| 
 | 
 | ||||||
|     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; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let nodeIndex = 0; |     let nodeIndex = 0; | ||||||
|  | @ -191,13 +288,13 @@ export default function (DOMNode, options = {}) { | ||||||
|           : null |           : null | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     /*eslint-disable*/ |  | ||||||
|     update(); |     update(); | ||||||
|     /*eslint-enable*/ |  | ||||||
| 
 | 
 | ||||||
|     function update() { |     function update() { | ||||||
|       // path generator for links
 |       // path generator for links
 | ||||||
|       const diagonal = d3.svg.diagonal().projection((d) => [d.y, d.x]); |       const diagonal = d3.svg | ||||||
|  |         .diagonal<NodePosition>() | ||||||
|  |         .projection((d) => [d.y!, 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)); | ||||||
| 
 | 
 | ||||||
|  | @ -206,12 +303,12 @@ export default function (DOMNode, options = {}) { | ||||||
|         width, |         width, | ||||||
|       ]); |       ]); | ||||||
| 
 | 
 | ||||||
|       let nodes = layout.nodes(data); |       const nodes = layout.nodes(data as d3.layout.tree.Node) as NodeWithId[]; | ||||||
|       let links = layout.links(nodes); |       const links = layout.links(nodes as d3.layout.tree.Node[]); | ||||||
| 
 | 
 | ||||||
|       nodes.forEach( |       nodes.forEach( | ||||||
|         (node) => |         (node) => | ||||||
|           (node.y = node.depth * (maxLabelLength * 7 * widthBetweenNodesCoeff)) |           (node.y = node.depth! * (maxLabelLength * 7 * widthBetweenNodesCoeff)) | ||||||
|       ); |       ); | ||||||
| 
 | 
 | ||||||
|       const nodePositions = nodes.map((n) => ({ |       const nodePositions = nodes.map((n) => ({ | ||||||
|  | @ -220,15 +317,18 @@ export default function (DOMNode, options = {}) { | ||||||
|         x: n.x, |         x: n.x, | ||||||
|         y: n.y, |         y: n.y, | ||||||
|       })); |       })); | ||||||
|       const nodePositionsById = {}; |       const nodePositionsById: { [nodeId: string]: NodePosition } = {}; | ||||||
|       nodePositions.forEach((node) => (nodePositionsById[node.id] = node)); |       nodePositions.forEach((node) => (nodePositionsById[node.id] = node)); | ||||||
| 
 | 
 | ||||||
|       // process the node selection
 |       // process the node selection
 | ||||||
|       let node = vis |       const node = vis | ||||||
|         .selectAll('g.node') |         .selectAll('g.node') | ||||||
|         .property('__oldData__', (d) => d) |         .property('__oldData__', (d: NodeWithId) => d) | ||||||
|         .data(nodes, (d) => d.id || (d.id = ++nodeIndex)); |         .data( | ||||||
|       let nodeEnter = node |           nodes, | ||||||
|  |           (d) => d.id || (d.id = (++nodeIndex as unknown) as string) | ||||||
|  |         ); | ||||||
|  |       const nodeEnter = node | ||||||
|         .enter() |         .enter() | ||||||
|         .append('g') |         .append('g') | ||||||
|         .attr({ |         .attr({ | ||||||
|  | @ -237,35 +337,39 @@ export default function (DOMNode, options = {}) { | ||||||
|             const position = findParentNodePosition( |             const position = findParentNodePosition( | ||||||
|               nodePositionsById, |               nodePositionsById, | ||||||
|               d.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: style.text.colors.default, |           fill: style.text.colors.default, | ||||||
|           cursor: 'pointer', |           cursor: 'pointer', | ||||||
|         }) |         }) | ||||||
|         .on({ |         .on('mouseover', function mouseover(this: any) { | ||||||
|           mouseover: function mouseover() { |           d3.select(this).style({ | ||||||
|             d3.select(this).style({ |             fill: style.text.colors.hover, | ||||||
|               fill: style.text.colors.hover, |           }); | ||||||
|             }); |         }) | ||||||
|           }, |         .on('mouseout', function mouseout(this: any) { | ||||||
|           mouseout: function mouseout() { |           d3.select(this).style({ | ||||||
|             d3.select(this).style({ |             fill: style.text.colors.default, | ||||||
|               fill: style.text.colors.default, |           }); | ||||||
|             }); |  | ||||||
|           }, |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|       if (!tooltipOptions.disabled) { |       if (!tooltipOptions.disabled) { | ||||||
|         nodeEnter.call( |         nodeEnter.call( | ||||||
|           d3tooltip(d3, 'tooltip', { ...tooltipOptions, root }) |           d3tooltip(d3, 'tooltip', { ...tooltipOptions, root }) | ||||||
|             .text((d, i) => getTooltipString(d, i, tooltipOptions)) |             .text((d, i) => | ||||||
|  |               getTooltipString( | ||||||
|  |                 d, | ||||||
|  |                 i, | ||||||
|  |                 (tooltipOptions as unknown) as { indentationSize: number } | ||||||
|  |               ) | ||||||
|  |             ) | ||||||
|             .style(tooltipOptions.style) |             .style(tooltipOptions.style) | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|  | @ -279,12 +383,10 @@ export default function (DOMNode, options = {}) { | ||||||
|           class: 'nodeCircle', |           class: 'nodeCircle', | ||||||
|           r: 0, |           r: 0, | ||||||
|         }) |         }) | ||||||
|         .on({ |         .on('click', (clickedNode) => { | ||||||
|           click: (clickedNode) => { |           if ((d3.event as Event).defaultPrevented) return; | ||||||
|             if (d3.event.defaultPrevented) return; |           toggleChildren(clickedNode); | ||||||
|             toggleChildren(clickedNode); |           update(); | ||||||
|             update(); |  | ||||||
|           }, |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|       nodeEnterInnerGroup |       nodeEnterInnerGroup | ||||||
|  | @ -299,9 +401,7 @@ export default function (DOMNode, options = {}) { | ||||||
|           'fill-opacity': 0, |           'fill-opacity': 0, | ||||||
|         }) |         }) | ||||||
|         .text((d) => d.name) |         .text((d) => d.name) | ||||||
|         .on({ |         .on('click', onClickText); | ||||||
|           click: onClickText, |  | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|       // update the text to reflect whether node has children or not
 |       // update the text to reflect whether node has children or not
 | ||||||
|       node.select('text').text((d) => d.name); |       node.select('text').text((d) => d.name); | ||||||
|  | @ -319,11 +419,11 @@ export default function (DOMNode, options = {}) { | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       // transition nodes to their new position
 |       // transition nodes to their new position
 | ||||||
|       let nodeUpdate = node |       const nodeUpdate = node | ||||||
|         .transition() |         .transition() | ||||||
|         .duration(transitionDuration) |         .duration(transitionDuration) | ||||||
|         .attr({ |         .attr({ | ||||||
|           transform: (d) => `translate(${d.y},${d.x})`, |           transform: (d) => `translate(${d.y!},${d.x!})`, | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|       // ensure circle radius is correct
 |       // ensure circle radius is correct
 | ||||||
|  | @ -334,7 +434,7 @@ export default function (DOMNode, options = {}) { | ||||||
|         .select('text') |         .select('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.children || d._children ? -1 : 1) * |               (d.children || d._children ? -1 : 1) * | ||||||
|               (this.getBBox().width / 2 + style.node.radius + 5); |               (this.getBBox().width / 2 + style.node.radius + 5); | ||||||
|  | @ -344,7 +444,7 @@ export default function (DOMNode, options = {}) { | ||||||
| 
 | 
 | ||||||
|       // blink updated nodes
 |       // blink updated nodes
 | ||||||
|       node |       node | ||||||
|         .filter(function flick(d) { |         .filter(function flick(this: any, 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,
 | ||||||
|  | @ -358,7 +458,7 @@ export default function (DOMNode, options = {}) { | ||||||
|         .style('opacity', '1'); |         .style('opacity', '1'); | ||||||
| 
 | 
 | ||||||
|       // transition exiting nodes to the parent's new position
 |       // transition exiting nodes to the parent's new position
 | ||||||
|       let nodeExit = node |       const nodeExit = node | ||||||
|         .exit() |         .exit() | ||||||
|         .transition() |         .transition() | ||||||
|         .duration(transitionDuration) |         .duration(transitionDuration) | ||||||
|  | @ -367,12 +467,12 @@ export default function (DOMNode, options = {}) { | ||||||
|             const position = findParentNodePosition( |             const position = findParentNodePosition( | ||||||
|               previousNodePositionsById, |               previousNodePositionsById, | ||||||
|               d.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(); | ||||||
|  | @ -382,7 +482,9 @@ export default function (DOMNode, options = {}) { | ||||||
|       nodeExit.select('text').style('fill-opacity', 0); |       nodeExit.select('text').style('fill-opacity', 0); | ||||||
| 
 | 
 | ||||||
|       // update the links
 |       // update the links
 | ||||||
|       let link = vis.selectAll('path.link').data(links, (d) => d.target.id); |       const link = vis | ||||||
|  |         .selectAll('path.link') | ||||||
|  |         .data(links, (d) => (d.target as NodeWithId).id); | ||||||
| 
 | 
 | ||||||
|       // enter any new links at the parent's previous position
 |       // enter any new links at the parent's previous position
 | ||||||
|       link |       link | ||||||
|  | @ -393,8 +495,8 @@ export default function (DOMNode, options = {}) { | ||||||
|           d: (d) => { |           d: (d) => { | ||||||
|             const position = findParentNodePosition( |             const position = findParentNodePosition( | ||||||
|               nodePositionsById, |               nodePositionsById, | ||||||
|               d.target.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]) || | ||||||
|  | @ -402,15 +504,18 @@ export default function (DOMNode, options = {}) { | ||||||
|             return diagonal({ |             return diagonal({ | ||||||
|               source: previousPosition, |               source: previousPosition, | ||||||
|               target: previousPosition, |               target: previousPosition, | ||||||
|             }); |             } as d3.svg.diagonal.Link<NodePosition>); | ||||||
|           }, |           }, | ||||||
|         }) |         }) | ||||||
|         .style(style.link); |         .style(style.link); | ||||||
| 
 | 
 | ||||||
|       // transition links to their new position
 |       // transition links to their new position
 | ||||||
|       link.transition().duration(transitionDuration).attr({ |       link | ||||||
|         d: diagonal, |         .transition() | ||||||
|       }); |         .duration(transitionDuration) | ||||||
|  |         .attr({ | ||||||
|  |           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 | ||||||
|  | @ -421,8 +526,8 @@ export default function (DOMNode, options = {}) { | ||||||
|           d: (d) => { |           d: (d) => { | ||||||
|             const position = findParentNodePosition( |             const position = findParentNodePosition( | ||||||
|               previousNodePositionsById, |               previousNodePositionsById, | ||||||
|               d.target.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]) || | ||||||
|  | @ -1,7 +1,8 @@ | ||||||
| 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'; | ||||||
| 
 | 
 | ||||||
| export function collapseChildren(node) { | export function collapseChildren(node: NodeWithId) { | ||||||
|   if (node.children) { |   if (node.children) { | ||||||
|     node._children = node.children; |     node._children = node.children; | ||||||
|     node._children.forEach(collapseChildren); |     node._children.forEach(collapseChildren); | ||||||
|  | @ -9,7 +10,7 @@ export function collapseChildren(node) { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function expandChildren(node) { | export function expandChildren(node: NodeWithId) { | ||||||
|   if (node._children) { |   if (node._children) { | ||||||
|     node.children = node._children; |     node.children = node._children; | ||||||
|     node.children.forEach(expandChildren); |     node.children.forEach(expandChildren); | ||||||
|  | @ -17,7 +18,7 @@ export function expandChildren(node) { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function toggleChildren(node) { | export function toggleChildren(node: NodeWithId) { | ||||||
|   if (node.children) { |   if (node.children) { | ||||||
|     node._children = node.children; |     node._children = node.children; | ||||||
|     node.children = null; |     node.children = null; | ||||||
|  | @ -28,16 +29,20 @@ export function toggleChildren(node) { | ||||||
|   return node; |   return node; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function visit(parent, visitFn, childrenFn) { | export function visit( | ||||||
|  |   parent: NodeWithId, | ||||||
|  |   visitFn: (parent: NodeWithId) => void, | ||||||
|  |   childrenFn: (parent: NodeWithId) => NodeWithId[] | null | undefined | ||||||
|  | ) { | ||||||
|   if (!parent) { |   if (!parent) { | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   visitFn(parent); |   visitFn(parent); | ||||||
| 
 | 
 | ||||||
|   let children = childrenFn(parent); |   const children = childrenFn(parent); | ||||||
|   if (children) { |   if (children) { | ||||||
|     let count = children.length; |     const count = children.length; | ||||||
| 
 | 
 | ||||||
|     for (let i = 0; i < count; i++) { |     for (let i = 0; i < count; i++) { | ||||||
|       visit(children[i], visitFn, childrenFn); |       visit(children[i], visitFn, childrenFn); | ||||||
|  | @ -45,10 +50,10 @@ export function visit(parent, visitFn, childrenFn) { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function getNodeGroupByDepthCount(rootNode) { | export function getNodeGroupByDepthCount(rootNode: NodeWithId) { | ||||||
|   let nodeGroupByDepthCount = [1]; |   const nodeGroupByDepthCount = [1]; | ||||||
| 
 | 
 | ||||||
|   const traverseFrom = function traverseFrom(node, depth = 0) { |   const traverseFrom = function traverseFrom(node: NodeWithId, depth = 0) { | ||||||
|     if (!node.children || node.children.length === 0) { |     if (!node.children || node.children.length === 0) { | ||||||
|       return 0; |       return 0; | ||||||
|     } |     } | ||||||
|  | @ -68,7 +73,11 @@ export function getNodeGroupByDepthCount(rootNode) { | ||||||
|   return nodeGroupByDepthCount; |   return nodeGroupByDepthCount; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function getTooltipString(node, i, { indentationSize = 4 }) { | export function getTooltipString( | ||||||
|  |   node: unknown, | ||||||
|  |   i: number | undefined, | ||||||
|  |   { indentationSize = 4 } | ||||||
|  | ) { | ||||||
|   if (!is(Object, node)) return ''; |   if (!is(Object, node)) return ''; | ||||||
| 
 | 
 | ||||||
|   const spacer = join('  '); |   const spacer = join('  '); | ||||||
|  | @ -76,10 +85,13 @@ export function getTooltipString(node, i, { indentationSize = 4 }) { | ||||||
|   const spaces2nbsp = replace(/\s{2}/g, spacer(new Array(indentationSize))); |   const spaces2nbsp = replace(/\s{2}/g, spacer(new Array(indentationSize))); | ||||||
|   const json2html = pipe(sortAndSerialize, cr2br, spaces2nbsp); |   const json2html = pipe(sortAndSerialize, cr2br, spaces2nbsp); | ||||||
| 
 | 
 | ||||||
|   const children = node.children || node._children; |   const children = (node as any).children || (node as any)._children; | ||||||
| 
 | 
 | ||||||
|   if (typeof node.value !== 'undefined') return json2html(node.value); |   if (typeof (node as any).value !== 'undefined') | ||||||
|   if (typeof node.object !== 'undefined') return json2html(node.object); |     return json2html((node as any).value); | ||||||
|   if (children && children.length) return 'childrenCount: ' + children.length; |   if (typeof (node as any).object !== 'undefined') | ||||||
|  |     return json2html((node as any).object); | ||||||
|  |   if (children && children.length) | ||||||
|  |     return `childrenCount: ${(children as unknown[]).length}`; | ||||||
|   return 'empty'; |   return 'empty'; | ||||||
| } | } | ||||||
							
								
								
									
										7
									
								
								packages/d3-state-visualizer/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/d3-state-visualizer/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | ||||||
|  | { | ||||||
|  |   "extends": "../../tsconfig.react.base.json", | ||||||
|  |   "compilerOptions": { | ||||||
|  |     "outDir": "lib" | ||||||
|  |   }, | ||||||
|  |   "include": ["src"] | ||||||
|  | } | ||||||
							
								
								
									
										4
									
								
								packages/d3-state-visualizer/tsconfig.webpack.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								packages/d3-state-visualizer/tsconfig.webpack.json
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | ||||||
|  | { | ||||||
|  |   "extends": "../../tsconfig.base.json", | ||||||
|  |   "include": ["webpack.config.umd.ts"] | ||||||
|  | } | ||||||
|  | @ -1,17 +0,0 @@ | ||||||
| 'use strict'; |  | ||||||
| 
 |  | ||||||
| module.exports = { |  | ||||||
|   module: { |  | ||||||
|     rules: [ |  | ||||||
|       { test: /\.js$/, loaders: ['babel-loader'], exclude: /node_modules/ }, |  | ||||||
|     ], |  | ||||||
|   }, |  | ||||||
|   output: { |  | ||||||
|     library: 'd3-state-visualizer', |  | ||||||
|     libraryExport: 'default', |  | ||||||
|     libraryTarget: 'umd', |  | ||||||
|   }, |  | ||||||
|   resolve: { |  | ||||||
|     extensions: ['.js'], |  | ||||||
|   }, |  | ||||||
| }; |  | ||||||
|  | @ -1,14 +0,0 @@ | ||||||
| 'use strict'; |  | ||||||
| 
 |  | ||||||
| var webpack = require('webpack'); |  | ||||||
| var baseConfig = require('./webpack.config.base'); |  | ||||||
| 
 |  | ||||||
| var config = Object.assign({}, baseConfig); |  | ||||||
| config.mode = 'development'; |  | ||||||
| config.plugins = [ |  | ||||||
|   new webpack.DefinePlugin({ |  | ||||||
|     'process.env.NODE_ENV': JSON.stringify('development'), |  | ||||||
|   }), |  | ||||||
| ]; |  | ||||||
| 
 |  | ||||||
| module.exports = config; |  | ||||||
|  | @ -1,14 +0,0 @@ | ||||||
| 'use strict'; |  | ||||||
| 
 |  | ||||||
| var webpack = require('webpack'); |  | ||||||
| var baseConfig = require('./webpack.config.base'); |  | ||||||
| 
 |  | ||||||
| var config = Object.assign({}, baseConfig); |  | ||||||
| config.mode = 'production'; |  | ||||||
| config.plugins = [ |  | ||||||
|   new webpack.DefinePlugin({ |  | ||||||
|     'process.env.NODE_ENV': JSON.stringify('production'), |  | ||||||
|   }), |  | ||||||
| ]; |  | ||||||
| 
 |  | ||||||
| module.exports = config; |  | ||||||
							
								
								
									
										28
									
								
								packages/d3-state-visualizer/webpack.config.umd.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								packages/d3-state-visualizer/webpack.config.umd.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,28 @@ | ||||||
|  | import * as path from 'path'; | ||||||
|  | 
 | ||||||
|  | export default (env: { production?: boolean } = {}) => ({ | ||||||
|  |   mode: env.production ? 'production' : 'development', | ||||||
|  |   entry: { | ||||||
|  |     app: ['./src/index'], | ||||||
|  |   }, | ||||||
|  |   output: { | ||||||
|  |     library: 'd3-state-visualizer', | ||||||
|  |     libraryTarget: 'umd', | ||||||
|  |     path: path.resolve(__dirname, 'dist'), | ||||||
|  |     filename: env.production | ||||||
|  |       ? 'd3-state-visualizer.min.js' | ||||||
|  |       : 'd3-state-visualizer.js', | ||||||
|  |   }, | ||||||
|  |   module: { | ||||||
|  |     rules: [ | ||||||
|  |       { | ||||||
|  |         test: /\.(js|ts)$/, | ||||||
|  |         loader: 'babel-loader', | ||||||
|  |         exclude: /node_modules/, | ||||||
|  |       }, | ||||||
|  |     ], | ||||||
|  |   }, | ||||||
|  |   resolve: { | ||||||
|  |     extensions: ['.js', '.jsx', '.ts', '.tsx'], | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | @ -34,14 +34,17 @@ | ||||||
|     "lint:fix": "eslint . --ext .ts --fix", |     "lint:fix": "eslint . --ext .ts --fix", | ||||||
|     "type-check": "tsc --noEmit", |     "type-check": "tsc --noEmit", | ||||||
|     "type-check:watch": "npm run type-check -- --watch", |     "type-check:watch": "npm run type-check -- --watch", | ||||||
|     "preversion": "npm run type-check && npm run lint && npm run test", |     "preversion": "npm run type-check && npm run lint", | ||||||
|     "prepublishOnly": "npm run clean && npm run build" |     "prepublishOnly": "npm run clean && npm run build" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "ramda": "^0.27.1" |     "ramda": "^0.27.1" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@types/d3": "^3.5.43", |  | ||||||
|     "@types/ramda": "^0.27.17" |     "@types/ramda": "^0.27.17" | ||||||
|  |   }, | ||||||
|  |   "peerDependencies": { | ||||||
|  |     "@types/d3": "^3.5.43", | ||||||
|  |     "d3": "^3.5.17" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,7 +3,17 @@ import { is } from 'ramda'; | ||||||
| import utils from './utils'; | import utils from './utils'; | ||||||
| const { prependClass, functor } = utils; | const { prependClass, functor } = utils; | ||||||
| 
 | 
 | ||||||
| const defaultOptions = { | interface Options<Datum> { | ||||||
|  |   left: number | undefined; | ||||||
|  |   top: number | undefined; | ||||||
|  |   offset: { | ||||||
|  |     left: number; | ||||||
|  |     top: number; | ||||||
|  |   }; | ||||||
|  |   root: Selection<Datum> | undefined; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const defaultOptions: Options<unknown> = { | ||||||
|   left: undefined, // mouseX
 |   left: undefined, // mouseX
 | ||||||
|   top: undefined, // mouseY
 |   top: undefined, // mouseY
 | ||||||
|   offset: { left: 0, top: 0 }, |   offset: { left: 0, top: 0 }, | ||||||
|  | @ -13,9 +23,12 @@ const defaultOptions = { | ||||||
| export default function tooltip<Datum>( | export default function tooltip<Datum>( | ||||||
|   d3: typeof d3Package, |   d3: typeof d3Package, | ||||||
|   className = 'tooltip', |   className = 'tooltip', | ||||||
|   options = {} |   options: Partial<Options<Datum>> = {} | ||||||
| ) { | ) { | ||||||
|   const { left, top, offset, root } = { ...defaultOptions, ...options }; |   const { left, top, offset, root } = { | ||||||
|  |     ...defaultOptions, | ||||||
|  |     ...options, | ||||||
|  |   } as Options<Datum>; | ||||||
| 
 | 
 | ||||||
|   let attrs = { class: className }; |   let attrs = { class: className }; | ||||||
|   let text: (datum: Datum, index?: number, outerIndex?: number) => string = ( |   let text: (datum: Datum, index?: number, outerIndex?: number) => string = ( | ||||||
|  | @ -44,7 +57,7 @@ export default function tooltip<Datum>( | ||||||
|           top: `${y}px`, |           top: `${y}px`, | ||||||
|           ...styles, |           ...styles, | ||||||
|         }) |         }) | ||||||
|         .html(() => text(node)); |         .html(() => text(node)) as Selection<Datum>; | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     selection.on('mousemove.tip', (node) => { |     selection.on('mousemove.tip', (node) => { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import * as path from 'path'; | import * as path from 'path'; | ||||||
| 
 | 
 | ||||||
| module.exports = (env: { production?: boolean } = {}) => ({ | export default (env: { production?: boolean } = {}) => ({ | ||||||
|   mode: env.production ? 'production' : 'development', |   mode: env.production ? 'production' : 'development', | ||||||
|   entry: { |   entry: { | ||||||
|     app: ['./src/index'], |     app: ['./src/index'], | ||||||
|  |  | ||||||
|  | @ -38,7 +38,7 @@ | ||||||
|     "lint:fix": "eslint . --ext .ts --fix", |     "lint:fix": "eslint . --ext .ts --fix", | ||||||
|     "type-check": "tsc --noEmit", |     "type-check": "tsc --noEmit", | ||||||
|     "type-check:watch": "npm run type-check -- --watch", |     "type-check:watch": "npm run type-check -- --watch", | ||||||
|     "preversion": "npm run type-check && npm run lint", |     "preversion": "npm run type-check && npm run lint && npm run test", | ||||||
|     "prepublishOnly": "npm run clean && npm run build" |     "prepublishOnly": "npm run clean && npm run build" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|  |  | ||||||
|  | @ -2,16 +2,16 @@ import isArray from 'lodash/isArray'; | ||||||
| import isPlainObject from 'lodash/isPlainObject'; | import isPlainObject from 'lodash/isPlainObject'; | ||||||
| import mapValues from 'lodash/mapValues'; | import mapValues from 'lodash/mapValues'; | ||||||
| 
 | 
 | ||||||
| interface Node { | export interface Node { | ||||||
|   name: string; |   name: string; | ||||||
|   children?: Node[]; |   children?: Node[] | null; | ||||||
|   value?: unknown; |   value?: unknown; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function visit( | function visit( | ||||||
|   parent: Node, |   parent: Node, | ||||||
|   visitFn: (parent: Node) => void, |   visitFn: (parent: Node) => void, | ||||||
|   childrenFn: (parent: Node) => Node[] | undefined |   childrenFn: (parent: Node) => Node[] | undefined | null | ||||||
| ) { | ) { | ||||||
|   if (!parent) return; |   if (!parent) return; | ||||||
| 
 | 
 | ||||||
|  | @ -47,17 +47,18 @@ export default function map2tree( | ||||||
|   root: {}, |   root: {}, | ||||||
|   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: [] } | ||||||
| ): Node { |   // eslint-disable-next-line @typescript-eslint/ban-types
 | ||||||
|  | ): Node | {} { | ||||||
|   // eslint-disable-next-line @typescript-eslint/ban-types
 |   // eslint-disable-next-line @typescript-eslint/ban-types
 | ||||||
|   if (!isPlainObject(root) && root && !(root as { toJS: () => {} }).toJS) { |   if (!isPlainObject(root) && root && !(root as { toJS: () => {} }).toJS) { | ||||||
|     return {} as Node; |     return {}; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const { key: rootNodeKey = 'state', pushMethod = 'push' } = options; |   const { key: rootNodeKey = 'state', pushMethod = 'push' } = options; | ||||||
|   const currentNode = getNode(tree, rootNodeKey); |   const currentNode = getNode(tree, rootNodeKey); | ||||||
| 
 | 
 | ||||||
|   if (currentNode === null) { |   if (currentNode === null) { | ||||||
|     return {} as Node; |     return {}; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   mapValues( |   mapValues( | ||||||
|  |  | ||||||
|  | @ -1,11 +1,11 @@ | ||||||
| import map2tree from '../src'; | import map2tree, { Node } from '../src'; | ||||||
| import * as immutable from 'immutable'; | import * as immutable from 'immutable'; | ||||||
| 
 | 
 | ||||||
| test('# rootNodeKey', () => { | test('# rootNodeKey', () => { | ||||||
|   const map = {}; |   const map = {}; | ||||||
|   const options = { key: 'foo' }; |   const options = { key: 'foo' }; | ||||||
| 
 | 
 | ||||||
|   expect(map2tree(map, options).name).toBe('foo'); |   expect((map2tree(map, options) as Node).name).toBe('foo'); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| describe('# shallow map', () => { | describe('# shallow map', () => { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import * as path from 'path'; | import * as path from 'path'; | ||||||
| 
 | 
 | ||||||
| module.exports = (env: { production?: boolean } = {}) => ({ | export default (env: { production?: boolean } = {}) => ({ | ||||||
|   mode: env.production ? 'production' : 'development', |   mode: env.production ? 'production' : 'development', | ||||||
|   entry: { |   entry: { | ||||||
|     app: ['./src/index'], |     app: ['./src/index'], | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ import { Action, Dispatch } from 'redux'; | ||||||
| import * as themes from 'redux-devtools-themes'; | import * as themes from 'redux-devtools-themes'; | ||||||
| import { Base16Theme } from 'redux-devtools-themes'; | import { Base16Theme } from 'redux-devtools-themes'; | ||||||
| import { ActionCreators, LiftedAction, LiftedState } from 'redux-devtools'; | import { ActionCreators, LiftedAction, LiftedState } from 'redux-devtools'; | ||||||
|  | import debounce from 'lodash.debounce'; | ||||||
| import { | import { | ||||||
|   updateScrollTop, |   updateScrollTop, | ||||||
|   startConsecutiveToggle, |   startConsecutiveToggle, | ||||||
|  | @ -12,9 +13,6 @@ import { | ||||||
| import reducer, { LogMonitorState } from './reducers'; | import reducer, { LogMonitorState } from './reducers'; | ||||||
| import LogMonitorButtonBar from './LogMonitorButtonBar'; | import LogMonitorButtonBar from './LogMonitorButtonBar'; | ||||||
| import LogMonitorEntryList from './LogMonitorEntryList'; | import LogMonitorEntryList from './LogMonitorEntryList'; | ||||||
| import debounce from 'lodash.debounce'; |  | ||||||
| import { DockMonitorState } from 'redux-devtools-dock-monitor/lib/reducers'; |  | ||||||
| import { DockMonitorAction } from 'redux-devtools-dock-monitor/lib/actions'; |  | ||||||
| 
 | 
 | ||||||
| // eslint-disable-next-line @typescript-eslint/unbound-method
 | // eslint-disable-next-line @typescript-eslint/unbound-method
 | ||||||
| const { toggleAction, setActionsActive } = ActionCreators; | const { toggleAction, setActionsActive } = ActionCreators; | ||||||
|  | @ -276,8 +274,8 @@ export default (LogMonitor as unknown) as React.ComponentType< | ||||||
| > & { | > & { | ||||||
|   update( |   update( | ||||||
|     monitorProps: ExternalProps<unknown, Action<unknown>>, |     monitorProps: ExternalProps<unknown, Action<unknown>>, | ||||||
|     state: DockMonitorState | undefined, |     state: LogMonitorState | undefined, | ||||||
|     action: DockMonitorAction |     action: LogMonitorAction | ||||||
|   ): DockMonitorState; |   ): LogMonitorState; | ||||||
|   defaultProps: DefaultProps<unknown>; |   defaultProps: DefaultProps<unknown>; | ||||||
| }; | }; | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user