2016-10-03 21:21:14 +13:00
|
|
|
|
|
|
|
|
import React, { PropTypes } from 'react'
|
2016-11-19 13:06:08 +13:00
|
|
|
import { Link } from 'react-router'
|
2017-02-28 16:13:27 +13:00
|
|
|
|
2016-10-06 16:38:06 +13:00
|
|
|
import FontAwesome from 'react-fontawesome'
|
2016-10-25 21:36:33 +13:00
|
|
|
import ArtistSentence from './ArtistSentence'
|
2016-10-31 16:49:42 +13:00
|
|
|
import Dater from './Dater'
|
2017-10-27 10:51:48 +13:00
|
|
|
import URILink from './URILink'
|
2017-11-18 08:15:06 +13:00
|
|
|
import ContextMenuTrigger from './ContextMenuTrigger'
|
2017-10-27 10:51:48 +13:00
|
|
|
|
2016-11-19 13:06:08 +13:00
|
|
|
import * as helpers from '../helpers'
|
2016-10-03 21:21:14 +13:00
|
|
|
|
|
|
|
|
export default class Track extends React.Component{
|
|
|
|
|
|
2017-10-10 13:05:28 +13:00
|
|
|
constructor(props){
|
2017-06-28 16:26:55 +12:00
|
|
|
super(props)
|
|
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
|
hover: false
|
|
|
|
|
}
|
2017-07-22 06:36:47 +12:00
|
|
|
|
2017-12-18 08:19:38 +13:00
|
|
|
this.start_time = 0;
|
2017-12-20 16:33:38 +13:00
|
|
|
this.end_time = 0;
|
2017-07-22 06:36:47 +12:00
|
|
|
this.start_position = false
|
2016-10-03 21:21:14 +13:00
|
|
|
}
|
|
|
|
|
|
2017-12-05 16:27:45 +13:00
|
|
|
handleMouseEnter(e){
|
|
|
|
|
this.setState({hover: true});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handleMouseLeave(e){
|
|
|
|
|
this.setState({hover: false});
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-15 17:08:21 +13:00
|
|
|
handleMouseDown(e){
|
2017-12-05 16:27:45 +13:00
|
|
|
var target = $(e.target);
|
|
|
|
|
|
2017-11-12 17:39:44 +13:00
|
|
|
// Clicked a nested link (ie Artist name), so no dragging required
|
2017-12-05 16:27:45 +13:00
|
|
|
if (target.is('a')){
|
2017-11-12 17:39:44 +13:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-28 17:23:29 +12:00
|
|
|
// 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
|
2016-11-16 14:58:08 +13:00
|
|
|
}
|
2016-11-15 17:08:21 +13:00
|
|
|
}
|
2016-11-10 16:17:10 +13:00
|
|
|
|
2017-07-22 06:36:47 +12:00
|
|
|
handleMouseMove(e){
|
2017-12-05 16:27:45 +13:00
|
|
|
var target = $(e.target);
|
2017-07-22 06:36:47 +12:00
|
|
|
|
2017-07-25 09:22:15 +12:00
|
|
|
// No drag handling means NO
|
2017-07-22 06:36:47 +12:00
|
|
|
if (this.props.handleDrag === undefined){
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.start_position){
|
|
|
|
|
let start_x = this.start_position.x
|
|
|
|
|
let start_y = this.start_position.y
|
|
|
|
|
let 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
|
|
|
|
|
}
|
2016-12-12 09:07:47 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-22 06:36:47 +12:00
|
|
|
handleMouseUp(e){
|
2017-12-05 16:27:45 +13:00
|
|
|
var target = $(e.target);
|
2017-07-25 09:22:15 +12:00
|
|
|
|
2017-07-28 17:23:29 +12:00
|
|
|
// Only listen for left clicks
|
|
|
|
|
if (e.button === 0){
|
|
|
|
|
if (this.props.dragger){
|
|
|
|
|
e.preventDefault()
|
2017-07-25 09:22:15 +12:00
|
|
|
|
2017-07-28 17:23:29 +12:00
|
|
|
if (this.props.handleDrop !== undefined){
|
2017-11-12 17:39:44 +13:00
|
|
|
this.props.handleDrop(e);
|
2017-07-28 17:23:29 +12:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (!target.is('a') && target.closest('a').length <= 0){
|
2017-12-20 16:33:38 +13:00
|
|
|
this.props.handleClick(e);
|
2017-11-12 17:39:44 +13:00
|
|
|
this.start_position = false;
|
2017-07-28 17:23:29 +12:00
|
|
|
}
|
2017-07-22 06:36:47 +12:00
|
|
|
}
|
2017-07-28 17:23:29 +12:00
|
|
|
|
|
|
|
|
// Not left click, then ensure no dragging
|
|
|
|
|
} else {
|
2017-11-12 17:39:44 +13:00
|
|
|
this.start_position = false;
|
|
|
|
|
return false;
|
2017-07-22 06:36:47 +12:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-05 16:27:45 +13:00
|
|
|
handleDoubleClick(e){
|
2017-12-06 17:02:11 +13:00
|
|
|
this.props.handleDoubleClick(e);
|
2017-12-05 16:27:45 +13:00
|
|
|
}
|
|
|
|
|
|
2017-07-22 06:36:47 +12:00
|
|
|
handleTouchStart(e){
|
2017-12-05 16:27:45 +13:00
|
|
|
var target = $(e.target);
|
2017-12-06 17:02:11 +13:00
|
|
|
var timestamp = Math.floor(Date.now());
|
|
|
|
|
|
2017-12-05 16:27:45 +13:00
|
|
|
// Touch-drag zone
|
2017-12-20 16:33:38 +13:00
|
|
|
if (target.hasClass('drag-zone')){
|
2017-12-05 16:27:45 +13:00
|
|
|
this.props.handleTouchDrag(e);
|
2017-12-07 16:54:28 +13:00
|
|
|
e.preventDefault();
|
2017-11-12 17:39:44 +13:00
|
|
|
}
|
|
|
|
|
|
2017-12-18 08:19:38 +13:00
|
|
|
// Save touch start details
|
|
|
|
|
this.start_time = timestamp;
|
|
|
|
|
this.start_position = {
|
|
|
|
|
x: e.touches[0].clientX,
|
|
|
|
|
y: e.touches[0].clientY
|
|
|
|
|
}
|
2017-12-06 17:02:11 +13:00
|
|
|
|
2017-12-05 16:27:45 +13:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handleTouchEnd(e){
|
2017-12-06 17:02:11 +13:00
|
|
|
var target = $(e.target);
|
|
|
|
|
var timestamp = Math.floor(Date.now());
|
2017-12-19 21:14:01 +13:00
|
|
|
var tap_distance_threshold = 10; // Max distance (px) between touchstart and touchend to qualify as a tap
|
|
|
|
|
var tap_time_threshold = 200; // Max time (ms) between touchstart and touchend to qualify as a tap
|
|
|
|
|
var end_position = {
|
|
|
|
|
x: e.changedTouches[0].clientX,
|
|
|
|
|
y: e.changedTouches[0].clientY
|
|
|
|
|
}
|
2017-12-06 17:02:11 +13:00
|
|
|
|
|
|
|
|
// Clicked a nested link (ie Artist name), so no dragging required
|
|
|
|
|
if (!target.is('a')){
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
}
|
2017-12-19 21:14:01 +13:00
|
|
|
|
2017-12-20 16:33:38 +13:00
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-19 21:14:01 +13:00
|
|
|
// Too long between touchstart and touchend
|
|
|
|
|
if (this.start_time + tap_time_threshold < timestamp){
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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){
|
2017-12-20 16:33:38 +13:00
|
|
|
this.props.handleTap(e);
|
2017-12-19 21:14:01 +13:00
|
|
|
}
|
2016-10-27 10:02:12 +13:00
|
|
|
|
2017-12-20 16:33:38 +13:00
|
|
|
this.end_time = timestamp;
|
2017-07-28 17:23:29 +12:00
|
|
|
}
|
|
|
|
|
|
2016-10-03 21:21:14 +13:00
|
|
|
render(){
|
2017-11-20 15:49:52 +13:00
|
|
|
if (!this.props.track){
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var track = this.props.track;
|
2017-12-05 16:27:45 +13:00
|
|
|
var className = 'list-item track mouse-draggable mouse-selectable mouse-contextable';
|
2017-11-20 15:49:52 +13:00
|
|
|
if (this.props.selected) className += ' selected';
|
|
|
|
|
if (this.props.can_sort) className += ' can-sort';
|
|
|
|
|
if (track.type !== undefined) className += ' '+track.type;
|
|
|
|
|
if (track.playing) className += ' playing';
|
|
|
|
|
if (this.state.hover) className += ' hover';
|
2017-03-23 11:51:04 +13:00
|
|
|
|
2017-11-20 15:49:52 +13:00
|
|
|
var album = '-';
|
2017-10-10 13:05:28 +13:00
|
|
|
if (track.album){
|
|
|
|
|
if (track.album.uri){
|
2017-11-20 15:49:52 +13:00
|
|
|
album = <URILink type="album" uri={track.album.uri}>{track.album.name}</URILink>;
|
2016-12-05 10:20:01 +13:00
|
|
|
} else {
|
2017-11-20 15:49:52 +13:00
|
|
|
album = <span>{track.album.name}</span>;
|
2016-12-05 10:20:01 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-20 15:49:52 +13:00
|
|
|
let track_columns = [];
|
|
|
|
|
let track_actions = [];
|
2017-07-22 06:36:47 +12:00
|
|
|
|
2017-02-14 20:24:35 +13:00
|
|
|
if (track.type == 'history'){
|
2017-02-10 09:06:41 +13:00
|
|
|
|
2017-07-22 06:36:47 +12:00
|
|
|
track_columns.push(
|
2017-08-03 16:40:34 +12:00
|
|
|
<span className="col name" key="name">
|
2017-12-05 16:27:45 +13:00
|
|
|
{track.name ? track.name : <span className="uri-placeholder grey-text">{track.uri}</span>}
|
2017-07-22 06:36:47 +12:00
|
|
|
</span>
|
|
|
|
|
)
|
|
|
|
|
track_columns.push(
|
2017-08-03 16:40:34 +12:00
|
|
|
<span className="col source" key="source">
|
2017-07-22 06:36:47 +12:00
|
|
|
{helpers.uriSource(track.uri)}
|
|
|
|
|
</span>
|
|
|
|
|
)
|
|
|
|
|
track_columns.push(
|
2017-08-03 16:40:34 +12:00
|
|
|
<span className="col played_at" key="played_at">
|
2017-07-22 06:36:47 +12:00
|
|
|
{track.played_at ? <span><Dater type="ago" data={track.played_at} /> ago</span> : null}
|
2017-02-14 20:24:35 +13:00
|
|
|
</span>
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
} else if (this.props.context == 'queue'){
|
|
|
|
|
|
2017-07-22 06:36:47 +12:00
|
|
|
if (track.added_from && track.added_by){
|
2018-02-15 09:47:49 +13:00
|
|
|
var type = (track.added_from ? helpers.uriType(track.added_from) : null);
|
|
|
|
|
|
|
|
|
|
switch (type){
|
|
|
|
|
case "discover":
|
|
|
|
|
var link = <URILink type="recommendations" uri={helpers.getFromUri('seeds',track.added_from)}>discover</URILink>
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case "browse":
|
|
|
|
|
var link = <URILink type="browse" uri={track.added_from.replace("iris:browse:","")}>browse</URILink>
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
var link = <URILink type={type} uri={track.added_from}>{type}{track.added_from}</URILink>;
|
2017-02-14 20:24:35 +13:00
|
|
|
}
|
2018-02-15 09:47:49 +13:00
|
|
|
|
2017-07-22 06:36:47 +12:00
|
|
|
var added = <span>{track.added_by} <span className="grey-text"> (from {link})</span></span>
|
2017-02-10 09:06:41 +13:00
|
|
|
|
2017-07-22 06:36:47 +12:00
|
|
|
} else if (track.added_by){
|
|
|
|
|
var added = track.added_by
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
var added = '-'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
track_columns.push(
|
|
|
|
|
<span className="col name" key="name">
|
|
|
|
|
{track.name ? track.name : <span className="grey-text">{track.uri}</span>}
|
|
|
|
|
{track.explicit ? <span className="flag dark">EXPLICIT</span> : null}
|
|
|
|
|
</span>
|
|
|
|
|
)
|
|
|
|
|
if (this.props.show_source_icon){
|
|
|
|
|
track_columns.push(
|
|
|
|
|
<FontAwesome name={helpers.sourceIcon(track.uri)} className="source" key="source" fixedWidth />
|
2017-02-10 09:06:41 +13:00
|
|
|
)
|
2017-07-22 06:36:47 +12:00
|
|
|
}
|
|
|
|
|
track_columns.push(
|
|
|
|
|
<span className="col artists" key="artists">
|
|
|
|
|
{track.artists ? <ArtistSentence artists={track.artists} /> : '-'}
|
|
|
|
|
</span>
|
|
|
|
|
)
|
|
|
|
|
track_columns.push(
|
|
|
|
|
<span className="col album" key="album">
|
|
|
|
|
{album}
|
|
|
|
|
</span>
|
|
|
|
|
)
|
|
|
|
|
track_columns.push(
|
|
|
|
|
<span className="col added" key="added">
|
|
|
|
|
{added}
|
|
|
|
|
</span>
|
|
|
|
|
)
|
|
|
|
|
track_columns.push(
|
|
|
|
|
<span className="col duration" key="duration">
|
2017-10-18 17:03:59 +13:00
|
|
|
{track.duration ? <Dater type="length" data={track.duration} /> : null}
|
2017-07-22 06:36:47 +12:00
|
|
|
</span>
|
|
|
|
|
)
|
2017-02-14 20:24:35 +13:00
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
2017-07-22 06:36:47 +12:00
|
|
|
track_columns.push(
|
|
|
|
|
<span className="col name" key="name">
|
|
|
|
|
{track.name ? track.name : <span className="grey-text">{track.uri}</span>}
|
|
|
|
|
{track.explicit ? <span className="flag dark">EXPLICIT</span> : null}
|
|
|
|
|
</span>
|
|
|
|
|
)
|
|
|
|
|
if (this.props.show_source_icon){
|
|
|
|
|
track_columns.push(
|
|
|
|
|
<FontAwesome name={helpers.sourceIcon(track.uri)} className="source" key="source" fixedWidth />
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
track_columns.push(
|
|
|
|
|
<span className="col artists" key="artists">
|
|
|
|
|
{track.artists ? <ArtistSentence artists={track.artists} /> : '-'}
|
|
|
|
|
</span>
|
|
|
|
|
)
|
|
|
|
|
track_columns.push(
|
|
|
|
|
<span className="col album" key="album">
|
|
|
|
|
{album}
|
|
|
|
|
</span>
|
|
|
|
|
)
|
|
|
|
|
track_columns.push(
|
|
|
|
|
<span className="col duration" key="duration">
|
2017-12-04 08:43:09 +13:00
|
|
|
{track.duration ? <Dater type="length" data={track.duration} /> : '-'}
|
2017-02-14 20:24:35 +13:00
|
|
|
</span>
|
|
|
|
|
)
|
2017-02-10 09:06:41 +13:00
|
|
|
}
|
|
|
|
|
|
2017-11-20 15:49:52 +13:00
|
|
|
track_actions.push(
|
2017-12-20 16:33:38 +13:00
|
|
|
<ContextMenuTrigger key="context" onTrigger={e => this.props.handleContextMenu(e)} />
|
2017-11-17 22:19:35 +13:00
|
|
|
)
|
|
|
|
|
|
2017-12-19 21:14:01 +13:00
|
|
|
// If we're touchable, and can sort this tracklist
|
|
|
|
|
if (helpers.isTouchDevice() && this.props.can_sort){
|
|
|
|
|
className += " has-touch-drag-zone"
|
2017-07-22 06:36:47 +12:00
|
|
|
|
2017-11-20 15:49:52 +13:00
|
|
|
track_actions.push(
|
2017-07-22 06:36:47 +12:00
|
|
|
<span
|
2017-12-19 21:14:01 +13:00
|
|
|
className="drag-zone touch-draggable mouse-draggable"
|
|
|
|
|
key="drag-zone">
|
|
|
|
|
<FontAwesome name="bars" fixedWidth />
|
2017-07-22 06:36:47 +12:00
|
|
|
</span>
|
|
|
|
|
)
|
2017-12-05 16:27:45 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className={className}
|
|
|
|
|
onMouseEnter={e => this.handleMouseEnter(e)}
|
|
|
|
|
onMouseLeave={e => this.handleMouseLeave(e)}
|
|
|
|
|
onMouseDown={e => this.handleMouseDown(e)}
|
|
|
|
|
onMouseUp={e => this.handleMouseUp(e)}
|
|
|
|
|
onMouseMove={e => this.handleMouseMove(e)}
|
2017-07-22 06:36:47 +12:00
|
|
|
|
2017-12-05 16:27:45 +13:00
|
|
|
onDoubleClick={e => this.handleDoubleClick(e)}
|
2017-12-20 16:33:38 +13:00
|
|
|
onContextMenu={e => this.props.handleContextMenu(e)}
|
2017-12-05 16:27:45 +13:00
|
|
|
|
|
|
|
|
onTouchStart={e => this.handleTouchStart(e)}
|
|
|
|
|
onTouchEnd={e => this.handleTouchEnd(e)}>
|
2017-11-20 15:49:52 +13:00
|
|
|
{track_actions}
|
2017-12-05 16:27:45 +13:00
|
|
|
{track_columns}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2016-10-03 21:21:14 +13:00
|
|
|
}
|
|
|
|
|
}
|