UNPKG

labo-components

Version:
310 lines (268 loc) 10.8 kB
import React from "react"; import PropTypes from "prop-types"; import classNames from "classnames"; import IDUtil from "../../../../util/IDUtil"; import { getEventPath } from "../../../../util/EventPath"; import { MSAnnotationUtil } from "../../AnnotationClient"; const LEFT = "left"; const RIGHT = "right"; const BOTH = "both"; // A wrapper for editable section class SectionEdit extends React.PureComponent { constructor(props) { super(props); this.direction = LEFT; this.lastX = 0; this.lastUserLayer = null; this.originalLayer = null; this.selection = null; this.dragging = false; this.state = { dragging: "", }; this.ref = React.createRef(); } componentWillUnmount() { this.stopDrag(); } /** * Dragging section */ startDragSection = (e) => { // shift key + left mouse button if (e.shiftKey && e.button == 0) { e.stopPropagation(); this.direction = BOTH; this.startDrag(e.pageX, e.pageY); } }; /** * Dragging timing left/right paddles to modify start/end position */ startDragLeft = (e) => { if (!e.button == 0) { return; } e.stopPropagation(); this.direction = e.shiftKey ? BOTH : LEFT; this.startDrag(e.pageX, e.pageY); }; startDragRight = (e) => { if (!e.button == 0) { return; } e.stopPropagation(); this.direction = e.shiftKey ? BOTH : RIGHT; this.startDrag(e.pageX, e.pageY); }; loadSelection() { // get selection this.selection = MSAnnotationUtil.extractSelectionFromTarget( this.props.segment.target ); if (!this.selection) { console.error( "Could not find selection for target", this.props.segment.target ); } } onWindowLeave = (e) => { if (e.toElement == null && e.relatedTarget == null) { this.stopDrag(null); } }; startDrag = (x, y) => { if (this.dragging) { return; } this.dragging = true; this.lastX = x; this.setState({ dragging: this.direction }); this.props.annotationClient.setActiveAnnotation(this.props.segment); this.loadSelection(); // for dragging between layers this.originalLayer = this.ref.current && this.ref.current.parentNode ? this.ref.current.parentNode.parentNode : null; this.lastUserLayer = this.originalLayer; document.addEventListener("mousemove", this.onDrag); document.addEventListener("mouseup", this.stopDrag); document.addEventListener("mouseleave", this.onWindowLeave); }; stopDrag = (e) => { if (!this.dragging) { return; } this.dragging = false; //this.direction = ""; this.setState({ dragging: "" }); document.removeEventListener("mousemove", this.onDrag); document.removeEventListener("mouseup", this.stopDrag); document.removeEventListener("mouseleave", this.onWindowLeave); // Update section layer if the last hovered user layer differs from current user layer // The layer id is obtained directly from the layer attribute of the last user layer DOM element if ( this.ref.current && this.lastUserLayer && this.lastUserLayer != this.ref.current.parentNode && this.lastUserLayer.attributes.layer ) { this.props.segment.target.layerId = parseInt( this.lastUserLayer.attributes.layer.value ); this.props.selectTimelineLayer(this.props.segment.target.layerId); } // Save changes this.props.updateSegment(this.props.segment, this.selection); }; onDrag = (e) => { e.stopPropagation(); const dx = (e.pageX - this.lastX) / this.props.getPixelsPerSecond(); const movePlayPos = e.ctrlKey || e.metaKey; switch (this.direction) { case BOTH: // use getEventPath to make it compatible to Chrome's e.path property const path = getEventPath(e); // Move segment between layers of type .tl-layer-header.user-segment // Here we directly manipulate the DOM, to show layer shift, but // prevent making changes to the data, which would result in the drag action to // be halted. if (this.ref.current && path) { // check if we are hovering another user layer const userLayer = path.find( (p) => p.classList && p.classList.contains("bg__tl-layer") && p.classList.contains("user-segment") ); // call may not be triggered by current ref const isCurrentRef = path.some( (p) => p == this.ref.current ); // we hit a new user layer, so change section layer if ( !isCurrentRef && userLayer && this.lastUserLayer !== userLayer ) { // snap newTop to new layer const newTop = userLayer.offsetTop - this.originalLayer.offsetTop; this.ref.current.parentNode.style.marginTop = newTop + "px"; this.lastUserLayer = userLayer; // update active timeline layer to indicate drop target userLayer.attributes.layer && this.props.selectTimelineLayer( parseInt(userLayer.attributes.layer.value) ); } } // move selection this.selection.start = this.props.withinRange( this.selection.start + dx ); this.selection.end = this.props.withinRange( this.selection.end + dx ); // Optionally, move the player position movePlayPos && this.props.updatePlayerPos(this.selection.start); break; case LEFT: this.selection.start = this.props.withinRange( e.shiftKey ? this.selection.start + dx : this.snapToSegments( this.selection.start + dx, this.props.segment.target.layerId ) ); // Prevent negative selection duration if (this.selection.start > this.selection.end) { const end = this.selection.end; this.selection.end = this.selection.start; this.selection.start = end; this.direction = RIGHT; this.setState({ dragging: this.direction }); } // Optionally, move the player position movePlayPos && this.props.updatePlayerPos(this.selection.start); break; case RIGHT: this.selection.end = this.props.withinRange( e.shiftKey ? this.selection.end + dx : this.snapToSegments( this.selection.end + dx, this.props.segment.target.layerId ) ); // Prevent negative selections duration if (this.selection.start > this.selection.end) { const start = this.selection.start; this.selection.start = this.selection.end; this.selection.end = start; this.direction = LEFT; this.setState({ dragging: this.direction }); } // Optionally, move the player position movePlayPos && this.props.updatePlayerPos(this.selection.end); break; } this.lastX = e.pageX; // Directly set the new values to the current selector object of the segment // Final values will be saved to the annotation API when the mouse is released this.props.segment.target.selector.refinedBy.start = this.selection.start; this.props.segment.target.selector.refinedBy.end = this.selection.end; // Force an update of the timeline layer this.props.updateAnnotationLayer( this.props.segment.target.layerId || 0 ); }; snapToSegments(x, layerId = null) { return this.props.snapToSegments( x, null, // no segments, so snap to all this.props.segment, layerId ); } render() { const { annotationClient, segment } = this.props; return ( <div ref={this.ref} className={classNames( IDUtil.cssClassName("tl-section-edit"), { dragging: this.state.dragging, active: annotationClient.activeAnnotation && annotationClient.activeAnnotation.id == segment.id, }, this.state.dragging ? "dragging-" + this.state.dragging : "" )} onMouseDown={this.startDragSection} > <div className="left" onMouseDown={this.startDragLeft} /> {this.props.children} <div className="right" onMouseDown={this.startDragRight} /> </div> ); } } SectionEdit.propTypes = { getPixelsPerSecond: PropTypes.func.isRequired, updateAnnotationLayer: PropTypes.func.isRequired, updatePlayerPos: PropTypes.func.isRequired, updateSegment: PropTypes.func.isRequired, snapToSegments: PropTypes.func.isRequired, withinRange: PropTypes.func.isRequired, selectTimelineLayer: PropTypes.func.isRequired, mediaObject: PropTypes.object.isRequired, segment: PropTypes.object.isRequired, annotationClient: PropTypes.object.isRequired, }; export default SectionEdit;