@bigfishtv/cockpit
Version:
343 lines (298 loc) • 12.5 kB
JavaScript
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' });
}
}
}
};