diff --git a/src/js/bootstrap.js b/src/js/bootstrap.js index 2594daef..59a0a12a 100755 --- a/src/js/bootstrap.js +++ b/src/js/bootstrap.js @@ -56,6 +56,7 @@ var initialState = { slim_mode: false, current_tracklist: [], current_tltrack: false, + selected_tracks: [], notifications: [], config: { authorization_url: 'https://jamesbarnsley.co.nz/auth_v2.php' diff --git a/src/js/components/DebugInfo.js b/src/js/components/DebugInfo.js index 41ff9e96..607bce7b 100755 --- a/src/js/components/DebugInfo.js +++ b/src/js/components/DebugInfo.js @@ -66,7 +66,7 @@ class DebugInfo extends React.Component{ Playlists: {this.props.ui.playlists ? Object.keys(this.props.ui.playlists).length : '0'}
- Tracks: {this.props.ui.tracks ? Object.keys(this.props.ui.tracks).length : '0'} + Tracks: {this.props.ui.tracks ? Object.keys(this.props.ui.tracks).length : '0'} ({this.props.ui.selected_tracks.length} selected)
Users: {this.props.ui.users ? Object.keys(this.props.ui.users).length : '0'} diff --git a/src/js/components/Track.js b/src/js/components/Track.js index 4b44b725..00ff344b 100755 --- a/src/js/components/Track.js +++ b/src/js/components/Track.js @@ -41,8 +41,8 @@ export default class Track extends React.Component{ var track = this.props.track var className = 'list-item track' - if (typeof(track.selected) !== 'undefined' && track.selected) className += ' selected' - if (typeof(track.type) !== 'undefined') className += ' '+track.type + if (this.props.selected) className += ' selected' + if (track.type !== undefined) className += ' '+track.type if (track.playing) className += ' playing' if (this.state.hover) className += ' hover' diff --git a/src/js/components/TrackList.js b/src/js/components/TrackList.js index cccd014e..d356dc7c 100755 --- a/src/js/components/TrackList.js +++ b/src/js/components/TrackList.js @@ -16,11 +16,6 @@ class TrackList extends React.Component{ constructor(props) { super(props) - this.state = { - tracks: [], - last_selected_track: false - } - this.touch_dragging = false this.handleKeyUp = this.handleKeyUp.bind(this) this.handleTouchMove = this.handleTouchMove.bind(this) @@ -36,14 +31,6 @@ class TrackList extends React.Component{ window.removeEventListener("touchmove", this.handleTouchMove, 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 * @@ -63,8 +50,25 @@ class TrackList extends React.Component{ return false } + + /** + * Build the track key + * This is our unique reference to a track in a particular tracklist + * + * @param track = Track obj + * @param index = int (position of track in tracklist) + * @return string + **/ + buildTrackKey(track, index){ + let key = index + key += '_'+track.uri + key += '_'+(this.props.uri ? this.props.uri : 'none') + key += '_'+(this.props.context ? this.props.context : 'none') + return key + } + handleKeyUp(e){ - if( this.selectedTracks().length <= 0 ) return; + if (!this.selectedTracks()) return switch(e.keyCode){ @@ -131,14 +135,26 @@ class TrackList extends React.Component{ e.preventDefault() } - handleDoubleClick(e,index){ + handleDoubleClick(e,track_key){ if (this.props.context_menu) this.props.uiActions.hideContextMenu() this.playTracks() } - handleMouseDown(e,index){ + handleMouseDown(e,track_key){ if (this.props.context_menu) this.props.uiActions.hideContextMenu() + + this.toggleTrackSelections(e, track_key) + + 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 ) + } + } +/* // Regular clicking an un-selected element // This selects the track before we potentially drag switch (this.triggerType(e)){ @@ -156,36 +172,12 @@ class TrackList extends React.Component{ } break } + */ - var selected_tracks = this.selectedTracks() - this.props.uiActions.dragStart(e, this.props.context, this.props.uri, selected_tracks, this.tracksIndexes(selected_tracks)) + //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 ) - } - } - } + handleMouseUp(e,track_key){ } handleContextMenu(e, native_event = true){ @@ -208,60 +200,57 @@ class TrackList extends React.Component{ this.props.uiActions.showContextMenu(data) if (!native_event){ - this.deselectAllTracks() - this.setState({edit_mode: false}) + + // Deselect all tracks + this.props.uiActions.setSelectedTracks([]) } } - toggleTrackSelections(e,index){ - var tracks = this.state.tracks - var last_selected_track = this.state.last_selected_track + toggleTrackSelections(e,track_key){ + let selected_tracks = this.props.selected_tracks - if( e.ctrlKey ){ + if (e.ctrlKey){ - if (tracks[index].selected){ - tracks[index].selected = false + // Already selected, so unselect it + if (selected_tracks.includes(track_key)){ + var index = selected_tracks.indexOf(track_key) + selected_tracks.splice(index,1) + + // Not selected, so add it } else { - tracks[index].selected = true - last_selected_track = index + selected_tracks.push(track_key) } - }else if( e.shiftKey ){ + } 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 + let last_selected_track_index = selected_tracks[selected_tracks.length-1].split('_')[0] + let newly_selected_track_index = track_key.split('_')[0] + + // We've selected a track further down the list, + // so proceed normally + if (last_selected_track_index < newly_selected_track_index){ + var start = last_selected_track_index + var end = newly_selected_track_index + + // Selected a track up the list, so + // our last selected is the END of our range + } else { + var start = newly_selected_track_index + var end = last_selected_track_index } - if (start !== false && end !== false){ - for( var i = start; i <= end; i++ ){ - tracks[i].selected = true - last_selected_track = index + if (start !== false && start >= 0 && end !== false && end >= 0){ + for (let i = start; i <= end; i++){ + selected_tracks.push(this.buildTrackKey(this.props.tracks[i], i)) } } - }else{ - - for( var i = 0; i < tracks.length; i++ ){ - tracks[i].selected = false - } - - tracks[index].selected = true - last_selected_track = index + // Regular, unmodified left click + } else { + selected_tracks = [track_key] } - 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}) + this.props.uiActions.setSelectedTracks(selected_tracks) } isRightClick(e){ @@ -289,52 +278,53 @@ class TrackList extends React.Component{ 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) + selectedTracks(indexes_only = false){ + if (!this.props.selected_tracks){ + return false } - } - tracksIndexes(tracks){ - var indexes = [] - for( var i = 0; i < tracks.length; i++ ){ - indexes.push( this.props.tracks.indexOf(tracks[i])) + // Construct a basic track object, based on our unique track key + // This is enough to perform interactions (dragging, selecting, etc) + let response = [] + for (let i = 0; i < this.props.selected_tracks.length; i++){ + let track = this.props.selected_tracks[i].split('_') + + if (indexes_only){ + response.push(track[0]) + + } else { + response.push({ + index: track[0], + uri: track[1], + context: track[2], + context_uri: track[3] + }) + } } - return indexes + + return response } playTracks(){ + let selected_tracks = this.selectedTracks() - var tracks = this.selectedTracks(); - - // if we've got a specific action, run it - if( typeof(this.props.playTracks) !== 'undefined' ){ - return this.props.playTracks( tracks ); + // Our parent handles playing + if (this.props.playTracks !== undefined){ + return this.props.playTracks( selected_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 ) + // Default to playing the URIs + return this.props.mopidyActions.playURIs(helpers.asURIs(selected_tracks), 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 ); + // Our parent handles removal + if (this.props.removeTracks !== undefined){ + return this.props.removeTracks(this.selectedTracks(true)) } - // by default, do nothing + // By default, do nothing } renderHeader(){ @@ -377,9 +367,8 @@ class TrackList extends React.Component{ } render(){ - if( !this.state.tracks || Object.prototype.toString.call(this.state.tracks) !== '[object Array]' ) return null + if (!this.props.tracks || Object.prototype.toString.call(this.props.tracks) !== '[object Array]' ) return null - let self = this; var className = 'list track-list '+this.props.context if (this.props.className){ className += ' '+this.props.className @@ -389,26 +378,28 @@ class TrackList extends React.Component{
{ this.renderHeader() } { - this.state.tracks.map( + this.props.tracks.map( (track, index) => { + let track_key = this.buildTrackKey(track, index) return ( self.handleDoubleClick(e, index)} - handleMouseUp={e => self.handleMouseUp(e, index)} - handleMouseDown={e => self.handleMouseDown(e, index)} - handleTouchStart={e => self.handleTouchStart(e, index)} - handleTouchEnd={e => self.handleTouchEnd(e, index)} - handleContextMenu={e => self.handleContextMenu(e)} + selected={this.props.selected_tracks.includes(track_key)} + handleDoubleClick={e => this.handleDoubleClick(e, track_key)} + handleMouseUp={e => this.handleMouseUp(e, track_key)} + handleMouseDown={e => this.handleMouseDown(e, track_key)} + handleTouchStart={e => this.handleTouchStart(e, track_key)} + handleTouchEnd={e => this.handleTouchEnd(e, track_key)} + handleContextMenu={e => this.handleContextMenu(e)} /> ) } ) } - {this.selectedTracks().length > 0 ? this.handleContextMenu(e, false)} /> : null} + {this.selectedTracks() ? this.handleContextMenu(e, false)} /> : null}
); } @@ -423,6 +414,7 @@ class TrackList extends React.Component{ const mapStateToProps = (state, ownProps) => { return { + selected_tracks: state.ui.selected_tracks, slim_mode: state.ui.slim_mode, dragger: state.ui.dragger, current_track: state.ui.current_track, diff --git a/src/js/services/ui/actions.js b/src/js/services/ui/actions.js index 5a7c8c75..0b416d38 100755 --- a/src/js/services/ui/actions.js +++ b/src/js/services/ui/actions.js @@ -21,6 +21,16 @@ export function getBroadcasts(){ } } +export function setSelectedTracks(keys = []){ + if (typeof(keys) === 'string'){ + keys = [keys] + } + return { + type: 'SET_SELECTED_TRACKS', + keys: keys + } +} + export function showContextMenu(data){ data.position_x = data.e.clientX data.position_y = data.e.clientY diff --git a/src/js/services/ui/reducer.js b/src/js/services/ui/reducer.js index 1aedd621..9b0c1ae5 100755 --- a/src/js/services/ui/reducer.js +++ b/src/js/services/ui/reducer.js @@ -24,6 +24,9 @@ export default function reducer(ui = {}, action){ if( typeof(action.new_state) !== 'undefined' ) new_state = action.new_state return Object.assign({}, ui, { sidebar_open : new_state }) + case 'SET_SELECTED_TRACKS': + return Object.assign({}, ui, { selected_tracks : Object.assign([],action.keys) }) + /** * Context menu