Load (and parse) playlist content on demand

This commit is contained in:
Phan An 2017-12-02 17:05:40 +01:00
parent fc90f4e74a
commit d949ea9e60
7 changed files with 118 additions and 58 deletions

View file

@ -25,16 +25,9 @@ class DataController extends Controller
*/
public function index(Request $request)
{
$playlists = Playlist::byCurrentUser()->orderBy('name')->with('songs')->get()->toArray();
// We don't need full song data, just ID's
foreach ($playlists as &$playlist) {
$playlist['songs'] = array_pluck($playlist['songs'], 'id');
}
return response()->json(MediaCache::get() + [
'settings' => $request->user()->is_admin ? Setting::pluck('value', 'key')->all() : [],
'playlists' => $playlists,
'playlists' => Playlist::byCurrentUser()->orderBy('name')->get()->toArray(),
'interactions' => Interaction::byCurrentUser()->get(),
'users' => $request->user()->is_admin ? User::all() : [],
'currentUser' => $request->user(),

View file

@ -77,6 +77,21 @@ class PlaylistController extends Controller
return response()->json();
}
/**
* Get a playlist's all songs.
*
* @param Request $request
* @param Playlist $playlist
* @return JsonResponse
*/
public function getSongs(Request $request, Playlist $playlist)
{
$this->authorize('owner', $playlist);
return response()->json($playlist->songs->pluck('id'));
}
/**
* Delete a playlist.
*

View file

@ -1,43 +1,45 @@
<template>
<section id="playlistWrapper">
<h1 class="heading">
<span>{{ playlist.name }}
<controls-toggler :showing-controls="showingControls" @toggleControls="toggleControls"/>
<template v-if="playlist.populated">
<h1 class="heading">
<span>{{ playlist.name }}
<controls-toggler :showing-controls="showingControls" @toggleControls="toggleControls"/>
<span class="meta" v-show="meta.songCount">
{{ meta.songCount | pluralize('song') }}
{{ meta.totalLength }}
<template v-if="sharedState.allowDownload && playlist.songs.length">
<span class="meta" v-show="meta.songCount">
{{ meta.songCount | pluralize('song') }}
<a href @click.prevent="download" title="Download all songs in playlist">
Download All
</a>
</template>
{{ meta.totalLength }}
<template v-if="sharedState.allowDownload && playlist.songs.length">
<a href @click.prevent="download" title="Download all songs in playlist">
Download All
</a>
</template>
</span>
</span>
</span>
<song-list-controls
v-show="!isPhone || showingControls"
@shuffleAll="shuffleAll"
@shuffleSelected="shuffleSelected"
@deletePlaylist="confirmDelete"
:config="songListControlConfig"
:selectedSongs="selectedSongs"
<song-list-controls
v-show="!isPhone || showingControls"
@shuffleAll="shuffleAll"
@shuffleSelected="shuffleSelected"
@deletePlaylist="confirmDelete"
:config="songListControlConfig"
:selectedSongs="selectedSongs"
/>
</h1>
<song-list v-show="playlist.songs.length"
:items="playlist.songs"
:playlist="playlist"
type="playlist"
ref="songList"
/>
</h1>
<song-list v-show="playlist.songs.length"
:items="playlist.songs"
:playlist="playlist"
type="playlist"
ref="songList"
/>
<div v-show="!playlist.songs.length" class="none">
The playlist is currently empty. You can fill it up by dragging songs into its name in the sidebar,
or use the &quot;Add To&quot; button.
</div>
<div v-show="!playlist.songs.length" class="none">
The playlist is currently empty. You can fill it up by dragging songs into its name in the sidebar,
or use the &quot;Add To&quot; button.
</div>
</template>
</section>
</template>
@ -71,11 +73,19 @@ export default {
* @param {String} view The view's name.
* @param {Object} playlist
*/
event.on('main-content-view:load', (view, playlist) => {
if (view === 'playlist') {
event.on('main-content-view:load', async (view, playlist) => {
if (view !== 'playlist') {
return
}
if (typeof playlist.populated === 'undefined') {
// playlistStore.populateContent(this.playlist)
await playlistStore.fetchSongs(playlist)
playlist.populated = true
this.playlist = playlist
// #530
this.$nextTick(() => this.$refs.songList.sort())
} else {
this.playlist = playlist
}
})
},

View file

@ -100,17 +100,7 @@ export default {
* Watch the items.
*/
items () {
if (this.sortable === false) {
this.sortKey = ''
}
// Update the song count and duration status on parent.
this.$parent.updateMeta({
songCount: this.items.length,
totalLength: songStore.getLength(this.items, true)
})
this.generateSongRows()
this.render()
},
selectedSongs (val) {
@ -163,6 +153,20 @@ export default {
},
methods: {
render () {
if (this.sortable === false) {
this.sortKey = ''
}
// Update the song count and duration status on parent.
this.$parent.updateMeta({
songCount: this.items.length,
totalLength: songStore.getLength(this.items, true)
})
this.generateSongRows()
},
/**
* Generate an array of "song row" or "song wrapper" objects. Since song objects themselves are
* shared by all song lists, we can't use them directly to determine their selection status
@ -456,6 +460,12 @@ export default {
}
},
mounted () {
if (this.items) {
this.render()
}
},
created () {
event.on({
/**

View file

@ -15,7 +15,6 @@ export const playlistStore = {
init (playlists) {
this.all = playlists
each(this.all, this.objectifySongs)
},
/**
@ -36,6 +35,22 @@ export const playlistStore = {
this.state.playlists = value
},
/**
* Fetch the songs for a playlist.
*
* @param {Object} playlist
*/
fetchSongs (playlist) {
NProgress.start()
return new Promise((resolve, reject) => {
http.get(`playlist/${playlist.id}/songs`, ({ data }) => {
playlist.songs = songStore.byIds(data)
resolve(playlist)
}, error => reject(error))
})
},
/**
* Find a playlist by its ID
*
@ -48,12 +63,12 @@ export const playlistStore = {
},
/**
* Objectify all songs in the playlist.
* Populate the playlist content by "objectifying" all songs in the playlist.
* (Initially, a playlist only contain the song IDs).
*
* @param {Object} playlist
*/
objectifySongs (playlist) {
populateContent (playlist) {
playlist.songs = songStore.byIds(playlist.songs)
},
@ -103,7 +118,7 @@ export const playlistStore = {
return new Promise((resolve, reject) => {
http.post('playlist', { name, songs }, ({ data: playlist }) => {
playlist.songs = songs
this.objectifySongs(playlist)
this.populateContent(playlist)
this.add(playlist)
alerts.success(`Created playlist &quot;${playlist.name}&quot;.`)
resolve(playlist)

View file

@ -45,6 +45,7 @@ Route::group(['namespace' => 'API'], function () {
// Playlist routes
Route::resource('playlist', 'PlaylistController');
Route::put('playlist/{playlist}/sync', 'PlaylistController@sync')->where(['playlist' => '\d+']);
Route::get('playlist/{playlist}/songs', 'PlaylistController@getSongs')->where(['playlist' => '\d+']);
// User and user profile routes
Route::resource('user', 'UserController', ['only' => ['store', 'update', 'destroy']]);

View file

@ -121,4 +121,20 @@ class PlaylistTest extends TestCase
$this->deleteAsUser("api/playlist/{$playlist->id}", [], $user)
->notSeeInDatabase('playlists', ['id' => $playlist->id]);
}
/** @test */
public function playlist_content_can_be_retrieved()
{
$user = factory(User::class)->create();
$playlist = factory(Playlist::class)->create([
'user_id' => $user->id,
]);
$songs = factory(Song::class, 2)->create();
$playlist->songs()->saveMany($songs);
$this->getAsUser("api/playlist/{$playlist->id}/songs", $user)
->seeJson($songs->pluck('id')->all());
}
}