UNPKG

labo-components

Version:
243 lines (210 loc) 8.59 kB
import React from 'react'; import ReactTooltip from 'react-tooltip'; //https://www.npmjs.com/package/react-tooltip import CreatableSelect from 'react-select/creatable'; import { components } from 'react-select'; import IDUtil from '../../util/IDUtil'; import ComponentUtil from '../../util/ComponentUtil'; import FlexModal from '../FlexModal'; import FieldCategoryCreator from './FieldCategoryCreator'; export default class FieldCategorySelector extends React.Component { constructor(props) { super(props); this.state = { showModal : false, clusterName : null }; this.CLASS_PREFIX = 'fcs'; } onComponentOutput = (componentClass, data) => { if(componentClass === 'FieldCategoryCreator') { this.onCustomFieldsSelected(data); } }; onOutput(data) { if(this.props.onOutput) { if(data !== null) { this.props.onOutput(this.constructor.name, data); } else { this.props.onOutput(this.constructor.name, null); } } } handleChange = ({ options }) => { let found = false; const tmp = {}; for(let i=0;i<options.length;i++) { const fc = options[i]; if(tmp[fc.id]) { found = true; break; } tmp[fc.id] = true; } if(!found) { this.onOutput(options); } } onCustomFieldsSelected(data) { ComponentUtil.hideModal(this, 'showBookmarkModal', 'fields__modal', true, () => { const fields = this.props.fieldCategory || []; if(data) { //when nothing was selected, it is no use to update the owner fields.push(data); this.onOutput(fields) } }); } onSelectFieldCategories = (options, { action }) => { if(['select-option', 'remove-value'].indexOf(action) !== -1) { let found = false; const tmp = {}; for(let i=0;i<options.length;i++) { const fc = options[i]; if(tmp[fc.value]) { found = true; break; } tmp[fc.value] = true; } if(!found) { this.onOutput(options); } } }; //TODO finish properly wiring up the custom field cluster stuff openFieldClusterCreator = inputValue => { this.setState({ showModal: true, clusterName : inputValue}); }; renderSelector = (queryId, collectionConfig, allOptions, selectedOptions) => { //console.debug('NOW THESE ARE ALL OPTIONS', allOptions, selectedOptions); const onTopOfSearchResults = { menu: styles => ({ ...styles, 'z-index': 999999 }) } return ( <CreatableSelect isMulti //placeholder="Type to find or create a new search filter" options={allOptions} value={selectedOptions} components={{MultiValueLabel}} onCreateOption={this.openFieldClusterCreator} onChange={this.onSelectFieldCategories} styles={onTopOfSearchResults} /> ) }; renderSelectionModal = (collectionConfig, fcCreatorCallback, clusterName) => { const allTextFields = collectionConfig ? collectionConfig.getStringFields() : null; if(!allTextFields) return null; return ( <FlexModal size="large" elementId="fields__modal" stateVariable="showModal" owner={this} title="Create a new cluster of metadata fields to narrow down search"> <FieldCategoryCreator clusterName={clusterName} data={ allTextFields.map(field => ( {'value': field, 'prettyName': collectionConfig.toPrettyFieldName(field)} )) } onOutput={fcCreatorCallback}/> </FlexModal> ); }; //returns all possible options for the user, react-select will figure out what remains in the drop-down list getAllOptions = (collectionConfig, selectedOptions) => { //include the configured options const allOptions = collectionConfig && collectionConfig.getMetadataFieldCategories() ? collectionConfig.getMetadataFieldCategories() : [] ; const allMetadataOption = this.__generateAllMetadataFieldCategory(this.props.collectionConfig) if(allMetadataOption) { allOptions.unshift(allMetadataOption); } allOptions.push(...this.__extractCustomOptions(selectedOptions)); //map the options to the desired object return this.__toOptionGroups(allOptions); }; __extractCustomOptions = selectedOptions => { if(!selectedOptions)return []; return selectedOptions.filter(fc => fc.value && fc.value.indexOf('customfc__') !== -1); }; //this function should return ALL possible options and not care about which are selected __toOptionGroups = allOptions => { const enrichments = allOptions.filter(fc => fc.enrichment); const metadataFields = allOptions.filter(fc => !fc.enrichment); const METADATA_LABEL = "-- Archive's metadata --"; const ENRICHMENT_LABEL = "-- Enrichments --"; const optionGroups = []; if(metadataFields.length > 0) { optionGroups.push({'label' : METADATA_LABEL, 'options' : metadataFields.map(this.__toFilterOption)}); } if(enrichments.length > 0) { optionGroups.push({'label' : ENRICHMENT_LABEL, 'options' : enrichments.map(this.__toFilterOption)}); } return optionGroups; } __toFilterOption = fieldCategory => ({ value: fieldCategory.value ? fieldCategory.value : fieldCategory.id, label: fieldCategory.label, fields: fieldCategory.fields, nestedPath : fieldCategory.nestedPath }); //based on the available string fields and configured enrichments fields, yields a list of all metadata fields __generateAllMetadataFieldCategory = collectionConfig => { const allTextFields = collectionConfig ? collectionConfig.getStringFields() : null; if(!allTextFields || allTextFields.length <= 0) return null; //now filter out the known (configured) enrichment fields out of the list of all string fields const configuredOptions = collectionConfig && collectionConfig.getMetadataFieldCategories() ? collectionConfig.getMetadataFieldCategories() : [] ; const fieldsToIgnore = [...configuredOptions.filter(el => el.enrichment)].map(i => i.fields).flat(); const nonEnrichedMetadata = fieldsToIgnore.length ? fieldsToIgnore.map( ignoreItem => allTextFields.indexOf(ignoreItem) !== -1 ? allTextFields.filter(e => e !== ignoreItem) : null ).flat() : null ; return { value: "allMetadata", label: "All metadata fields", fields: nonEnrichedMetadata ? nonEnrichedMetadata : allTextFields } } render() { //render the modal for creating new field clusters const fieldSelectionModal = this.state.showModal ? this.renderSelectionModal( this.props.collectionConfig, this.onComponentOutput, this.state.clusterName ) : null; const allOptions = this.getAllOptions(this.props.collectionConfig, this.props.fieldCategory) const fieldCategorySelector = this.renderSelector( this.props.queryId, this.props.collectionConfig, allOptions, //all possible options (with "value") this.props.fieldCategory //selected optoins ); return ( <div className={IDUtil.cssClassName('field-category-selector')}> {fieldCategorySelector} <ReactTooltip id={'__fs__tt' + this.props.queryId} /> {fieldSelectionModal} </div> ) } } //custom component, representing a selected field categories WITH tooltips for enclosed metadata fields export const MultiValueLabel = props => { const optionId = 'tt__' + props.data.value; return ( <components.MultiValueLabel {...props}> <a data-tip="true" data-for={optionId}>{props.data.label}</a> <ReactTooltip id={optionId}> {props.data.fields.map(f => <span key={f}>{f}<br/></span>)} </ReactTooltip> </components.MultiValueLabel> ); };