import React from 'react'; import Icon from './Icon'; import LinksSentence from './LinksSentence'; import { Dater, dater } from './Dater'; import URILink from './URILink'; import ContextMenuTrigger from './ContextMenuTrigger'; import ErrorBoundary from './ErrorBoundary'; import { getFromUri, titleCase, isTouchDevice, sourceIcon, uriType, } from '../util/helpers'; import { I18n, i18n } from '../locale'; export default class Track extends React.Component { constructor(props) { super(props); this.state = { hover: false, }; this.start_time = 0; this.end_time = 0; this.start_position = false; } handleMouseEnter = () => { this.setState({ hover: true }); } handleMouseLeave = () => { this.setState({ hover: false }); } handleMouseDown = (e) => { const target = $(e.target); // Clicked a nested link (ie Artist name), so no dragging required if (target.is('a')) { return false; } // Only listen for left mouse clicks if (e.button === 0) { this.start_position = { x: e.pageX, y: e.pageY, }; // Not left click, then ensure no dragging } else { this.start_position = false; } } handleMouseMove = (e) => { const { handleDrag, } = this.props; if (handleDrag === undefined) return false; if (this.start_position) { const start_x = this.start_position.x; const start_y = this.start_position.y; const threshold = 5; // Have we dragged outside of our threshold zone? if ( e.pageX > start_x + threshold || e.pageX < start_x - threshold || e.pageY > start_y + threshold || e.pageY < start_y - threshold ) { // Handover to parent for dragging. We can unset all our behaviour now. handleDrag(e); this.start_position = false; } } } handleMouseUp = (e) => { const { dragger, handleDrop, handleClick, } = this.props; const target = $(e.target); // Only listen for left clicks if (e.button === 0) { if (dragger) { e.preventDefault(); if (handleDrop !== undefined) { handleDrop(e); } } else if (!target.is('a') && target.closest('a').length <= 0) { handleClick(e); this.start_position = false; } return; } // Not left click, then ensure no dragging this.start_position = false; } handleDoubleClick = (e) => { const { handleDoubleClick } = this.props; handleDoubleClick(e); } handleTouchStart = (e) => { const { handleTouchDrag } = this.props; const target = $(e.target); const timestamp = Math.floor(Date.now()); if (target.hasClass('drag-zone')) { handleTouchDrag(e); e.preventDefault(); } // Save touch start details this.start_time = timestamp; this.start_position = { x: e.touches[0].clientX, y: e.touches[0].clientY, }; return false; } handleTouchEnd = (e) => { const { handleDoubleTap, handleContextMenu, handleTap, } = this.props; const target = $(e.target); const timestamp = Math.floor(Date.now()); const tap_distance_threshold = 10; // Max distance (px) between touchstart and touchend to qualify as a tap const tap_time_threshold = 200; // Max time (ms) between touchstart and touchend to qualify as a tap const end_position = { x: e.changedTouches[0].clientX, y: e.changedTouches[0].clientY, }; // Too long between touchstart and touchend if (this.start_time + tap_time_threshold < timestamp) { return false; } // Make sure there's enough distance between start and end before we handle // this event as a 'tap' if ( this.start_position.x + tap_distance_threshold > end_position.x && this.start_position.x - tap_distance_threshold < end_position.x && this.start_position.y + tap_distance_threshold > end_position.y && this.start_position.y - tap_distance_threshold < end_position.y ) { // Clicked a nested link (ie Artist name), so no dragging required if (!target.is('a')) { e.preventDefault(); } // Context trigger if (target.hasClass('touch-contextable')) { // Update our selection. By not passing touch = true selection will work like a regular // click this.props.handleSelection(e); handleContextMenu(e); return false; } // We received a touchend within 300ms ago, so handle as double-tap if ((timestamp - this.end_time) > 0 && (timestamp - this.end_time) <= 300) { handleDoubleTap(e); e.preventDefault(); return false; } handleTap(e); } this.end_time = timestamp; } renderTrackMiddleColumn = () => { const { track_context, track: { added_from, added_by, played_at, } = {}, } = this.props; let content; switch (track_context) { case 'history': { content = (