Skip to content
Snippets Groups Projects
Commit 4b91633e authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Better browsing, added detailed artist page

parent 2da084b7
No related branches found
No related tags found
No related merge requests found
var Album = {
clean (album) {
// we manually rebind the album and artist to each child track
album.tracks = album.tracks.map((track) => {
track.artist = album.artist
track.album = album
return track
})
return album
}
}
var Artist = {
clean (artist) {
// clean data as given by the API
artist.albums = artist.albums.map((album) => {
return Album.clean(album)
})
return artist
}
}
export default {
absoluteUrl (url) {
return process.env.BACKEND_URL + url
if (url.startsWith('http')) {
return url
}
return process.env.BACKEND_URL + url
},
Artist: Artist,
Album: Album
}
......@@ -25,7 +25,7 @@
<router-link class="item" v-if="auth.user.authenticated" :to="{name: 'logout'}">Logged in as {{ auth.user.username }}</router-link>
<router-link class="item" v-if="auth.user.authenticated" :to="{name: 'logout'}">Logout</router-link>
<router-link class="item" v-else :to="{name: 'login'}">Login</router-link>
<router-link class="item" :to="{path: 'browse'}">Browse library</router-link>
<router-link class="item" :to="{path: '/browse'}">Browse library</router-link>
</div>
</div>
<div class="ui bottom attached tab" data-tab="queue">
......
......@@ -9,7 +9,11 @@
<div class="middle aligned content">
<a class="header">{{ queue.currentTrack.title }}</a>
<div class="meta">
<p><a class="artist"><i class="users tiny icon"></i> {{ queue.currentTrack.artist.name }}</a></p>
<p>
<router-link class="artist" :to="{name: 'browse.artist', params: {id: queue.currentTrack.artist.id }}">
<i class="users tiny icon"></i> {{ queue.currentTrack.artist.name }}
</router-link>
</p>
<p><a v-if="queue.currentTrack.album" class="album"><i class="sound tiny icon"></i> {{ queue.currentTrack.album.title }}</a></p>
</div>
</div>
......
......@@ -4,20 +4,20 @@
<div :class="['ui', {'loading': isLoading }, 'search']">
<div class="ui icon input">
<i class="search icon"></i>
<input class="prompt" placeholder="Artist, album, track..." v-model.trim="query" type="text" />
<input ref="search" class="prompt" placeholder="Artist, album, track..." v-model.trim="query" type="text" />
</div>
</div>
<template v-if="query.length > 0">
<h3 class="ui title">Artists</h3>
<div v-if="results.artists.length > 0" class="ui cards">
<artist-card :artist="artist" v-for="artist in results.artists"></artist-card>
<artist-card :artist="artist" :key="artist.id" v-for="artist in results.artists"></artist-card>
</div>
<p v-else>Sorry, we did not found any artist matching your query</p>
</template>
<template v-if="query.length > 0">
<h3 class="ui title">Albums</h3>
<div v-if="results.albums.length > 0" class="ui cards">
<album-card :album="album" v-for="album in results.albums"></album-card>
<album-card :album="album" :key="album.id" v-for="album in results.albums"></album-card>
</div>
<p v-else>Sorry, we did not found any album matching your query</p>
</template>
......@@ -38,6 +38,9 @@ export default {
AlbumCard,
ArtistCard
},
props: {
autofocus: {type: Boolean, default: false}
},
data () {
return {
query: '',
......@@ -51,6 +54,9 @@ export default {
}
},
mounted () {
if (this.autofocus) {
this.$refs.search.focus()
}
this.search()
},
methods: {
......@@ -69,26 +75,13 @@ export default {
self.isLoading = false
})
},
rebindTrackData (album) {
album.tracks = album.tracks.map((track) => {
track.artist = album.artist
track.album = album
return track
})
},
castResults (results) {
let self = this
return {
albums: results.albums.map((album) => {
self.rebindTrackData(album)
return album
return backend.Album.clean(album)
}),
artists: results.artists.map((artist) => {
artist.albums.map((album) => {
self.rebindTrackData(album)
return album
})
return artist
return backend.Artist.clean(artist)
})
}
}
......
<template>
<div class="card">
<div class="ui card">
<div class="content">
<div class="right floated tiny ui image">
<img v-if="album.cover" :src="backend.absoluteUrl(album.cover)">
......@@ -7,13 +7,15 @@
</div>
<div class="header">{{ album.title }}</div>
<div class="meta">
By <a>{{ album.artist.name }}</a>
By <router-link :to="{name: 'browse.artist', params: {id: album.artist.id }}">
{{ album.artist.name }}
</router-link>
</div>
<div class="description">
</div>
</div>
<div class="extra content">
<button @click="queue.appendMany(album.tracks)" class="ui mini icon right floated teal button">
<button @click="queue.appendMany(album.tracks)" class="ui mini icon right floated teal labeled button">
<i class="ui play icon"></i>
Play
</button>
......
<template>
<div class="card">
<div class="ui card">
<div class="content">
<div class="header">{{ artist.name }}</div>
<div class="header">
<router-link :to="{name: 'browse.artist', params: {id: artist.id }}">
{{ artist.name }}
</router-link>
</div>
<div class="description">
<table class="ui compact very basic fixed single line table">
<tbody>
......
<template>
<div>
<div :class="['ui', 'centered', {'active': isLoading}, 'inline', 'loader']"></div>
<template v-if="artist">
<div class="ui vertical center aligned stripe segment">
<h2 class="ui center aligned icon header">
<i class="circular inverted users violet icon"></i>
{{ artist.name }}
</h2>
<p><i class="ui sound icon"></i> {{ totalTracks }} tracks in {{ albums.length }} albums</p>
<div class="ui hidden divider"></div>
<button class="ui labeled blue icon button">
<i class="play icon"></i>
Start radio
</button>
<button class="ui labeled teal icon button" @click="playAllALbums">
<i class="play icon"></i>
Play all albums
</button>
<a :href="wikipediaUrl" target="_blank" class="ui labeled icon button">
<i class="wikipedia icon"></i>
Search on wikipedia
</a>
<a :href="musicbrainzUrl" target="_blank" class="ui labeled icon button">
<i class="external icon"></i>
View on MusicBrainz
</a>
</div>
<h2>Albums by this artist</h2>
<div class="ui cards">
<album-card :album="album" :key="album.id" v-for="album in albums"></album-card>
</div>
</template>
</div>
</template>
<script>
import logger from '@/logging'
import backend from '@/audio/backend'
import queue from '@/audio/queue'
import AlbumCard from '@/components/audio/album/Card'
const FETCH_URL = process.env.API_URL + 'artists/'
export default {
props: ['id'],
components: {
AlbumCard
},
data () {
return {
isLoading: true,
artist: null,
albums: null
}
},
created () {
this.fetchData()
},
methods: {
fetchData () {
var self = this
this.isLoading = true
let url = FETCH_URL + this.id + '/'
logger.default.debug('Fetching artist "' + this.id + '"')
this.$http.get(url).then((response) => {
self.artist = response.data
self.albums = JSON.parse(JSON.stringify(self.artist.albums)).map((album) => {
return backend.Album.clean(album)
})
self.isLoading = false
})
},
playAllALbums () {
this.albums.forEach((album) => {
queue.appendMany(album.tracks)
})
}
},
computed: {
totalTracks () {
return this.albums.map((album) => {
return album.tracks.length
}).reduce((a, b) => {
return a + b
})
},
wikipediaUrl () {
return 'https://en.wikipedia.org/w/index.php?search=' + this.artist.name
},
musicbrainzUrl () {
return 'https://musicbrainz.org/artist/' + this.artist.mbid
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
<template>
<div class="ui row">
<search class="eight wide column"></search>
<div>
<div class="ui vertical center aligned stripe segment">
<search :autofocus="true"></search>
</div>
<div class="ui hidden divider"></div>
<div class="ui stackable two column grid">
<div class="column">
<h2 class="ui header">Latest artists</h2>
<div :class="['ui', {'active': isLoadingArtists}, 'inline', 'loader']"></div>
<div v-if="artists.length > 0" v-for="artist in artists.slice(0, 3)" :key="artist" class="ui cards">
<artist-card :artist="artist"></artist-card>
</div>
</div>
</div>
</div>
</template>
<script>
import Search from '@/components/audio/Search'
import backend from '@/audio/backend'
import logger from '@/logging'
import ArtistCard from '@/components/audio/artist/Card'
const ARTISTS_URL = process.env.API_URL + 'artists/'
export default {
name: 'browse',
components: { Search }
components: {
Search,
ArtistCard
},
data () {
return {
artists: [],
isLoadingArtists: false
}
},
created () {
this.fetchArtists()
},
methods: {
fetchArtists () {
var self = this
this.isLoadingArtists = true
let params = {
ordering: '-creation_date'
}
let url = ARTISTS_URL
logger.default.time('Loading latest artists')
this.$http.get(url, {params: params}).then((response) => {
self.artists = response.data.results
self.artists.map((artist) => {
var albums = JSON.parse(JSON.stringify(artist.albums)).map((album) => {
return backend.Album.clean(album)
})
artist.albums = albums
return artist
})
logger.default.timeEnd('Loading latest artists')
self.isLoadingArtists = false
})
}
}
}
</script>
......
......@@ -5,6 +5,7 @@ import Login from '@/components/auth/Login'
import Logout from '@/components/auth/Logout'
import Browse from '@/components/browse/Browse'
import BrowseHome from '@/components/browse/Home'
import BrowseArtist from '@/components/browse/Artist'
Vue.use(Router)
......@@ -30,7 +31,8 @@ export default new Router({
path: '/browse',
component: Browse,
children: [
{ path: '', component: BrowseHome }
{ path: '', component: BrowseHome },
{ path: 'artist/:id', name: 'browse.artist', component: BrowseArtist, props: true }
]
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment