UNPKG

@bigfishtv/cockpit

Version:

343 lines (298 loc) 12.5 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 _class, _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 ReactDOM from 'react-dom'; import * as SortTypes from '../../constants/SortTypes'; import * as Conditions from '../../constants/Conditions'; import { post } from '../../api/xhrUtils'; import { isObject } from '../../utils/typeUtils'; import { titleCase } from '../../utils/stringUtils'; import { showDeletePrompt } from '../../utils/promptUtils'; import { filterOutProtectedSchema, removeAssocationsFromSchema } from '../../utils/formUtils'; import { filterDataByQueryWithSchema, filterDataByFilterset, sortByObjectKey } from '../../utils/tableUtils'; import Tree from '../tree/Tree'; import Panel from '../container/panel/Panel'; import Button from '../button/Button'; import Bulkhead from '../page/Bulkhead'; import MainContent from '../container/MainContent'; import SearchInput from '../input/SearchInput'; import ReorderableCell from '../tree/ReorderableCell'; var DefaultPanelToolbar = function DefaultPanelToolbar(_ref) { var duplicable = _ref.duplicable, handleDuplicate = _ref.handleDuplicate, selectedIds = _ref.selectedIds, data = _ref.data, handleDelete = _ref.handleDelete; return React.createElement( 'div', { className: 'gutter-left' }, duplicable && React.createElement(Button, { text: 'Duplicate', onClick: handleDuplicate, disabled: selectedIds.length !== 1 }), React.createElement(Button, { text: 'Delete', onClick: handleDelete, style: 'error', disabled: !selectedIds.length }) ); }; var DefaultBulkheadToolbar = function DefaultBulkheadToolbar(_ref2) { var modelLabel = _ref2.modelLabel, model = _ref2.model, addUrl = _ref2.addUrl; return React.createElement(Button, { text: 'New ' + titleCase(modelLabel || model), onClick: function onClick() { return window.location.href = addUrl; }, style: 'primary', size: 'large' }); }; // we define this because react-docgen fails when defaultProp directly references an imported component var DefaultReorderableCell = function DefaultReorderableCell(props) { return React.createElement(ReorderableCell, props); }; var AutoReorderableIndex = (_temp = _class = function (_Component) { _inherits(AutoReorderableIndex, _Component); function AutoReorderableIndex(props) { _classCallCheck(this, AutoReorderableIndex); var _this = _possibleConstructorReturn(this, _Component.call(this, props)); _this.handleQueryChange = function (value) { var query = typeof value == 'string' ? value : value.target.value; _this.setState({ query: query, selectedIds: [] }); }; _this.handleFilterChange = function (property, value) { var filterset = _this.state.filterset; if (isObject(property)) { Object.keys(property).forEach(function (key) { return filterset[key] = property[key]; }); } else { filterset[property] = value; } _this.setState({ filterset: filterset }); }; _this.handleEdit = function (entity) { if (_this.props.onSelect) _this.props.onSelect(entity);else window.location.href = _this.props.updateUrl + entity.id; }; _this.handleDelete = function () { var _this$state = _this.state, data = _this$state.data, selectedIds = _this$state.selectedIds; showDeletePrompt({ subject: _this.props.modelLabel || null, selectedIds: selectedIds, data: data.filter(function (item) { return selectedIds.indexOf(item.id) >= 0; }), queryUrl: _this.props.deleteUrl, callback: function callback() { var deletedIds = selectedIds; // deletedData.map(item => item.id) var data = _this.state.data.filter(function (item) { return deletedIds.indexOf(item.id) < 0; }); _this.setState({ data: data, selectedIds: [] }); } }); }; _this.handleReorder = function (value, changes) { if (!changes.length) return; var _this$props = _this.props, orderKey = _this$props.orderKey, reorderUrl = _this$props.reorderUrl; var selectedIds = _this.state.selectedIds; var oldData = [].concat(_this.state.data); var entities = _this.state.data; // begin new articles as current issue articles without any of the selected items var newEntities = entities.filter(function (item) { return selectedIds.indexOf(item.id) < 0; }); changes.forEach(function (change) { var item = entities.reduce(function (prev, item) { return item.id === change.id ? item : prev; }, null); if (item) newEntities.splice(change.index, 0, item); }); // update order value newEntities = newEntities.map(function (item, order) { var _extends2; return _extends({}, item, (_extends2 = {}, _extends2[orderKey] = order, _extends2)); }); // save the new orders var postData = newEntities.reduce(function (data, entity) { var _extends3; return _extends({}, data, (_extends3 = {}, _extends3[entity.id] = entity[orderKey], _extends3)); }, {}); post({ url: reorderUrl, data: postData, quietSuccess: true, failureMessage: 'Failed to reorder', callback: function callback() {}, callbackError: function callbackError() { return _this.setState({ data: oldData }); } }); _this.setState({ data: newEntities }); }; _this.handleDataUpdate = function (data) { _this.setState({ data: data, selectedIds: [] }); }; _this.state = { data: props.defaultValue, selectedIds: [], filterset: props.defaultFilterset, query: props.defaultQuery, loading: false }; return _this; } AutoReorderableIndex.prototype.getSubmitUrl = function getSubmitUrl(data) { return data.id ? this.props.updateUrl + '/' + data.id + '.json' : this.props.addUrl; }; /** * Overloaded function -- handles updates to filterset object, usually used by dropdown selectors * @param {String / Object} property - is property or object of key values * @param {String} value - is value of property, otherwise leave blank */ AutoReorderableIndex.prototype.render = function render() { var _this2 = this; var _props = this.props, schema = _props.schema, associations = _props.associations, model = _props.model, orderKey = _props.orderKey, protectedFieldWhitelist = _props.protectedFieldWhitelist, Cell = _props.Cell, Sidebar = _props.Sidebar, PanelToolbar = _props.PanelToolbar, BulkheadToolbar = _props.BulkheadToolbar; var _state = this.state, data = _state.data, query = _state.query, selectedIds = _state.selectedIds, filterset = _state.filterset; var _schema = filterOutProtectedSchema(removeAssocationsFromSchema(associations, schema), protectedFieldWhitelist); var _data = filterDataByFilterset(filterDataByQueryWithSchema(data, _schema, query), filterset, Conditions.AND).sort(sortByObjectKey(orderKey, SortTypes.ASC, 'numeric')); var panelProps = { handleDelete: this.handleDelete, handleQueryChange: this.handleQueryChange, handleFilterChange: this.handleFilterChange, handleDataUpdate: this.handleDataUpdate, originalData: data, data: _data, selectedIds: selectedIds, filterset: filterset, query: query }; return React.createElement( MainContent, null, React.createElement(Bulkhead, { title: titleCase(model), Toolbar: function Toolbar() { return React.createElement(BulkheadToolbar, _this2.props); } }), React.createElement( 'div', { className: 'finder' }, Sidebar && React.createElement( 'div', { className: 'finder-menu' }, React.createElement(Sidebar, panelProps) ), React.createElement( 'div', { className: 'finder-content', ref: 'finderContent' }, React.createElement( Panel, _extends({ title: React.createElement(SearchInput, { value: query, onChange: this.handleQueryChange }), PanelToolbar: PanelToolbar }, panelProps), React.createElement(Tree, { value: _data, collapsedIds: [], TreeCell: Cell, selectedIds: selectedIds, treeItemTarget: treeItemTarget, onChange: this.handleReorder, onSelectItem: this.handleEdit, onSelectionChange: function onSelectionChange(selectedIds) { return _this2.setState({ selectedIds: selectedIds }); }, onCombinationChange: function onCombinationChange(_ref3) { var selectedIds = _ref3.selectedIds; return _this2.setState({ selectedIds: selectedIds }); } }) ) ) ) ); }; return AutoReorderableIndex; }(Component), _class.propTypes = { /** the lowercase plural model e.g. volunteer_applications */ model: PropTypes.string.isRequired, /** the schema key for the order field, default is 'order' */ orderKey: PropTypes.string, /** array of table schema passed in from backend */ schema: PropTypes.array, /** array of entity assocations passed in from backend */ assocations: PropTypes.array, /** function to handle when a row is double clicked */ onSelect: PropTypes.func, /** default form value provided from backend, typically array of entity objects */ defaultValue: PropTypes.array, /** query string to pre-entered */ defaultQuery: PropTypes.string, /** filterset state */ defaultFilterset: PropTypes.object, /** fields that start with an underscore (_) get excluded by default, this offers a way to whitelist some of those */ protectedFieldWhitelist: PropTypes.array, /** panel toolbar component to replace default one, takes props: selectedIds, data, originalData, handleDelete, handleQueryChange, handleFilterChange, handleDataUpdate */ PanelToolbar: PropTypes.func, /** panel toolbar component to replace default one, takes props: selectedIds, data, originalData, handleDelete, handleQueryChange, handleFilterChange, handleDataUpdate */ BulkheadToolbar: PropTypes.func }, _class.defaultProps = { defaultValue: [], schema: [], associations: [], orderKey: 'order', model: 'model', defaultQuery: '', defaultFilterset: {}, protectedFieldWhitelist: [], Cell: DefaultReorderableCell, Sidebar: null, PanelToolbar: DefaultPanelToolbar, BulkheadToolbar: DefaultBulkheadToolbar }, _temp); /** * We have to pass in our own treeItemTarget function to prevent the 'drag-into' functionality */ export { AutoReorderableIndex as default }; var EDGE_SIZE = 10; var treeItemTarget = { drop: function drop(props, monitor, component) { 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 = ReactDOM.findDOMNode(component).getBoundingClientRect(); var clientOffset = monitor.getClientOffset(); var offsetY = clientOffset.y - boundingRect.top; if (clientOffset.y > boundingRect.top && clientOffset.y < boundingRect.bottom) { if (offsetY < EDGE_SIZE) component.setState({ position: 'above' });else component.setState({ position: 'below' }); } } } };