@intuitionrobotics/thunderstorm
Version:
287 lines • 12 kB
JavaScript
import * as React from 'react';
import {} from 'react';
import { removeItemFromArray } from "@intuitionrobotics/ts-common";
import {} from "./types.js";
import { KeyboardListener } from "../../tools/KeyboardListener.js";
import { stopPropagation } from '../../utils/tools.js';
import { Adapter } from "../adapter/Adapter.js";
import {} from "../adapter/BaseRenderer.js";
export class Tree extends React.Component {
static defaultProps = {
indentPx: 20,
checkExpanded: (expanded, path) => expanded[path]
};
containerRefs = {};
rendererRefs = {};
renderedElements = [];
constructor(props) {
super(props);
this.state = {
adapter: this.props.adapter,
expanded: props.expanded || { "/": true }
};
}
static getDerivedStateFromProps(props, state) {
if (props.adapter.data === state.adapter.data)
return null;
state.adapter = props.adapter;
// Tree.recalculateExpanded(props, state);
return state;
}
componentDidMount() {
this.renderedElementsInit();
}
renderedElementsInit = () => {
const keys = Object.keys(this.state.expanded);
this.renderedElements = keys.reduce((carry, key) => {
if (this.state.expanded[key])
return carry;
if (this.state.adapter.hideRoot)
removeItemFromArray(carry, '/');
keys.forEach(el => {
if (el.startsWith(key) && el !== key)
removeItemFromArray(carry, el);
});
return carry;
}, keys);
};
render() {
return React.createElement(KeyboardListener, { id: this.props.id, onKeyboardEventListener: this.keyEventHandler, onFocus: this.onFocus, onBlur: this.onBlur }, this.renderNode(this.state.adapter.data, "", "", 1));
}
renderNode = (_data, key, _path, level) => {
const nodePath = `${_path}${key}/`;
const adjustedNode = this.state.adapter.adjust(_data);
const data = adjustedNode.data;
let filteredKeys = [];
let expanded = !!this.props.checkExpanded(this.state.expanded, nodePath);
if (nodePath.endsWith("_children/"))
expanded = true;
let renderChildren = expanded;
if (typeof data !== "object")
renderChildren = false;
if (renderChildren)
filteredKeys = this.state.adapter.getFilteredChildren(data);
const nodeRefResolver = this.nodeResolver(nodePath, renderChildren, filteredKeys);
const containerRefResolver = this.resolveContainer(nodePath, renderChildren, filteredKeys);
return React.createElement("div", { key: nodePath, ref: nodeRefResolver },
this.renderItem(data, nodePath, key, expanded),
this.renderChildren(data, nodePath, _path, level, filteredKeys, renderChildren, adjustedNode, containerRefResolver));
};
nodeResolver(nodePath, renderChildren, filteredKeys) {
return (_ref) => {
if (this.rendererRefs[nodePath])
return;
this.rendererRefs[nodePath] = _ref;
if (this.containerRefs[nodePath] && renderChildren && filteredKeys.length > 0)
this.forceUpdate();
};
}
resolveContainer(nodePath, renderChildren, filteredKeys) {
return (_ref) => {
if (this.containerRefs[nodePath])
return;
this.containerRefs[nodePath] = _ref;
if (renderChildren && filteredKeys.length > 0)
this.forceUpdate();
};
}
renderChildren(data, nodePath, _path, level, filteredKeys, renderChildren, adjustedNode, containerRefResolver) {
if (!(filteredKeys.length > 0 && renderChildren))
return;
const containerRef = this.containerRefs[nodePath];
return (React.createElement("div", { style: this.getChildrenContainerStyle(level, this.rendererRefs[nodePath], containerRef, this.containerRefs[_path]), ref: containerRefResolver }, containerRef && filteredKeys.map((childKey) => this.renderNode(data[childKey], childKey, nodePath + (adjustedNode.deltaPath ? adjustedNode.deltaPath + "/" : ""), level + 1))));
}
renderItem(item, path, key, expanded) {
if (this.state.adapter.hideRoot && path.length === 1)
return null;
const TreeNodeRenderer = this.state.adapter.treeNodeRenderer;
// console.log("isParent: ", this.state.adapter.isParent(item));
const node = {
adapter: this.state.adapter,
propKey: key,
path,
item,
expandToggler: this.state.adapter.isParent(item) ? this.toggleExpandState : this.ignoreToggler,
onClick: this.onNodeClicked,
onFocus: this.onNodeFocused,
expanded: !!expanded,
focused: path === this.state.focused,
selected: item === this.props.selectedItem
};
return React.createElement("div", { onMouseEnter: () => this.setState({ focused: node.path }), onMouseLeave: () => this.setState({ focused: '' }) },
React.createElement(TreeNodeRenderer, { item: item, node: node }));
}
getChildrenContainerStyle = (level, parentNodeRef, containerRef, parentContainerRef) => {
if (!containerRef)
return {};
if (this.props.childrenContainerStyle)
return this.props.childrenContainerStyle(level, parentNodeRef, containerRef, parentContainerRef);
return { marginLeft: this.props.indentPx };
};
setFocusedNode(path) {
this.rendererRefs[path].scrollIntoView({ block: "nearest" });
this.setState({ focused: path });
}
keyEventHandler = (node, e) => {
if (this.props.keyEventHandler)
this.props.keyEventHandler(node, e);
let keyCode = e.code;
if (keyCode === "Escape") {
stopPropagation(e);
return this.props.unMountFromOutside ? this.props.unMountFromOutside() : node.blur();
}
const focused = this.state.focused;
const idx = this.renderedElements.findIndex(el => el === focused);
if (idx >= this.renderedElements.length)
return;
if (focused && keyCode === "ArrowRight") {
stopPropagation(e);
if (!this.props.checkExpanded(this.state.expanded, focused))
return this.expandOrCollapse(focused, true);
else
keyCode = "ArrowDown";
}
if (focused && keyCode === "ArrowLeft") {
stopPropagation(e);
if (this.props.checkExpanded(this.state.expanded, focused))
return this.expandOrCollapse(focused, false);
else {
const temp = focused.substr(0, focused.length - 1);
if (temp.length === 0)
return;
const parentFocused = temp.substring(0, temp.lastIndexOf("/") + 1);
return this.setFocusedNode(parentFocused);
}
}
if (keyCode === "ArrowDown") {
stopPropagation(e);
if (idx === -1 || idx + 1 === this.renderedElements.length)
return this.setFocusedNode(this.renderedElements[0]);
return this.setFocusedNode(this.renderedElements[idx + 1]);
}
if (keyCode === "ArrowUp") {
stopPropagation(e);
if (idx === -1)
return this.setFocusedNode(this.renderedElements[0]);
if (idx === 0)
return this.setFocusedNode(this.renderedElements[this.renderedElements.length - 1]);
return this.setFocusedNode(this.renderedElements[idx - 1]);
}
if (focused && keyCode === "Enter") {
stopPropagation(e);
const item = this.getItemByPath(focused);
if (item.action && typeof item.action === "function")
return item.action();
if (this.props.onNodeClicked)
this.props.onNodeClicked(focused, item);
}
};
getItemByPath(path) {
let item = this.state.adapter.data;
const hierarchy = path.split('/');
hierarchy.shift();
for (const el of hierarchy) {
if (el) {
item = item[el];
if (!item)
return;
}
}
const deltaPath = this.state.adapter.adjust(item).deltaPath;
if (deltaPath)
item = item[deltaPath];
return item;
}
ignoreToggler = () => {
};
toggleExpandState = (e, _expanded) => {
const path = e.currentTarget.id;
this.expandOrCollapse(path, _expanded);
};
expandOrCollapse = (path, forceExpandState) => {
if (path === "/" && this.state.adapter.hideRoot && forceExpandState === false)
return;
const treeExpandedState = this.state.expanded;
const currentExpandState = treeExpandedState[path];
let newExpandState = currentExpandState === undefined;
if (forceExpandState !== undefined)
newExpandState = forceExpandState ? forceExpandState : false;
if (newExpandState)
treeExpandedState[path] = newExpandState;
else
delete treeExpandedState[path];
this.setState({ focused: path });
this.forceUpdate();
};
onNodeFocused = (e) => {
// This is an assumption that we should document somewhere
const path = e.currentTarget.id;
const item = this.getItemByPath(path);
if (this.props.onNodeFocused)
this.props.onNodeFocused(path, item);
if (this.state.focused === path)
return;
this.setFocusedNode(path);
};
onNodeClicked = (e) => {
this.onNodeFocused(e);
// This is an assumption that we should document somewhere
const path = e.currentTarget.id;
const item = this.getItemByPath(path);
if (this.props.onNodeClicked)
this.props.onNodeClicked(path, item);
};
onBlur = () => {
if (this.props.onBlur)
this.props.onBlur();
this.setState(state => {
if (!state.focused)
return state;
return {
...state,
lastFocused: state.focused,
focused: ''
};
});
};
onFocus = () => {
if (this.props.onFocus)
this.props.onFocus();
this.setState(state => {
const focused = state.lastFocused || (this.state.adapter.hideRoot ? Object.keys(state.expanded)[1] : Object.keys(state.expanded)[0]);
if (state.focused === focused)
return state;
return {
...state,
lastFocused: '',
focused
};
});
};
static recursivelyExpand(adapter, expandCondition = () => true, state = {
'/': expandCondition('/', adapter.data, 0, '/') || undefined
}) {
return recursivelyExpandImpl(adapter.data, state, expandCondition, adapter);
}
}
const recursivelyExpandImpl = (obj, state, condition, adapter, path = "/", level = 1) => {
if (obj === null)
return state;
const _obj = adapter.adjust(obj);
const children = adapter.getFilteredChildren(obj);
return children.reduce((_state, _key) => {
const key = _key;
const value = obj[_key];
const newPath = `${path}${key}/`;
if (!_obj.deltaPath) {
const b = condition(key, value, level, newPath);
if (b)
_state[newPath] = b;
}
// if (condition(key, value, level, newPath) && typeof value === "object")
if (typeof value === "object")
recursivelyExpandImpl(value, _state, condition, adapter, newPath, level + 1);
return _state;
}, state);
};
//# sourceMappingURL=Tree.js.map