import React from 'react'; import Link from './Link'; import Icon from './Icon'; import ArtistSentence from './ArtistSentence'; import Dater from './Dater'; import URILink from './URILink'; import ContextMenuTrigger from './ContextMenuTrigger'; import Popularity from './Popularity'; import ErrorBoundary from './ErrorBoundary'; import * as helpers from '../helpers'; 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(e) { this.setState({ hover: true }); } handleMouseLeave(e) { 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 target = $(e.target); // No drag handling means NO if (this.props.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. this.props.handleDrag(e); this.start_position = false; } } } handleMouseUp(e) { const target = $(e.target); // Only listen for left clicks if (e.button === 0) { if (this.props.dragger) { e.preventDefault(); if (this.props.handleDrop !== undefined) { this.props.handleDrop(e); } } else if (!target.is('a') && target.closest('a').length <= 0) { this.props.handleClick(e); this.start_position = false; } // Not left click, then ensure no dragging } else { this.start_position = false; return false; } } handleDoubleClick(e) { this.props.handleDoubleClick(e); } handleTouchStart(e) { const target = $(e.target); const timestamp = Math.floor(Date.now()); // Touch-drag zone if (target.hasClass('drag-zone')) { this.props.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 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); this.props.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) { this.props.handleDoubleTap(e); e.preventDefault(); return false; } this.props.handleTap(e); } this.end_time = timestamp; } render() { if (!this.props.track) { return null; } const { track } = this.props; let className = 'list__item list__item--track mouse-draggable mouse-selectable mouse-contextable'; const track_details = []; const track_actions = []; if (track.artists) { track_details.push(
  • {track.artists ? : '-'}
  • , ); } if (track.album) { if (track.album.uri) { var album = {track.album.name}; } else { var album = {track.album.name}; } track_details.push(
  • {album}
  • , ); } if (this.props.track_context == 'history') { var track_middle_column = (
    {track.played_at ? ( {' '} ago ) : '-'}
    ); } else if (this.props.track_context == 'queue') { if (track.added_from && track.added_by) { const type = (track.added_from ? helpers.uriType(track.added_from) : null); switch (type) { case 'discover': var link = discover; break; case 'browse': var link = browse; break; case 'search': var link = search; break; case 'radio': var link = Radio; break; default: var link = {type}; } var track_middle_column = (
    {`${track.added_by} `} {`from `} {link}
    ); } else if (track.added_by) { var track_middle_column = (
    {track.added_by}
    ); } } // If we're touchable, and can sort this tracklist let drag_zone = null; if (helpers.isTouchDevice() && this.props.can_sort) { className += ' list__item--has-drag-zone'; drag_zone = ( ); } if (this.props.selected) className += ' list__item--selected'; if (this.props.can_sort) className += ' list__item--can-sort'; if (track.type !== undefined) className += ` list__item--${track.type}`; if (track.playing) className += ' list__item--playing'; if (this.state.hover) className += ' list__item--hover'; if (track_middle_column) className += ' list__item--has-middle-column'; if (track_details.length > 0) className += ' list__item--has-details'; return (
    this.handleMouseEnter(e)} onMouseLeave={(e) => this.handleMouseLeave(e)} onMouseDown={(e) => this.handleMouseDown(e)} onMouseUp={(e) => this.handleMouseUp(e)} onMouseMove={(e) => this.handleMouseMove(e)} onDoubleClick={(e) => this.handleDoubleClick(e)} onContextMenu={(e) => this.props.handleContextMenu(e)} onTouchStart={(e) => this.handleTouchStart(e)} onTouchEnd={(e) => this.handleTouchEnd(e)} >
    {track.name ? track.name : {track.uri}} {track.explicit ? EXPLICIT : null} {track.playing ? : null}
    {track_details ? (
      {track_details}
    ) : null}
    {track_middle_column ?
    {track_middle_column}
    : null}
    {drag_zone} {track.duration ? : '-'} {this.props.show_source_icon ? ( ) : null} this.props.handleContextMenu(e)} />
    ); } }