UNPKG

hbp-quickfire

Version:

A library of useful user-interface components built with React on top of React Bootstrap and MobX

261 lines (195 loc) 21.3 kB
var _extends = Object.assign || function (target) {for (var i = 1; i < arguments.length; i++) {var source = arguments[i];for (var key in source) {if (Object.prototype.hasOwnProperty.call(source, key)) {target[key] = source[key];}}}return target;};var _createClass = function () {function defineProperties(target, props) {for (var i = 0; i < props.length; i++) {var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);}}return function (Constructor, protoProps, staticProps) {if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;};}();var _class, _temp2, _dec, _class2;function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}function _possibleConstructorReturn(self, call) {if (!self) {throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return call && (typeof call === "object" || typeof call === "function") ? call : self;}function _inherits(subClass, superClass) {if (typeof superClass !== "function" && superClass !== null) {throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);}subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;} /* * Copyright (c) Human Brain Project * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import React from "react"; import injectStyles from "react-jss";import FormControl from "react-bootstrap/lib/FormControl";import FormGroup from "react-bootstrap/lib/FormGroup";import Glyphicon from "react-bootstrap/lib/Glyphicon";import InputGroup from "react-bootstrap/lib/InputGroup";import Button from "react-bootstrap/lib/Button";import Label from "react-bootstrap/lib/Label";import intersection from "lodash/intersection";import find from "lodash/find";import isString from "lodash/isString";var Tree = (_temp2 = _class = function (_React$Component) {_inherits(Tree, _React$Component);function Tree() {var _ref;var _temp, _this, _ret;_classCallCheck(this, Tree);for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {args[_key] = arguments[_key];}return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = Tree.__proto__ || Object.getPrototypeOf(Tree)).call.apply(_ref, [this].concat(args))), _this), _this. state = { query: null, queryInput: "" }, _this. timer = null, _temp), _possibleConstructorReturn(_this, _ret);} //Timer identifier bound to the triggering of the search into the tree structure //Gets reset if a key is stroke by user before the end of the timer //Is triggered if no keys are stroke before the end of the timer //Limits the number of time the search is done on the entire tree structure _createClass(Tree, [{ key: "render", value: function render() {var defaultExpanded = this.props.defaultExpanded;if (!defaultExpanded || defaultExpanded.length === undefined) { defaultExpanded = []; } else if (defaultExpanded.length > 0 && isString(defaultExpanded[0])) { defaultExpanded = [defaultExpanded]; } return ( React.createElement("div", { className: "quickfire-tree" }, React.createElement(FormGroup, { controlId: "Tree Node Search", className: "quickfire-tree-search" }, React.createElement(InputGroup, null, React.createElement(InputGroup.Addon, null, React.createElement(Glyphicon, { glyph: "search" })), React.createElement(FormControl, { type: "text", className: "quickfire-tree-search-input", placeholder: "Search", onChange: this.handleChange.bind(this), value: this.state.queryInput }), React.createElement(InputGroup.Button, null, React.createElement(Button, { onClick: this.reset.bind(this) }, React.createElement(Glyphicon, { glyph: "remove" }))))), React.createElement(TreeNode, { data: this.props.data, onSelect: this.props.onSelect, query: this.state.query, mappingLabel: this.props.mappingLabel, mappingChildren: this.props.mappingChildren, selectOnlyLeaf: this.props.selectOnlyLeaf, selectedNodes: this.props.selectedNodes, expandToSelectedNodes: this.props.expandToSelectedNodes, defaultExpanded: defaultExpanded, showOnlySearchedNodes: this.props.showOnlySearchedNodes, level: 0 }))); } }, { key: "handleChange", value: function handleChange( e) { e.stopPropagation(); var self = this; var newQuery = e.target.value; self.setState({ queryInput: e.target.value }); if (this.timer) { clearTimeout(this.timer); } var newQueryRegexp = void 0; try { //We use a regexp to search into the serialized tree structure as it's fast and practical to be able to use a regexp for search newQueryRegexp = newQuery.length >= 1 ? new RegExp(":\".*?" + newQuery + ".*?\"", "i") : null; this.timer = setTimeout(function () { self.setState({ query: newQueryRegexp }); }, 750); } catch (e) {"";} //Silent fail is intended to prevent errors thrown during the type of a regexp by the user } }, { key: "reset", value: function reset() { this.setState({ query: null, queryInput: "" }); } }]);return Tree;}(React.Component), _class.defaultProps = { mappingLabel: "label", mappingChildren: "children", selectOnlyLeaf: false, selectedNodes: [], expandToSelectedNodes: false, defaultExpanded: [] }, _temp2);export { Tree as default }; var treeNodeStyles = { node: { listStyleType: "none", "& .quickfire-tree-node-selector": { marginLeft: "3px" }, "& .quickfire-tree-node-expand:focus": { outline: "none" }, "&.hidden": { display: "none" } }, childrenList: { marginLeft: "20px", paddingLeft: 0 }, leafPlaceHolder: { width: "23px", display: "inline-block" }, labelSelected: { display: "inline-block", marginRight: "4px", padding: "2px 3px 3px 2px", fontSize: "0.6em", verticalAlign: "middle" } };var TreeNode = (_dec = injectStyles(treeNodeStyles), _dec(_class2 = function (_React$Component2) {_inherits(TreeNode, _React$Component2); function TreeNode(props) {_classCallCheck(this, TreeNode);var _this2 = _possibleConstructorReturn(this, (TreeNode.__proto__ || Object.getPrototypeOf(TreeNode)).call(this, props)); _this2.matchesExpanded = []; if (props.expandToSelectedNodes && _this2.hasSelectedChildren(props.data)) { _this2.state = { expand: true }; } else if (props.query && props.data.children) { _this2.state = { expand: props.query.test(JSON.stringify(props.data.children)) }; } else if (_this2.matchDefaultExpanded()) { _this2.state = { expand: true }; } else { _this2.state = { expand: false }; }return _this2; }_createClass(TreeNode, [{ key: "matchDefaultExpanded", value: function matchDefaultExpanded() {var _this3 = this; this.matchesExpanded = []; if (this.props.defaultExpanded && this.props.defaultExpanded.length > 0) { this.props.defaultExpanded.forEach(function (path) { if (path.length > _this3.props.level && new RegExp(path[_this3.props.level], "gi").test(_this3.props.data[_this3.props.mappingLabel])) { _this3.matchesExpanded.push(path); } }); } return this.matchesExpanded.length > 0; } }, { key: "hasSelectedChildren", value: function hasSelectedChildren( node) {var _this4 = this; return node.children !== undefined && node.children.length !== 0 && ( intersection(this.props.selectedNodes, node.children).length > 0 || find(node.children, function (child) {return _this4.hasSelectedChildren(child);})); } }, { key: "render", value: function render() {var _this5 = this;var _props = _extends({}, this.props),classes = _props.classes,data = _props.data,mappingLabel = _props.mappingLabel,mappingChildren = _props.mappingChildren,selectOnlyLeaf = _props.selectOnlyLeaf,selectedNodes = _props.selectedNodes,expandToSelectedNodes = _props.expandToSelectedNodes,showOnlySearchedNodes = _props.showOnlySearchedNodes; var hideThisNode = false; //showOnlySearchedNodes: If search doesnt match the node and we want to hide parts of the tree that are not relevant to the search if (showOnlySearchedNodes && this.props.query) { var dataCopy = JSON.parse(JSON.stringify(data)); if (dataCopy[mappingChildren]) { delete dataCopy[mappingChildren]; } var match = this.props.query.test(JSON.stringify(dataCopy)); if (!match && !this.state.expand) { hideThisNode = true; } else if (match && this.state.expand) { showOnlySearchedNodes = false; } } var label = data[mappingLabel]; var children = undefined; var isSelected = this.props.selectedNodes.indexOf(this.props.data) !== -1; if (data[mappingChildren] && data[mappingChildren].length !== undefined) { if (this.state.expand) { children = data[mappingChildren].map(function (child, index) { return ( React.createElement(TreeNode, { data: child, onSelect: _this5.props.onSelect, key: index, query: _this5.props.query, classes: _this5.props.classes, mappingLabel: mappingLabel, mappingChildren: mappingChildren, selectOnlyLeaf: selectOnlyLeaf, selectedNodes: selectedNodes, expandToSelectedNodes: expandToSelectedNodes, defaultExpanded: _this5.props.defaultExpanded, showOnlySearchedNodes: showOnlySearchedNodes, level: _this5.props.level + 1 })); }); } else { children = []; } } return ( React.createElement("li", { className: classes.node + " " + (hideThisNode && "hidden") + " quickfire-tree-node" }, children != undefined ? React.createElement(Button, { className: "quickfire-tree-node-expand", bsStyle: "link", bsSize: "xsmall", onClick: this.toggle.bind(this) }, React.createElement(Glyphicon, { glyph: this.state.expand ? "minus" : "plus" })) : React.createElement("span", { className: classes.leafPlaceHolder }), React.createElement("a", { className: "quickfire-tree-node-selector", onClick: this.handleClickSelect.bind(this), style: { cursor: "pointer", textDecoration: "none", color: "inherit" } }, isSelected ? React.createElement(Label, { bsStyle: "success", className: classes.labelSelected }, React.createElement(Glyphicon, { glyph: "ok" })) : null, label), children != undefined && this.state.expand && React.createElement("ul", { className: classes.childrenList + " quickfire-tree-node-children" }, children))); } }, { key: "componentWillReceiveProps", value: function componentWillReceiveProps( nextProps) { if (nextProps.query !== this.props.query) { if (nextProps.query && nextProps.data.children) { this.setState({ expand: nextProps.query.test(JSON.stringify(nextProps.data.children)) }); } else if (this.matchDefaultExpanded()) { this.setState({ expand: true }); } else { this.setState({ expand: false }); } } } }, { key: "handleClickSelect", value: function handleClickSelect() {var _props2 = _extends({}, this.props),mappingChildren = _props2.mappingChildren; if (this.props.selectOnlyLeaf && this.props.data[mappingChildren] !== undefined && this.props.data[mappingChildren].length > 0) { this.toggle(); } else if (this.props.onSelect) { this.props.onSelect(this.props.data); } } }, { key: "toggle", value: function toggle() { this.setState({ expand: !this.state.expand }); } }]);return TreeNode;}(React.Component)) || _class2);