332 lines
8.7 KiB
Vue
332 lines
8.7 KiB
Vue
<template>
|
|
<main
|
|
v-title="labels.title"
|
|
class="main pusher"
|
|
>
|
|
<section class="ui vertical stripe segment">
|
|
<div
|
|
v-if="initialId"
|
|
class="ui small text container"
|
|
>
|
|
<h2>{{ labels.title }}</h2>
|
|
<remote-search-form
|
|
:initial-id="initialId"
|
|
:type="initialType"
|
|
/>
|
|
</div>
|
|
<div
|
|
v-else
|
|
class="ui container"
|
|
>
|
|
<h2>
|
|
<label for="query">
|
|
<translate translate-context="Content/Search/Input.Label/Noun">Search</translate>
|
|
</label>
|
|
</h2>
|
|
<form
|
|
class="ui form"
|
|
@submit.prevent="page = 1; search()"
|
|
>
|
|
<div class="ui field">
|
|
<div class="ui action input">
|
|
<input
|
|
id="query"
|
|
v-model="query"
|
|
class="ui input"
|
|
name="query"
|
|
type="text"
|
|
>
|
|
<button
|
|
:aria-label="labels.submitSearch"
|
|
type="submit"
|
|
class="ui icon button"
|
|
>
|
|
<i class="search icon" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
<div class="ui secondary pointing menu">
|
|
<a
|
|
v-for="t in types"
|
|
:key="t.id"
|
|
:class="['item', {active: type === t.id}]"
|
|
href=""
|
|
@click.prevent="type = t.id"
|
|
>
|
|
{{ t.label }}
|
|
<span
|
|
v-if="results[t.id]"
|
|
class="ui circular mini right floated label"
|
|
>
|
|
{{ results[t.id].count }}</span>
|
|
</a>
|
|
</div>
|
|
<div v-if="isLoading">
|
|
<div
|
|
v-if="isLoading"
|
|
class="ui inverted active dimmer"
|
|
>
|
|
<div class="ui loader" />
|
|
</div>
|
|
</div>
|
|
|
|
<empty-state
|
|
v-else-if="!currentResults || currentResults.count === 0"
|
|
:refresh="true"
|
|
@refresh="search"
|
|
/>
|
|
|
|
<div
|
|
v-else-if="type === 'artists' || type === 'podcasts'"
|
|
class="ui five app-cards cards"
|
|
>
|
|
<artist-card
|
|
v-for="artist in currentResults.results"
|
|
:key="artist.id"
|
|
:artist="artist"
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
v-else-if="type === 'albums' || type === 'series'"
|
|
class="ui five app-cards cards"
|
|
>
|
|
<album-card
|
|
v-for="album in currentResults.results"
|
|
:key="album.id"
|
|
:album="album"
|
|
/>
|
|
</div>
|
|
<track-table
|
|
v-else-if="type === 'tracks'"
|
|
:tracks="currentResults.results"
|
|
/>
|
|
<playlist-card-list
|
|
v-else-if="type === 'playlists'"
|
|
:playlists="currentResults.results"
|
|
/>
|
|
<div
|
|
v-else-if="type === 'radios'"
|
|
class="ui cards"
|
|
>
|
|
<radio-card
|
|
v-for="radio in currentResults.results"
|
|
:key="radio.id"
|
|
type="custom"
|
|
:custom-radio="radio"
|
|
/>
|
|
</div>
|
|
<tags-list
|
|
v-else-if="type === 'tags'"
|
|
:truncate-size="200"
|
|
:limit="paginateBy"
|
|
:tags="currentResults.results.map(t => {return t.name })"
|
|
/>
|
|
|
|
<pagination
|
|
v-if="currentResults && currentResults.count > paginateBy"
|
|
:current="page"
|
|
:paginate-by="paginateBy"
|
|
:total="currentResults.count"
|
|
@page-changed="page = $event"
|
|
/>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
</template>
|
|
|
|
<script>
|
|
import RemoteSearchForm from '@/components/RemoteSearchForm'
|
|
import ArtistCard from '@/components/audio/artist/Card'
|
|
import AlbumCard from '@/components/audio/album/Card'
|
|
import TrackTable from '@/components/audio/track/Table'
|
|
import Pagination from '@/components/Pagination'
|
|
import PlaylistCardList from '@/components/playlists/CardList'
|
|
import RadioCard from '@/components/radios/Card'
|
|
import TagsList from '@/components/tags/List'
|
|
|
|
import axios from 'axios'
|
|
|
|
export default {
|
|
components: {
|
|
RemoteSearchForm,
|
|
ArtistCard,
|
|
AlbumCard,
|
|
TrackTable,
|
|
Pagination,
|
|
PlaylistCardList,
|
|
RadioCard,
|
|
TagsList
|
|
},
|
|
props: {
|
|
initialId: { type: String, required: false, default: '' },
|
|
initialType: { type: String, required: false, default: '' },
|
|
initialQuery: { type: String, required: false, default: '' },
|
|
initialPage: { type: Number, required: false, default: 0 }
|
|
},
|
|
data () {
|
|
return {
|
|
query: this.initialQuery,
|
|
type: this.initialType,
|
|
page: this.initialPage,
|
|
results: {
|
|
artists: null,
|
|
albums: null,
|
|
tracks: null,
|
|
playlists: null,
|
|
radios: null,
|
|
tags: null,
|
|
podcasts: null,
|
|
series: null
|
|
},
|
|
isLoading: false,
|
|
paginateBy: 25
|
|
}
|
|
},
|
|
computed: {
|
|
labels () {
|
|
const submitSearch = this.$pgettext('Content/Search/Button.Label/Verb', 'Submit Search Query')
|
|
let title = this.$pgettext('Content/Search/Input.Label/Noun', 'Search')
|
|
if (this.initialId) {
|
|
title = this.$pgettext('Head/Fetch/Title', 'Search a remote object')
|
|
if (this.type === 'rss') {
|
|
title = this.$pgettext('Head/Fetch/Title', 'Subscribe to a podcast RSS feed')
|
|
}
|
|
}
|
|
return {
|
|
title,
|
|
submitSearch
|
|
}
|
|
},
|
|
axiosParams () {
|
|
const params = new URLSearchParams()
|
|
params.append('q', this.query)
|
|
params.append('page', this.page)
|
|
params.append('page_size', this.paginateBy)
|
|
if (this.currentType.contentCategory !== undefined) { params.append('content_category', this.currentType.contentCategory) };
|
|
if (this.currentType.includeChannels !== undefined) { params.append('include_channels', this.currentType.includeChannels) };
|
|
return params
|
|
},
|
|
types () {
|
|
return [
|
|
{
|
|
id: 'artists',
|
|
label: this.$pgettext('*/*/*/Noun', 'Artists'),
|
|
includeChannels: true,
|
|
contentCategory: 'music'
|
|
},
|
|
{
|
|
id: 'albums',
|
|
label: this.$pgettext('*/*/*', 'Albums'),
|
|
includeChannels: true,
|
|
contentCategory: 'music'
|
|
},
|
|
{
|
|
id: 'tracks',
|
|
label: this.$pgettext('*/*/*', 'Tracks')
|
|
},
|
|
{
|
|
id: 'playlists',
|
|
label: this.$pgettext('*/*/*', 'Playlists')
|
|
},
|
|
{
|
|
id: 'radios',
|
|
label: this.$pgettext('*/*/*', 'Radios'),
|
|
endpoint: 'radios/radios'
|
|
},
|
|
{
|
|
id: 'tags',
|
|
label: this.$pgettext('*/*/*', 'Tags')
|
|
},
|
|
{
|
|
id: 'podcasts',
|
|
label: this.$pgettext('*/*/*', 'Podcasts'),
|
|
endpoint: '/artists',
|
|
contentCategory: 'podcast',
|
|
includeChannels: true
|
|
},
|
|
{
|
|
id: 'series',
|
|
label: this.$pgettext('*/*/*', 'Series'),
|
|
endpoint: '/albums',
|
|
includeChannels: true,
|
|
contentCategory: 'podcast'
|
|
}
|
|
]
|
|
},
|
|
currentType () {
|
|
return this.types.filter(t => {
|
|
return t.id === this.type
|
|
})[0]
|
|
},
|
|
currentResults () {
|
|
return this.results[this.currentType.id]
|
|
}
|
|
},
|
|
watch: {
|
|
async type () {
|
|
this.page = 1
|
|
this.updateQueryString()
|
|
await this.search()
|
|
},
|
|
async page () {
|
|
this.updateQueryString()
|
|
await this.search()
|
|
},
|
|
'$route.query.q': async function (v) {
|
|
this.query = v
|
|
this.updateQueryString()
|
|
await this.search()
|
|
}
|
|
},
|
|
created () {
|
|
this.search()
|
|
},
|
|
methods: {
|
|
async search () {
|
|
this.updateQueryString()
|
|
if (!this.query) {
|
|
this.types.forEach(t => {
|
|
this.results[t.id] = null
|
|
})
|
|
return
|
|
}
|
|
this.isLoading = true
|
|
const response = await axios.get(
|
|
this.currentType.endpoint || this.currentType.id,
|
|
{ params: this.axiosParams }
|
|
)
|
|
this.results[this.currentType.id] = response.data
|
|
this.isLoading = false
|
|
this.types.forEach(t => {
|
|
if (t.id !== this.currentType.id) {
|
|
axios.get(t.endpoint || t.id, {
|
|
params: {
|
|
q: this.query,
|
|
page_size: 1,
|
|
content_category: t.contentCategory,
|
|
include_channels: t.includeChannels
|
|
}
|
|
}).then(response => {
|
|
this.results[t.id] = response.data
|
|
})
|
|
}
|
|
})
|
|
},
|
|
updateQueryString: function () {
|
|
history.pushState(
|
|
{},
|
|
null,
|
|
this.$route.path + '?' + new URLSearchParams(
|
|
{
|
|
q: this.query,
|
|
page: this.page,
|
|
type: this.type
|
|
}).toString()
|
|
)
|
|
}
|
|
}
|
|
}
|
|
</script>
|