Allow song selection on mobile devices

This commit is contained in:
An Phan 2016-01-07 00:41:59 +08:00
parent 5a085e2bed
commit d17b9d8103
21 changed files with 134 additions and 77 deletions

View file

@ -204,8 +204,7 @@
}
@media only screen
and (max-device-width : 667px)
and (orientation : portrait) {
and (max-device-width : 667px) {
width: 100%;
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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;
}
}
}
}

View file

@ -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>

View file

@ -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%;

View file

@ -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>

View file

@ -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%;
}

View file

@ -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>

View file

@ -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;

View file

@ -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

View file

@ -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>

View file

@ -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);
}

View file

@ -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;
}
}
}
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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) {

View file

@ -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 {