UNPKG

@bigfishtv/cockpit

Version:

379 lines (340 loc) 12.9 kB
var _dec, _class, _class2, _temp; 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; } import PropTypes from 'prop-types'; import React, { Component } from 'react'; import classnames from 'classnames'; import Immutable from 'immutable'; import { connect } from 'react-redux'; import ReactDOM from 'react-dom'; import isEqual from 'lodash/isEqual'; import MainContent from '../container/MainContent'; import Icon from '../Icon'; import Tree from '../tree/Tree'; import Bulkhead from '../page/Bulkhead'; import Button from '../button/Button'; import { post } from '../../api/xhrUtils'; import { showDeletePrompt } from '../../utils/promptUtils'; import { userCanAccess } from '../../utils/roleUtils'; import { pruneTreeImmutable, collectValuesImmutable } from '../../utils/treeUtils'; import { notifyFailure } from '../../actions/notifications'; var HeaderToolbar = function HeaderToolbar(props) { var _props$headerToolbarP = props.headerToolbarProps, allCollapsed = _props$headerToolbarP.allCollapsed, collapsedIds = _props$headerToolbarP.collapsedIds, selectedIds = _props$headerToolbarP.selectedIds, onCollapseAll = _props$headerToolbarP.onCollapseAll, onExpandAll = _props$headerToolbarP.onExpandAll, onDelete = _props$headerToolbarP.onDelete; return React.createElement( 'div', null, React.createElement(Button, { text: 'Collapse All', onClick: onCollapseAll, disabled: allCollapsed }), React.createElement(Button, { text: 'Expand All', onClick: onExpandAll, disabled: !collapsedIds.length }), React.createElement(Button, { text: 'Delete', style: 'error', onClick: onDelete, disabled: !selectedIds.length }) ); }; var PageTreeCell = function PageTreeCell(props) { var id = props.id, title = props.title, status = props.status, path = props.path, userCanAccess = props.userCanAccess, isCollapsed = props.isCollapsed, selectedDrag = props.selectedDrag, showIndicator = props.showIndicator, onIndicatorClick = props.onIndicatorClick, onIndicatorDoubleClick = props.onIndicatorDoubleClick, isOver = props.isOver, position = props.position, onClick = props.onClick, onDoubleClick = props.onDoubleClick, selected = props.selected; return React.createElement( 'div', { className: classnames('tree-item', isOver && 'drag-' + position) }, React.createElement( 'div', { className: classnames('tree-cell', { dragging: selectedDrag, selected: selected, disabled: !userCanAccess }), onClick: userCanAccess ? onClick : null, onDoubleClick: userCanAccess ? onDoubleClick : null }, React.createElement( 'div', { className: 'tree-cell-icon' }, showIndicator && React.createElement( 'div', { className: classnames('tree-cell-control', isCollapsed && 'collapsed'), onClick: onIndicatorClick, onDoubleClick: onIndicatorDoubleClick }, React.createElement(Icon, { name: 'chevron-' + (isCollapsed ? 'right' : 'down'), size: '18' }) ) ), React.createElement( 'div', { className: 'tree-cell-status' }, React.createElement('div', { className: classnames('status', status) }) ), !userCanAccess && React.createElement( 'div', { className: 'tree-cell-icon' }, React.createElement(Icon, { name: 'lock', size: 12 }) ), userCanAccess ? React.createElement( 'div', { className: 'tree-cell-title' }, React.createElement( 'a', { href: '/admin/pages/edit/' + id, onClick: function onClick(event) { return event.stopPropagation(); } }, title ) ) : React.createElement( 'div', { className: 'tree-cell-title disabled' }, title ), React.createElement( 'div', { className: 'tree-cell-text' }, React.createElement( 'a', { href: path, target: '_blank', onClick: function onClick(event) { return event.stopPropagation(); } }, path ) ) ) ); }; function filterPages(data, user) { return data.map(function (page) { page.userCanAccess = userCanAccess([{ model: 'Pages', foreign_key: page.id }], user); if (page.userCanAccess) { markRecursive(page.children); } else { page.children = filterPages(page.children, user); } return page; }); } function markRecursive(children) { children.map(function (page) { page.userCanAccess = true; markRecursive(page.children); }); } /** * Pages tree view page template */ var Pages = (_dec = connect(function (_ref) { var viewer = _ref.viewer; return { viewer: viewer }; }), _dec(_class = (_temp = _class2 = function (_Component) { _inherits(Pages, _Component); function Pages(props) { _classCallCheck(this, Pages); var _this = _possibleConstructorReturn(this, _Component.call(this)); _this.handleChange = function (newTree, delta) { if (_this.props.moveUrl) { _this.setState({ data: newTree }); post({ url: _this.props.moveUrl, data: delta, quietSuccess: true, errorMessage: 'Failed to update tree', callback: function callback(data) { return _this.setState({ data: Immutable.fromJS(filterPages(data, _this.props.viewer)) }); } }); } else { _this.props.dispatch(notifyFailure('Failed to update tree')); console.warn('[Pages] moveUrl not supplied, not saving tree'); } }; _this.handleDelete = function () { var _this$state = _this.state, selectedIds = _this$state.selectedIds, data = _this$state.data; var itemsToDelete = collectValuesImmutable(data, 'title', function (item) { return selectedIds.indexOf(item.get('id')) >= 0; }).map(function (value) { return { title: value }; }); showDeletePrompt({ subject: 'page', style: 'error', selectedIds: selectedIds, data: itemsToDelete, callback: function callback() { _this.setState({ data: pruneTreeImmutable(data, 'id', selectedIds, 'children'), selectedIds: [] }); } }); }; _this.handleSelectionChange = function (selectedIds) { _this.setState({ selectedIds: selectedIds }); }; _this.handleCollapseChange = function (collapsedIds) { var collapsableIds = collectValuesImmutable(_this.state.data, 'id', function (item) { return item.get('children') && item.get('children').size > 0; }); var allCollapsed = isEqual(collapsableIds.sort(), collapsedIds.sort()); _this.setState({ collapsedIds: collapsedIds, allCollapsed: allCollapsed }); }; _this.handleCombinationChange = function (mixed) { var newState = {}; Object.keys(mixed).map(function (key) { if (key in _this.state) newState[key] = mixed[key]; }); _this.setState(newState); }; _this.handleCollapseAll = function () { var collapsedIds = collectValuesImmutable(_this.state.data, 'id', function (item) { return item.get('children') && item.get('children').size > 0; }); _this.setState({ collapsedIds: collapsedIds, allCollapsed: true }); }; _this.handleExpandAll = function () { _this.setState({ collapsedIds: [], allCollapsed: false }); }; _this.handleSelectedItem = function (item) { window.location = '/admin/pages/edit/' + item.get('id'); }; var pages = filterPages(props.treeData, props.viewer); _this.state = { data: Immutable.fromJS(pages), selectedIds: [], collapsedIds: localStorage.pagesCollapsedIds ? JSON.parse(localStorage.pagesCollapsedIds) : [], //getInitialCollapsed(pages), allCollapsed: false }; return _this; } Pages.prototype.componentDidUpdate = function componentDidUpdate() { localStorage.pagesCollapsedIds = JSON.stringify(this.state.collapsedIds); }; Pages.prototype.render = function render() { var _state = this.state, data = _state.data, selectedIds = _state.selectedIds, collapsedIds = _state.collapsedIds, allCollapsed = _state.allCollapsed; var headerToolbarProps = { selectedIds: selectedIds, allCollapsed: allCollapsed, collapsedIds: collapsedIds, onCollapseAll: this.handleCollapseAll, onExpandAll: this.handleExpandAll, onDelete: this.handleDelete }; return React.createElement( MainContent, null, React.createElement(Bulkhead, { title: 'Pages', Toolbar: HeaderToolbar, headerToolbarProps: headerToolbarProps }), React.createElement( 'div', { className: 'panel margin-medium' }, React.createElement(Tree, { value: data, immutable: true, TreeCell: PageTreeCell, treeItemSource: treeItemSource, treeItemTarget: treeItemTarget, selectedIds: selectedIds, collapsedIds: collapsedIds, onChange: this.handleChange, onSelectItem: this.handleSelectedItem, onSelectionChange: this.handleSelectionChange, onCollapseChange: this.handleCollapseChange, onCombinationChange: this.handleCombinationChange }) ) ); }; return Pages; }(Component), _class2.propTypes = { /** update url to hit on page reorder */ moveUrl: PropTypes.string, /** threaded array of objects - tree data */ treeData: PropTypes.array }, _class2.defaultProps = { treeData: [] }, _temp)) || _class); // 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 export { Pages as default }; var treeItemSource = { canDrag: function canDrag(props) { return props.userCanAccess; }, beginDrag: function beginDrag(props) { props.beginDrag(props.id); return { id: props.id, index: props.index }; }, endDrag: function endDrag(props, monitor) { 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 (!component.decoratedComponentInstance.props.userCanAccess) return; 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 (!component.decoratedComponentInstance.props.userCanAccess) return; 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 = ReactDOM.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' }); } } } } };