diff --git a/src/js/components/Dropzone.js b/src/js/components/Dropzone.js
new file mode 100755
index 00000000..eb5e625e
--- /dev/null
+++ b/src/js/components/Dropzone.js
@@ -0,0 +1,30 @@
+
+import React, { PropTypes } from 'react'
+import { connect } from 'react-redux'
+import { bindActionCreators } from 'redux'
+
+import Icon from './Icon'
+
+export default class Dropzone extends React.Component{
+
+ constructor(props) {
+ super(props);
+ }
+
+ handleMouseUp(e){
+ // TODO: sanity check if this is a valid dropzone
+
+ return this.props.handleMouseUp(e)
+ }
+
+ render(){
+ if( !this.props.data ) return null
+
+ return (
+
this.handleMouseUp(e) }>
+
+ { this.props.data.title }
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/js/components/Dropzones.js b/src/js/components/Dropzones.js
new file mode 100755
index 00000000..e2819048
--- /dev/null
+++ b/src/js/components/Dropzones.js
@@ -0,0 +1,89 @@
+
+import React, { PropTypes } from 'react'
+import { connect } from 'react-redux'
+import { bindActionCreators } from 'redux'
+
+import Dropzone from './Dropzone'
+
+import * as uiActions from '../services/ui/actions'
+import * as mopidyActions from '../services/mopidy/actions'
+import * as spotifyActions from '../services/spotify/actions'
+
+class Dropzones extends React.Component{
+
+ constructor(props) {
+ super(props);
+
+ this._zones = [
+ {
+ title: 'Add to queue',
+ icon: 'play',
+ action: 'enqueue'
+ },
+ {
+ title: 'Add to library',
+ icon: 'music',
+ action: 'add_to_library',
+ accepts: ['track','album','playlist','artist']
+ },
+ {
+ title: 'Add to playlist',
+ icon: 'playlist',
+ action: 'add_to_playlist',
+ accepts: ['tltrack','track','album','playlist','artist']
+ }
+ ]
+ }
+
+ handleMouseMove(e){
+ if( !this.props.dragger || !this.props.dragger.dragging ) return null;
+ this.props.uiActions.dragMove( e )
+ }
+
+ handleMouseUp(e, index){
+ var target = this._zones[index]
+ var victims = this.props.dragger.victims
+ var uris = []
+ for( var i = 0; i < victims.length; i++ ){
+ uris.push( victims[i].uri )
+ }
+
+ switch( target.action ){
+
+ case 'enqueue':
+ this.props.mopidyActions.enqueueTracks( uris )
+ break
+
+ }
+ }
+
+ render(){
+ if( !this.props.dragger || !this.props.dragger.dragging ) return null
+
+ return (
+
+ {
+ this._zones.map( (zone, index) => {
+ return this.handleMouseUp(e, index) }/>
+ })
+ }
+
+ );
+ }
+}
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ dragger: state.ui.dragger
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ uiActions: bindActionCreators(uiActions, dispatch),
+ spotifyActions: bindActionCreators(spotifyActions, dispatch),
+ mopidyActions: bindActionCreators(mopidyActions, dispatch)
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Dropzones)
\ No newline at end of file
diff --git a/src/js/components/Sidebar.js b/src/js/components/Sidebar.js
index e564e7d2..d4836fc1 100755
--- a/src/js/components/Sidebar.js
+++ b/src/js/components/Sidebar.js
@@ -8,6 +8,7 @@ import MiniPlayer from './MiniPlayer'
import Icon from './Icon'
import Thumbnail from './Thumbnail'
import SearchForm from './SearchForm'
+import Dropzones from './Dropzones'
import FontAwesome from 'react-fontawesome'
import * as mopidyActions from '../services/mopidy/actions'
@@ -18,18 +19,6 @@ class Sidebar extends React.Component{
super(props);
}
- renderDropzones(){
- if( !this.props.dragger || !this.props.dragger.dragging ) return null
-
- return (
-
- )
- }
-
render(){
return (
diff --git a/src/js/services/ui/actions.js b/src/js/services/ui/actions.js
index 1ebf1aa7..475c6137 100755
--- a/src/js/services/ui/actions.js
+++ b/src/js/services/ui/actions.js
@@ -57,6 +57,10 @@ export function dragMove( e ){
}
}
+export function dragCancel(){
+ return { type: 'DRAG_CANCEL' }
+}
+
export function dragEnd(){
return { type: 'DRAG_END' }
}
diff --git a/src/js/services/ui/reducer.js b/src/js/services/ui/reducer.js
index ff6012bd..d1cf3711 100755
--- a/src/js/services/ui/reducer.js
+++ b/src/js/services/ui/reducer.js
@@ -55,6 +55,15 @@ export default function reducer(ui = {}, action){
}
});
+ case 'DRAG_CANCEL':
+ return Object.assign({}, ui, {
+ dragger: {
+ dragging: false,
+ context: false,
+ victims: false
+ }
+ });
+
/**
diff --git a/src/js/views/App.js b/src/js/views/App.js
index 998e3d0a..af2ab08b 100755
--- a/src/js/views/App.js
+++ b/src/js/views/App.js
@@ -63,7 +63,7 @@ class App extends React.Component{
shouldTriggerShortcut(e){
var ignoreNodes = ['INPUT', 'TEXTAREA'];
- var keyCodes = [32];
+ var keyCodes = [32,27];
if( ignoreNodes.indexOf(e.target.nodeName) > -1 ){
return false;
@@ -82,13 +82,20 @@ class App extends React.Component{
handleKeyUp(e){
if( !this.shouldTriggerShortcut(e) ) return;
- switch(e.keyCode){
+ switch(e.keyCode){
+
case 32: // spacebar
if( this.props.play_state == 'playing' ){
this.props.mopidyActions.pause();
}else{
this.props.mopidyActions.play();
}
+ break;
+
+ case 27: // esc
+ if( this.props.dragger.dragging ){
+ this.props.uiActions.dragCancel();
+ }
break;
}
}
diff --git a/src/scss/app.scss b/src/scss/app.scss
index 935ea3ca..5c8f387b 100755
--- a/src/scss/app.scss
+++ b/src/scss/app.scss
@@ -1,5 +1,4 @@
-
@import 'vendor/font-awesome/index';
@import 'global/variables';
diff --git a/src/scss/components/_dragger.scss b/src/scss/components/_dragger.scss
index ea398ffe..d3b075ee 100755
--- a/src/scss/components/_dragger.scss
+++ b/src/scss/components/_dragger.scss
@@ -1,4 +1,10 @@
+.dragging * {
+ cursor: grabbing !important;
+ cursor: -moz-grabbing !important;
+ cursor: -webkit-grabbing !important;
+}
+
.dragger {
position: fixed;
left: 0;
@@ -19,12 +25,23 @@
z-index: 9998;
background: rgba(25, 25, 25, 0.9);
- .zone {
+ .dropzone {
+ @include animate();
margin: 15px;
- padding: 50px 20px;
- background: $turquoise;
+ padding: 40px 20px;
+ background: $dark_grey;
color: #FFFFFF;
- font-size: 16px;
+ font-size: 18px;
text-align: center;
+
+ .icon {
+ display: block;
+ margin: 0 auto 10px;
+ width: 32px;
+ }
+
+ &:hover {
+ background: $turquoise;
+ }
}
}
\ No newline at end of file