Allow drag and drop to rearrange queued songs

This commit is contained in:
An Phan 2016-01-16 09:37:29 +08:00
parent a45222518a
commit 346aedd1c0
6 changed files with 106 additions and 16 deletions

View file

@ -61,6 +61,9 @@
this.authenticated = true;
this.init();
}
// Create the element to be the ghost drag image.
$('<div id="dragGhost"></div>').appendTo('body');
},
methods: {
@ -290,6 +293,18 @@
@import "resources/assets/sass/partials/_mixins.scss";
@import "resources/assets/sass/partials/_shared.scss";
#dragGhost {
position: relative;
display: inline-block;
background: $colorGreen;
padding: 10px;
border-radius: 3px;
color: #fff;
font-family: $fontFamily;
font-size: $fontSize;
font-weight: $fontWeight_Thin;
}
#app, .login-wrapper {
display: flex;
min-height: 100vh;

View file

@ -10,7 +10,7 @@
@click.prevent="$root.loadMainView('queue')"
@dragleave="removeDroppableState"
@dragover.prevent="allowDrop"
@drop.stop="handleDrop($event)">Current Queue</a>
@drop.stop.prevent="handleDrop">Current Queue</a>
</li>
<li>
<a class="songs" :class="[currentView == 'songs' ? 'active' : '']"

View file

@ -3,7 +3,7 @@
<a @click.prevent="load"
@dragleave="removeDroppableState"
@dragover.prevent="allowDrop"
@drop.stop="handleDrop($event)"
@drop.stop.prevent="handleDrop"
:class="{ 'active': active }"
>{{ playlist.name }}</a>
@ -101,7 +101,7 @@
/**
* Remove the droppable state when a dragleave event occurs on the playlist's DOM element.
*
* @param {Object} e The dragleave event.
* @param {Object} e The dragleave event.
*/
removeDroppableState(e) {
$(e.target).removeClass('droppable');
@ -111,7 +111,7 @@
* Add a "droppable" class and set the drop effect when an item is dragged over the playlist's
* DOM element.
*
* @param object e The dragover event.
* @param {Object} e The dragover event.
*/
allowDrop(e) {
$(e.target).addClass('droppable');

View file

@ -39,9 +39,12 @@
data-song-id="{{ item.id }}"
track-by="id"
:song="item"
@click="rowClick($event)"
@click="rowClick"
draggable="true"
@dragstart="dragStart"
@dragleave="removeDroppableState"
@dragover.prevent="allowDrop"
@drop.stop.prevent="handleDrop"
>
</tr>
</tbody>
@ -241,6 +244,7 @@
if (isMobile.any) {
this.toggleRow(row);
this.gatherSelected();
return;
}
@ -254,7 +258,7 @@
this.toggleRow(row);
}
if (e.shiftKey) {
if (e.shiftKey && this.lastSelectedRow) {
this.selectRowsBetweenIndexes([this.lastSelectedRow.rowIndex, row.rowIndex])
}
}
@ -298,18 +302,60 @@
// Select the current target as well.
$(e.target).addClass('selected');
this.gatherSelected();
// We can opt for something like application/x-koel.text+plain here to sound fancy,
// but forget it.
e.dataTransfer.setData('text/plain', _.pluck(this.selectedSongs, 'id'));
var songIds = _.pluck(this.selectedSongs, 'id');
e.dataTransfer.setData('text/plain', songIds);
e.dataTransfer.effectAllowed = 'move';
// Set a fancy icon
var dragIcon = document.createElement('img');
dragIcon.src = '/public/img/drag-icon.png';
dragIcon.width = 16;
// Set a fancy drop image using our ghost element.
var $ghost = $('#dragGhost').text(`${songIds.length} song${songIds.length === 1 ? '' : 's'}`);
e.dataTransfer.setDragImage($ghost[0], 0, 0);
},
e.dataTransfer.setDragImage(dragIcon, -10, -10);
}
/**
* Add a "droppable" class and set the drop effect when other songs are dragged over a row.
*
* @param {Object} e The dragover event.
*/
allowDrop(e) {
if (this.type !== 'queue') {
return;
}
$(e.target).parents('tr').addClass('droppable');
e.dataTransfer.dropEffect = 'move';
return false;
},
handleDrop(e) {
if (this.type !== 'queue') {
return;
}
if (!e.dataTransfer.getData('text/plain')) {
return false;
}
var songs = this.selectedSongs;
if (!songs.length) {
return false;
}
var $row = this.removeDroppableState(e);
queueStore.move(songs, songStore.byId($row.data('song-id')));
return false;
},
removeDroppableState(e) {
return $(e.target).parents('tr').removeClass('droppable');
},
},
events: {
@ -403,6 +449,11 @@
width: 100%;
}
tr.droppable {
border-bottom-width: 3px;
border-bottom-color: $colorGreen;
}
td, th {
text-align: left;
padding: 8px;

View file

@ -1,5 +1,3 @@
import $ from 'jquery';
import ls from './services/ls';
window.Vue = require('vue');

View file

@ -96,7 +96,7 @@ export default {
return this.queue(songs);
}
var head = this.state.songs.splice(0, _.indexOf(this.state.songs, this.state.current) + 1);
var head = this.state.songs.splice(0, this.indexOf(this.state.current) + 1);
this.state.songs = head.concat(songs, this.state.songs);
},
@ -113,6 +113,21 @@ export default {
this.state.songs = _.difference(this.state.songs, songs);
},
/**
* Move some songs to after a target.
*
* @param {Array} songs Songs to move
* @param {Object} target The target song object
*/
move(songs, target) {
var $targetIndex = this.indexOf(target);
songs.forEach(song => {
this.state.songs.splice(this.indexOf(song), 1);
this.state.songs.splice($targetIndex, 0, song);
});
},
/**
* Clear the current queue.
*/
@ -125,6 +140,17 @@ export default {
}
},
/**
* Get index of a song in the queue.
*
* @param {Object} song
*
* @return {?integer}
*/
indexOf(song) {
return _.indexOf(this.state.songs, song);
},
/**
* Get the next song in queue.
*