labo-components
Version:
382 lines (335 loc) • 12 kB
JSX
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>
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
};