mirror of
https://github.com/koel/koel
synced 2024-11-24 21:23:06 +00:00
Allow song selection on mobile devices
This commit is contained in:
parent
5a085e2bed
commit
d17b9d8103
21 changed files with 134 additions and 77 deletions
|
@ -204,8 +204,7 @@
|
|||
}
|
||||
|
||||
@media only screen
|
||||
and (max-device-width : 667px)
|
||||
and (orientation : portrait) {
|
||||
and (max-device-width : 667px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
<section id="albumsWrapper">
|
||||
<h1 class="heading">
|
||||
<span>Albums
|
||||
<i class="fa fa-chevron-down toggler"
|
||||
<i class="fa fa-angle-down toggler"
|
||||
v-show="isPhone && !showingControls"
|
||||
@click="showingControls = true"></i>
|
||||
<i class="fa fa-chevron-up toggler"
|
||||
<i class="fa fa-angle-up toggler"
|
||||
v-show="isPhone && showingControls"
|
||||
@click.prevent="showingControls = false"></i>
|
||||
</span>
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
<h1 class="heading">
|
||||
<span>
|
||||
Artists
|
||||
<i class="fa fa-chevron-down toggler"
|
||||
<i class="fa fa-angle-down toggler"
|
||||
v-show="isPhone && !showingControls"
|
||||
@click="showingControls = true"></i>
|
||||
<i class="fa fa-chevron-up toggler"
|
||||
<i class="fa fa-angle-up toggler"
|
||||
v-show="isPhone && showingControls"
|
||||
@click.prevent="showingControls = false"></i>
|
||||
</span>
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
<section id="favoritesWrapper">
|
||||
<h1 class="heading">
|
||||
<span>Songs You Love
|
||||
<i class="fa fa-chevron-down toggler"
|
||||
<i class="fa fa-angle-down toggler"
|
||||
v-show="isPhone && !showingControls"
|
||||
@click="showingControls = true"></i>
|
||||
<i class="fa fa-chevron-up toggler"
|
||||
<i class="fa fa-angle-up toggler"
|
||||
v-show="isPhone && showingControls"
|
||||
@click.prevent="showingControls = false"></i>
|
||||
</span>
|
||||
|
|
|
@ -147,8 +147,7 @@
|
|||
|
||||
|
||||
@media only screen
|
||||
and (max-device-width : 768px)
|
||||
and (orientation : portrait) {
|
||||
and (max-device-width : 768px) {
|
||||
h1.heading {
|
||||
font-size: 18px;
|
||||
min-height: 0;
|
||||
|
@ -168,8 +167,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.main-scroll-wrap {
|
||||
padding: 24px 16px $footerHeight;
|
||||
> section {
|
||||
.main-scroll-wrap {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
<section id="playlistWrapper">
|
||||
<h1 class="heading">
|
||||
<span>{{ playlist.name }}
|
||||
<i class="fa fa-chevron-down toggler"
|
||||
<i class="fa fa-angle-down toggler"
|
||||
v-show="isPhone && !showingControls"
|
||||
@click="showingControls = true"></i>
|
||||
<i class="fa fa-chevron-up toggler"
|
||||
<i class="fa fa-angle-up toggler"
|
||||
v-show="isPhone && showingControls"
|
||||
@click.prevent="showingControls = false"></i>
|
||||
</span>
|
||||
|
|
|
@ -240,8 +240,7 @@
|
|||
}
|
||||
|
||||
@media only screen
|
||||
and (max-device-width : 667px)
|
||||
and (orientation : portrait) {
|
||||
and (max-device-width : 667px) {
|
||||
input {
|
||||
&[type="text"], &[type="email"], &[type="password"] {
|
||||
width: 100%;
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
<section id="queueWrapper">
|
||||
<h1 class="heading">
|
||||
<span title="That's a freaking lot of U's and E's">Current Queue
|
||||
<i class="fa fa-chevron-down toggler"
|
||||
<i class="fa fa-angle-down toggler"
|
||||
v-show="isPhone && !showingControls"
|
||||
@click="showingControls = true"></i>
|
||||
<i class="fa fa-chevron-up toggler"
|
||||
<i class="fa fa-angle-up toggler"
|
||||
v-show="isPhone && showingControls"
|
||||
@click.prevent="showingControls = false"></i>
|
||||
</span>
|
||||
|
@ -162,8 +162,7 @@
|
|||
}
|
||||
|
||||
@media only screen
|
||||
and (max-device-width : 667px)
|
||||
and (orientation : portrait) {
|
||||
and (max-device-width : 667px) {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -69,8 +69,7 @@
|
|||
}
|
||||
|
||||
@media only screen
|
||||
and (max-device-width : 667px)
|
||||
and (orientation : portrait) {
|
||||
and (max-device-width : 667px) {
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
<section id="songsWrapper">
|
||||
<h1 class="heading">
|
||||
<span>All Songs
|
||||
<i class="fa fa-chevron-down toggler"
|
||||
<i class="fa fa-angle-down toggler"
|
||||
v-show="isPhone && !showingControls"
|
||||
@click="showingControls = true"></i>
|
||||
<i class="fa fa-chevron-up toggler"
|
||||
<i class="fa fa-angle-up toggler"
|
||||
v-show="isPhone && showingControls"
|
||||
@click.prevent="showingControls = false"></i>
|
||||
</span>
|
||||
|
@ -74,12 +74,5 @@
|
|||
<style lang="sass">
|
||||
@import "resources/assets/sass/partials/_vars.scss";
|
||||
@import "resources/assets/sass/partials/_mixins.scss";
|
||||
|
||||
#songsWrapper {
|
||||
.none {
|
||||
color: $color2ndText;
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
<section id="usersWrapper">
|
||||
<h1 class="heading">
|
||||
<span>Users
|
||||
<i class="fa fa-chevron-down toggler"
|
||||
<i class="fa fa-angle-down toggler"
|
||||
v-show="isPhone && !showingControls"
|
||||
@click="showingControls = true"></i>
|
||||
<i class="fa fa-chevron-up toggler"
|
||||
<i class="fa fa-angle-up toggler"
|
||||
v-show="isPhone && showingControls"
|
||||
@click.prevent="showingControls = false"></i>
|
||||
</span>
|
||||
|
@ -334,8 +334,7 @@
|
|||
}
|
||||
|
||||
@media only screen
|
||||
and (max-device-width : 667px)
|
||||
and (orientation : portrait) {
|
||||
and (max-device-width : 667px) {
|
||||
form.user-create, form.user-edit {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
@ -369,8 +368,7 @@
|
|||
|
||||
@media only screen
|
||||
and (min-device-width : 668px)
|
||||
and (max-device-width : 768px)
|
||||
and (orientation : portrait) {
|
||||
and (max-device-width : 768px) {
|
||||
.users {
|
||||
flex-direction: column;
|
||||
|
||||
|
|
|
@ -248,8 +248,7 @@
|
|||
|
||||
|
||||
@media only screen
|
||||
and (max-device-width : 667px)
|
||||
and (orientation : portrait) {
|
||||
and (max-device-width : 667px) {
|
||||
position: fixed;
|
||||
height: calc(100vh - #{$headerHeight + $footerHeight});
|
||||
padding-bottom: $footerHeight; // make sure the footer can never overlap the content
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="add-to-playlist" v-show="showing">
|
||||
<p>Add {{ songs.length }} song{{ songs.length > 1 ? 's' : '' }} in</p>
|
||||
<p>Add {{ songs.length }} song{{ songs.length > 1 ? 's' : '' }} to</p>
|
||||
<ul>
|
||||
<li v-if="mergedSettings.canQueue" @click="queueSongsToBottom">Bottom of Queue</li>
|
||||
<li v-if="mergedSettings.canQueue" @click="queueSongsToTop">Top of Queue</li>
|
||||
|
|
|
@ -9,10 +9,12 @@
|
|||
<td class="artist">{{ song.album.artist.name }}</td>
|
||||
<td class="album">{{ song.album.name }}</td>
|
||||
<td class="time">{{ song.fmtLength }}</td>
|
||||
<td class="check"><input type="checkbox" @click.stop="select($event)"></td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import $ from 'jquery';
|
||||
import playback from '../../services/playback';
|
||||
|
||||
export default {
|
||||
|
@ -31,13 +33,27 @@
|
|||
play() {
|
||||
playback.play(this.song);
|
||||
},
|
||||
|
||||
/**
|
||||
* Select a row.
|
||||
*/
|
||||
select(e) {
|
||||
if ($(e.target).prop('checked')) {
|
||||
$(e.target).parents('tr').addClass('selected');
|
||||
} else {
|
||||
$(e.target).parents('tr').removeClass('selected');
|
||||
}
|
||||
|
||||
// Let the parent listing know to collect the selected songs.
|
||||
this.$dispatch('song:selection-changed');
|
||||
},
|
||||
},
|
||||
|
||||
events: {
|
||||
/**
|
||||
* Listen to 'song:play' event and set the "playing" status.
|
||||
*
|
||||
* @param object song The current playing song.
|
||||
* @param {Object} song The current playing song.
|
||||
*/
|
||||
'song:play': function (song) {
|
||||
this.playing = this.song.id === song.id;
|
||||
|
@ -67,6 +83,10 @@
|
|||
min-width: 192px;
|
||||
}
|
||||
|
||||
.check {
|
||||
max-width: 32px;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: rgba(255, 255, 255, .08);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
<i class="fa fa-angle-down" v-show="sortKey === 'fmtLength' && order > 0"></i>
|
||||
<i class="fa fa-angle-up" v-show="sortKey === 'fmtLength' && order < 0"></i>
|
||||
</th>
|
||||
<th class="check"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
|
@ -37,7 +38,7 @@
|
|||
is="song-item"
|
||||
data-song-id="{{ item.id }}"
|
||||
:song="item"
|
||||
@click="rowClick"
|
||||
@click="rowClick($event)"
|
||||
draggable="true"
|
||||
@dragstart="dragStart"
|
||||
>
|
||||
|
@ -90,7 +91,7 @@
|
|||
/**
|
||||
* Handle sorting the song list.
|
||||
*
|
||||
* @param string key The sort key. Can be 'title', 'album', 'artist', or 'fmtLength'
|
||||
* @param {String} key The sort key. Can be 'title', 'album', 'artist', or 'fmtLength'
|
||||
*/
|
||||
sort(key) {
|
||||
// We don't allow sorting in the Queue screen.
|
||||
|
@ -195,14 +196,16 @@
|
|||
return;
|
||||
}
|
||||
|
||||
$(this.$els.wrapper).find('.song-item').addClass('selected');
|
||||
$(this.$els.wrapper)
|
||||
.find('.song-item').addClass('selected')
|
||||
.find(':checkbox').prop('checked', true);
|
||||
this.gatherSelected();
|
||||
},
|
||||
|
||||
/**
|
||||
* Gather all selected songs
|
||||
* Gather all selected songs.
|
||||
*
|
||||
* @return {Array} An array of Song object
|
||||
* @return {Array} An array of Song objects
|
||||
*/
|
||||
gatherSelected() {
|
||||
var ids = _.map($(this.$els.wrapper).find('.song-item.selected'), row => $(row).data('song-id'));
|
||||
|
@ -227,6 +230,7 @@
|
|||
// If we're on a touch device, tapping a row means playing right away.
|
||||
if (isMobile.any) {
|
||||
playback.play(songStore.byId($(row).data('song-id')));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -249,7 +253,7 @@
|
|||
},
|
||||
|
||||
toggleRow(row) {
|
||||
$(row).toggleClass('selected');
|
||||
$(row).find(':checkbox').click();
|
||||
this.lastSelectedRow = row;
|
||||
},
|
||||
|
||||
|
@ -259,13 +263,16 @@
|
|||
var rows = $(this.lastSelectedRow).parents('tbody').find('tr');
|
||||
|
||||
for (var i = indexes[0]; i <= indexes[1]; ++i) {
|
||||
$(rows[i-1]).addClass('selected');
|
||||
$(rows[i-1]).addClass('selected')
|
||||
.find(':checkbox').prop('checked', true);
|
||||
}
|
||||
},
|
||||
|
||||
clearSelection() {
|
||||
this.selectedSongs = [];
|
||||
$('.song-item').removeClass('selected');
|
||||
$(this.$els.wrapper)
|
||||
.find('.song-item.selected').removeClass('selected')
|
||||
.find(':checked').prop('checked', false);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -295,7 +302,7 @@
|
|||
/**
|
||||
* Listen to queue:select-rows event and mark a range of song-item rows as selected
|
||||
*
|
||||
* @param array range An array in the format of [startIndex, stopIndex]
|
||||
* @param {Array} range An array in the format of [startIndex, stopIndex]
|
||||
*/
|
||||
'queue:select-rows': function (range) {
|
||||
if (this.type != 'queue') {
|
||||
|
@ -312,7 +319,7 @@
|
|||
/**
|
||||
* Listen to song:play event to do some logic.
|
||||
*
|
||||
* @param object song The current playing song.
|
||||
* @param {Object} song The current playing song.
|
||||
*/
|
||||
'song:play': function (song) {
|
||||
// If the song is at the end of the current displayed items, load more.
|
||||
|
@ -351,6 +358,14 @@
|
|||
'main-content-view:load': function () {
|
||||
this.clearSelection();
|
||||
},
|
||||
|
||||
/**
|
||||
* Listens to the 'song:selection-changed' dispatched from a child song-item
|
||||
* to collect the selected songs.
|
||||
*/
|
||||
'song:selection-changed': function () {
|
||||
this.gatherSelected();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -375,6 +390,10 @@
|
|||
width: 72px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&.check {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
|
@ -398,8 +417,7 @@
|
|||
|
||||
|
||||
@media only screen
|
||||
and (max-device-width : 768px)
|
||||
and (orientation : portrait) {
|
||||
and (max-device-width : 768px) {
|
||||
table, tbody, tr {
|
||||
display: block;
|
||||
}
|
||||
|
@ -409,7 +427,8 @@
|
|||
}
|
||||
|
||||
tr {
|
||||
padding: 8px 0;
|
||||
padding: 8px 32px 8px 4px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
td {
|
||||
|
@ -426,6 +445,13 @@
|
|||
font-size: 90%;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
&.check {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -377,8 +377,7 @@
|
|||
|
||||
|
||||
@media only screen
|
||||
and (max-device-width : 768px)
|
||||
and (orientation : portrait) {
|
||||
and (max-device-width : 768px) {
|
||||
position: absolute !important;
|
||||
right: 0;
|
||||
height: $footerHeight;
|
||||
|
@ -441,8 +440,7 @@
|
|||
|
||||
|
||||
@media only screen
|
||||
and (max-device-width : 768px)
|
||||
and (orientation : portrait) {
|
||||
and (max-device-width : 768px) {
|
||||
width: 50%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -470,8 +468,7 @@
|
|||
|
||||
|
||||
@media only screen
|
||||
and (max-device-width : 768px)
|
||||
and (orientation : portrait) {
|
||||
and (max-device-width : 768px) {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -536,8 +533,7 @@
|
|||
|
||||
|
||||
@media only screen
|
||||
and (max-device-width : 768px)
|
||||
and (orientation : portrait) {
|
||||
and (max-device-width : 768px) {
|
||||
.meta, .title {
|
||||
display: none;
|
||||
}
|
||||
|
@ -560,8 +556,7 @@
|
|||
}
|
||||
|
||||
@media only screen
|
||||
and (max-device-width : 768px)
|
||||
and (orientation : portrait) {
|
||||
and (max-device-width : 768px) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,8 +63,7 @@
|
|||
}
|
||||
|
||||
@media only screen
|
||||
and (max-device-width : 667px)
|
||||
and (orientation : portrait) {
|
||||
and (max-device-width : 667px) {
|
||||
display: flex;
|
||||
align-content: stretch;
|
||||
justify-content: flext-start;
|
||||
|
|
|
@ -64,8 +64,7 @@
|
|||
|
||||
|
||||
@media only screen
|
||||
and (max-device-width : 667px)
|
||||
and (orientation : portrait) {
|
||||
and (max-device-width : 667px) {
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
|
|
@ -58,8 +58,7 @@
|
|||
}
|
||||
|
||||
@media only screen
|
||||
and (max-device-width : 667px)
|
||||
and (orientation : portrait) {
|
||||
and (max-device-width : 667px) {
|
||||
flex: 0 0 96px;
|
||||
margin-right: 0;
|
||||
padding-right: 0;
|
||||
|
|
|
@ -19,7 +19,7 @@ export default {
|
|||
/**
|
||||
* Initialize the playback service for this whole Koel app.
|
||||
*
|
||||
* @param object app The root Vue component.
|
||||
* @param {Object} app The root Vue component.
|
||||
*/
|
||||
init(app) {
|
||||
// We don't need to init this service twice, or the media events will be duplicated.
|
||||
|
@ -83,7 +83,7 @@ export default {
|
|||
* So many dreams swinging out of the blue
|
||||
* We'll let them come true
|
||||
*
|
||||
* @param object song The song to play
|
||||
* @param {Object} song The song to play
|
||||
*/
|
||||
play(song) {
|
||||
if (!song) {
|
||||
|
@ -216,8 +216,8 @@ export default {
|
|||
/**
|
||||
* Set the volume level.
|
||||
*
|
||||
* @param integer volume 0-10
|
||||
* @param boolean persist Whether the volume should be saved into local storage
|
||||
* @param {Integer} volume 0-10
|
||||
* @param {Boolean} persist Whether the volume should be saved into local storage
|
||||
*/
|
||||
setVolume(volume, persist = true) {
|
||||
this.player.setVolume(volume);
|
||||
|
@ -276,8 +276,8 @@ export default {
|
|||
/**
|
||||
* Queue up songs (replace them into the queue) and start playing right away.
|
||||
*
|
||||
* @param array|null songs An array of song objects. Defaults to all songs if null.
|
||||
* @param bool shuffle Whether to shuffle the songs before playing.
|
||||
* @param {Array|Null} songs An array of song objects. Defaults to all songs if null.
|
||||
* @param {Boolean} shuffle Whether to shuffle the songs before playing.
|
||||
*/
|
||||
queueAndPlay(songs = null, shuffle = false) {
|
||||
if (!songs) {
|
||||
|
|
|
@ -33,7 +33,39 @@ input, select, button {
|
|||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
-webkit-appearance: checkbox;
|
||||
border: 1px solid rgba(255, 255, 255, .3);
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: #555;
|
||||
clear: none;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
line-height: 0;
|
||||
height: 16px;
|
||||
margin: -4px 4px 0 0;
|
||||
outline: 0;
|
||||
padding: 0!important;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
width: 16px;
|
||||
min-width: 16px;
|
||||
box-shadow: inset 0 1px 2px rgba(0,0,0,.1);
|
||||
|
||||
&:checked{
|
||||
background: #fff;
|
||||
|
||||
&:before {
|
||||
font-family: FontAwesome;
|
||||
content: "\f00c";
|
||||
color: $colorHighlight;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a, a:link, a:visited {
|
||||
|
|
Loading…
Reference in a new issue