mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
Load (and parse) playlist content on demand
This commit is contained in:
parent
fc90f4e74a
commit
d949ea9e60
7 changed files with 118 additions and 58 deletions
|
@ -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(),
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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 "Add To…" 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 "Add To…" 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
|
||||
}
|
||||
})
|
||||
},
|
||||
|
|
|
@ -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({
|
||||
/**
|
||||
|
|
|
@ -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 "${playlist.name}".`)
|
||||
resolve(playlist)
|
||||
|
|
|
@ -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']]);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue