UNPKG

core-resource-app-test

Version:

App that contains assets and scripts for the core apps

423 lines (383 loc) 16.3 kB
import React from 'react'; import { findDOMNode } from 'react-dom'; // 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 TranslateMixin from '../i18n/Translate.mixin.js'; 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'); export default React.createClass({ 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: React.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: React.PropTypes.object.isRequired, // filterText: A string that will be used to filter items in both columns filterText: React.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: React.PropTypes.func.isRequired, // remove items callback, called with an array of values to be removed from the group onRemoveItems: React.PropTypes.func.isRequired, // The height of the component, defaults to 500px height: React.PropTypes.number, }, contextTypes: { d2: React.PropTypes.object, }, mixins: [TranslateMixin], componentDidMount() { this.disposables = []; this.disposables.push(this.props.itemStore.subscribe(state => this.setState({ loading: !state }))); this.disposables.push(this.props.assignedItemStore.subscribe(() => this.forceUpdate())); }, componentWillReceiveProps(props) { if (props.hasOwnProperty('filterText') && this.leftSelect && this.rightSelect) { this.setState({ selectedLeft: [].filter.call(this.leftSelect.selectedOptions, item => item.text.toLowerCase().indexOf((`${props.filterText}`).trim().toLowerCase()) !== -1).length, selectedRight: [].filter.call(this.rightSelect.selectedOptions, item => item.text.toLowerCase().indexOf((`${props.filterText}`).trim().toLowerCase()) !== -1).length, }); } }, componentWillUnmount() { this.disposables.forEach((disposable) => { disposable.unsubscribe(); }); }, getDefaultProps() { return { height: 500, filterText: '', }; }, getInitialState() { return { // Number of items selected in the left/right columns selectedLeft: 0, selectedRight: 0, // Loading loading: true, }; }, // // Data handling utility functions // getItemStoreIsCollection() { return this.props.itemStore.state !== undefined && (typeof this.props.itemStore.state.values === 'function' && typeof this.props.itemStore.state.has === 'function'); }, getItemStoreIsArray() { return this.props.itemStore.state !== undefined && this.props.itemStore.state.constructor.name === 'Array'; }, getAssignedItemStoreIsCollection() { return this.props.assignedItemStore.state !== undefined && (typeof this.props.assignedItemStore.state.values === 'function' && typeof this.props.assignedItemStore.state.has === 'function'); }, getAssignedItemStoreIsArray() { return this.props.assignedItemStore.state !== undefined && this.props.assignedItemStore.state.constructor.name === 'Array'; }, getAllItems() { return this.getItemStoreIsCollection() ? Array.from(this.props.itemStore.state.values()).map(item => ({ value: item.id, text: item.name })) : (this.props.itemStore.state || []); }, getItemCount() { return this.getItemStoreIsCollection() && this.props.itemStore.state.size || this.getItemStoreIsArray() && this.props.itemStore.state.length || 0; }, getIsValueAssigned(value) { return this.getAssignedItemStoreIsCollection() ? this.props.assignedItemStore.state.has(value) : this.props.assignedItemStore.state && this.props.assignedItemStore.state.indexOf(value) !== -1; }, getAssignedItems() { return this.getAllItems().filter(item => this.getIsValueAssigned(item.value)); }, getAvailableItems() { return this.getAllItems().filter(item => !this.getIsValueAssigned(item.value)); }, getAllItemsFiltered() { return this.filterItems(this.getAllItems()); }, getAssignedItemsFiltered() { return this.filterItems(this.getAssignedItems()); }, getAvailableItemsFiltered() { return this.filterItems(this.getAvailableItems()); }, getAssignedItemsCount() { return this.getAssignedItems().length; }, getAvailableItemsCount() { return this.getAvailableItems().length; }, getAssignedItemsFilterCount() { return this.getFilterText().length === 0 ? 0 : this.getAssignedItems().length - this.getAssignedItemsFiltered().length; }, getAvailableItemsFilterCount() { return this.getFilterText().length === 0 ? 0 : this.getAvailableItems().length - this.getAvailableItemsFiltered().length; }, getAssignedItemsUnfilteredCount() { return this.getFilterText().length === 0 ? this.getAssignedItemsCount() : this.getAssignedItemsCount() - this.getAssignedItemsFilterCount(); }, getAvailableItemsUnfilteredCount() { return this.getFilterText().length === 0 ? this.getAvailableItemsCount() : this.getAvailableItemsCount() - this.getAvailableItemsFilterCount(); }, getFilterText() { return this.props.filterText ? this.props.filterText.trim().toLowerCase() : ''; }, getAvailableSelectedCount() { return Math.max(this.state.selectedLeft, 0); }, getAssignedSelectedCount() { return Math.max(this.state.selectedRight, 0); }, getSelectedCount() { return Math.max(this.getAvailableSelectedCount(), this.getAssignedSelectedCount()); }, byAssignedItemsOrder(left, right) { const 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; }, // // Rendering // render() { const filterHeight = this.getFilterText().length > 0 ? 15 : 0; const 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', }, }; const onChangeLeft = (e) => { this.clearSelection(false, true); this.setState({ selectedLeft: e.target.selectedOptions.length, }); }; const onChangeRight = (e) => { this.clearSelection(true, false); this.setState({ selectedRight: e.target.selectedOptions.length, }); }; const hiddenLabel = itemCount => (this.getItemCount() > 0 && this.getFilterText().length > 0 ? `${itemCount} ${this.getTranslation('hidden_by_filters')}` : ''); const selectedLabel = () => (this.getSelectedCount() > 0 ? `${this.getSelectedCount()} ${this.getTranslation('selected')}` : ''); return ( <div style={styles.container}> <div style={styles.left}> <Paper style={styles.paper}> <div style={styles.hidden}>{hiddenLabel(this.getAvailableItemsFilterCount())}</div> <select multiple style={styles.select} onChange={onChangeLeft} ref={(r) => { this.leftSelect = findDOMNode(r); }} > {this.getAvailableItemsFiltered().map(item => ( <option key={item.value} value={item.value} onDoubleClick={this._assignItems} style={styles.options} >{item.text}</option> ))} </select> </Paper> <RaisedButton label={`${this.getTranslation('assign_all')} ${this.getAvailableItemsUnfilteredCount() === 0 ? '' : this.getAvailableItemsUnfilteredCount()} \u2192`} disabled={this.state.loading || this.getAvailableItemsUnfilteredCount() === 0} onClick={this._assignAll} style={{ marginTop: '1rem' }} secondary /> </div> <div style={styles.middle}> <div style={styles.selected}>{selectedLabel()}</div> <RaisedButton label="&rarr;" secondary onClick={this._assignItems} style={styles.buttons} disabled={this.state.loading || this.state.selectedLeft === 0} /> <RaisedButton label="&larr;" secondary onClick={this._removeItems} style={styles.buttons} disabled={this.state.loading || this.state.selectedRight === 0} /> <div style={styles.status}> {this.state.loading ? <CircularProgress small style={{ width: 60, height: 60 }} /> : undefined } </div> </div> <div style={styles.right}> <Paper style={styles.paper}> <div style={styles.hidden}>{hiddenLabel(this.getAssignedItemsFilterCount())}</div> <select multiple style={styles.select} onChange={onChangeRight} ref={(r) => { this.rightSelect = findDOMNode(r); }} > {this.getAssignedItemsFiltered() .sort(this.byAssignedItemsOrder) .map(item => (<option key={item.value} value={item.value} onDoubleClick={this._removeItems} style={styles.options} >{item.text}</option>)) } </select> </Paper> <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._removeAll} secondary /> </div> </div> ); }, clearSelection(left = true, right = true) { if (left) { this.leftSelect.selectedIndex = -1; } if (right) { this.rightSelect.selectedIndex = -1; } this.setState(state => ({ selectedLeft: left ? 0 : state.selectedLeft, selectedRight: right ? 0 : state.selectedRight, })); }, filterItems(items) { return items.filter(item => this.getFilterText().length === 0 || item.text.trim().toLowerCase().indexOf(this.getFilterText()) !== -1); }, getSelectedItems() { return [].map.call(this.rightSelect.selectedOptions, item => item.value); }, // // Event handlers // _assignItems() { this.setState({ loading: true }); this.props.onAssignItems([].map.call(this.leftSelect.selectedOptions, item => item.value)) .then(() => { this.clearSelection(); this.setState({ loading: false }); }) .catch(() => { this.setState({ loading: false }); }); }, _removeItems() { this.setState({ loading: true }); this.props.onRemoveItems([].map.call(this.rightSelect.selectedOptions, item => item.value)) .then(() => { this.clearSelection(); this.setState({ loading: false }); }) .catch(() => { this.setState({ loading: false }); }); }, _assignAll() { this.setState({ loading: true }); this.props.onAssignItems([].map.call(this.leftSelect.options, item => item.value)) .then(() => { this.clearSelection(); this.setState({ loading: false }); }).catch(() => { this.setState({ loading: false }); }); }, _removeAll() { this.setState({ loading: true }); this.props.onRemoveItems([].map.call(this.rightSelect.options, item => item.value)) .then(() => { this.clearSelection(); this.setState({ loading: false }); }).catch(() => { this.setState({ loading: false }); }); }, });