UNPKG

labo-components

Version:
282 lines (248 loc) 8.59 kB
import React from "react"; import PropTypes from "prop-types"; import classNames from "classnames"; import IDUtil from "../../../util/IDUtil"; import SegmentLayerHeader from "./SegmentLayerHeader"; import Segment from "./Segment"; import SelectionTemporalForm from "./SelectionTemporalForm"; import SelectionSpatialForm from "./SelectionSpatialForm"; import Strings from "../_Strings"; import { AnnotationEvents } from "../AnnotationClient"; import { SELECTION_TEMPORAL, SELECTION_SPATIAL, ANNOTATION_COLUMN_SEGMENT_ID, } from "../../../util/AnnotationConstants"; export default class SegmentAnnotations extends React.PureComponent { constructor(props) { super(props); this.state = { selectionType: this.getSelectionType(this.props.mediaObject), }; this.lastActiveAnnotationId = ""; } componentDidMount() { this.props.annotationClient.events.bind( AnnotationEvents.ON_SET_ANNOTATION, this.onUpdate ); } componentWillUnmount() { this.props.annotationClient.events.unbind( AnnotationEvents.ON_SET_ANNOTATION, this.onUpdate ); } onUpdate = (data) => { if (data && data.annotation && data.annotation.id) { // skip if already active if (this.lastActiveAnnotationId == data.annotation.id) { return; } // scroll segment into view const elem = document.getElementById( ANNOTATION_COLUMN_SEGMENT_ID + data.annotation.id ); if (elem) { elem.scrollIntoView(); this.lastActiveAnnotationId = data.annotation.id; // Prevent the column itself from scrolling const column = (document.getElementsByClassName( "bg__annotation-column" )[0].scrollTop = 0); } } }; getSelectionType(mediaObject) { const mimeType = mediaObject.mimeType; switch (true) { case mimeType.startsWith("video"): case mimeType.startsWith("audio"): return SELECTION_TEMPORAL; case mimeType.startsWith("image"): return SELECTION_SPATIAL; default: return ""; } } toggle = () => { this.props.toggle(this.props.layerId); }; // Create a new segment, set it to 0:00 async createSegment(selection) { // set activeAnnotation to null, so a new root annotation is created & activated (in saveSelection) this.props.annotationClient.activeAnnotation = null; // Create and activate a new segment for current layerId await this.props.annotationClient.saveSelection( selection, true, true, this.props.layerId ); } // Update segment with given selection // If the selection is null, delete the segment async updateSegment(segment, selection) { // When the selection is null: delete the segment if (selection == null) { this.props.annotationClient.delete(segment); return; } // make the segment the active annotation, so it will be updated this.props.annotationClient.activeAnnotation = segment; // set the active selection this.props.annotationClient.activeSelection = selection; // Update the segment await this.props.annotationClient.saveSelection(selection); } onUpdateSegment = (segment, selection) => { this.updateSegment(segment, selection); }; onAddSegment = (selection) => { this.createSegment(selection); }; onChangeTitle = (title) => { this.props.annotationClient.segmentLayers.renameLayer( this.props.layerId, title ); }; onDeleteLayer = () => { this.props.annotationClient.segmentLayers.deleteLayerWithCheck( this.props.layerId ); }; createTemporalSelection = (start, end) => ({ type: "temporal", start, end, }); createSpatialSelection = (x, y, w, h) => ({ type: "spatial", rect: { x, y, w, h }, }); createSelection(selectionType) { switch (selectionType) { case SELECTION_TEMPORAL: return this.createTemporalSelection(0, 0); case SELECTION_SPATIAL: return this.createSpatialSelection(0, 0, 0, 0); default: console.error( "Could not create selection for type", selectionType ); } } renderForm(selection) { switch (selection.type) { case SELECTION_TEMPORAL: return ( <SelectionTemporalForm selection={selection} onUpdate={this.onAddSegment} /> ); case SELECTION_SPATIAL: return ( <SelectionSpatialForm selection={selection} onUpdate={this.onAddSegment} /> ); default: console.error("Could not render form for type ", selectionType); } } sortSegments = (segments, selectionType) => { switch (selectionType) { case SELECTION_TEMPORAL: // start -> end return segments.sort((a, b) => { a = a.target.selector.refinedBy; b = b.target.selector.refinedBy; // fallback for erronous values if (a === null) { return b; } if (b === null) { return a; } if (a.start == b.start) { return a.end - b.end; } return a.start - b.start; }); case SELECTION_SPATIAL: // top -> down return segments.sort((a, b) => { a = a.target.selector.refinedBy.rect; b = b.target.selector.refinedBy.rect; if (!a || !b) { return 0; } if (a.y == b.y) { return a.x - b.x; } return a.y - b.y; }); default: return segments; } }; render() { // count const count = this.props.segments ? this.props.segments.length : 0; // header const header = ( <SegmentLayerHeader active={this.props.active} onToggle={this.toggle} title={this.props.title} count={count} onChangeTitle={this.onChangeTitle} onDeleteLayer={this.onDeleteLayer} /> ); // Form const form = this.props.active ? this.renderForm(this.createSelection(this.state.selectionType)) : null; // All segments const segments = this.props.active ? this.sortSegments( this.props.segments, this.state.selectionType ).map((segment) => ( <Segment key={segment.id} segment={segment} updateSegment={this.onUpdateSegment} {...this.props} /> )) : null; return this.state.selectionType === "" ? null : ( <div className={classNames( IDUtil.cssClassName("segment-annotations") )} > {header} {form} {segments} </div> ); } } SegmentAnnotations.propTypes = { annotationClient: PropTypes.object.isRequired, title: PropTypes.string.isRequired, target: PropTypes.string.isRequired, layerId: PropTypes.number.isRequired, toggle: PropTypes.func.isRequired, active: PropTypes.bool.isRequired, activeTypes: PropTypes.arrayOf(PropTypes.string).isRequired, segments: PropTypes.arrayOf(PropTypes.object), mediaObject: PropTypes.object.isRequired, };