UNPKG

labo-components

Version:
382 lines (335 loc) 12 kB
import React from 'react'; import PropTypes from 'prop-types'; import IDUtil from '../../../util/IDUtil'; import IconUtil from '../../../util/IconUtil'; import { InformationCardUtil } from '../AnnotationClient'; /* TODO (old comment): - validate the config that is passed, see the metadata block in e.g. arttube-item-details.json - gracefully deal with the fact that the template is not stored in the annotation (which makes it impossible to fill the dropdown box with the template that was used to create the annotation) */ const NO_TEMPLATE = 'NO_TEMPLATE'; export default class MetadataForm extends React.PureComponent { constructor(props) { super(props); const activeCard = this.props.card ? JSON.parse(JSON.stringify(this.props.card)) // deep copy : null; const templates = InformationCardUtil.determinePossibleTemplates( this.props.annotationClient.config.motivationConfig['metadata'], this.props.annotation ); let activeTemplate = templates ? templates[0] : ''; //default if (activeCard && activeCard.annotationTemplate) { activeTemplate = this.getTemplateById( templates, activeCard.annotationTemplate ); } //TODO also fetch these from the annotation client rather than adding it to the state this.state = { new: !activeCard, activeCard: activeCard || this.createEmptyCard(activeTemplate), templates: templates, //list of templates activeTemplate: activeTemplate, singleCardMode: InformationCardUtil.determineSingleCardMode( this.props.annotationClient.config.motivationConfig['metadata'], this.props.annotation ) }; this.CLASS_PREFIX = 'icf'; } /* --------------- RELATED TO (ACTIVE) TEMPLATES --------------*/ setActiveTemplate = e => { // template const templateId = e.target.value; const activeTemplate = templateId === NO_TEMPLATE ? '' : this.getTemplateById(this.state.templates, templateId); // create a new empty card with the active template const activeCard = this.createEmptyCard(activeTemplate); // add additional property fields this.state.activeCard.properties.forEach(property => { if ( !activeCard.properties.some(p => { if (p.key == property.key) { // update value p.value = property.value; return true; } return false; }) ) { // add missing property activeCard.properties.push(property); } }); this.setState({ activeTemplate, activeCard }); }; getTemplateById(templates, templateId) { if (templates) { const temp = templates.filter(t => { return t.id == templateId; }); if (temp.length > 0) { return temp[0]; } } return ''; } // If the card is based on a template (and has the annotationTemplate property), check if there is a // type configured for the kind of input field getInputFieldType(configuredTemplates, card, property) { let fieldType = 'string'; if (card && card.annotationTemplate) { const t = configuredTemplates[card.annotationTemplate]; if (t && t.properties) { const tmp = t.properties.filter(p => { return p.key == property; }); if (tmp.length == 1 && tmp[0].type) { fieldType = tmp[0].type; } } } return fieldType; } isTemplateLocked(template) { return template && template.locked; } /* --------------- CRUD ON CARDS -------------------- */ async saveCard(card) { //(if there is an active template) attach the selected template ID to the annotation/card (otherwise remove it) if (this.state.activeTemplate) { card.annotationTemplate = this.state.activeTemplate.id; } else if (card.hasOwnProperty('annotationTemplate')) { delete card['annotationTemplate']; } const saveData = await this.props.annotationClient.saveBodyElement( Object.assign({ annotationType: 'metadata' }, card), false, true, this.props.annotation ); return new Promise(resolve => { resolve(saveData); }); } setCard(activeCard) { this.setState({ activeCard }); } /* --------------- CRUD ON PROPERTIES -------------------- */ addProperty = e => { e.preventDefault(); const card = Object.assign({}, this.state.activeCard); card.properties.push({ key: '', value: '' }); this.setCard(card); }; createEmptyCard(template) { return template ? { annotationTemplate: template, properties: template.properties.map(prop => { return { key: prop.key, value: '' }; }) } : { annotationTemplate: template, properties: [] }; } updateProperty(index, isKey, e) { const card = Object.assign({}, this.state.activeCard); if (isKey) { card.properties[index].key = e.target.value; } else { card.properties[index].value = e.target.value; } this.setCard(card); // save on enter key if (e.keyCode === 13) { this.saveCard(card).then(() => { this.props.onSave(); }); } } removeProperty(index, e) { const card = Object.assign({}, this.state.activeCard); card.properties.splice(index, 1); this.setCard(card); } onSave = () => { this.saveCard(this.state.activeCard).then(saveData => { this.props.onSave(); }); }; renderFormFields = ( activeCard, activeTemplate, isTemplateLocked, updatePropFunc ) => { const templateProperties = activeTemplate ? this.createEmptyCard(activeTemplate) : ''; const formProperties = activeCard ? activeCard.properties : templateProperties; if (!formProperties) return null; return formProperties.map((prop, i) => { const inputField = this.getInputFieldType( this.props.annotationClient.config.motivationConfig[ 'metadata' ], activeCard, prop.key ) === 'markdown' ? ( <textarea value={prop.value} rows="5" onChange={updatePropFunc.bind(this, i, false)} ></textarea> ) : ( <input type="text" value={prop.value} onChange={updatePropFunc.bind(this, i, false)} /> ); //only add delete buttons and editable property fields when the template is not locked const delPropBtn = !isTemplateLocked ? ( <div className="remove-prop"> <span className={IconUtil.getUserActionIcon( 'remove', false, false, true )} onClick={this.removeProperty.bind(this, i)} ></span> </div> ) : null; const propertyField = isTemplateLocked ? ( <strong>{prop.key}</strong> ) : ( <input type="text" value={prop.key} onChange={updatePropFunc.bind(this, i, true)} /> ); //assemble the elements into the eventual form return ( <div key={'prop__' + i} className="card-row"> <div className="card-key">{propertyField}</div> <div className="card-value">{inputField}</div> {delPropBtn} </div> ); }); }; renderTemplateSelector = ( templates, activeTemplate, setActiveTemplateFunc ) => { const templateOptions = Object.keys(templates).map(key => { const t = templates[key]; return ( <option key={t.id + '__option'} value={t.id}> {t.label} </option> ); }); //whenever no template is used/defined templateOptions.splice( 0, 0, <option key="null__option" value={NO_TEMPLATE}> No template </option> ); return ( <div className="filter"> <strong>Template: </strong> <select value={activeTemplate ? activeTemplate.id : NO_TEMPLATE} onChange={setActiveTemplateFunc} > {templateOptions} </select> </div> ); }; render() { const isTemplateBlocked = this.isTemplateLocked( this.state.activeTemplate ); const formFields = this.renderFormFields( this.state.activeCard, this.state.activeTemplate, isTemplateBlocked, this.updateProperty ); //draw the template selector (if any have been defined) const templateSelect = this.state.templates ? this.renderTemplateSelector( this.state.templates, this.state.activeTemplate, this.setActiveTemplate ) : null; //draw the add property button if the template is not locked const addPropBtn = !isTemplateBlocked ? ( <div className="add-property" onClick={this.addProperty}> <span className={IconUtil.getUserActionIcon( 'add', false, false, true )} ></span> &nbsp; Add property </div> ) : null; // draw the save button const saveBtn = ( <div className="save-button" onClick={this.onSave}> Save </div> ); // draw the cancel button const cancelBtn = ( <div className="cancel-button" onClick={this.props.onSave}> Cancel </div> ); const cardForm = formFields ? ( <div className="card-form"> {templateSelect} <div className="card-fields">{formFields}</div> <div className="actions"> {addPropBtn} {cancelBtn} {saveBtn} </div> </div> ) : null; return ( <div className={IDUtil.cssClassName('metadata-form')}> {cardForm} </div> ); } } MetadataForm.propTypes = { annotationClient: PropTypes.object.isRequired, annotation: PropTypes.object.isRequired, card: PropTypes.object, onSave: PropTypes.func.isRequired };