labo-components
Version:
338 lines (306 loc) • 12.1 kB
JSX
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import AnnotationUtil from '../../util/AnnotationUtil';
import IDUtil from '../../util/IDUtil';
import AnnotationAPI from '../../api/AnnotationAPI';
import Project from '../../model/Project';
import ProjectList from "../workspace/projects/ProjectList";
class BookmarkSelector extends React.Component {
constructor(props) {
super(props);
this.state = {
bookmarkGroups: [],
selectedGroups: {},
emptyFieldNameError: false,
nameExistsError: false,
selectedProject: this.props.project
};
this.CLASS_PREFIX = 'bms';
this.newGroupRef = React.createRef();
}
componentDidMount() {
this.reloadBookmarkGroupList(this.props.user, this.state.selectedProject);
}
// load all "bookmark group annotations" of current user project
reloadBookmarkGroupList = (user, project) => {
const filter = {
'user.keyword': user.id,
'motivation': 'bookmarking'
};
if (project) {
filter['project'] = project.id;
}
AnnotationAPI.getFilteredAnnotations(
user.id,
filter,
null, //not_filters
this.onLoadBookmarkAnnotations
);
}
/*--------------------------------------------- ON LOAD FUNCTIONS -------------------------------- */
onLoadBookmarkAnnotations = bookmarkGroups => {
if (this.props.resourceId) {
//only when opened from the resource viewer (flags the groups the resource is a member of)
this.updateGroupMembership(this.props.resourceId, bookmarkGroups);
} else {
//only when opened from the search results/selection list
this.setState({ bookmarkGroups: bookmarkGroups || [] });
}
};
//update this.state.selectedGroups with the groups the specified resource is a member of
updateGroupMembership = (resourceId, bookmarkGroups) => {
const filter = {
'target.selector.value.id': resourceId,
'user.keyword': this.props.user.id,
motivation: 'bookmarking'
};
if (this.state.selectedProject && this.state.selectedProject.id) {
filter['project'] = this.state.selectedProject.id;
}
AnnotationAPI.getFilteredAnnotations(
this.props.user.id,
filter,
null,
this.onUpdateGroupMembership.bind(this, bookmarkGroups),
0, //offset
250, //size
null, //sort direction
null //date range
);
};
onUpdateGroupMembership(bookmarkGroups, resourceGroups) {
const selectedGroups = {};
bookmarkGroups.forEach(group => {
if (
resourceGroups.findIndex(
resourceGroup => resourceGroup.id === group.id
) !== -1
) {
selectedGroups[group.id] = true;
}
});
this.setState({
bookmarkGroups: bookmarkGroups,
selectedGroups: selectedGroups
});
}
/*--------------------------------------------- USER TRIGGERED FUNCTIONS -------------------------------- */
//selects or deselects a bunch of groups
toggleBookmarkGroup(group) {
const selectedGroups = this.state.selectedGroups;
if (selectedGroups[group.id] === true) {
delete selectedGroups[group.id];
} else {
selectedGroups[group.id] = true;
}
this.setState({
selectedGroups: selectedGroups,
emptyFieldNameError: false, //reset both errors when toggling a group
nameExistsError: false
});
}
addNewBookmarkGroup = (e, callback) => {
e.preventDefault();
const nameExist = this.state.bookmarkGroups.some(item =>
item.body.some(it => it.label === this.newGroupRef.current.value)
);
if (!this.newGroupRef.current.value.trim().length) {
//show error msg
this.setState({
emptyFieldNameError: true,
nameExistsError: false
});
return false;
} else if (nameExist) {
this.setState({
nameExistsError: true,
emptyFieldNameError: false
});
return false;
}
const bookmarkGroup = AnnotationUtil.generateBookmarkGroupAnnotation(
this.props.user,
this.state.selectedProject,
this.props.collectionId,
this.newGroupRef.current.value.trim()
);
//update the state and reset the text field to ''.
this.setState(
{
bookmarkGroups: this.state.bookmarkGroups.concat([
bookmarkGroup
]), //add the new bookmarkgroup
emptyFieldNameError: false,
selectedGroups: {
...this.state.selectedGroups,
...{ [bookmarkGroup.id]: true }
} //add the new group to the selected groups
},
() => {
this.newGroupRef.current.value = '';
if (callback && typeof callback === 'function') {
callback(); //callback the owner
}
}
);
};
submitForm = e => {
//if the user entered a bookmark group name, try to add it & subsequently save it
if (this.newGroupRef.current.value && this.newGroupRef.current.value.trim().length > 0) {
this.addNewBookmarkGroup(e, this.onOutput);
} else { //only allow saving when a bookmark group is selected
if(this.state.selectedGroups && Object.keys(this.state.selectedGroups).length > 0) {
this.onOutput();
}
}
};
/*--------------------------------------------- COMMUNICATE BACK TO THE OWNER -------------------------------- */
//communicate back a multi-target annotation with a classification body
onOutput = () => {
if (this.props.onOutput) {
//returns all bookmark groups (multi-target annotations) + which have been selected
this.props.onOutput(this.constructor.name, {
allGroups: this.state.bookmarkGroups,
selectedGroups: this.state.selectedGroups,
selectedProject: this.state.selectedProject
});
}
};
onSelectProject = project => {
if(project && project.name) {
this.setState({
selectedProject: project
}, this.reloadBookmarkGroupList.bind(this, this.props.user, project))
}
};
/*--------------------------------------------- RENDER FUNCTIONS -------------------------------- */
renderProjectList = (activeProject, userProjects, user, onSelectProject) => (
<div
title={activeProject ? "Current user project. Click to change." : ""}
style={{float: activeProject ? 'right' : 'none'}}
>
<ProjectList
buttonText="Set active project"
activeProject={activeProject}
onSelect={onSelectProject}
projects={userProjects}
user={user}
projectIcon={false}
/>
</div>
);
renderBookmarkGroupForm = (bookmarkGroupList) => (
<div className={IDUtil.cssClassName('bgroup-form', this.CLASS_PREFIX)}>
{bookmarkGroupList && <h4>Your bookmark groups</h4>}
{bookmarkGroupList}
<h4>Create bookmark group</h4>
<div className={IDUtil.cssClassName('new-bgroup', this.CLASS_PREFIX)}>
<form onSubmit={this.addNewBookmarkGroup}>
<input ref={this.newGroupRef} type="text" aria-label="Create new bookmark group"/>
<button type="submit" className="btn btn-default">
Add
</button>
</form>
</div>
</div>
);
renderBookmarkGroupList = (bookmarkGroups, selectedGroups) => {
//TODO which part of the body is the name of the bookmark group?
const options = bookmarkGroups
.sort((a, b) => {
const nameA = a.body[0].label.toUpperCase(); // ignore upper and lowercase
const nameB = b.body[0].label.toUpperCase(); // ignore upper and lowercase
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
// names must be equal
return 0;
})
.map((group, index) => {
return (
<a
className={classNames('list-group-item', {
selected: selectedGroups[group.id]
})}
href="#"
key={'an__' + index}
onClick={this.toggleBookmarkGroup.bind(this, group)}
>
<i className="fas fa-bookmark" />
{group.body[0].label}
<span className="member-count" title="Bookmarks">
{group.target.length}
</span>
</a>
);
});
return <div className="list-group">{options}</div>;
};
renderErrorMsg = (emptyFieldNameError, nameExistsError, selectedGroups) => {
let msg = null;
if (emptyFieldNameError === true) {
msg = 'Please enter a label for the bookmark group';
} else if (nameExistsError === true) {
msg = 'The label you entered already exists';
} else if(selectedGroups == null || Object.keys(selectedGroups).length === 0) {
msg = 'Please create or select a bookmark group to save your selection to';
}
return msg ? <div className="validation-error">{msg}</div> : null;
};
render() {
let bookmarkGroupForm = null;
let errorMsg = null;
const projectList = this.renderProjectList(
this.state.selectedProject,
this.props.userProjects,
this.props.user,
this.onSelectProject
);
if(this.state.selectedProject) {
const bookmarkGroupList = this.state.bookmarkGroups.length > 0 ?
this.renderBookmarkGroupList(
this.state.bookmarkGroups,
this.state.selectedGroups
) : null
;
bookmarkGroupForm = this.renderBookmarkGroupForm(bookmarkGroupList);
errorMsg = this.renderErrorMsg(
this.state.emptyFieldNameError,
this.state.nameExistsError,
this.state.selectedGroups
);
}
return (
<div className={IDUtil.cssClassName('bookmark-selector')}>
<div className="user-input">
{bookmarkGroupForm}
{projectList}
</div>
<div className="save-btn">
<button className="btn btn-primary" onClick={this.submitForm}>
Save
</button>
</div>
{errorMsg}
</div>
);
}
}
BookmarkSelector.propTypes = {
collectionId: PropTypes.string.isRequired,
onOutput: PropTypes.func.isRequired,
project: Project.getPropTypes(false),
userProjects : PropTypes.array,
user: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string,
attributes: PropTypes.shape({
allowPersonalCollections: PropTypes.bool
})
})
};
export default BookmarkSelector;