UNPKG

labo-components

Version:
338 lines (306 loc) 12.1 kB
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;