From c108e833dddaab5f87b7292724ecda573a8a5c82 Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Sun, 29 Jan 2017 16:25:05 +1300 Subject: [PATCH] Revamp context menu; Specialist touch menu --- src/js/bootstrap.js | 3 - src/js/components/ContextMenu.js | 139 +++++++++------------ src/js/components/DebugInfo.js | 10 +- src/js/components/TouchContextMenu.js | 164 +++++++++++++++++++++++++ src/js/components/Track.js | 7 ++ src/js/components/TrackList.js | 78 +++++++----- src/js/helpers.js | 4 + src/js/services/ui/actions.js | 21 +++- src/js/services/ui/reducer.js | 20 +-- src/js/views/App.js | 13 +- src/js/views/Artist.js | 2 +- src/js/views/Playlist.js | 9 +- src/js/views/Queue.js | 2 +- src/scss/components/_context-menu.scss | 93 +++++++------- 14 files changed, 379 insertions(+), 186 deletions(-) create mode 100755 src/js/components/TouchContextMenu.js diff --git a/src/js/bootstrap.js b/src/js/bootstrap.js index 87a29d61..477c24f1 100755 --- a/src/js/bootstrap.js +++ b/src/js/bootstrap.js @@ -54,9 +54,6 @@ var initialState = { me: false }, ui: { - context_menu: { - show: false - }, current_tracklist: [], current_tltrack: false, notifications: [] diff --git a/src/js/components/ContextMenu.js b/src/js/components/ContextMenu.js index 2b940d56..06fc53d7 100755 --- a/src/js/components/ContextMenu.js +++ b/src/js/components/ContextMenu.js @@ -31,25 +31,21 @@ class ContextMenu extends React.Component{ } handleScroll(){ - if( this.props.context_menu.show && this.props.context_menu.trigger == 'click' ){ - this.props.uiActions.hideContextMenu(); - } + if (this.props.menu) this.props.uiActions.hideContextMenu() } handleClick(){ - if( this.props.context_menu.show && this.props.context_menu.trigger == 'click' ){ - this.props.uiActions.hideContextMenu(); - } + if (this.props.menu) this.props.uiActions.hideContextMenu() } playQueueItem(){ - var tracks = this.props.context_menu.data.items; + var tracks = this.props.menu.items; this.props.mopidyActions.changeTrack( tracks[0].tlid ); this.props.uiActions.hideContextMenu(); } removeFromQueue(){ - var tracks = this.props.context_menu.data.items; + var tracks = this.props.menu.items; var tracks_tlids = []; for( var i = 0; i < tracks.length; i++ ){ tracks_tlids.push( tracks[i].tlid ); @@ -59,48 +55,48 @@ class ContextMenu extends React.Component{ } playURIs(){ - this.props.mopidyActions.playURIs(this.props.context_menu.data.uris); + this.props.mopidyActions.playURIs(this.props.menu.uris); this.props.uiActions.hideContextMenu(); } playURIsNext(){ - this.props.mopidyActions.enqueueURIsNext(this.props.context_menu.data.uris); + this.props.mopidyActions.enqueueURIsNext(this.props.menu.uris); this.props.uiActions.hideContextMenu(); } addToPlaylist(){ - this.props.uiActions.openModal( 'add_to_playlist', { tracks_uris: this.props.context_menu.data.uris } ) + this.props.uiActions.openModal('add_to_playlist', { tracks_uris: this.props.menu.uris }) this.props.uiActions.hideContextMenu(); } addToQueue(){ - this.props.mopidyActions.enqueueURIs(this.props.context_menu.data.uris) + this.props.mopidyActions.enqueueURIs(this.props.menu.uris) this.props.uiActions.hideContextMenu() } - addTracksToPlaylist( playlist_uri ){ - this.props.uiActions.addTracksToPlaylist( playlist_uri, this.props.context_menu.data.uris ) + addTracksToPlaylist(playlist_uri){ + this.props.uiActions.addTracksToPlaylist(playlist_uri, this.props.menu.uris) this.props.uiActions.hideContextMenu(); } removeFromPlaylist(){ - this.props.uiActions.removeTracksFromPlaylist( this.props.playlist.uri, this.props.context_menu.data.indexes ) + this.props.uiActions.removeTracksFromPlaylist(this.props.menu.tracklist_uri, this.props.menu.indexes) this.props.uiActions.hideContextMenu(); } startRadio(){ - this.props.pusherActions.startRadio(this.props.context_menu.data.uris) + this.props.pusherActions.startRadio(this.props.menu.uris) this.props.uiActions.hideContextMenu(); } copyURIs(e){ var temp = $(""); $("body").append(temp); - temp.val(this.props.context_menu.data.uris.join(',')).select(); + temp.val(this.props.menu.uris.join(',')).select(); document.execCommand("copy"); temp.remove(); - this.props.uiActions.createNotification( "Copied "+this.props.context_menu.data.uris.length+" URIs" ) + this.props.uiActions.createNotification( "Copied "+this.props.menu.uris.length+" URIs" ) this.props.uiActions.hideContextMenu() } @@ -134,63 +130,63 @@ class ContextMenu extends React.Component{ } getItems(){ - switch (this.props.context_menu.context) { - - case 'queue': - var items = [ - { handleClick: 'playQueueItem', label: 'Play', icon: 'play' }, - { handleClick: 'addToPlaylist', label: 'Add to playlist', icon: 'plus', playlists: true }, - { handleClick: 'copyURIs', label: 'Copy URIs', icon: 'copy' }, - { handleClick: 'removeFromQueue', label: 'Remove', icon: 'trash' } - ] - break + switch (this.props.menu.context) { case 'album': var items = [ - { handleClick: 'playURIs', label: 'Play', icon: 'play' }, - { handleClick: 'playURIsNext', label: 'Play next', icon: 'play' }, - { handleClick: 'addToQueue', label: 'Add to queue', icon: 'plus' }, - { handleClick: 'startRadio', label: 'Start radio', icon: 'spotify' }, - { handleClick: 'copyURIs', label: 'Copy URI', icon: 'copy' } + { handleClick: 'playURIs', label: 'Play' }, + { handleClick: 'playURIsNext', label: 'Play next' }, + { handleClick: 'addToQueue', label: 'Add to queue' }, + { handleClick: 'startRadio', label: 'Start radio' }, + { handleClick: 'copyURIs', label: 'Copy URI' } ] break case 'artist': var items = [ - { handleClick: 'startRadio', label: 'Start radio', icon: 'spotify' }, - { handleClick: 'toggleFollow', label: 'Follow/unfollow', icon: 'toggle-on' }, - { handleClick: 'copyURIs', label: 'Copy URI', icon: 'copy' } + { handleClick: 'startRadio', label: 'Start radio' }, + { handleClick: 'toggleFollow', label: 'Follow/unfollow' }, + { handleClick: 'copyURIs', label: 'Copy URI' } ] break case 'playlist': var items = [ - { handleClick: 'playURIs', label: 'Play', icon: 'play' }, - { handleClick: 'toggleFollow', label: 'Follow/unfollow', icon: 'toggle-on' }, - { handleClick: 'copyURIs', label: 'Copy URI', icon: 'copy' } + { handleClick: 'playURIs', label: 'Play' }, + { handleClick: 'toggleFollow', label: 'Follow/unfollow' }, + { handleClick: 'copyURIs', label: 'Copy URI' } + ] + break + + case 'queue-track': + var items = [ + { handleClick: 'playQueueItem', label: 'Play' }, + { handleClick: 'addToPlaylist', label: 'Add to playlist' }, + { handleClick: 'copyURIs', label: 'Copy URIs' }, + { handleClick: 'removeFromQueue', label: 'Remove' } ] break case 'editable-playlist-track': var items = [ - { handleClick: 'playURIs', label: 'Play', icon: 'play' }, - { handleClick: 'playURIsNext', label: 'Play next', icon: 'play' }, - { handleClick: 'addToQueue', label: 'Add to queue', icon: 'plus' }, - { handleClick: 'addToPlaylist', label: 'Add to playlist', icon: 'plus', playlists: true }, - { handleClick: 'startRadio', label: 'Start radio', icon: 'spotify' }, - { handleClick: 'copyURIs', label: 'Copy URIs', icon: 'copy' }, - { handleClick: 'removeFromPlaylist', label: 'Remove', icon: 'trash' } + { handleClick: 'playURIs', label: 'Play' }, + { handleClick: 'playURIsNext', label: 'Play next' }, + { handleClick: 'addToQueue', label: 'Add to queue' }, + { handleClick: 'addToPlaylist', label: 'Add to playlist' }, + { handleClick: 'startRadio', label: 'Start radio' }, + { handleClick: 'copyURIs', label: 'Copy URIs' }, + { handleClick: 'removeFromPlaylist', label: 'Remove' } ] break default: var items = [ - { handleClick: 'playURIs', label: 'Play', icon: 'play' }, - { handleClick: 'playURIsNext', label: 'Play next', icon: 'play' }, - { handleClick: 'addToQueue', label: 'Add to queue', icon: 'plus' }, - { handleClick: 'addToPlaylist', label: 'Add to playlist', icon: 'plus', playlists: true }, - { handleClick: 'startRadio', label: 'Start radio', icon: 'spotify' }, - { handleClick: 'copyURIs', label: 'Copy URIs', icon: 'copy' } + { handleClick: 'playURIs', label: 'Play' }, + { handleClick: 'playURIsNext', label: 'Play next' }, + { handleClick: 'addToQueue', label: 'Add to queue' }, + { handleClick: 'addToPlaylist', label: 'Add to playlist' }, + { handleClick: 'startRadio', label: 'Start radio' }, + { handleClick: 'copyURIs', label: 'Copy URIs' } ] break } @@ -199,7 +195,7 @@ class ContextMenu extends React.Component{ } renderTitle(){ - var item = this.props.context_menu.data.item + var item = this.props.menu.item var style = null if (item && item.images){ style = { @@ -221,11 +217,10 @@ class ContextMenu extends React.Component{
{ items.map((item, index) => { - if( item.playlists ){ + if (item.handleClick == 'addToPlaylist'){ return ( this[item.handleClick](e)}> - { item.label } @@ -236,7 +231,6 @@ class ContextMenu extends React.Component{ return ( this[item.handleClick](e)}> - { item.label } @@ -249,37 +243,28 @@ class ContextMenu extends React.Component{ } render(){ - if( !this.props.context_menu.show ) return null; + if (!this.props.menu) return null; var style = { - left: this.props.context_menu.position_x, - top: this.props.context_menu.position_y, + left: this.props.menu.position_x, + top: this.props.menu.position_y, } - var className = 'context-menu' - if (this.props.emulate_touch) { - className += ' touch' - var trigger = 'touch' - } else { - className += ' '+this.props.context_menu.trigger - var trigger = this.props.context_menu.trigger - } - - var items = this.getItems(trigger) + var items = this.getItems() var height = 0 if (items) height = items.length * 34 // this is an approximation of how tall each menu item is - if (this.props.context_menu.position_x > (window.innerWidth - 154)) className += ' right-align' - if (this.props.context_menu.position_x > (window.innerWidth - 308)) className += ' right-align-submenu' - if (this.props.context_menu.position_y > (window.innerHeight - height)){ + if (this.props.menu.position_x > (window.innerWidth - 154)) className += ' right-align' + if (this.props.menu.position_x > (window.innerWidth - 308)) className += ' right-align-submenu' + if (this.props.menu.position_y > (window.innerHeight - height)){ style.top = style.top - height className += ' bottom-align-submenu' } return ( -
- {this.props.context_menu.data.item ? this.renderTitle() : null} - {this.renderItems(trigger)} +
+ {this.props.menu.item ? this.renderTitle() : null} + {this.renderItems()}
); } @@ -287,11 +272,9 @@ class ContextMenu extends React.Component{ const mapStateToProps = (state, ownProps) => { return { - emulate_touch: state.ui.emulate_touch, - context_menu: state.ui.context_menu, + menu: state.ui.context_menu, current_track: state.ui.current_track, current_tracklist: state.ui.current_tracklist, - playlist: state.ui.playlist, playlists: state.ui.playlists } } diff --git a/src/js/components/DebugInfo.js b/src/js/components/DebugInfo.js index 4d808866..97473e3c 100755 --- a/src/js/components/DebugInfo.js +++ b/src/js/components/DebugInfo.js @@ -4,6 +4,7 @@ import { connect } from 'react-redux' import { Link } from 'react-router' import { createStore, bindActionCreators } from 'redux' +import * as helpers from '../helpers' import * as uiActions from '../services/ui/actions' class DebugInfo extends React.Component{ @@ -13,6 +14,13 @@ class DebugInfo extends React.Component{ } render(){ + var touch_state = 'no touch' + if (this.props.ui.emulate_touch){ + touch_state = 'emulated touch' + } else if (helpers.isTouchDevice()){ + touch_state = 'touch device' + } + return (
@@ -28,7 +36,7 @@ class DebugInfo extends React.Component{ {this.props.ui.users ? Object.keys(this.props.ui.users).length : '0'} users
- {'ontouchstart' in document.documentElement ? 'can touch' : 'no touch'} + {touch_state}
); diff --git a/src/js/components/TouchContextMenu.js b/src/js/components/TouchContextMenu.js new file mode 100755 index 00000000..5265b09a --- /dev/null +++ b/src/js/components/TouchContextMenu.js @@ -0,0 +1,164 @@ + +import React, { PropTypes } from 'react' +import { Link } from 'react-router' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import FontAwesome from 'react-fontawesome' + +import TrackList from './TrackList' +import * as helpers from '../helpers' +import * as uiActions from '../services/ui/actions' +import * as pusherActions from '../services/pusher/actions' +import * as mopidyActions from '../services/mopidy/actions' +import * as spotifyActions from '../services/spotify/actions' + +class TouchContextMenu extends React.Component{ + + constructor(props) { + super(props); + } + + close(){ + this.props.uiActions.hideTouchContextMenu(); + } + + playQueueItem(){ + var tracks = this.props.menu.items + this.props.mopidyActions.changeTrack( tracks[0].tlid ) + this.close() + } + + removeFromQueue(e){ + var tracks = this.props.menu.items + var tracks_tlids = [] + for( var i = 0; i < tracks.length; i++ ){ + tracks_tlids.push( tracks[i].tlid ); + } + this.props.mopidyActions.removeTracks( tracks_tlids ) + this.close() + } + + playURIs(e){ + this.props.mopidyActions.playURIs(this.props.menu.uris) + this.close() + } + + playURIsNext(e){ + this.props.mopidyActions.enqueueURIsNext(this.props.menu.uris) + this.close() + } + + addToPlaylist(e){ + this.props.uiActions.openModal('add_to_playlist', { tracks_uris: this.props.menu.uris }) + this.close() + } + + addToQueue(e){ + this.props.mopidyActions.enqueueURIs(this.props.menu.uris) + this.close() + } + + removeFromPlaylist(e){ + this.props.uiActions.removeTracksFromPlaylist(this.props.menu.tracklist_uri, this.props.menu.indexes) + this.close() + } + + startRadio(e){ + this.props.pusherActions.startRadio(this.props.menu.uris) + this.close() + } + + getItems(){ + switch (this.props.menu.context) { + + case 'queue-track': + var items = [ + { handleClick: 'playQueueItem', label: 'Play', icon: 'play' }, + { handleClick: 'addToPlaylist', label: 'Playlist', icon: 'plus' }, + { handleClick: 'removeFromQueue', label: 'Remove', icon: 'trash' } + ] + break + + case 'editable-playlist-track': + var items = [ + { handleClick: 'playURIs', label: 'Play', icon: 'play' }, + { handleClick: 'playURIsNext', label: 'Next', icon: 'play' }, + { handleClick: 'addToQueue', label: 'Queue', icon: 'plus' }, + { handleClick: 'addToPlaylist', label: 'Playlist', icon: 'plus' }, + { handleClick: 'startRadio', label: 'Start radio', icon: 'spotify' }, + { handleClick: 'removeFromPlaylist', label: 'Remove', icon: 'trash' } + ] + break + + default: + var items = [ + { handleClick: 'playURIs', label: 'Play', icon: 'play' }, + { handleClick: 'playURIsNext', label: 'Next', icon: 'play' }, + { handleClick: 'addToQueue', label: 'Queue', icon: 'plus' }, + { handleClick: 'addToPlaylist', label: 'Playlist', icon: 'plus' }, + { handleClick: 'startRadio', label: 'Start radio', icon: 'spotify' } + ] + break + } + + return items + } + + renderItems(){ + var items = this.getItems() + + return ( +
+ { + items.map((item, index) => { + return ( + + this[item.handleClick]()}> + + { item.label } + + + ) + }) + } + + this.close()}> + + Cancel + + +
+ ) + } + + render(){ + if( !this.props.menu ) return null; + + return ( +
+ {this.renderItems()} +
+ ); + } +} + +const mapStateToProps = (state, ownProps) => { + return { + menu: state.ui.touch_context_menu, + current_track: state.ui.current_track, + current_tracklist: state.ui.current_tracklist, + playlist: state.ui.playlist, + playlists: state.ui.playlists + } +} + +const mapDispatchToProps = (dispatch) => { + return { + uiActions: bindActionCreators(uiActions, dispatch), + pusherActions: bindActionCreators(pusherActions, dispatch), + spotifyActions: bindActionCreators(spotifyActions, dispatch), + mopidyActions: bindActionCreators(mopidyActions, dispatch) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(TouchContextMenu) \ No newline at end of file diff --git a/src/js/components/Track.js b/src/js/components/Track.js index 0d7cfc11..4020739d 100755 --- a/src/js/components/Track.js +++ b/src/js/components/Track.js @@ -20,6 +20,13 @@ export default class Track extends React.Component{ } } + handleTouchStart(e){ + var target = $(e.target); + if( !target.is('a') && target.closest('a').length <= 0 ){ + this.props.handleTouchStart(e); + } + } + handleTouchEnd(e){ var target = $(e.target); if( !target.is('a') && target.closest('a').length <= 0 ){ diff --git a/src/js/components/TrackList.js b/src/js/components/TrackList.js index b43da2e5..8200f531 100755 --- a/src/js/components/TrackList.js +++ b/src/js/components/TrackList.js @@ -34,7 +34,7 @@ class TrackList extends React.Component{ window.removeEventListener("keyup", this.handleKeyUp, false); } - componentWillReceiveProps( nextProps ){ + componentWillReceiveProps(nextProps){ this.setState({ tracks: this.keyifyTracks(nextProps.tracks) }); } @@ -53,12 +53,12 @@ class TrackList extends React.Component{ } } - handleTouchStart(e, index){ + handleTouchStart(e,index){ this._touch_x = Math.round(e.changedTouches[0].pageX) this._touch_y = Math.round(e.changedTouches[0].pageY) } - handleTouchEnd(e, index){ + handleTouchEnd(e,index){ var pageX = Math.round(e.changedTouches[0].pageX) var pageY = Math.round(e.changedTouches[0].pageY) @@ -69,45 +69,30 @@ class TrackList extends React.Component{ this._touch_x > ( pageX - this._touch_threshold ) && this._touch_y < ( pageY + this._touch_threshold ) && this._touch_y > ( pageY - this._touch_threshold ) ){ - - // toggle selection - var tracks = this.state.tracks - tracks[index].selected = !tracks[index].selected - this.setState({ tracks: tracks, lastSelectedTrack: index }) - - // update our context menu to hide/show - var selected_tracks = this.selectedTracks() - if( selected_tracks.length > 0 ){ - var data = { - items: selected_tracks, - uris: helpers.asURIs(selected_tracks), - indexes: this.tracksIndexes(selected_tracks) - } - this.props.uiActions.showContextMenu( e, data, this.props.context, 'touch' ) - }else{ - this.props.uiActions.hideContextMenu() - } + this.handleTouchContextMenu(e,index) } e.preventDefault() } - handleDoubleClick(e, index){ - if( this.props.context_menu.show ) this.props.uiActions.hideContextMenu() + handleDoubleClick(e,index){ + if (this.props.context_menu) this.props.uiActions.hideContextMenu() this.playTracks() } - handleMouseDown(e, index){ + handleMouseDown(e,index){ + if (this.props.emulate_touch){ + this.handleTouchContextMenu(e,index) + }else{ + if (this.props.context_menu) this.props.uiActions.hideContextMenu() + if (!this.state.tracks[index].selected && !this.isRightClick(e) && !e.ctrlKey) this.toggleTrackSelections(e, index) - if( this.props.context_menu.show ) this.props.uiActions.hideContextMenu() - - if( !this.state.tracks[index].selected && !this.isRightClick(e) && !e.ctrlKey ) this.toggleTrackSelections(e, index) - - var selected_tracks = this.selectedTracks() - this.props.uiActions.dragStart( e, this.props.context, selected_tracks, this.tracksIndexes(selected_tracks) ) + var selected_tracks = this.selectedTracks() + this.props.uiActions.dragStart( e, this.props.context, selected_tracks, this.tracksIndexes(selected_tracks) ) + } } - handleMouseUp(e, index){ + handleMouseUp(e,index){ // right-clicking on an un-highlighted track if( !this.state.tracks[index].selected && this.isRightClick(e) ){ @@ -132,14 +117,40 @@ class TrackList extends React.Component{ } } - handleContextMenu(e, index){ + handleContextMenu(e,index){ var selected_tracks = this.selectedTracks() var data = { + e: e, + context: (this.props.context ? this.props.context : 'track'), + tracklist_uri: (this.props.tracklist_uri ? this.props.tracklist_uri : null), items: selected_tracks, uris: helpers.asURIs(selected_tracks), indexes: this.tracksIndexes(selected_tracks) } - this.props.uiActions.showContextMenu( e, data, this.props.context, 'click' ) + this.props.uiActions.showContextMenu(data) + } + + handleTouchContextMenu(e,index){ + + // toggle selection + var tracks = this.state.tracks + tracks[index].selected = !tracks[index].selected + this.setState({ tracks: tracks, lastSelectedTrack: index }) + + // update our context menu to hide/show + var selected_tracks = this.selectedTracks() + if( selected_tracks.length > 0 ){ + var data = { + context: (this.props.context ? this.props.context : 'track'), + tracklist_uri: (this.props.tracklist_uri ? this.props.tracklist_uri : null), + items: selected_tracks, + uris: helpers.asURIs(selected_tracks), + indexes: this.tracksIndexes(selected_tracks) + } + this.props.uiActions.showTouchContextMenu(data) + }else{ + this.props.uiActions.hideTouchContextMenu() + } } toggleTrackSelections(e, index){ @@ -285,6 +296,7 @@ class TrackList extends React.Component{ const mapStateToProps = (state, ownProps) => { return { dragger: state.ui.dragger, + emulate_touch: state.ui.emulate_touch, current_track: state.ui.current_track, context_menu: state.ui.context_menu } diff --git a/src/js/helpers.js b/src/js/helpers.js index 64bd088f..38ac6524 100755 --- a/src/js/helpers.js +++ b/src/js/helpers.js @@ -1,5 +1,9 @@ +export let isTouchDevice = function(){ + return 'ontouchstart' in document.documentElement +} + export let sizedImages = function( images ){ var sizes = { diff --git a/src/js/services/ui/actions.js b/src/js/services/ui/actions.js index f4c51895..bf219c0f 100755 --- a/src/js/services/ui/actions.js +++ b/src/js/services/ui/actions.js @@ -1,13 +1,11 @@ import * as helpers from '../../helpers' -export function showContextMenu( e, data, context = false, trigger = 'click' ){ +export function showContextMenu(data){ + data.position_x = data.e.clientX + data.position_y = data.e.clientY return { type: 'SHOW_CONTEXT_MENU', - position_x: e.clientX, - position_y: e.clientY, - context: context, - trigger: trigger, data: data } } @@ -18,6 +16,19 @@ export function hideContextMenu(){ } } +export function showTouchContextMenu(data){ + return { + type: 'SHOW_TOUCH_CONTEXT_MENU', + data: data + } +} + +export function hideTouchContextMenu(){ + return { + type: 'HIDE_TOUCH_CONTEXT_MENU' + } +} + export function startSearch( query ){ return { type: 'SEARCH_STARTED', diff --git a/src/js/services/ui/reducer.js b/src/js/services/ui/reducer.js index e42aed1b..52640abd 100755 --- a/src/js/services/ui/reducer.js +++ b/src/js/services/ui/reducer.js @@ -22,20 +22,22 @@ export default function reducer(ui = {}, action){ /** * Context menu **/ + case 'SHOW_CONTEXT_MENU': return Object.assign({}, ui, { - context_menu: { - show: true, - position_x: action.position_x, - position_y: action.position_y, - context: action.context, - trigger: action.trigger, - data: action.data - } + context_menu: action.data }); case 'HIDE_CONTEXT_MENU': - return Object.assign({}, ui, { context_menu: { show: false } }); + return Object.assign({}, ui, {context_menu: null}); + + case 'SHOW_TOUCH_CONTEXT_MENU': + return Object.assign({}, ui, { + touch_context_menu: action.data + }); + + case 'HIDE_TOUCH_CONTEXT_MENU': + return Object.assign({}, ui, {touch_context_menu: null}); diff --git a/src/js/views/App.js b/src/js/views/App.js index 76605906..44f9b279 100755 --- a/src/js/views/App.js +++ b/src/js/views/App.js @@ -9,11 +9,13 @@ import Sidebar from '../components/Sidebar' import MiniPlayer from '../components/MiniPlayer' import SidebarToggleButton from '../components/SidebarToggleButton' import ContextMenu from '../components/ContextMenu' +import TouchContextMenu from '../components/TouchContextMenu' import Dragger from '../components/Dragger' import Modal from '../components/Modal/Modal' import Notifications from '../components/Notifications' import DebugInfo from '../components/DebugInfo' +import * as helpers from '../helpers' import * as uiActions from '../services/ui/actions' import * as pusherActions from '../services/pusher/actions' import * as mopidyActions from '../services/mopidy/actions' @@ -117,10 +119,10 @@ class App extends React.Component{ render(){ var className = ''; - if( this.props.dragger && this.props.dragger.active ) className += ' dragging' - if( this.props.sidebar_open ) className += ' sidebar-open' - if( this.props.modal ) className += ' modal-open' - if( 'ontouchstart' in document.documentElement ) className += ' has-touch-events' + if (this.props.dragger && this.props.dragger.active) className += ' dragging' + if (this.props.sidebar_open) className += ' sidebar-open' + if (this.props.modal) className += ' modal-open' + if (helpers.isTouchDevice() || this.props.emulate_touch) className += ' can-touch' return (
@@ -129,7 +131,8 @@ class App extends React.Component{
{this.props.children}
- + + diff --git a/src/js/views/Artist.js b/src/js/views/Artist.js index 6c2ab45f..259276d1 100755 --- a/src/js/views/Artist.js +++ b/src/js/views/Artist.js @@ -151,7 +151,7 @@ class Artist extends React.Component{
0 ? "col w70" : "col w100"}>

Top tracks

- { this.props.artist.tracks ? : null } + { this.props.artist.tracks ? : null }
diff --git a/src/js/views/Playlist.js b/src/js/views/Playlist.js index 91228e00..174528b2 100755 --- a/src/js/views/Playlist.js +++ b/src/js/views/Playlist.js @@ -115,8 +115,11 @@ class Playlist extends React.Component{ } render(){ - if( !this.props.playlist || !this.props.playlist.name ) return null; - var scheme = helpers.uriSource( this.props.playlist.uri ); + if( !this.props.playlist || !this.props.playlist.name ) return null + var scheme = helpers.uriSource( this.props.playlist.uri ) + + var context = 'playlist-track' + if (this.props.playlist.can_edit) context = 'editable-playlist-track' return (
@@ -153,7 +156,7 @@ class Playlist extends React.Component{
- { this.props.playlist.tracks ? this.removeTracks(tracks_indexes) } reorderTracks={ (indexes, index) => this.reorderTracks(indexes, index) } /> : null } + { this.props.playlist.tracks ? this.removeTracks(tracks_indexes) } reorderTracks={ (indexes, index) => this.reorderTracks(indexes, index) } /> : null } this.loadMore() }/>
diff --git a/src/js/views/Queue.js b/src/js/views/Queue.js index 7c7197d5..e454c259 100755 --- a/src/js/views/Queue.js +++ b/src/js/views/Queue.js @@ -98,7 +98,7 @@ class Queue extends React.Component{ this.removeTracks( tracks ) } playTracks={ tracks => this.playTracks( tracks ) } diff --git a/src/scss/components/_context-menu.scss b/src/scss/components/_context-menu.scss index c3bed4a3..1b634625 100755 --- a/src/scss/components/_context-menu.scss +++ b/src/scss/components/_context-menu.scss @@ -99,11 +99,6 @@ } } - &.cancel { - float: right; - opacity: 0.5; - } - .menu-item { border-top: 1px solid lighten($dark_grey, 8%); } @@ -117,57 +112,61 @@ } } } +} - &.touch{ - - bottom: 0 !important; - left: 0 !important; - right: 0 !important; - top: auto !important; - background: $blue; +.touch-context-menu { + position: fixed; + z-index: 99; + bottom: 0; + left: 0; + right: 0; + top: auto; + background: $blue; + color: #FFFFFF; - .menu-item-wrapper { + .menu-item-wrapper { + display: inline-block; + + .menu-item { display: inline-block; + text-align: center; + width: 50px; + height: 44px; + padding-top: 6px; + border: 0; + vertical-align: top; - .menu-item { - display: inline-block; - text-align: center; - width: 56px; - height: 56px; - border: 0 !important; - vertical-align: top; - - &:hover { - background: inherit; - } - - &:active, - &:focus { - background: rgba(255,255,255,0.2); - } - - .icon { - padding: 5px; - display: inline-block; - } - - .label { - display: block; - font-size: 10px; - } - - .submenu-icon { - display: none !important; - } + &:hover { + background: inherit; } - .submenu { + &:active, + &:focus { + background: rgba(255,255,255,0.2); + } + + .icon { + padding: 5px; + display: inline-block; + } + + .label { + display: block; + font-size: 10px; + } + + .submenu-icon { display: none !important; } + } - &:not(:first-child) .menu-item { - border-left: 1px solid rgba(255,255,255,0.2) !important; - } + &.cancel { + float: right; + opacity: 0.5; + } + + &:not(:first-child) .menu-item { + border-left: 1px solid rgba(255,255,255,0.2) !important; } } } \ No newline at end of file