Revamp context menu; Specialist touch menu

This commit is contained in:
James Barnsley
2017-01-29 16:25:05 +13:00
parent 138f2f3f08
commit c108e833dd
14 changed files with 379 additions and 186 deletions

3
src/js/bootstrap.js vendored
View File

@ -54,9 +54,6 @@ var initialState = {
me: false me: false
}, },
ui: { ui: {
context_menu: {
show: false
},
current_tracklist: [], current_tracklist: [],
current_tltrack: false, current_tltrack: false,
notifications: [] notifications: []

View File

@ -31,25 +31,21 @@ class ContextMenu extends React.Component{
} }
handleScroll(){ handleScroll(){
if( this.props.context_menu.show && this.props.context_menu.trigger == 'click' ){ if (this.props.menu) this.props.uiActions.hideContextMenu()
this.props.uiActions.hideContextMenu();
}
} }
handleClick(){ handleClick(){
if( this.props.context_menu.show && this.props.context_menu.trigger == 'click' ){ if (this.props.menu) this.props.uiActions.hideContextMenu()
this.props.uiActions.hideContextMenu();
}
} }
playQueueItem(){ playQueueItem(){
var tracks = this.props.context_menu.data.items; var tracks = this.props.menu.items;
this.props.mopidyActions.changeTrack( tracks[0].tlid ); this.props.mopidyActions.changeTrack( tracks[0].tlid );
this.props.uiActions.hideContextMenu(); this.props.uiActions.hideContextMenu();
} }
removeFromQueue(){ removeFromQueue(){
var tracks = this.props.context_menu.data.items; var tracks = this.props.menu.items;
var tracks_tlids = []; var tracks_tlids = [];
for( var i = 0; i < tracks.length; i++ ){ for( var i = 0; i < tracks.length; i++ ){
tracks_tlids.push( tracks[i].tlid ); tracks_tlids.push( tracks[i].tlid );
@ -59,48 +55,48 @@ class ContextMenu extends React.Component{
} }
playURIs(){ playURIs(){
this.props.mopidyActions.playURIs(this.props.context_menu.data.uris); this.props.mopidyActions.playURIs(this.props.menu.uris);
this.props.uiActions.hideContextMenu(); this.props.uiActions.hideContextMenu();
} }
playURIsNext(){ playURIsNext(){
this.props.mopidyActions.enqueueURIsNext(this.props.context_menu.data.uris); this.props.mopidyActions.enqueueURIsNext(this.props.menu.uris);
this.props.uiActions.hideContextMenu(); this.props.uiActions.hideContextMenu();
} }
addToPlaylist(){ 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(); this.props.uiActions.hideContextMenu();
} }
addToQueue(){ addToQueue(){
this.props.mopidyActions.enqueueURIs(this.props.context_menu.data.uris) this.props.mopidyActions.enqueueURIs(this.props.menu.uris)
this.props.uiActions.hideContextMenu() this.props.uiActions.hideContextMenu()
} }
addTracksToPlaylist(playlist_uri){ addTracksToPlaylist(playlist_uri){
this.props.uiActions.addTracksToPlaylist( playlist_uri, this.props.context_menu.data.uris ) this.props.uiActions.addTracksToPlaylist(playlist_uri, this.props.menu.uris)
this.props.uiActions.hideContextMenu(); this.props.uiActions.hideContextMenu();
} }
removeFromPlaylist(){ 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(); this.props.uiActions.hideContextMenu();
} }
startRadio(){ startRadio(){
this.props.pusherActions.startRadio(this.props.context_menu.data.uris) this.props.pusherActions.startRadio(this.props.menu.uris)
this.props.uiActions.hideContextMenu(); this.props.uiActions.hideContextMenu();
} }
copyURIs(e){ copyURIs(e){
var temp = $("<input>"); var temp = $("<input>");
$("body").append(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"); document.execCommand("copy");
temp.remove(); 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() this.props.uiActions.hideContextMenu()
} }
@ -134,63 +130,63 @@ class ContextMenu extends React.Component{
} }
getItems(){ getItems(){
switch (this.props.context_menu.context) { switch (this.props.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
case 'album': case 'album':
var items = [ var items = [
{ handleClick: 'playURIs', label: 'Play', icon: 'play' }, { handleClick: 'playURIs', label: 'Play' },
{ handleClick: 'playURIsNext', label: 'Play next', icon: 'play' }, { handleClick: 'playURIsNext', label: 'Play next' },
{ handleClick: 'addToQueue', label: 'Add to queue', icon: 'plus' }, { handleClick: 'addToQueue', label: 'Add to queue' },
{ handleClick: 'startRadio', label: 'Start radio', icon: 'spotify' }, { handleClick: 'startRadio', label: 'Start radio' },
{ handleClick: 'copyURIs', label: 'Copy URI', icon: 'copy' } { handleClick: 'copyURIs', label: 'Copy URI' }
] ]
break break
case 'artist': case 'artist':
var items = [ var items = [
{ handleClick: 'startRadio', label: 'Start radio', icon: 'spotify' }, { handleClick: 'startRadio', label: 'Start radio' },
{ handleClick: 'toggleFollow', label: 'Follow/unfollow', icon: 'toggle-on' }, { handleClick: 'toggleFollow', label: 'Follow/unfollow' },
{ handleClick: 'copyURIs', label: 'Copy URI', icon: 'copy' } { handleClick: 'copyURIs', label: 'Copy URI' }
] ]
break break
case 'playlist': case 'playlist':
var items = [ var items = [
{ handleClick: 'playURIs', label: 'Play', icon: 'play' }, { handleClick: 'playURIs', label: 'Play' },
{ handleClick: 'toggleFollow', label: 'Follow/unfollow', icon: 'toggle-on' }, { handleClick: 'toggleFollow', label: 'Follow/unfollow' },
{ handleClick: 'copyURIs', label: 'Copy URI', icon: 'copy' } { 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 break
case 'editable-playlist-track': case 'editable-playlist-track':
var items = [ var items = [
{ handleClick: 'playURIs', label: 'Play', icon: 'play' }, { handleClick: 'playURIs', label: 'Play' },
{ handleClick: 'playURIsNext', label: 'Play next', icon: 'play' }, { handleClick: 'playURIsNext', label: 'Play next' },
{ handleClick: 'addToQueue', label: 'Add to queue', icon: 'plus' }, { handleClick: 'addToQueue', label: 'Add to queue' },
{ handleClick: 'addToPlaylist', label: 'Add to playlist', icon: 'plus', playlists: true }, { handleClick: 'addToPlaylist', label: 'Add to playlist' },
{ handleClick: 'startRadio', label: 'Start radio', icon: 'spotify' }, { handleClick: 'startRadio', label: 'Start radio' },
{ handleClick: 'copyURIs', label: 'Copy URIs', icon: 'copy' }, { handleClick: 'copyURIs', label: 'Copy URIs' },
{ handleClick: 'removeFromPlaylist', label: 'Remove', icon: 'trash' } { handleClick: 'removeFromPlaylist', label: 'Remove' }
] ]
break break
default: default:
var items = [ var items = [
{ handleClick: 'playURIs', label: 'Play', icon: 'play' }, { handleClick: 'playURIs', label: 'Play' },
{ handleClick: 'playURIsNext', label: 'Play next', icon: 'play' }, { handleClick: 'playURIsNext', label: 'Play next' },
{ handleClick: 'addToQueue', label: 'Add to queue', icon: 'plus' }, { handleClick: 'addToQueue', label: 'Add to queue' },
{ handleClick: 'addToPlaylist', label: 'Add to playlist', icon: 'plus', playlists: true }, { handleClick: 'addToPlaylist', label: 'Add to playlist' },
{ handleClick: 'startRadio', label: 'Start radio', icon: 'spotify' }, { handleClick: 'startRadio', label: 'Start radio' },
{ handleClick: 'copyURIs', label: 'Copy URIs', icon: 'copy' } { handleClick: 'copyURIs', label: 'Copy URIs' }
] ]
break break
} }
@ -199,7 +195,7 @@ class ContextMenu extends React.Component{
} }
renderTitle(){ renderTitle(){
var item = this.props.context_menu.data.item var item = this.props.menu.item
var style = null var style = null
if (item && item.images){ if (item && item.images){
style = { style = {
@ -221,11 +217,10 @@ class ContextMenu extends React.Component{
<div> <div>
{ {
items.map((item, index) => { items.map((item, index) => {
if( item.playlists ){ if (item.handleClick == 'addToPlaylist'){
return ( return (
<span key={item.handleClick} className="menu-item-wrapper has-submenu"> <span key={item.handleClick} className="menu-item-wrapper has-submenu">
<a className="menu-item" onClick={e => this[item.handleClick](e)}> <a className="menu-item" onClick={e => this[item.handleClick](e)}>
<FontAwesome className="icon" fixedWidth name={item.icon} />
<span className="label">{ item.label }</span> <span className="label">{ item.label }</span>
<FontAwesome className="submenu-icon" name="caret-right" /> <FontAwesome className="submenu-icon" name="caret-right" />
</a> </a>
@ -236,7 +231,6 @@ class ContextMenu extends React.Component{
return ( return (
<span className="menu-item-wrapper" key={item.handleClick}> <span className="menu-item-wrapper" key={item.handleClick}>
<a className="menu-item" onClick={e => this[item.handleClick](e)}> <a className="menu-item" onClick={e => this[item.handleClick](e)}>
<FontAwesome className="icon" fixedWidth name={item.icon} />
<span className="label">{ item.label }</span> <span className="label">{ item.label }</span>
</a> </a>
</span> </span>
@ -249,37 +243,28 @@ class ContextMenu extends React.Component{
} }
render(){ render(){
if( !this.props.context_menu.show ) return null; if (!this.props.menu) return null;
var style = { var style = {
left: this.props.context_menu.position_x, left: this.props.menu.position_x,
top: this.props.context_menu.position_y, top: this.props.menu.position_y,
} }
var className = 'context-menu' var items = this.getItems()
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 height = 0 var height = 0
if (items) height = items.length * 34 // this is an approximation of how tall each menu item is 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.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.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_y > (window.innerHeight - height)){
style.top = style.top - height style.top = style.top - height
className += ' bottom-align-submenu' className += ' bottom-align-submenu'
} }
return ( return (
<div className={className} style={style}> <div className="context-menu" style={style}>
{this.props.context_menu.data.item ? this.renderTitle() : null} {this.props.menu.item ? this.renderTitle() : null}
{this.renderItems(trigger)} {this.renderItems()}
</div> </div>
); );
} }
@ -287,11 +272,9 @@ class ContextMenu extends React.Component{
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state, ownProps) => {
return { return {
emulate_touch: state.ui.emulate_touch, menu: state.ui.context_menu,
context_menu: state.ui.context_menu,
current_track: state.ui.current_track, current_track: state.ui.current_track,
current_tracklist: state.ui.current_tracklist, current_tracklist: state.ui.current_tracklist,
playlist: state.ui.playlist,
playlists: state.ui.playlists playlists: state.ui.playlists
} }
} }

View File

@ -4,6 +4,7 @@ import { connect } from 'react-redux'
import { Link } from 'react-router' import { Link } from 'react-router'
import { createStore, bindActionCreators } from 'redux' import { createStore, bindActionCreators } from 'redux'
import * as helpers from '../helpers'
import * as uiActions from '../services/ui/actions' import * as uiActions from '../services/ui/actions'
class DebugInfo extends React.Component{ class DebugInfo extends React.Component{
@ -13,6 +14,13 @@ class DebugInfo extends React.Component{
} }
render(){ 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 ( return (
<div className="debug-info"> <div className="debug-info">
<div className="item"> <div className="item">
@ -28,7 +36,7 @@ class DebugInfo extends React.Component{
{this.props.ui.users ? Object.keys(this.props.ui.users).length : '0'}&nbsp;users {this.props.ui.users ? Object.keys(this.props.ui.users).length : '0'}&nbsp;users
</div> </div>
<div className="item"> <div className="item">
{'ontouchstart' in document.documentElement ? 'can touch' : 'no touch'} {touch_state}
</div> </div>
</div> </div>
); );

View File

@ -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 (
<div>
{
items.map((item, index) => {
return (
<span className="menu-item-wrapper" key={item.handleClick}>
<a className="menu-item" onClick={e => this[item.handleClick]()}>
<FontAwesome className="icon" fixedWidth name={item.icon} />
<span className="label">{ item.label }</span>
</a>
</span>
)
})
}
<span className="menu-item-wrapper cancel" key="cancel">
<a className="menu-item" onClick={e => this.close()}>
<FontAwesome className="icon" fixedWidth name="close" />
<span className="label">Cancel</span>
</a>
</span>
</div>
)
}
render(){
if( !this.props.menu ) return null;
return (
<div className="touch-context-menu">
{this.renderItems()}
</div>
);
}
}
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)

View File

@ -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){ handleTouchEnd(e){
var target = $(e.target); var target = $(e.target);
if( !target.is('a') && target.closest('a').length <= 0 ){ if( !target.is('a') && target.closest('a').length <= 0 ){

View File

@ -69,43 +69,28 @@ class TrackList extends React.Component{
this._touch_x > ( pageX - this._touch_threshold ) && this._touch_x > ( pageX - this._touch_threshold ) &&
this._touch_y < ( pageY + this._touch_threshold ) && this._touch_y < ( pageY + this._touch_threshold ) &&
this._touch_y > ( pageY - this._touch_threshold ) ){ this._touch_y > ( pageY - this._touch_threshold ) ){
this.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 = {
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()
}
} }
e.preventDefault() e.preventDefault()
} }
handleDoubleClick(e,index){ handleDoubleClick(e,index){
if( this.props.context_menu.show ) this.props.uiActions.hideContextMenu() if (this.props.context_menu) this.props.uiActions.hideContextMenu()
this.playTracks() this.playTracks()
} }
handleMouseDown(e,index){ handleMouseDown(e,index){
if (this.props.emulate_touch){
if( this.props.context_menu.show ) this.props.uiActions.hideContextMenu() 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.state.tracks[index].selected && !this.isRightClick(e) && !e.ctrlKey) this.toggleTrackSelections(e, index)
var selected_tracks = this.selectedTracks() var selected_tracks = this.selectedTracks()
this.props.uiActions.dragStart( e, this.props.context, selected_tracks, this.tracksIndexes(selected_tracks) ) this.props.uiActions.dragStart( e, this.props.context, selected_tracks, this.tracksIndexes(selected_tracks) )
} }
}
handleMouseUp(e,index){ handleMouseUp(e,index){
@ -135,11 +120,37 @@ class TrackList extends React.Component{
handleContextMenu(e,index){ handleContextMenu(e,index){
var selected_tracks = this.selectedTracks() var selected_tracks = this.selectedTracks()
var data = { 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, items: selected_tracks,
uris: helpers.asURIs(selected_tracks), uris: helpers.asURIs(selected_tracks),
indexes: this.tracksIndexes(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){ toggleTrackSelections(e, index){
@ -285,6 +296,7 @@ class TrackList extends React.Component{
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state, ownProps) => {
return { return {
dragger: state.ui.dragger, dragger: state.ui.dragger,
emulate_touch: state.ui.emulate_touch,
current_track: state.ui.current_track, current_track: state.ui.current_track,
context_menu: state.ui.context_menu context_menu: state.ui.context_menu
} }

View File

@ -1,5 +1,9 @@
export let isTouchDevice = function(){
return 'ontouchstart' in document.documentElement
}
export let sizedImages = function( images ){ export let sizedImages = function( images ){
var sizes = { var sizes = {

View File

@ -1,13 +1,11 @@
import * as helpers from '../../helpers' 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 { return {
type: 'SHOW_CONTEXT_MENU', type: 'SHOW_CONTEXT_MENU',
position_x: e.clientX,
position_y: e.clientY,
context: context,
trigger: trigger,
data: data 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 ){ export function startSearch( query ){
return { return {
type: 'SEARCH_STARTED', type: 'SEARCH_STARTED',

View File

@ -22,20 +22,22 @@ export default function reducer(ui = {}, action){
/** /**
* Context menu * Context menu
**/ **/
case 'SHOW_CONTEXT_MENU': case 'SHOW_CONTEXT_MENU':
return Object.assign({}, ui, { return Object.assign({}, ui, {
context_menu: { context_menu: action.data
show: true,
position_x: action.position_x,
position_y: action.position_y,
context: action.context,
trigger: action.trigger,
data: action.data
}
}); });
case 'HIDE_CONTEXT_MENU': 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});

View File

@ -9,11 +9,13 @@ import Sidebar from '../components/Sidebar'
import MiniPlayer from '../components/MiniPlayer' import MiniPlayer from '../components/MiniPlayer'
import SidebarToggleButton from '../components/SidebarToggleButton' import SidebarToggleButton from '../components/SidebarToggleButton'
import ContextMenu from '../components/ContextMenu' import ContextMenu from '../components/ContextMenu'
import TouchContextMenu from '../components/TouchContextMenu'
import Dragger from '../components/Dragger' import Dragger from '../components/Dragger'
import Modal from '../components/Modal/Modal' import Modal from '../components/Modal/Modal'
import Notifications from '../components/Notifications' import Notifications from '../components/Notifications'
import DebugInfo from '../components/DebugInfo' import DebugInfo from '../components/DebugInfo'
import * as helpers from '../helpers'
import * as uiActions from '../services/ui/actions' import * as uiActions from '../services/ui/actions'
import * as pusherActions from '../services/pusher/actions' import * as pusherActions from '../services/pusher/actions'
import * as mopidyActions from '../services/mopidy/actions' import * as mopidyActions from '../services/mopidy/actions'
@ -120,7 +122,7 @@ class App extends React.Component{
if (this.props.dragger && this.props.dragger.active) className += ' dragging' if (this.props.dragger && this.props.dragger.active) className += ' dragging'
if (this.props.sidebar_open) className += ' sidebar-open' if (this.props.sidebar_open) className += ' sidebar-open'
if (this.props.modal) className += ' modal-open' if (this.props.modal) className += ' modal-open'
if( 'ontouchstart' in document.documentElement ) className += ' has-touch-events' if (helpers.isTouchDevice() || this.props.emulate_touch) className += ' can-touch'
return ( return (
<div className={className}> <div className={className}>
@ -129,7 +131,8 @@ class App extends React.Component{
<main> <main>
{this.props.children} {this.props.children}
</main> </main>
<ContextMenu state={this.props.context_menu} /> <ContextMenu />
<TouchContextMenu />
<Dragger /> <Dragger />
<Modal /> <Modal />
<Notifications /> <Notifications />

View File

@ -115,8 +115,11 @@ class Playlist extends React.Component{
} }
render(){ render(){
if( !this.props.playlist || !this.props.playlist.name ) return null; if( !this.props.playlist || !this.props.playlist.name ) return null
var scheme = helpers.uriSource( this.props.playlist.uri ); var scheme = helpers.uriSource( this.props.playlist.uri )
var context = 'playlist-track'
if (this.props.playlist.can_edit) context = 'editable-playlist-track'
return ( return (
<div className="view playlist-view"> <div className="view playlist-view">
@ -153,7 +156,7 @@ class Playlist extends React.Component{
</div> </div>
<section className="list-wrapper"> <section className="list-wrapper">
{ this.props.playlist.tracks ? <TrackList context={ this.props.playlist.can_edit ? 'editable-playlist-track' : 'playlist-track'} tracks={ this.props.playlist.tracks } removeTracks={ tracks_indexes => this.removeTracks(tracks_indexes) } reorderTracks={ (indexes, index) => this.reorderTracks(indexes, index) } /> : null } { this.props.playlist.tracks ? <TrackList tracklist_uri={this.props.params.uri} context={context} tracks={this.props.playlist.tracks} removeTracks={ tracks_indexes => this.removeTracks(tracks_indexes) } reorderTracks={ (indexes, index) => this.reorderTracks(indexes, index) } /> : null }
<LazyLoadListener enabled={this.props.playlist.tracks_more} loadMore={ () => this.loadMore() }/> <LazyLoadListener enabled={this.props.playlist.tracks_more} loadMore={ () => this.loadMore() }/>
</section> </section>

View File

@ -98,7 +98,7 @@ class Queue extends React.Component{
<TrackList <TrackList
show_source_icon={true} show_source_icon={true}
context="queue" context="queue-track"
tracks={this.props.current_tracklist} tracks={this.props.current_tracklist}
removeTracks={ tracks => this.removeTracks( tracks ) } removeTracks={ tracks => this.removeTracks( tracks ) }
playTracks={ tracks => this.playTracks( tracks ) } playTracks={ tracks => this.playTracks( tracks ) }

View File

@ -99,11 +99,6 @@
} }
} }
&.cancel {
float: right;
opacity: 0.5;
}
.menu-item { .menu-item {
border-top: 1px solid lighten($dark_grey, 8%); border-top: 1px solid lighten($dark_grey, 8%);
} }
@ -117,14 +112,17 @@
} }
} }
} }
}
&.touch{ .touch-context-menu {
position: fixed;
bottom: 0 !important; z-index: 99;
left: 0 !important; bottom: 0;
right: 0 !important; left: 0;
top: auto !important; right: 0;
top: auto;
background: $blue; background: $blue;
color: #FFFFFF;
.menu-item-wrapper { .menu-item-wrapper {
display: inline-block; display: inline-block;
@ -132,9 +130,10 @@
.menu-item { .menu-item {
display: inline-block; display: inline-block;
text-align: center; text-align: center;
width: 56px; width: 50px;
height: 56px; height: 44px;
border: 0 !important; padding-top: 6px;
border: 0;
vertical-align: top; vertical-align: top;
&:hover { &:hover {
@ -161,8 +160,9 @@
} }
} }
.submenu { &.cancel {
display: none !important; float: right;
opacity: 0.5;
} }
&:not(:first-child) .menu-item { &:not(:first-child) .menu-item {
@ -170,4 +170,3 @@
} }
} }
} }
}