import React from "react"; import PropTypes from "prop-types"; import TreeNode from "./TreeNode"; import { DEFAULT_ROOT_PATH, hasChildNodes, getExpandedPaths } from "./pathUtils"; const reducer = (state, action) => { if (action.type === "TOGGLE_EXPAND") { let { path } = action; if (!Array.isArray(path)) { path = [path]; } const ret = { ...state }; path.forEach((objPath) => { const { expandedPaths } = state; const expanded = !!expandedPaths[objPath]; ret.expandedPaths[objPath] = !expanded; }); return ret; } else { return state; } }; class ConnectedTreeNode extends React.Component<{ name: string; data: any; dataIterator: Function; depth: number; expanded: boolean; nodeRenderer: (args: any) => JSX.Element; filterStrings: string[]; searchBoxText: string; path: string; onMatchFound: Function; }> { state: { depth: number; expandedPaths: string[]; }; constructor(props, context) { super(props); this.state = context.store.storeState; this.state.depth = props.depth; } shouldComponentUpdate(nextProps, nextState) { const filterChanged = this.props.filterStrings.every((str) => nextProps.filterStrings.includes(str)); const searchBoxTextChanged = nextProps.searchBoxText !== this.props.searchBoxText; return ( filterChanged || searchBoxTextChanged || !!nextState.expandedPaths[nextProps.path] !== !!this.state.expandedPaths[this.props.path] || nextProps.data !== this.props.data || nextProps.name !== this.props.name ); } handleRightClick(path: string) { this.handleClick(path); } handleClick(path: string) { this.context.store.storeState = reducer(this.context.store.storeState, { type: "TOGGLE_EXPAND", path, }); this.setState(this.context.store.storeState); } renderChildNodes(parentData, parentPath) { const { dataIterator } = this.props; const { depth } = this.props; const { onMatchFound, nodeRenderer, searchBoxText, filterStrings, expanded } = this.props; const childNodes: any[] = []; for (const { name, data } of dataIterator(parentData)) { const key = name; const path = `${parentPath}.${key}`; childNodes.push( ); } return childNodes; } render() { const { data, dataIterator, path, depth, name } = this.props; const nodeHasChildNodes = hasChildNodes(data, dataIterator); const { expandedPaths } = this.state; const expanded = !!expandedPaths[path]; const { nodeRenderer } = this.props; return ( { this.handleRightClick(path); }} onClick={ nodeHasChildNodes ? () => { this.handleClick(path); } : () => {} } // show arrow anyway even if not expanded and not rendering children shouldShowArrow={nodeHasChildNodes} // show placeholder only for non root nodes shouldShowPlaceholder={depth > 0} // Render a node from name and data (or possibly other props like isNonenumerable) nodeRenderer={nodeRenderer} name={name} data={data} > { // only render if the node is expanded expanded ? this.renderChildNodes(data, path) : undefined } ); } } // @ts-ignore Convert to modern React context ConnectedTreeNode.contextTypes = { store: PropTypes.any, }; /** * See ObjectInspector for description of props */ class TreeView extends React.PureComponent<{ name: string; data: any; dataIterator: Function; nodeRenderer: (args: any) => JSX.Element; filterStrings: string[]; searchBoxText: string; onMatchFound: Function; expanded: boolean; expandPaths?: string[]; expandLevel?: number; }> { store: any; static defaultProps: { expandLevel: number; expandPaths: string[]; } = { expandLevel: 0, expandPaths: [], }; static childContextTypes = { store: PropTypes.any, }; constructor(props) { super(props); this.state = { expandLevel: 0, }; this.store = { storeState: { expandedPaths: getExpandedPaths(props.data, props.dataIterator, props.expandPaths, props.expandLevel), }, }; } UNSAFE_componentWillReceiveProps(nextProps) { this.store = { storeState: { expandedPaths: getExpandedPaths( nextProps.data, nextProps.dataIterator, nextProps.expandPaths, nextProps.expandLevel, this.store.storeState.expandedPaths ), }, }; } getChildContext() { return { store: this.store, }; } render() { const { name, data, dataIterator, expanded, nodeRenderer, filterStrings, searchBoxText, onMatchFound } = this.props; const rootPath = DEFAULT_ROOT_PATH; return ( ); } } export default TreeView;