labo-components
Version:
243 lines (210 loc) • 8.59 kB
JSX
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>
);
};