diff --git a/src/js/components/ContextMenu.js b/src/js/components/ContextMenu.js
index 24a2562d..3d5d0e08 100755
--- a/src/js/components/ContextMenu.js
+++ b/src/js/components/ContextMenu.js
@@ -14,9 +14,6 @@ class ContextMenu extends React.Component{
super(props);
}
- handleClick(e){
- }
-
renderItems(){
var items = [];
diff --git a/src/js/components/Dragger.js b/src/js/components/Dragger.js
new file mode 100755
index 00000000..23eb4a4f
--- /dev/null
+++ b/src/js/components/Dragger.js
@@ -0,0 +1,68 @@
+
+import React, { PropTypes } from 'react'
+import { connect } from 'react-redux'
+import { bindActionCreators } from 'redux'
+
+import * as uiActions from '../services/ui/actions'
+import * as mopidyActions from '../services/mopidy/actions'
+import * as spotifyActions from '../services/spotify/actions'
+
+class Dragger extends React.Component{
+
+ constructor(props) {
+ super(props);
+ this.handleMouseMove = this.handleMouseMove.bind(this)
+ this.handleMouseUp = this.handleMouseUp.bind(this)
+ }
+
+ componentDidMount(){
+ window.addEventListener("mousemove", this.handleMouseMove, false);
+ window.addEventListener("mouseup", this.handleMouseUp, false);
+ }
+
+ componentWillUnmount(){
+ window.removeEventListener("mousemove", this.handleMouseMove, false);
+ window.removeEventListener("mouseup", this.handleMouseUp, false);
+ }
+
+ handleMouseMove(e){
+ if( !this.props.dragger || !this.props.dragger.dragging ) return null;
+ this.props.uiActions.dragMove( e )
+ }
+
+ handleMouseUp(e){
+ if( !this.props.dragger || !this.props.dragger.dragging ) return null;
+ this.props.uiActions.dragEnd( e )
+ }
+
+ render(){
+ if( !this.props.dragger || !this.props.dragger.dragging ) return null;
+
+ var style = {
+ left: this.props.dragger.position_x,
+ top: this.props.dragger.position_y,
+ }
+
+ return (
+
+ Dragging { this.props.dragger.victims.length } things
+
+ );
+ }
+}
+
+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)(Dragger)
\ No newline at end of file
diff --git a/src/js/components/Sidebar.js b/src/js/components/Sidebar.js
index 7a76449e..e564e7d2 100755
--- a/src/js/components/Sidebar.js
+++ b/src/js/components/Sidebar.js
@@ -18,6 +18,18 @@ class Sidebar extends React.Component{
super(props);
}
+ renderDropzones(){
+ if( !this.props.dragger || !this.props.dragger.dragging ) return null
+
+ return (
+
+ )
+ }
+
render(){
return (
@@ -109,7 +123,8 @@ const mapStateToProps = (state, ownProps) => {
pusher_connected: state.pusher.connected,
spotify_connected: state.spotify.connected,
spotify_authorized: state.spotify.authorized,
- current_track: state.ui.current_track
+ current_track: state.ui.current_track,
+ dragger: state.ui.dragger
}
}
diff --git a/src/js/components/Track.js b/src/js/components/Track.js
index 8e6fe59f..739e5cd4 100755
--- a/src/js/components/Track.js
+++ b/src/js/components/Track.js
@@ -23,6 +23,13 @@ export default class Track extends React.Component{
return this.props.handleDoubleClick(e);
}
+ handleDragStart(e){
+ // if we're not selected, perform click behavior [first] << this is assumed
+ if( !this.props.track.selected ) this.handleClick(e)
+
+ this.props.handleDragStart(e)
+ }
+
handleContextMenu(e){
e.preventDefault();
@@ -44,9 +51,11 @@ export default class Track extends React.Component{
return (
this.handleDoubleClick(e) }
- onClick={ (e) => this.handleClick(e) }
- onContextMenu={ (e) => this.handleContextMenu(e) }>
+ draggable='true'
+ onDragStart={ e => this.handleDragStart(e) }
+ onClick={ e => this.handleClick(e) }
+ onDoubleClick={ e => this.handleDoubleClick(e) }
+ onContextMenu={ e => this.handleContextMenu(e) }>
{ this.props.track.selected ?
: null }
{ track.name ? track.name : '-' }
diff --git a/src/js/components/TrackList.js b/src/js/components/TrackList.js
index ecf8e405..cdf011a8 100755
--- a/src/js/components/TrackList.js
+++ b/src/js/components/TrackList.js
@@ -2,11 +2,12 @@
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
-import * as mopidyActions from '../services/mopidy/actions'
-import * as uiActions from '../services/ui/actions'
import Track from './Track'
+import * as mopidyActions from '../services/mopidy/actions'
+import * as uiActions from '../services/ui/actions'
+
class TrackList extends React.Component{
constructor(props) {
@@ -81,12 +82,16 @@ class TrackList extends React.Component{
this.setState({ tracks: tracks, lastSelectedTrack: index });
}
- handleDoubleClick( e, index ){
+ handleDoubleClick(e, index){
if( this.props.context_menu.show ) this.props.uiActions.hideContextMenu();
this.playTracks()
}
- handleContextMenu( e, index ){
+ handleDragStart(e, index){
+ this.props.uiActions.dragStart( e, 'tltracks', this.selectedTracks() )
+ }
+
+ handleContextMenu(e, index){
var data = {
selected_tracks: this.selectedTracks()
}
@@ -148,9 +153,10 @@ class TrackList extends React.Component{
show_source_icon={ this.props.show_source_icon }
key={index+'_'+track.uri}
track={track}
- handleDoubleClick={(e) => self.handleDoubleClick(e, index)}
- handleClick={(e) => self.handleClick(e, index)}
- handleContextMenu={(e) => self.handleContextMenu(e, index)} />
+ handleDoubleClick={ e => self.handleDoubleClick(e, index)}
+ handleClick={ e => self.handleClick(e, index)}
+ handleDragStart={ e => self.handleDragStart(e, index)}
+ handleContextMenu={ e => self.handleContextMenu(e, index)} />
}
)
}
diff --git a/src/js/services/localstorage/middleware.js b/src/js/services/localstorage/middleware.js
index 71932e75..db626418 100755
--- a/src/js/services/localstorage/middleware.js
+++ b/src/js/services/localstorage/middleware.js
@@ -12,7 +12,7 @@ const localstorageMiddleware = (function(){
// append our state to a global variable. This gives us access to debug the store at any point
window._store = store
- console.log(action)
+ //console.log(action)
switch( action.type ){
diff --git a/src/js/services/ui/actions.js b/src/js/services/ui/actions.js
index 8a2ff1dd..1ebf1aa7 100755
--- a/src/js/services/ui/actions.js
+++ b/src/js/services/ui/actions.js
@@ -39,4 +39,26 @@ export function lazyLoading( start ){
}
}
+export function dragStart( e, context, victims ){
+ return {
+ type: 'DRAG_START',
+ context: context,
+ victims: victims,
+ position_x: e.clientX,
+ position_y: e.clientY
+ }
+}
+
+export function dragMove( e ){
+ return {
+ type: 'DRAG_MOVE',
+ position_x: e.clientX,
+ position_y: e.clientY,
+ }
+}
+
+export function dragEnd(){
+ return { type: 'DRAG_END' }
+}
+
diff --git a/src/js/services/ui/reducer.js b/src/js/services/ui/reducer.js
index 21828e0d..ff6012bd 100755
--- a/src/js/services/ui/reducer.js
+++ b/src/js/services/ui/reducer.js
@@ -23,6 +23,40 @@ export default function reducer(ui = {}, action){
return Object.assign({}, ui, { context_menu: { show: false } });
+
+ /**
+ * Dragging
+ **/
+
+ case 'DRAG_START':
+ return Object.assign({}, ui, {
+ dragger: {
+ dragging: true,
+ context: action.context,
+ victims: action.victims,
+ position_x: action.position_x,
+ position_y: action.position_y
+ }
+ });
+
+ case 'DRAG_MOVE':
+ var dragger = Object.assign({}, ui.dragger, {
+ position_x: action.position_x,
+ position_y: action.position_y
+ })
+ return Object.assign({}, ui, { dragger: dragger });
+
+ case 'DRAG_END':
+ return Object.assign({}, ui, {
+ dragger: {
+ dragging: false,
+ context: false,
+ victims: false
+ }
+ });
+
+
+
/**
* Albums
**/
diff --git a/src/js/views/App.js b/src/js/views/App.js
index 269274cc..998e3d0a 100755
--- a/src/js/views/App.js
+++ b/src/js/views/App.js
@@ -7,6 +7,7 @@ import { connect } from 'react-redux'
import Sidebar from '../components/Sidebar'
import ContextMenu from '../components/ContextMenu'
+import Dragger from '../components/Dragger'
import * as uiActions from '../services/ui/actions'
import * as pusherActions from '../services/pusher/actions'
@@ -93,13 +94,17 @@ class App extends React.Component{
}
render(){
+ var className = '';
+ if( this.props.dragger && this.props.dragger.dragging ) className += ' dragging'
+
return (
-
+
{this.props.children}
+
);
}
@@ -116,6 +121,7 @@ const mapStateToProps = (state, ownProps) => {
mopidy_connected: state.mopidy.connected,
spotify_authorized: state.spotify.authorized,
play_state: state.mopidy.play_state,
+ dragger: state.ui.dragger,
context_menu: state.ui.context_menu
}
}
diff --git a/src/scss/app.scss b/src/scss/app.scss
index e7891af4..935ea3ca 100755
--- a/src/scss/app.scss
+++ b/src/scss/app.scss
@@ -8,6 +8,7 @@
@import 'global/forms';
@import 'components/context-menu';
+@import 'components/dragger';
@import 'components/images';
@import 'components/icon';
@import 'components/search-form';
diff --git a/src/scss/components/_dragger.scss b/src/scss/components/_dragger.scss
new file mode 100755
index 00000000..ea398ffe
--- /dev/null
+++ b/src/scss/components/_dragger.scss
@@ -0,0 +1,30 @@
+
+.dragger {
+ position: fixed;
+ left: 0;
+ top: 0;
+ z-index: 9999;
+ background: $blue;
+ color: #FFFFFF;
+ padding: 12px 20px;
+ pointer-events: none;
+}
+
+.dropzones {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 9998;
+ background: rgba(25, 25, 25, 0.9);
+
+ .zone {
+ margin: 15px;
+ padding: 50px 20px;
+ background: $turquoise;
+ color: #FFFFFF;
+ font-size: 16px;
+ text-align: center;
+ }
+}
\ No newline at end of file
diff --git a/src/scss/global/_variables.scss b/src/scss/global/_variables.scss
index d39c4677..09d7edc4 100755
--- a/src/scss/global/_variables.scss
+++ b/src/scss/global/_variables.scss
@@ -7,6 +7,7 @@ $dark_grey: #333333;
$secondary_grey: #888888;
$red: #cf2d2d;
$green: #47af2a;
+$blue: #32b5f2;
@mixin one_line_text {
white-space: nowrap;