Commit 4b91633e authored by Agate's avatar Agate 💬

Better browsing, added detailed artist page

parent 2da084b7
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) {
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 }
]
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment