UNPKG

labo-components

Version:
369 lines (329 loc) 13.6 kB
import React from "react"; import PropTypes from "prop-types"; import IconUtil from "../../../util/IconUtil"; import IDUtil from "../../../util/IDUtil"; import SessionStorageHandler from "../../../util/SessionStorageHandler"; import Strings from "../_Strings"; import Info from "../../shared/Info"; import { AnnotationEvents } from "../AnnotationClient"; import SingleAnnotationTarget from "./SingleAnnotationTarget"; import AnnotationFilter from "./AnnotationFilter"; import SegmentAnnotations from "./SegmentAnnotations"; import TargetHeader from "./TargetHeader"; import { ANNOTATION_TARGET, SESSION_STORAGE_ACTIVE_TARGETS, } from "../../../util/AnnotationConstants"; export default class AnnotationList extends React.PureComponent { constructor(props) { super(props); // get active targets from session storage const activeTargets = SessionStorageHandler.getSplit( SESSION_STORAGE_ACTIVE_TARGETS ); // initial state this.state = { activeTargets, activeLayers: [], }; // Annotation events to listen to this.annotationEvents = [ AnnotationEvents.ON_SET_ANNOTATION, AnnotationEvents.ON_SAVE, AnnotationEvents.ON_DELETE, AnnotationEvents.ON_CHANGE_TARGET, ]; this.segmentTitleInput = React.createRef(); } componentDidMount() { // Bind to annotation updates this.annotationEvents.forEach((event) => { this.props.annotationClient.events.bind(event, this.onUpdate); }); } componentWillUnmount() { // Unbind annotation updatess this.annotationEvents.forEach((event) => { this.props.annotationClient.events.unbind(event, this.onUpdate); }); } onUpdate = () => this.forceUpdate(); // Wrapper function for retrieving an annotation / all annotations for a given target getAnnotationsForTarget(allAnnotations, resource, mediaObject) { return (target) => { const annotations = allAnnotations.filter( (annotation) => // only with target type annotation.target && annotation.target.type === target ); // the checks on current resource/mediaobject are needed // as sometimes other annotations from the project seem to slip in switch (target) { case ANNOTATION_TARGET.RESOURCE: { const result = annotations.filter( (annotation) => // current resource annotation.target.source == resource.resourceId ); // check number of annotations if (result.length > 1) { console.error( "Multiple root annotations for target " + target + ". Expecting 1 or 0" ); console.debug(result); // this.props.annotationClient.delete(result[1]); } if (result.length > 0) { return result[0]; } return null; } case ANNOTATION_TARGET.MEDIAOBJECT: { let result = annotations.filter( (annotation) => // current mediaObject mediaObject && annotation.target.source == mediaObject.assetId ); // // DEV: remove duplicate MediaObject annotations // // You'll need this if you accidentially messed up selection creation and create multiple // // annotations directly on the MediaObject (only 1 allowed) // console.log(result); // // The Media Object id tou want to keep // const validMediaObjectAnnotationId = // "19f47bb8-5fe0-4452-9d78-78da9514ee77"; // // Delete unwanted // result.forEach((r) => { // if (r.id !== validMediaObjectAnnotationId) { // console.log(r); // this.props.annotationClient.delete(r); // } // }); // // Filter out unwanted for displaying // result = result.filter( // (r) => r.id == validMediaObjectAnnotationId // ); // check number of annotations if (result.length > 1) { console.error( "Multiple root annotations for target " + target + ". Expecting 1 or 0" ); console.debug(result); // this.props.annotationClient.delete(result[1]); } if (result.length > 0) { return result[0]; } return null; } case ANNOTATION_TARGET.SEGMENT: // can return multiple in an array return annotations.filter((annotation) => // segment from current mediaObject resource.playableContent.some( (mo) => annotation.target.source == mo.assetId ) ); default: console.error( "Could not find annotations for type ", target ); return []; } }; } /* ---------------------------------- UI FUNCTIONS -------------------------------- */ // Toggle given target // Persist the state in session storage toggleTarget = (target) => { this.setState( { activeTargets: this.state.activeTargets.includes(target) ? this.state.activeTargets.filter((l) => l != target) : [target, ...this.state.activeTargets], }, () => { // store active targets to session storage SessionStorageHandler.set( SESSION_STORAGE_ACTIVE_TARGETS, this.state.activeTargets.join(",") ); } ); }; toggleSegmentsTarget = () => { this.toggleTarget(ANNOTATION_TARGET.SEGMENT); }; toggleLayer = (layerId) => { this.setState({ activeLayers: this.state.activeLayers.includes(layerId) ? this.state.activeLayers.filter((l) => l != layerId) : [layerId, ...this.state.activeLayers], }); }; // /* ---------------------------------- RENDER FUNCTIONS -------------------------------- */ renderNewSegmentLayer() { return ( <div className="segment-actions"> <input ref={this.segmentTitleInput} /> <div key="new-layer-button" className="button-new-layer btn btn-primary" onClick={() => { this.props.annotationClient.segmentLayers.addLayer( this.segmentTitleInput.current.value ); this.segmentTitleInput.current.value = ""; }} > {Strings.BUTTON_ADD_USER_LAYER} </div> </div> ); } render() { const { resource, mediaObject, annotationClient } = this.props; // at least a resource is required if (!resource) { return null; } // all annotations for this project const annotations = annotationClient.annotations; // create a wrapper for getting all annotations per target // for the RESOURCE and MEDIAOBJECT targets, there should only be 1 annotation // for the SEGMENTS, there will be multiple const getAnnotations = this.getAnnotationsForTarget( annotations, resource, mediaObject ); // Props used by all targets const defaultTargetProps = { annotationClient: annotationClient, activeTypes: this.props.activeAnnotationTypes, toggle: this.toggleTarget, }; // Resource target annotations const resourceAnnotation = getAnnotations(ANNOTATION_TARGET.RESOURCE); const resourceActive = this.state.activeTargets.includes( ANNOTATION_TARGET.RESOURCE ); const resourceTarget = ( <SingleAnnotationTarget target={ANNOTATION_TARGET.RESOURCE} icon={ <Info id="annotation-target-resource-help" text={Strings.ANNOTATION_TARGET_HELP_RESOURCE} className="black left" /> } title={Strings.ANNOTATION_TARGET_TITLE_RESOURCE} active={resourceActive} annotation={resourceAnnotation} {...defaultTargetProps} /> ); // Mediaobject target annotations const mediaObjectAnnotation = getAnnotations( ANNOTATION_TARGET.MEDIAOBJECT ); const mediaObjectActive = this.state.activeTargets.includes( ANNOTATION_TARGET.MEDIAOBJECT ); const mediaObjectTarget = this.props.mediaObject ? ( <SingleAnnotationTarget target={ANNOTATION_TARGET.MEDIAOBJECT} icon={ <span title={Strings.ANNOTATION_TARGET_HELP_MEDIAOBJECT} className={IconUtil.getMimeTypeIcon( this.props.mediaObject.mimeType, false, false, false )} /> } title={this.props.mediaObject.assetId} active={mediaObjectActive} annotation={mediaObjectAnnotation} {...defaultTargetProps} /> ) : null; let segmentHeader = null; let segmentTargets = null; const segmentsActive = this.state.activeTargets.includes( ANNOTATION_TARGET.SEGMENT ); // Segment header if (this.props.mediaObject) { segmentHeader = ( <> <TargetHeader active={segmentsActive} onToggle={this.toggleSegmentsTarget} title={Strings.ANNOTATION_TARGET_TITLE_SEGMENT} count={getAnnotations(ANNOTATION_TARGET.SEGMENT).length} icon={ <Info id="annotation-target-segment-help" text={Strings.ANNOTATION_TARGET_HELP_SEGMENT} className="black left" /> } /> {segmentsActive && this.renderNewSegmentLayer()} </> ); } // Segments per layer if (this.props.mediaObject && segmentsActive) { // Segment target annotations per segment layer const layers = annotationClient.segmentLayers.getLayersSorted(); segmentTargets = layers.map((layer) => { return ( <SegmentAnnotations key={layer.id} target={ANNOTATION_TARGET.SEGMENT} layerId={layer.id} title={layer.title} active={this.state.activeLayers.includes(layer.id)} segments={annotationClient.segmentLayers.getSegments( layer.id )} mediaObject={this.props.mediaObject} annotationClient={annotationClient} activeTypes={this.props.activeAnnotationTypes} toggle={this.toggleLayer} /> ); }); } // Full annotation list return ( <div className={IDUtil.cssClassName("annotation-list")}> {/* All annotation targets */} <div className="targets"> {resourceTarget} {mediaObjectTarget} {segmentHeader} {segmentTargets} </div> {/* Filter*/} <AnnotationFilter /> </div> ); } } AnnotationList.propTypes = { annotationClient: PropTypes.object.isRequired, activeAnnotationTypes: PropTypes.arrayOf(PropTypes.string).isRequired, mediaObject: PropTypes.object, resource: PropTypes.object, };