@intuitionrobotics/thunderstorm
Version:
274 lines • 12.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Tree = void 0;
const React = require("react");
const ts_common_1 = require("@intuitionrobotics/ts-common");
const KeyboardListener_1 = require("../../tools/KeyboardListener");
const tools_1 = require("../../utils/tools");
class Tree extends React.Component {
constructor(props) {
super(props);
this.containerRefs = {};
this.rendererRefs = {};
this.renderedElements = [];
this.renderedElementsInit = () => {
const keys = Object.keys(this.state.expanded);
this.renderedElements = keys.reduce((carry, key) => {
if (this.state.expanded[key])
return carry;
this.state.adapter.hideRoot && (0, ts_common_1.removeItemFromArray)(carry, '/');
keys.forEach(el => {
if (el.startsWith(key) && el !== key)
(0, ts_common_1.removeItemFromArray)(carry, el);
});
return carry;
}, keys);
};
this.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));
};
this.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 };
};
this.keyEventHandler = (node, e) => {
this.props.keyEventHandler && this.props.keyEventHandler(node, e);
let keyCode = e.code;
if (keyCode === "Escape") {
(0, tools_1.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") {
(0, tools_1.stopPropagation)(e);
if (!this.props.checkExpanded(this.state.expanded, focused))
return this.expandOrCollapse(focused, true);
else
keyCode = "ArrowDown";
}
if (focused && keyCode === "ArrowLeft") {
(0, tools_1.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") {
(0, tools_1.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") {
(0, tools_1.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") {
(0, tools_1.stopPropagation)(e);
const item = this.getItemByPath(focused);
if (item.action && typeof item.action === "function")
return item.action();
this.props.onNodeClicked && this.props.onNodeClicked(focused, item);
}
};
this.ignoreToggler = () => {
};
this.toggleExpandState = (e, _expanded) => {
const path = e.currentTarget.id;
this.expandOrCollapse(path, _expanded);
};
this.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();
};
this.onNodeFocused = (e) => {
// This is an assumption that we should document somewhere
const path = e.currentTarget.id;
const item = this.getItemByPath(path);
this.props.onNodeFocused && this.props.onNodeFocused(path, item);
if (this.state.focused === path)
return;
this.setFocusedNode(path);
};
this.onNodeClicked = (e) => {
this.onNodeFocused(e);
// This is an assumption that we should document somewhere
const path = e.currentTarget.id;
const item = this.getItemByPath(path);
this.props.onNodeClicked && this.props.onNodeClicked(path, item);
};
this.onBlur = () => {
if (this.props.onBlur)
this.props.onBlur();
this.setState(state => {
if (!state.focused)
return state;
return Object.assign(Object.assign({}, state), { lastFocused: state.focused, focused: '' });
});
};
this.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 Object.assign(Object.assign({}, state), { lastFocused: '', focused });
});
};
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();
}
render() {
return React.createElement(KeyboardListener_1.KeyboardListener, { id: this.props.id, onKeyboardEventListener: this.keyEventHandler, onFocus: this.onFocus, onBlur: this.onBlur }, this.renderNode(this.state.adapter.data, "", "", 1));
}
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 }));
}
setFocusedNode(path) {
this.rendererRefs[path].scrollIntoView({ block: "nearest" });
this.setState({ focused: path });
}
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;
}
static recursivelyExpand(adapter, expandCondition = () => true, state = {
'/': expandCondition('/', adapter.data, 0, '/') || undefined
}) {
return recursivelyExpandImpl(adapter.data, state, expandCondition, adapter);
}
}
exports.Tree = Tree;
Tree.defaultProps = {
indentPx: 20,
checkExpanded: (expanded, path) => expanded[path]
};
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