labo-components
Version:
174 lines (147 loc) • 5.37 kB
JSX
import React from "react";
import PropTypes from "prop-types";
import IDUtil from "../../../../util/IDUtil";
const MODE_MOVE = 0; // move/drag the timeline
const MODE_SET = 1; // set the current position/cursor
// Handle zoom, drag, and set cursor position all in a single box that can wrap the dynamic timeline components
class ZoomDragBox extends React.Component {
constructor(props) {
super(props);
// refs
this.ref = React.createRef();
// default values
this.dragCursorOffset = this.props.dragCursorOffset
? this.props.dragCursorOffset
: 10;
this.zoomSensitivity = this.props.zoomSensitivity
? this.props.zoomSensitivity
: 500;
this.doubleClickTime = this.props.doubleClickTime
? this.props.doubleClickTime
: 200;
this.doubleClickOffset = this.props.doubleClickOffset
? this.props.doubleClickOffset
: 5;
this.boundingBox = { x: 0, y: 0, width: 0, height: 0 };
}
componentDidMount() {
this.ref.current.addEventListener("wheel", this.onWheel, {
passive: false,
});
}
componentWillUnmount() {
this.clearEventListeners();
this.ref.current.removeEventListener("wheel", this.onWheel);
}
onMouseDown = (e) => {
const leftMouseButton = e.button === 0;
const time = new Date().getTime();
let forceModeSet = false;
// check for ctrlKey, which enables MODE_SET
if (e.ctrlKey) {
forceModeSet = true;
}
// check for double click, which enables MODE_SET
if (
leftMouseButton &&
this.interactionVars &&
time - this.interactionVars.lastDown < this.doubleClickTime &&
e.pageX - this.interactionVars.downX < this.doubleClickOffset
) {
forceModeSet = true;
}
// initialize interaction variables
this.interactionVars = {
downX: e.pageX,
offsetX: 0,
mode: forceModeSet ? MODE_SET : MODE_MOVE,
lastDown: leftMouseButton ? time : 0,
};
// If click is nearby the cursor, enable SET mode
const cursorPos =
this.props.start +
(e.pageX - this.props.boundingBox.x) / this.props.pixelsPerSecond;
if (
// near cursor
Math.abs(cursorPos - this.props.position) <
this.dragCursorOffset / this.props.pixelsPerSecond ||
// label region (left + right)
(e.clientY - this.props.boundingBox.y < 25 &&
Math.abs(cursorPos - this.props.position) <
60 / this.props.pixelsPerSecond)
) {
this.interactionVars.mode = MODE_SET;
this.interactionVars.offsetX = cursorPos - this.props.position;
}
// add event listeners
document.addEventListener("mousemove", this.onMouseMove);
document.addEventListener("mouseup", this.onMouseUp);
// trigger mousemove
this.onMouseMove(e);
};
onMouseMove = (e) => {
e.preventDefault();
const cursorPos =
(e.pageX - this.props.boundingBox.x) / this.props.pixelsPerSecond;
switch (this.interactionVars.mode) {
// set cursor
case MODE_SET:
// set position
this.props.setPosition(
this.props.start + cursorPos - this.interactionVars.offsetX,
true
);
return;
case MODE_MOVE:
// drag timeline
const posDiff =
(this.interactionVars.downX - e.pageX) /
this.props.pixelsPerSecond;
this.interactionVars.downX = e.pageX;
this.props.onMove(posDiff, !e.shiftKey && !e.ctrlKey);
return;
default:
console.error("Unknown mode:", this.interactionVars.mode);
}
};
onMouseUp = (e) => {
this.clearEventListeners();
};
clearEventListeners = () => {
document.removeEventListener("mousemove", this.onMouseMove);
document.removeEventListener("mouseup", this.onMouseUp);
};
// scroll wheel
onWheel = (e) => {
const perc =
(e.pageX - this.props.boundingBox.x) / this.props.boundingBox.width;
this.props.onZoom(1 - e.deltaY / this.zoomSensitivity, perc);
// prevent scrolling
e.preventDefault();
e.stopPropagation();
};
render() {
return (
<div
ref={this.ref}
className={IDUtil.cssClassName("tl-zoom-drag-box")}
onMouseDown={this.onMouseDown}
>
{this.props.children}
</div>
);
}
}
ZoomDragBox.propTypes = {
start: PropTypes.number.isRequired,
position: PropTypes.number.isRequired,
setPosition: PropTypes.func.isRequired,
onMove: PropTypes.func.isRequired,
onZoom: PropTypes.func.isRequired,
// optional
dragCursorOffset: PropTypes.number,
zoomSensitivity: PropTypes.number,
doubleClickTime: PropTypes.number,
doubleClickOffset: PropTypes.number,
};
export default ZoomDragBox;