UNPKG

d2-ui

Version:
573 lines (518 loc) 23.5 kB
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; }; }(); 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 React, { Component } from 'react'; import PropTypes from 'prop-types'; // Material UI import Paper from 'material-ui/Paper/Paper'; import RaisedButton from 'material-ui/RaisedButton/RaisedButton'; // D2 import { config } from 'd2/lib/d2'; // D2-UI import CircularProgress from '../circular-progress/CircularProgress'; // TODO: TOAST! // TODO: Undo support (in TOAST?) config.i18n.strings.add('selected'); config.i18n.strings.add('assign_all'); config.i18n.strings.add('remove_all'); config.i18n.strings.add('hidden_by_filters'); var GroupEditor = function (_Component) { _inherits(GroupEditor, _Component); function GroupEditor(props, context) { _classCallCheck(this, GroupEditor); var _this = _possibleConstructorReturn(this, (GroupEditor.__proto__ || Object.getPrototypeOf(GroupEditor)).call(this, props, context)); _this.state = { // Number of items selected in the left/right columns selectedLeft: 0, selectedRight: 0, // Loading loading: true }; _this.onAssignItems = function () { _this.setState({ loading: true }); _this.props.onAssignItems([].map.call(_this.leftSelect.selectedOptions, function (item) { return item.value; })).then(function () { _this.clearSelection(); _this.setState({ loading: false }); }).catch(function () { _this.setState({ loading: false }); }); }; _this.onRemoveItems = function () { _this.setState({ loading: true }); _this.props.onRemoveItems([].map.call(_this.rightSelect.selectedOptions, function (item) { return item.value; })).then(function () { _this.clearSelection(); _this.setState({ loading: false }); }).catch(function () { _this.setState({ loading: false }); }); }; _this.onAssignAll = function () { _this.setState({ loading: true }); _this.props.onAssignItems([].map.call(_this.leftSelect.options, function (item) { return item.value; })).then(function () { _this.clearSelection(); _this.setState({ loading: false }); }).catch(function () { _this.setState({ loading: false }); }); }; _this.onRemoveAll = function () { _this.setState({ loading: true }); _this.props.onRemoveItems([].map.call(_this.rightSelect.options, function (item) { return item.value; })).then(function () { _this.clearSelection(); _this.setState({ loading: false }); }).catch(function () { _this.setState({ loading: false }); }); }; _this.byAssignedItemsOrder = function (left, right) { var assignedItemStore = _this.props.assignedItemStore.state; // Don't order anything if the assignedItemStore is not an array // TODO: Support sorting for a ModelCollectionProperty if (!Array.isArray(assignedItemStore)) { return 0; } return assignedItemStore.indexOf(left.value) > assignedItemStore.indexOf(right.value) ? 1 : -1; }; var i18n = _this.context.d2.i18n; _this.getTranslation = i18n.getTranslation.bind(i18n); return _this; } _createClass(GroupEditor, [{ key: 'componentDidMount', value: function componentDidMount() { var _this2 = this; this.disposables = []; this.disposables.push(this.props.itemStore.subscribe(function (state) { return _this2.setState({ loading: !state }); })); this.disposables.push(this.props.assignedItemStore.subscribe(function () { return _this2.forceUpdate(); })); } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(props) { if (props.hasOwnProperty('filterText') && this.leftSelect && this.rightSelect) { this.setState({ selectedLeft: [].filter.call(this.leftSelect.selectedOptions, function (item) { return item.text.toLowerCase().indexOf(('' + props.filterText).trim().toLowerCase()) !== -1; }).length, selectedRight: [].filter.call(this.rightSelect.selectedOptions, function (item) { return item.text.toLowerCase().indexOf(('' + props.filterText).trim().toLowerCase()) !== -1; }).length }); } } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { this.disposables.forEach(function (disposable) { disposable.unsubscribe(); }); } // // Event handlers // }, { key: 'getItemStoreIsCollection', // // Data handling utility functions // value: function getItemStoreIsCollection() { return this.props.itemStore.state !== undefined && typeof this.props.itemStore.state.values === 'function' && typeof this.props.itemStore.state.has === 'function'; } }, { key: 'getItemStoreIsArray', value: function getItemStoreIsArray() { return this.props.itemStore.state !== undefined && this.props.itemStore.state.constructor.name === 'Array'; } }, { key: 'getAssignedItemStoreIsCollection', value: function getAssignedItemStoreIsCollection() { return this.props.assignedItemStore.state !== undefined && typeof this.props.assignedItemStore.state.values === 'function' && typeof this.props.assignedItemStore.state.has === 'function'; } }, { key: 'getAssignedItemStoreIsArray', value: function getAssignedItemStoreIsArray() { return this.props.assignedItemStore.state !== undefined && this.props.assignedItemStore.state.constructor.name === 'Array'; } }, { key: 'getAllItems', value: function getAllItems() { return this.getItemStoreIsCollection() ? Array.from(this.props.itemStore.state.values()).map(function (item) { return { value: item.id, text: item.name }; }) : this.props.itemStore.state || []; } }, { key: 'getItemCount', value: function getItemCount() { return this.getItemStoreIsCollection() && this.props.itemStore.state.size || this.getItemStoreIsArray() && this.props.itemStore.state.length || 0; } }, { key: 'getIsValueAssigned', value: function getIsValueAssigned(value) { return this.getAssignedItemStoreIsCollection() ? this.props.assignedItemStore.state.has(value) : this.props.assignedItemStore.state && this.props.assignedItemStore.state.indexOf(value) !== -1; } }, { key: 'getAssignedItems', value: function getAssignedItems() { var _this3 = this; return this.getAllItems().filter(function (item) { return _this3.getIsValueAssigned(item.value); }); } }, { key: 'getAvailableItems', value: function getAvailableItems() { var _this4 = this; return this.getAllItems().filter(function (item) { return !_this4.getIsValueAssigned(item.value); }); } }, { key: 'getAllItemsFiltered', value: function getAllItemsFiltered() { return this.filterItems(this.getAllItems()); } }, { key: 'getAssignedItemsFiltered', value: function getAssignedItemsFiltered() { return this.filterItems(this.getAssignedItems()); } }, { key: 'getAvailableItemsFiltered', value: function getAvailableItemsFiltered() { return this.filterItems(this.getAvailableItems()); } }, { key: 'getAssignedItemsCount', value: function getAssignedItemsCount() { return this.getAssignedItems().length; } }, { key: 'getAvailableItemsCount', value: function getAvailableItemsCount() { return this.getAvailableItems().length; } }, { key: 'getAssignedItemsFilterCount', value: function getAssignedItemsFilterCount() { return this.getFilterText().length === 0 ? 0 : this.getAssignedItems().length - this.getAssignedItemsFiltered().length; } }, { key: 'getAvailableItemsFilterCount', value: function getAvailableItemsFilterCount() { return this.getFilterText().length === 0 ? 0 : this.getAvailableItems().length - this.getAvailableItemsFiltered().length; } }, { key: 'getAssignedItemsUnfilteredCount', value: function getAssignedItemsUnfilteredCount() { return this.getFilterText().length === 0 ? this.getAssignedItemsCount() : this.getAssignedItemsCount() - this.getAssignedItemsFilterCount(); } }, { key: 'getAvailableItemsUnfilteredCount', value: function getAvailableItemsUnfilteredCount() { return this.getFilterText().length === 0 ? this.getAvailableItemsCount() : this.getAvailableItemsCount() - this.getAvailableItemsFilterCount(); } }, { key: 'getFilterText', value: function getFilterText() { return this.props.filterText ? this.props.filterText.trim().toLowerCase() : ''; } }, { key: 'getAvailableSelectedCount', value: function getAvailableSelectedCount() { return Math.max(this.state.selectedLeft, 0); } }, { key: 'getAssignedSelectedCount', value: function getAssignedSelectedCount() { return Math.max(this.state.selectedRight, 0); } }, { key: 'getSelectedCount', value: function getSelectedCount() { return Math.max(this.getAvailableSelectedCount(), this.getAssignedSelectedCount()); } }, { key: 'getSelectedItems', value: function getSelectedItems() { return [].map.call(this.rightSelect.selectedOptions, function (item) { return item.value; }); } }, { key: 'clearSelection', value: function clearSelection() { var left = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; var right = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; if (left) { this.leftSelect.selectedIndex = -1; } if (right) { this.rightSelect.selectedIndex = -1; } this.setState(function (state) { return { selectedLeft: left ? 0 : state.selectedLeft, selectedRight: right ? 0 : state.selectedRight }; }); } }, { key: 'filterItems', value: function filterItems(items) { var _this5 = this; return items.filter(function (item) { return _this5.getFilterText().length === 0 || item.text.trim().toLowerCase().indexOf(_this5.getFilterText()) !== -1; }); } // // Rendering // }, { key: 'render', value: function render() { var _this6 = this; var filterHeight = this.getFilterText().length > 0 ? 15 : 0; var styles = { container: { display: 'flex', marginTop: 16, marginBottom: 32, height: this.props.height + 'px' }, left: { flex: '1 0 120px' }, middle: { flex: '0 0 120px', alignSelf: 'center', textAlign: 'center' }, right: { flex: '1 0 120px' }, paper: { width: '100%', height: '100%' }, select: { width: '100%', minHeight: '50px', height: this.props.height - filterHeight + 'px', border: 'none', fontFamily: 'Roboto', fontSize: 13, outline: 'none' }, options: { padding: '.25rem .5rem' }, buttons: { minWidth: '100px', maxWidth: '100px', marginTop: '8px' }, selected: { fontSize: 13, minHeight: '15px', marginTop: '45px', padding: '0 8px' }, status: { marginTop: '8px', minHeight: '60px' }, hidden: { fontSize: 13, color: '#404040', fontStyle: 'italic', textAlign: 'center', width: '100%', background: '#d0d0d0', maxHeight: '15px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }; var onChangeLeft = function onChangeLeft(e) { _this6.clearSelection(false, true); _this6.setState({ selectedLeft: e.target.selectedOptions.length }); }; var onChangeRight = function onChangeRight(e) { _this6.clearSelection(true, false); _this6.setState({ selectedRight: e.target.selectedOptions.length }); }; var hiddenLabel = function hiddenLabel(itemCount) { return _this6.getItemCount() > 0 && _this6.getFilterText().length > 0 ? itemCount + ' ' + _this6.getTranslation('hidden_by_filters') : ''; }; var selectedLabel = function selectedLabel() { return _this6.getSelectedCount() > 0 ? _this6.getSelectedCount() + ' ' + _this6.getTranslation('selected') : ''; }; return React.createElement( 'div', { style: styles.container }, React.createElement( 'div', { style: styles.left }, React.createElement( Paper, { style: styles.paper }, React.createElement( 'div', { style: styles.hidden }, hiddenLabel(this.getAvailableItemsFilterCount()) ), React.createElement( 'select', { multiple: true, style: styles.select, onChange: onChangeLeft, ref: function ref(r) { _this6.leftSelect = r; } }, this.getAvailableItemsFiltered().map(function (item) { return React.createElement( 'option', { key: item.value, value: item.value, onDoubleClick: _this6.onAssignItems, style: styles.options }, item.text ); }) ) ), React.createElement(RaisedButton, { label: this.getTranslation('assign_all') + ' ' + (this.getAvailableItemsUnfilteredCount() === 0 ? '' : this.getAvailableItemsUnfilteredCount()) + ' \u2192', disabled: this.state.loading || this.getAvailableItemsUnfilteredCount() === 0, onClick: this.onAssignAll, style: { marginTop: '1rem' }, secondary: true }) ), React.createElement( 'div', { style: styles.middle }, React.createElement( 'div', { style: styles.selected }, selectedLabel() ), React.createElement(RaisedButton, { label: '\u2192', secondary: true, onClick: this.onAssignItems, style: styles.buttons, disabled: this.state.loading || this.state.selectedLeft === 0 }), React.createElement(RaisedButton, { label: '\u2190', secondary: true, onClick: this.onRemoveItems, style: styles.buttons, disabled: this.state.loading || this.state.selectedRight === 0 }), React.createElement( 'div', { style: styles.status }, this.state.loading ? React.createElement(CircularProgress, { small: true, style: { width: 60, height: 60 } }) : undefined ) ), React.createElement( 'div', { style: styles.right }, React.createElement( Paper, { style: styles.paper }, React.createElement( 'div', { style: styles.hidden }, hiddenLabel(this.getAssignedItemsFilterCount()) ), React.createElement( 'select', { multiple: true, style: styles.select, onChange: onChangeRight, ref: function ref(r) { _this6.rightSelect = r; } }, this.getAssignedItemsFiltered().sort(this.byAssignedItemsOrder).map(function (item) { return React.createElement( 'option', { key: item.value, value: item.value, onDoubleClick: _this6.onRemoveItems, style: styles.options }, item.text ); }) ) ), React.createElement(RaisedButton, { label: '\u2190 ' + this.getTranslation('remove_all') + ' ' + (this.getAssignedItemsUnfilteredCount() > 0 ? this.getAssignedItemsUnfilteredCount() : ''), style: { float: 'right', marginTop: '1rem' }, disabled: this.state.loading || this.getAssignedItemsUnfilteredCount() === 0, onClick: this.onRemoveAll, secondary: true }) ) ); } }]); return GroupEditor; }(Component); GroupEditor.propTypes = { // itemStore: d2-ui store containing all available items, either as a D2 ModelCollection, // or an array on the following format: [{value: 1, text: '1'}, {value: 2, text: '2'}, ...] itemStore: PropTypes.object.isRequired, // assignedItemStore: d2-ui store containing all items assigned to the current group, either // as a D2 ModelCollectionProperty or an array of ID's that match values in the itemStore assignedItemStore: PropTypes.object.isRequired, // filterText: A string that will be used to filter items in both columns filterText: PropTypes.string, // Note: Callbacks should return a promise that will resolve when the operation succeeds // and is rejected when it fails. The component will be in a loading state until the promise // resolves or is rejected. // assign items callback, called with an array of values to be assigned to the group onAssignItems: PropTypes.func.isRequired, // remove items callback, called with an array of values to be removed from the group onRemoveItems: PropTypes.func.isRequired, // remove items callback, called with an array of values to be removed from the group onMoveItems: PropTypes.func, // The height of the component, defaults to 500px height: PropTypes.number }; GroupEditor.contextTypes = { d2: PropTypes.object }; GroupEditor.defaultProps = { height: 500, filterText: '', onMoveItems: function onMoveItems() {} }; export default GroupEditor;