UNPKG

@bigfishtv/cockpit

Version:

674 lines (580 loc) 25.2 kB
'use strict'; exports.__esModule = true; exports.default = undefined; 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 _class, _temp, _class$defaultProps; var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _reactImmutableProptypes = require('react-immutable-proptypes'); var _reactImmutableProptypes2 = _interopRequireDefault(_reactImmutableProptypes); var _immutable = require('immutable'); var _immutable2 = _interopRequireDefault(_immutable); var _deepEqual = require('deep-equal'); var _deepEqual2 = _interopRequireDefault(_deepEqual); var _keycode = require('keycode'); var _keycode2 = _interopRequireDefault(_keycode); var _treeUtils = require('../../utils/treeUtils'); var _selectKeyUtils = require('../../utils/selectKeyUtils'); var _DragTypes = require('../../constants/DragTypes'); var DragTypes = _interopRequireWildcard(_DragTypes); var _TreeItem = require('../tree/TreeItem'); var _TreeItem2 = _interopRequireDefault(_TreeItem); var _TreeCell = require('../tree/TreeCell'); var _TreeCell2 = _interopRequireDefault(_TreeCell); var _TreeDragLayer = require('../tree/TreeDragLayer'); var _TreeDragLayer2 = _interopRequireDefault(_TreeDragLayer); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } 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; } // this is referenced in TreeItem -- it needs to be provided as a prop for react-dnd decorator in order for it to be optionally replaced by tree props var treeItemSource = { beginDrag: function beginDrag(props, monitor, component) { props.beginDrag(props.id); return { id: props.id, index: props.index }; }, endDrag: function endDrag(props, monitor, component) { if (!monitor.didDrop()) { props.endDrag(); } } }; // this is referenced in TreeItem -- it needs to be provided as a prop for react-dnd decorator in order for it to be optionally replaced by tree props var expandTimeout = null; var EDGE_SIZE = 10; var PARENT_BELOW_EDGE_SIZE = 50; var treeItemTarget = { drop: function drop(props, monitor, component) { if (!props.reorderable) return; // goes through all drop targets and resets their forceExpand state so they don't randomly open upon drag if hover expanded for (var key in monitor.internalMonitor.registry.handlers) { if (key.charAt(0) == 'T') { var item = monitor.internalMonitor.registry.handlers[key].component; if (item.state && item.state.forceExpand) { item.props.onCollapse(); item.setState({ forceExpand: false }); } } } if (monitor.isOver({ shallow: true })) { var draggedId = monitor.getItem().id; var targetId = props.id; var position = component.state.position; props.endDrag(draggedId, targetId, position); } }, hover: function hover(props, monitor, component) { if (!props.reorderable) return; var isOverCurrent = monitor.isOver({ shallow: true }); if (isOverCurrent) { var ownId = props.id; var draggedId = monitor.getItem().id; if (draggedId === ownId || component.props.selected) return; var boundingRect = _reactDom2.default.findDOMNode(component).getBoundingClientRect(); var clientOffset = monitor.getClientOffset(); var offsetY = clientOffset.y - boundingRect.top; var rowHeight = boundingRect.bottom - boundingRect.top; var bottomEdgeSize = !props.collapsed && props.children && props.children.size > 0 ? PARENT_BELOW_EDGE_SIZE : EDGE_SIZE; if (clientOffset.y > boundingRect.top && clientOffset.y < boundingRect.bottom) { if (clientOffset.y > boundingRect.top + EDGE_SIZE && clientOffset.y < boundingRect.top + rowHeight - bottomEdgeSize) { if (component.props.collapsed && component.state.position != 'into') { if (expandTimeout !== null) clearTimeout(expandTimeout); expandTimeout = setTimeout(function () { component.setState({ forceExpand: true }); }, 1000); } component.setState({ position: 'into' }); } else if (offsetY < EDGE_SIZE) { component.setState({ position: 'above' }); } else { component.setState({ position: 'below' }); } } } } }; var Tree = (_temp = _class = function (_Component) { _inherits(Tree, _Component); function Tree(props) { _classCallCheck(this, Tree); var _this = _possibleConstructorReturn(this, _Component.call(this)); _this.beginDrag = function (id) { if (!_this.props.reorderable) return; _this.dragging = true; var selectedIds = _this.state.selectedIds.indexOf(id) >= 0 ? _this.state.selectedIds : [id]; _this.preDragCollapsedIds = _this.state.collapsedIds.slice(); var collapsedIds = [].concat(selectedIds, _this.state.collapsedIds); if (!_this.props.uncontrolled) { _this.props.onCombinationChange({ collapsedIds: collapsedIds, selectedIds: selectedIds }); } else { _this.setState({ selectedIds: selectedIds, collapsedIds: collapsedIds }); } }; _this.endDrag = function (draggedId, targetId, position) { if (!_this.props.reorderable) return; _this.dragging = false; // don't do anything if the dragged item ends up over an item being dragged // or the drag is cancelled if (!targetId || _this.state.selectedIds.indexOf(targetId) > -1) { if (!_this.props.uncontrolled) { _this.props.onCollapseChange(_this.preDragCollapsedIds); } else { _this.setState({ collapsedIds: _this.preDragCollapsedIds }); } return; } var newTree = _this.state.InflatedTree; var FlatTree = (0, _treeUtils.flattenWithPath)(newTree.toJS()); // find items matching selected ids var draggedItems = FlatTree.filter(function (item) { return _this.state.selectedIds.indexOf(item.item.id) >= 0; }); // remove children from the dragged items list and order items in reverse order draggedItems = draggedItems.filter(function (item) { return draggedItems.filter(function (item2) { if (item2.path.length < item.path.length) { if (item2.path.join('') == item.path.slice(0, item2.path.length).join('')) { return true; } } return false; }).length == 0; }).sort(function (a, b) { a = a.path; b = b.path; var len = Math.min(a.length, b.length); for (var i = 0; i < len; i++) { if (a[i] < b[i]) { return 1; } else if (a[i] > b[i]) { return -1; } } return 0; }); draggedItems.forEach(function (draggedItem) { newTree = newTree.deleteIn(draggedItem.path); }); FlatTree = (0, _treeUtils.flattenWithPath)(newTree.toJS()); var targetItem = (0, _treeUtils.getChildByIdImmutable)(targetId, newTree); var draggedItem = (0, _treeUtils.getChildByIdImmutable)(draggedId, newTree); var draggedParentId = draggedItem && draggedItem.get('parent_id') || null; var targetParentId = targetItem && targetItem.get('parent_id') || null; var targetPath = FlatTree.filter(function (item) { return item.item.id === targetId; })[0].path; var targetParentPath = targetParentId ? FlatTree.filter(function (item) { return item.item.id === targetParentId; })[0].path : null; var targetIndex = targetPath[targetPath.length - 1]; var children = null; var changes = []; var index = 0; switch (position) { case 'into': if (targetId == draggedParentId) { break; } draggedItems = draggedItems.reverse(); if (targetItem.get('children')) { draggedItems.map(function (draggedItem, i) { index = targetItem.get('children').size + i; newTree = newTree.setIn(targetPath.concat(['children', index]), _immutable2.default.fromJS(draggedItem.item)); changes.push({ id: draggedItem.item.id, parent_id: targetId, index: index }); }); } else { newTree = newTree.setIn(targetPath.concat(['children']), _immutable2.default.fromJS(draggedItems.map(function (draggedItem) { return draggedItem.item; }))); draggedItems.map(function (draggedItem, i) { changes.push({ id: draggedItem.item.id, parent_id: targetId, index: i }); }); } break; case 'above': if (!targetParentId) { draggedItems.map(function (draggedItem, i) { newTree = newTree.splice(targetIndex, 0, _immutable2.default.fromJS(draggedItem.item)); changes.push({ id: draggedItem.item.id, parent_id: null, index: targetIndex }); }); } else { draggedItems.map(function (draggedItem, i) { children = newTree.getIn(targetParentPath.concat(['children'])).splice(targetIndex, 0, _immutable2.default.fromJS(draggedItem.item)); newTree = newTree.setIn(targetParentPath.concat(['children']), children); changes.push({ id: draggedItem.item.id, parent_id: targetParentId, index: targetIndex }); }); } break; case 'below': if (!targetParentId) { draggedItems.map(function (draggedItem, i) { newTree = newTree.splice(targetIndex + 1, 0, _immutable2.default.fromJS(draggedItem.item)); changes.push({ id: draggedItem.item.id, parent_id: null, index: targetIndex + 1 }); }); } else { draggedItems.map(function (draggedItem, i) { children = newTree.getIn(targetParentPath.concat(['children'])).splice(targetIndex + 1, 0, _immutable2.default.fromJS(draggedItem.item)); newTree = newTree.setIn(targetParentPath.concat(['children']), children); changes.push({ id: draggedItem.item.id, parent_id: targetParentId, index: targetIndex + 1 }); }); } break; } if (!_this.props.uncontrolled) { _this.props.onCollapseChange(_this.preDragCollapsedIds); _this.props.onChange(_this.props.immutable ? newTree : newTree.toJS(), changes); } else { _this.setState({ collapsedIds: _this.preDragCollapsedIds, InflatedTree: newTree }); } }; _this.lastSelectedId = null; _this.lastSelectedList = []; _this.dragging = false; _this.handleKeyDown = _this.handleKeyDown.bind(_this); _this.state = { InflatedTree: props.immutable ? props.value : _immutable2.default.fromJS(props.value), selectedIds: props.selectedIds, collapsedIds: props.collapsedIds }; return _this; } Tree.prototype.componentDidMount = function componentDidMount() { window.addEventListener('keydown', this.handleKeyDown); }; Tree.prototype.componentWillUnmount = function componentWillUnmount() { window.removeEventListener('keydown', this.handleKeyDown); }; Tree.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) { var treeEqual = this.props.immutable ? nextProps.value == this.props.value : (0, _deepEqual2.default)(nextProps.value, this.props.value); if (!treeEqual) { this.setState({ InflatedTree: this.props.immutable ? nextProps.value : _immutable2.default.fromJS(nextProps.value) }); } if (!this.props.uncontrolled) { this.setState({ selectedIds: nextProps.selectedIds, collapsedIds: nextProps.collapsedIds }); } }; Tree.prototype.handleKeyDown = function handleKeyDown(event) { var _this2 = this; var immutable = this.props.immutable; var _state = this.state, InflatedTree = _state.InflatedTree, selectedIds = _state.selectedIds, collapsedIds = _state.collapsedIds; if (!InflatedTree) return; var data = (0, _treeUtils.flattenWithoutCollapsed)(InflatedTree.toJS(), collapsedIds); if (!data || !data.length) return; var key = (0, _keycode2.default)(event); var activeElement = document.activeElement; if (!activeElement || activeElement.nodeName !== 'INPUT' && activeElement.nodeName !== 'TEXTAREA') { if (key === 'up') { event.preventDefault(); var index = this.lastSelectedId === null ? data.length - 1 : data.reduce(function (value, row, i) { return row.id === _this2.lastSelectedId ? i - 1 : value; }, null); var row = data[index <= 0 ? 0 : index]; this.handleSelect(immutable ? _immutable2.default.Map(row) : row); } else if (key === 'down') { event.preventDefault(); var _index = this.lastSelectedId === null ? 0 : data.reduce(function (value, row, i) { return row.id === _this2.lastSelectedId ? i + 1 : value; }, null); var _row = data[_index >= data.length - 1 ? data.length - 1 : _index]; this.handleSelect(immutable ? _immutable2.default.Map(_row) : _row); } else if ((key === 'left' || key === 'right') && selectedIds.length) { event.preventDefault(); var newCollapsedIds = collapsedIds.slice(); data.map(function (item) { if (selectedIds.indexOf(item.id) >= 0) { if (collapsedIds.indexOf(item.id) < 0) newCollapsedIds.push(item.id);else newCollapsedIds.splice(collapsedIds.indexOf(item.id), 1); } }); if (!this.props.uncontrolled) { this.props.onCollapseChange(newCollapsedIds); } else { this.setState({ collapsedIds: newCollapsedIds }); } } else if (key === 'esc') { event.preventDefault(); this.handleDeselectAll(); } else if (key === 'a' && (0, _selectKeyUtils.isCtrlKeyPressed)()) { event.preventDefault(); this.handleSelectAll(data); } else if (key === 'enter' && selectedIds.length === 1) { var _row2 = data.filter(function (item) { return item.id == selectedIds[0]; })[0]; this.handleDoubleClick(immutable ? _immutable2.default.Map(_row2) : _row2); } } }; Tree.prototype.handleSelect = function handleSelect(item) { var _this3 = this; var immutable = _immutable2.default.Map.isMap(item); var _props = this.props, multiselect = _props.multiselect, stickySelect = _props.stickySelect; var _state2 = this.state, selectedIds = _state2.selectedIds, InflatedTree = _state2.InflatedTree; var id = immutable ? item.get('id') : item.id; if (!multiselect || !(0, _selectKeyUtils.isCtrlKeyPressed)() && !(0, _selectKeyUtils.isShiftKeyPressed)()) { selectedIds = selectedIds.length === 1 && selectedIds[0] === id && !stickySelect ? [] : [id]; } else if ((0, _selectKeyUtils.isCtrlKeyPressed)()) { if (selectedIds.indexOf(id) >= 0) { selectedIds = selectedIds.filter(function (_id) { return _id !== id; }); } else { selectedIds = [].concat(selectedIds, [id]); } } else if ((0, _selectKeyUtils.isShiftKeyPressed)()) { var tree = (0, _treeUtils.flatten)(immutable ? InflatedTree.toJS() : InflatedTree); var lastIndex = tree.indexOf(tree.filter(function (item) { return item.id === _this3.lastSelectedId; })[0]); var nextIndex = tree.indexOf(tree.filter(function (item) { return item.id === id; })[0]); var lower = Math.min(lastIndex, nextIndex); var upper = Math.max(lastIndex, nextIndex); var ids = tree.filter(function (item, i) { return i >= lower && i <= upper; }).map(function (item) { return item.id; }); selectedIds = [].concat(this.lastSelectedList, ids); } // remove duplicate ids selectedIds = selectedIds.filter(function (val, i) { return selectedIds.indexOf(val) === i; }); this.lastSelectedId = id; this.lastSelectedList = selectedIds.slice(); if (this.props.uncontrolled) { this.setState({ selectedIds: selectedIds }); } this.props.onSelectionChange(selectedIds); }; Tree.prototype.handleCollapse = function handleCollapse(item) { var immutable = _immutable2.default.Map.isMap(item); var id = immutable ? item.get('id') : item.id; var collapsedIds = this.state.collapsedIds; if (collapsedIds.indexOf(id) >= 0) collapsedIds.splice(collapsedIds.indexOf(id), 1);else collapsedIds.push(id); if (!this.props.uncontrolled) { this.props.onCollapseChange(collapsedIds); } else { this.setState({ collapsedIds: collapsedIds }); } }; Tree.prototype.handleDoubleClick = function handleDoubleClick(item) { if (_immutable2.default.Map.isMap(item) && !this.props.immutable) item = item.toJS();else if (!_immutable2.default.Map.isMap(item) && this.props.immutable) item = _immutable2.default.Map(item); this.props.onSelectItem && this.props.onSelectItem(item); }; Tree.prototype.handleSelectAll = function handleSelectAll() { var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; var _state3 = this.state, InflatedTree = _state3.InflatedTree, collapsedIds = _state3.collapsedIds; data = data || (0, _treeUtils.flattenWithoutCollapsed)(InflatedTree.toJS(), collapsedIds); var selectedIds = data.map(function (item) { return item.id; }); this.lastSelectedList = selectedIds.slice(); if (this.props.uncontrolled) { this.setState({ selectedIds: selectedIds }); } this.props.onSelectionChange(selectedIds); }; Tree.prototype.handleDeselectAll = function handleDeselectAll() { var selectedIds = []; this.lastSelectedList = []; if (this.props.uncontrolled) { this.setState({ selectedIds: selectedIds }); } this.props.onSelectionChange(selectedIds); }; Tree.prototype.renderList = function renderList(list) { var _this4 = this; if (!list) return null; return list.map(function (item, key) { return _this4.renderItem(item, key); }); }; Tree.prototype.treeItemTarget = function treeItemTarget() { if (typeof this.props.treeItemTarget == 'function') { return this.props.treeItemTarget(); } else { return this.props.treeItemTarget; } }; Tree.prototype.treeItemSource = function treeItemSource() { if (typeof this.props.treeItemSource == 'function') { return this.props.treeItemSource(); } else { return this.props.treeItemSource; } }; Tree.prototype.renderItem = function renderItem(item, key) { var _this5 = this; var id = item.get('id'); var selected = this.state.selectedIds.indexOf(id) >= 0 || this.state.selectedIds.length === 0 && id === null; var collapsed = this.state.collapsedIds.indexOf(id) >= 0; var _item$toObject = item.toObject(), children = _item$toObject.children, rest = _objectWithoutProperties(_item$toObject, ['children']); var showIndicator = children && children.size > 0; var _props2 = this.props, TreeCell = _props2.TreeCell, dropTargetType = _props2.dropTargetType, dragTargetType = _props2.dragTargetType, reorderable = _props2.reorderable, breadcrumbs = _props2.breadcrumbs; return _react2.default.createElement( _TreeItem2.default, _extends({ key: key, TreeCell: TreeCell }, rest, { dropTargetType: dropTargetType, dragTargetType: dragTargetType, treeItemTarget: this.treeItemTarget(), treeItemSource: this.treeItemSource(), beginDrag: this.beginDrag, endDrag: this.endDrag, onClick: function onClick() { return _this5.handleSelect(item); }, onDoubleClick: function onDoubleClick() { return _this5.handleDoubleClick(item); }, selected: selected, collapsed: collapsed, reorderable: reorderable, breadcrumbs: breadcrumbs, onCollapse: function onCollapse() { return _this5.handleCollapse(item); }, showIndicator: showIndicator }), showIndicator && this.renderList(item.get('children')) ); }; Tree.prototype.render = function render() { var TreeDragLayer = this.props.TreeDragLayer; var _state4 = this.state, InflatedTree = _state4.InflatedTree, selectedIds = _state4.selectedIds; var nestedTree = this.renderList(InflatedTree); return _react2.default.createElement( 'div', { className: 'tree' }, _react2.default.createElement( 'div', { className: 'tree-items' }, nestedTree ), _react2.default.createElement(TreeDragLayer, { selectedIds: selectedIds, InflatedTree: InflatedTree, dragging: this.dragging }) ); }; return Tree; }(_react.Component), _class.propTypes = { /** Tree data provided as an array of objects. Objects can contain key 'children' which contains the same data structure, repeat recursively */ value: _propTypes2.default.oneOfType([_propTypes2.default.array, _reactImmutableProptypes2.default.list]), /** Whether or not component needs to handle its own state e.g. selectedIds & collapsedIds */ uncontrolled: _propTypes2.default.bool, /** Whether or not to convert data to Immutable data -- changes what callback functions give */ immutable: _propTypes2.default.bool, /** Allow selection of multiple cells */ multiselect: _propTypes2.default.bool, /** Allow cells to be reordable */ reorderable: _propTypes2.default.bool, /** Enable stickSelect -- as if CTRL key were held down */ stickySelect: _propTypes2.default.bool, /** Wrap cells in breadcrumbs for easy navigation -- passed on to TreeItem */ breadcrumbs: _propTypes2.default.bool, /** Only applicable if component is controlled */ selectedIds: _propTypes2.default.arrayOf(_propTypes2.default.number), /** Only applicable if component is controlled */ collapsedIds: _propTypes2.default.arrayOf(_propTypes2.default.number), /** See Constants/dragTypes */ dropTargetType: _propTypes2.default.string, /** ReactDnD drag target functions, has defaults */ treeItemTarget: _propTypes2.default.oneOfType([_propTypes2.default.objectOf(_propTypes2.default.func), _propTypes2.default.func]), /** ReactDnD drag source functions, has defaults */ treeItemSource: _propTypes2.default.oneOfType([_propTypes2.default.objectOf(_propTypes2.default.func), _propTypes2.default.func]), /** Called on tree structure change e.g. re-order, delete. (Will return immutable data if prop is set) */ onChange: _propTypes2.default.func, /** Called on cell select, ie. double click. (Will return immutable data if prop is set) */ onSelectItem: _propTypes2.default.func, /** Called on cell selection, probably only useful if controlled. (Will return immutable data if prop is set) */ onSelectionChange: _propTypes2.default.func, /** Called on cell (de)collapse, probably only useful if controlled. (Will return immutable data if prop is set) */ onCollapseChange: _propTypes2.default.func, /** Is a combination of selectedItems and collapsedItems, probably only useful if controlled. (Will return immutable data if prop is set) */ onCombinationChange: _propTypes2.default.func }, _class.defaultProps = (_class$defaultProps = { value: [], uncontrolled: false, immutable: false, multiselect: true, reorderable: true, stickySelect: true, breadcrumbs: true, selectedIds: [], collapsedIds: [], dropTargetType: DragTypes.TREE_ITEM, dragTargetType: DragTypes.TREE_ITEM, treeItemTarget: treeItemTarget, treeItemSource: treeItemSource, TreeCell: _TreeCell2.default, TreeDragLayer: _TreeDragLayer2.default, onChange: function onChange() { return console.warn('[Tree] no onChange prop provided'); }, onSelectItem: function onSelectItem() { return console.warn('[Tree] no onSelectItem prop provided'); }, onSelectionChange: function onSelectionChange() { return console.warn('[Tree] no onSelectionChange prop provided'); }, onCollapseChange: function onCollapseChange() { return console.warn('[Tree] no onCollapseChange prop provided'); } }, _class$defaultProps['onSelectionChange'] = function onSelectionChange() { return console.warn('[Tree] no onSelectionChange prop provided'); }, _class$defaultProps.onCombinationChange = function onCombinationChange() { return console.warn('[Tree] no onCombinationChange prop provided'); }, _class$defaultProps), _temp); exports.default = Tree;