Revamp context menu; Specialist touch menu
This commit is contained in:
3
src/js/bootstrap.js
vendored
3
src/js/bootstrap.js
vendored
@ -54,9 +54,6 @@ var initialState = {
|
||||
me: false
|
||||
},
|
||||
ui: {
|
||||
context_menu: {
|
||||
show: false
|
||||
},
|
||||
current_tracklist: [],
|
||||
current_tltrack: false,
|
||||
notifications: []
|
||||
|
||||
@ -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 = $("<input>");
|
||||
$("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{
|
||||
<div>
|
||||
{
|
||||
items.map((item, index) => {
|
||||
if( item.playlists ){
|
||||
if (item.handleClick == 'addToPlaylist'){
|
||||
return (
|
||||
<span key={item.handleClick} className="menu-item-wrapper has-submenu">
|
||||
<a className="menu-item" onClick={e => this[item.handleClick](e)}>
|
||||
<FontAwesome className="icon" fixedWidth name={item.icon} />
|
||||
<span className="label">{ item.label }</span>
|
||||
<FontAwesome className="submenu-icon" name="caret-right" />
|
||||
</a>
|
||||
@ -236,7 +231,6 @@ class ContextMenu extends React.Component{
|
||||
return (
|
||||
<span className="menu-item-wrapper" key={item.handleClick}>
|
||||
<a className="menu-item" onClick={e => this[item.handleClick](e)}>
|
||||
<FontAwesome className="icon" fixedWidth name={item.icon} />
|
||||
<span className="label">{ item.label }</span>
|
||||
</a>
|
||||
</span>
|
||||
@ -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 (
|
||||
<div className={className} style={style}>
|
||||
{this.props.context_menu.data.item ? this.renderTitle() : null}
|
||||
{this.renderItems(trigger)}
|
||||
<div className="context-menu" style={style}>
|
||||
{this.props.menu.item ? this.renderTitle() : null}
|
||||
{this.renderItems()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
<div className="debug-info">
|
||||
<div className="item">
|
||||
@ -28,7 +36,7 @@ class DebugInfo extends React.Component{
|
||||
{this.props.ui.users ? Object.keys(this.props.ui.users).length : '0'} users
|
||||
</div>
|
||||
<div className="item">
|
||||
{'ontouchstart' in document.documentElement ? 'can touch' : 'no touch'}
|
||||
{touch_state}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
164
src/js/components/TouchContextMenu.js
Executable file
164
src/js/components/TouchContextMenu.js
Executable 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)
|
||||
@ -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 ){
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
|
||||
|
||||
export let isTouchDevice = function(){
|
||||
return 'ontouchstart' in document.documentElement
|
||||
}
|
||||
|
||||
export let sizedImages = function( images ){
|
||||
|
||||
var sizes = {
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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});
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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 (
|
||||
<div className={className}>
|
||||
@ -129,7 +131,8 @@ class App extends React.Component{
|
||||
<main>
|
||||
{this.props.children}
|
||||
</main>
|
||||
<ContextMenu state={this.props.context_menu} />
|
||||
<ContextMenu />
|
||||
<TouchContextMenu />
|
||||
<Dragger />
|
||||
<Modal />
|
||||
<Notifications />
|
||||
|
||||
@ -151,7 +151,7 @@ class Artist extends React.Component{
|
||||
<div className="body overview">
|
||||
<div className={related_artists.length > 0 ? "col w70" : "col w100"}>
|
||||
<h4 className="left-padding">Top tracks</h4>
|
||||
{ this.props.artist.tracks ? <TrackList tracks={ this.props.artist.tracks } /> : null }
|
||||
{ this.props.artist.tracks ? <TrackList tracks={this.props.artist.tracks} /> : null }
|
||||
</div>
|
||||
|
||||
<div className="col w5"></div>
|
||||
|
||||
@ -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 (
|
||||
<div className="view playlist-view">
|
||||
@ -153,7 +156,7 @@ class Playlist extends React.Component{
|
||||
</div>
|
||||
|
||||
<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() }/>
|
||||
</section>
|
||||
|
||||
|
||||
@ -98,7 +98,7 @@ class Queue extends React.Component{
|
||||
|
||||
<TrackList
|
||||
show_source_icon={true}
|
||||
context="queue"
|
||||
context="queue-track"
|
||||
tracks={this.props.current_tracklist}
|
||||
removeTracks={ tracks => this.removeTracks( tracks ) }
|
||||
playTracks={ tracks => this.playTracks( tracks ) }
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user