mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
Allow drag and drop to rearrange queued songs
This commit is contained in:
parent
a45222518a
commit
346aedd1c0
6 changed files with 106 additions and 16 deletions
|
@ -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;
|
||||
|
|
|
@ -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' : '']"
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import $ from 'jquery';
|
||||
|
||||
import ls from './services/ls';
|
||||
|
||||
window.Vue = require('vue');
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
Loading…
Reference in a new issue