UNPKG

labo-components

Version:
401 lines (363 loc) 13.6 kB
import React from "react"; import PropTypes from "prop-types"; //TODO fix autosuggest https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html import Autosuggest from "react-autosuggest"; //See: https://github.com/moroshko/react-autosuggest import IDUtil from "../../../util/IDUtil"; import ExternalAPI from "../../../api/ExternalAPI"; import debounce from "debounce"; import { AnnotationEvents } from "../AnnotationClient"; import Strings from "../_Strings"; import ClassificationSelector from "./ClassificationSelector"; import { getAnnotationsPerType } from "../AnnotationHelpers"; import { CLASSIFICATION } from "../../../util/AnnotationConstants"; export default class ClassificationForm extends React.PureComponent { constructor(props) { super(props); this.config = this.props.annotationClient.config.motivationConfig[ "classification" ]; this.xhrs = []; this.getSuggestions = debounce(this.getSuggestions.bind(this), 400); this.state = { vocabulary: this.config.vocabularies ? this.config.vocabularies[0] : null, value: "", //the label of the selected classification (autocomplete) suggestionId: null, //stores the id/uri of the selected classification (e.g. GTAA URI) suggestions: [], //current list of suggestions shown isLoading: false, //loading the suggestions from the server showClassificationSelector: false, // show classification selector }; } /* --------------- ANNOTATION CLIENT EVENT HANDLING -------------- */ componentDidMount() { this.props.annotationClient.events.bind( AnnotationEvents.ON_SAVE, this.onSave ); } componentWillUnmount() { this.props.annotationClient.events.unbind( AnnotationEvents.ON_SAVE, this.onSave ); //cancel all previous outgoing requests for (let x = this.xhrs.length; x > 0; x--) { this.xhrs[x - 1].abort(); this.xhrs.pop(); } } onSave = () => { this.forceUpdate(); }; /* ------------------- CRUD / loading of classifications ------------------- */ addClassification = (e) => { if (this.state.value != "") { let suggestionId = this.state.suggestionId; if (this.state.vocabulary && this.state.vocabulary == "custom") { suggestionId = IDUtil.guid(); } const classification = { id: suggestionId, label: this.state.value, vocabulary: this.state.vocabulary, annotationType: "classification", }; //after setting the state save the (annotation) body this.setState( { value: "", suggestionId: suggestionId, }, () => { this.props.annotationClient.saveBodyElement( classification, false, true, this.props.annotation ); } ); } }; setVocabulary = (event) => this.setState({ vocabulary: event.target.value }); getSuggestions(value, callback) { //cancel all previous outgoing requests for (let x = this.xhrs.length; x > 0; x--) { this.xhrs[x - 1].abort(); this.xhrs.pop(); } const xhr = ExternalAPI.autocomplete( this.state.vocabulary, value, '', callback ); this.xhrs.push(xhr); } /* ------------------- REACT-AUTOSUGGEST FUNCTIONS ------------------- */ loadSuggestions = (value) => { this.setState({ isLoading: true, suggestions: [], }); if (value.value === this.state.chosenValue) { this.setState({ isLoading: false, }); } else { this.getSuggestions(value.value, (data) => { if (!data || data.error || !Array.isArray(data)) { this.setState({ isLoading: false, suggestions: [], }); console.error("Autocomplete failed"); } else { this.setState({ isLoading: false, suggestions: data, }); } }); } }; onSuggestionsFetchRequested = (value) => this.loadSuggestions(value); onSuggestionsClearRequested = () => this.setState({ suggestions: [] }); getSuggestionValue = (suggestion) => { this.setState({ suggestionId: suggestion.value }); return suggestion.label.split("|")[0]; }; //TODO the rendering should be adapted for different vocabularies renderSuggestion = (suggestion) => { const arr = suggestion.label.split("|"); let label = arr[1]; const scopeNote = arr[2] ? "(" + arr[2] + ")" : ""; if (this.state.vocabulary == "GTAA") { switch (arr[1]) { case "Persoon": label = ( <span className="label label-warning">Persoon</span> ); break; case "Maker": label = <span className="label label-warning">Maker</span>; break; case "Geografisch": label = ( <span className="label label-success">Locatie</span> ); break; case "Naam": label = <span className="label label-info">Naam</span>; break; case "Onderwerp": label = ( <span className="label label-primary">Onderwerp</span> ); break; case "Genre": label = <span className="label label-default">Genre</span>; break; case "B&G Onderwerp": label = ( <span className="label label-danger"> B&G Onderwerp </span> ); break; default: label = ( <span className="label label-default">Concept</span> ); break; } } else if (this.state.vocabulary == "DBpedia") { label = <span className="label label-default">Concept</span>; } else if (this.state.vocabulary == "UNESCO") { switch (arr[1]) { case "Education": label = ( <span className="label label-warning">{arr[1]}</span> ); break; case "Science": label = ( <span className="label label-warning">{arr[1]}</span> ); break; case "Social and human sciences": label = ( <span className="label label-success">{arr[1]}</span> ); break; case "Information and communication": label = <span className="label label-info">{arr[1]}</span>; break; case "Politics, law and economics": label = ( <span className="label label-primary">{arr[1]}</span> ); break; case "Countries and country groupings": label = ( <span className="label label-default">{arr[1]}</span> ); break; default: label = ( <span className="label label-default">{arr[1]}</span> ); break; } } return ( <span> {arr[0]}&nbsp;{label}&nbsp;{scopeNote} </span> ); }; onChange(event, { newValue }) { this.setState({ chosenValue: newValue, value: newValue, }); } // after selection a suggestion, add it immediately onSelect = (e, { suggestionValue }) => { this.setState( { chosenValue: suggestionValue, value: suggestionValue, }, () => { this.addClassification(); } ); }; onSubmit = (e) => { e.preventDefault(); this.addClassification(); }; /* ------------------- CLASSIFICATION SELECTOR ------------------- */ showClassificationSelector = () => { this.setState({ showClassificationSelector: true }); }; hideClassificationSelector = () => { this.setState({ showClassificationSelector: false }); }; toggleClassificationSelector = () => { this.setState({ showClassificationSelector: !this.state.showClassificationSelector, }); }; toggleClassificationFromSelector = (_classification) => { // Check if there is an existing classification with the same id let existingClassification = null; getAnnotationsPerType([CLASSIFICATION], this.props.annotation)[ CLASSIFICATION ].some((classification) => { if (classification.id == _classification.id) { existingClassification = classification; return true; } return false; }); if (existingClassification) { this.props.deleteAnnotation(existingClassification); return; } // Create a fresh classification and add it to the annotation const classification = Object.assign({}, _classification); delete classification.annotationId; this.props.annotationClient.saveBodyElement( classification, false, true, this.props.annotation ); }; /* ------------------- RENDER FUNCTIONS ------------------- */ renderVocabularySelector = ( vocabularies, activeVocabulary, setVocabFunc ) => { const options = vocabularies.map((v, index) => ( <option key={"__v__" + index} value={v}> {v} </option> )); options.push( <option key="__v__custom" value="custom"> Custom (no external lookup) </option> ); return ( <select value={activeVocabulary} onChange={setVocabFunc}> {options} </select> ); }; render() { const vocabSelector = this.renderVocabularySelector( this.config.vocabularies, this.state.vocabulary, this.setVocabulary ); const inputProps = { placeholder: "Search", value: this.state.value, onChange: this.onChange.bind(this), }; const currentClassificationsButton = ( <div className="current-classifications-button" title={Strings.ANNOTATION_TYPE_CLASSIFICATION_CURRENT} onClick={this.showClassificationSelector} ></div> ); const classificationSelector = this.state.showClassificationSelector ? ( <ClassificationSelector activeAnnotation={this.props.annotation} onClose={this.hideClassificationSelector} onSelect={this.toggleClassificationFromSelector} /> ) : null; return ( <div className={IDUtil.cssClassName("classification-form")}> {classificationSelector} <form onSubmit={this.onSubmit}> <div className="filter"> <strong> {Strings.ANNOTATION_TYPE_CLASSIFICATION_VOCABULARY} </strong> {vocabSelector} <div className="spacer" /> {currentClassificationsButton} </div> <Autosuggest ref={(input) => (this.classifications = input)} suggestions={this.state.suggestions} onSuggestionsFetchRequested={ this.onSuggestionsFetchRequested } onSuggestionsClearRequested={ this.onSuggestionsClearRequested } getSuggestionValue={this.getSuggestionValue} renderSuggestion={this.renderSuggestion} inputProps={inputProps} onSuggestionSelected={this.onSelect} /> </form> </div> ); } } ClassificationForm.propTypes = { annotationClient: PropTypes.object.isRequired, annotation: PropTypes.object.isRequired, deleteAnnotation: PropTypes.func.isRequired, };