import React, { PropTypes } from 'react' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import FontAwesome from 'react-fontawesome' import Track from './Track' import ContextMenuTrigger from './ContextMenuTrigger' import * as helpers from '../helpers' import * as mopidyActions from '../services/mopidy/actions' import * as uiActions from '../services/ui/actions' class TrackList extends React.Component{ constructor(props) { super(props) this.state = { tracks: [], last_selected_track: false } this._touching = false this.handleKeyUp = this.handleKeyUp.bind(this) } componentWillMount(){ window.addEventListener("keyup", this.handleKeyUp, false); } componentWillUnmount(){ window.removeEventListener("keyup", this.handleKeyUp, false); } componentDidMount(){ this.setState({ tracks: this.keyifyTracks(this.props.tracks, true) }) } componentWillReceiveProps(nextProps){ this.setState({ tracks: this.keyifyTracks(nextProps.tracks) }); } /** * Figure out if our click/touch event is valid and we can act accordingly * * @param e = event obj * @return string **/ triggerType(e){ var target = $(e.target) // Wide screen, so no worries if ($(window).width() > 800){ return 'default' } else if (target.is('.state-icon') || target.closest('.state-icon').length > 0){ return 'mobile' } return false } handleKeyUp(e){ if( this.selectedTracks().length <= 0 ) return; switch(e.keyCode){ case 13: // enter this.playTracks(); break; case 46: // delete this.removeTracks(); break; } } handleTouchStart(e,index){ this._touching = true } handleTouchEnd(e,index){ this._touching = false this.handleMouseDown(e,index) // Prevent any event bubbling. This prevents clicks and mouse events // from also being fired e.preventDefault() } handleDoubleClick(e,index){ if (this.props.context_menu) this.props.uiActions.hideContextMenu() this.playTracks() } handleMouseDown(e,index){ if (this.props.context_menu) this.props.uiActions.hideContextMenu() // Regular clicking an un-selected element // This selects the track before we potentially drag switch (this.triggerType(e)){ case 'mobile': // simple toggle var tracks = this.state.tracks tracks[index].selected = !tracks[index].selected this.setState({tracks: tracks, last_selected_track: index}) break case 'default': if (!this.state.tracks[index].selected && !this.isRightClick(e) && !e.ctrlKey){ this.toggleTrackSelections(e, index) } break } var selected_tracks = this.selectedTracks() this.props.uiActions.dragStart( e, this.props.context, this.props.uri, selected_tracks, this.tracksIndexes(selected_tracks) ) } handleMouseUp(e,index){ if (this.triggerType(e) == 'default'){ // right-clicking on an un-highlighted track if (!this.state.tracks[index].selected && this.isRightClick(e)){ this.toggleTrackSelections(e, index) // selected track, regular click }else if (this.state.tracks[index].selected && !this.isRightClick(e)){ this.toggleTrackSelections(e, index) // ctrl key } else if(e.ctrlKey){ this.toggleTrackSelections(e, index) } if (this.props.dragger && this.props.dragger.active){ // if this tracklist handles sorting, handle it if (typeof(this.props.reorderTracks) !== 'undefined'){ var indexes = this.props.dragger.victims_indexes return this.props.reorderTracks( indexes, index ) } } } } handleContextMenu(e, native_event = true){ // Only enable direct context menu events in default (desktop) view // This prevents tap-and-hold functionality if (native_event && this.triggerType(e) != 'default'){ return false } var selected_tracks = this.selectedTracks() var data = { e: e, context: (this.props.context ? this.props.context+'-track' : 'track'), tracklist_uri: (this.props.uri ? this.props.uri : null), items: selected_tracks, uris: helpers.asURIs(selected_tracks), indexes: this.tracksIndexes(selected_tracks) } this.props.uiActions.showContextMenu(data) if (!native_event){ this.deselectAllTracks() this.setState({edit_mode: false}) } } toggleTrackSelections(e,index){ var tracks = this.state.tracks var last_selected_track = this.state.last_selected_track if( e.ctrlKey ){ if (tracks[index].selected){ tracks[index].selected = false } else { tracks[index].selected = true last_selected_track = index } }else if( e.shiftKey ){ if( this.state.last_selected_track < index ){ var start = this.state.last_selected_track var end = index }else{ var start = index var end = this.state.last_selected_track } if (start !== false && end !== false){ for( var i = start; i <= end; i++ ){ tracks[i].selected = true last_selected_track = index } } }else{ for( var i = 0; i < tracks.length; i++ ){ tracks[i].selected = false } tracks[index].selected = true last_selected_track = index } this.setState({ tracks: tracks, last_selected_track: last_selected_track }) } deselectAllTracks(){ var tracks = this.props.tracks for (var i = 0; i < tracks.length; i++){ tracks[i].selected = false } this.setState({tracks: tracks}) } isRightClick(e){ if( 'which' in e ) return e.which == 3 if( 'button' in e ) return e.button == 2 return false } keyifyTracks(tracks, deselect = false){ for( var i = 0; i < tracks.length; i++ ){ var new_properties = { key: i+'_'+tracks[i].uri } if (deselect){ new_properties.selected = false } tracks[i] = Object.assign( {}, tracks[i], new_properties ) } return tracks } selectedTracks(){ if (!this.state.tracks || this.state.tracks.length <= 0){ return [] } else { function isSelected( track ){ return ( typeof(track.selected) !== 'undefined' && track.selected ); } return this.state.tracks.filter(isSelected) } } tracksIndexes(tracks){ var indexes = [] for( var i = 0; i < tracks.length; i++ ){ indexes.push( this.props.tracks.indexOf(tracks[i])) } return indexes } playTracks(){ var tracks = this.selectedTracks(); // if we've got a specific action, run it if( typeof(this.props.playTracks) !== 'undefined' ){ return this.props.playTracks( tracks ); } // default to playing a bunch of uris var uris = []; for( var i = 0; i < tracks.length; i++ ){ uris.push( tracks[i].uri ) } return this.props.mopidyActions.playURIs( uris, this.props.uri ) } removeTracks(){ // if this tracklist handles removal, handle it if( typeof(this.props.removeTracks) !== 'undefined' ){ var tracks = this.selectedTracks(); var tracks_indexes = this.tracksIndexes(tracks); return this.props.removeTracks( tracks_indexes ); } // by default, do nothing } renderHeader(){ if( this.props.noheader ) return null switch (this.props.context){ case 'history': return (