Merge branch 'podcast-search-capabilities' into 'develop'
Podcast search capabilities See merge request funkwhale/funkwhale!1252
This commit is contained in:
commit
7213d9327c
|
@ -103,6 +103,7 @@ class ArtistFilter(
|
||||||
playable = filters.BooleanFilter(field_name="_", method="filter_playable")
|
playable = filters.BooleanFilter(field_name="_", method="filter_playable")
|
||||||
has_albums = filters.BooleanFilter(field_name="_", method="filter_has_albums")
|
has_albums = filters.BooleanFilter(field_name="_", method="filter_has_albums")
|
||||||
tag = TAG_FILTER
|
tag = TAG_FILTER
|
||||||
|
content_category = filters.CharFilter("content_category")
|
||||||
scope = common_filters.ActorScopeFilter(
|
scope = common_filters.ActorScopeFilter(
|
||||||
actor_field="tracks__uploads__library__actor",
|
actor_field="tracks__uploads__library__actor",
|
||||||
distinct=True,
|
distinct=True,
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Added new search functions to allow users to more easily search for podcasts in the UI.
|
|
@ -118,10 +118,9 @@ Scope:
|
||||||
- "actor:alice@example.com"
|
- "actor:alice@example.com"
|
||||||
- "domain:example.com"
|
- "domain:example.com"
|
||||||
|
|
||||||
ContentType:
|
ContentCategory:
|
||||||
name: "content_type"
|
name: "content_category"
|
||||||
in: "query"
|
in: "query"
|
||||||
default: "all"
|
|
||||||
description: |
|
description: |
|
||||||
Limits the results to those whose artist content type matches the query.
|
Limits the results to those whose artist content type matches the query.
|
||||||
|
|
||||||
|
|
|
@ -407,6 +407,7 @@ paths:
|
||||||
- $ref: "./api/parameters.yml#/PageSize"
|
- $ref: "./api/parameters.yml#/PageSize"
|
||||||
- $ref: "./api/parameters.yml#/Related"
|
- $ref: "./api/parameters.yml#/Related"
|
||||||
- $ref: "./api/parameters.yml#/Scope"
|
- $ref: "./api/parameters.yml#/Scope"
|
||||||
|
- $ref: "./api/parameters.yml#/ContentCategory"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
content:
|
content:
|
||||||
|
@ -505,7 +506,7 @@ paths:
|
||||||
- $ref: "./api/parameters.yml#/PageSize"
|
- $ref: "./api/parameters.yml#/PageSize"
|
||||||
- $ref: "./api/parameters.yml#/Related"
|
- $ref: "./api/parameters.yml#/Related"
|
||||||
- $ref: "./api/parameters.yml#/Scope"
|
- $ref: "./api/parameters.yml#/Scope"
|
||||||
- $ref: "./api/parameters.yml#/ContentType"
|
- $ref: "./api/parameters.yml#/ContentCategory"
|
||||||
|
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
|
|
|
@ -114,20 +114,22 @@
|
||||||
<div class="ui small hidden divider"></div>
|
<div class="ui small hidden divider"></div>
|
||||||
<section :class="['ui', 'bottom', 'attached', {active: selectedTab === 'library'}, 'tab']" :aria-label="labels.mainMenu">
|
<section :class="['ui', 'bottom', 'attached', {active: selectedTab === 'library'}, 'tab']" :aria-label="labels.mainMenu">
|
||||||
<nav class="ui vertical large fluid inverted menu" role="navigation" :aria-label="labels.mainMenu">
|
<nav class="ui vertical large fluid inverted menu" role="navigation" :aria-label="labels.mainMenu">
|
||||||
<div :class="[{collapsed: !exploreExpanded}, 'collaspable item']">
|
<div :class="[{collapsed: !exploreExpanded}, 'collapsible item']">
|
||||||
<h2 class="header" role="button" @click="exploreExpanded = true" tabindex="0" @focus="exploreExpanded = true">
|
<h2 class="header" role="button" @click="exploreExpanded = true" tabindex="0" @focus="exploreExpanded = true">
|
||||||
<translate translate-context="*/*/*/Verb">Explore</translate>
|
<translate translate-context="*/*/*/Verb">Explore</translate>
|
||||||
<i class="angle right icon" v-if="!exploreExpanded"></i>
|
<i class="angle right icon" v-if="!exploreExpanded"></i>
|
||||||
</h2>
|
</h2>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
|
<router-link class="item" :to="{name: 'search'}"><i class="search icon"></i><translate translate-context="Sidebar/Navigation/List item.Link/Verb">Search</translate></router-link>
|
||||||
<router-link class="item" :exact="true" :to="{name: 'library.index'}"><i class="music icon"></i><translate translate-context="Sidebar/Navigation/List item.Link/Verb">Browse</translate></router-link>
|
<router-link class="item" :exact="true" :to="{name: 'library.index'}"><i class="music icon"></i><translate translate-context="Sidebar/Navigation/List item.Link/Verb">Browse</translate></router-link>
|
||||||
|
<router-link class="item" :to="{name: 'library.podcasts.browse'}"><i class="podcast icon"></i><translate translate-context="*/*/*">Podcasts</translate></router-link>
|
||||||
<router-link class="item" :to="{name: 'library.albums.browse'}"><i class="compact disc icon"></i><translate translate-context="*/*/*">Albums</translate></router-link>
|
<router-link class="item" :to="{name: 'library.albums.browse'}"><i class="compact disc icon"></i><translate translate-context="*/*/*">Albums</translate></router-link>
|
||||||
<router-link class="item" :to="{name: 'library.artists.browse'}"><i class="user icon"></i><translate translate-context="*/*/*">Artists</translate></router-link>
|
<router-link class="item" :to="{name: 'library.artists.browse'}"><i class="user icon"></i><translate translate-context="*/*/*">Artists</translate></router-link>
|
||||||
<router-link class="item" :to="{name: 'library.playlists.browse'}"><i class="list icon"></i><translate translate-context="*/*/*">Playlists</translate></router-link>
|
<router-link class="item" :to="{name: 'library.playlists.browse'}"><i class="list icon"></i><translate translate-context="*/*/*">Playlists</translate></router-link>
|
||||||
<router-link class="item" :to="{name: 'library.radios.browse'}"><i class="feed icon"></i><translate translate-context="*/*/*">Radios</translate></router-link>
|
<router-link class="item" :to="{name: 'library.radios.browse'}"><i class="feed icon"></i><translate translate-context="*/*/*">Radios</translate></router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="[{collapsed: !myLibraryExpanded}, 'collaspable item']" v-if="$store.state.auth.authenticated">
|
<div :class="[{collapsed: !myLibraryExpanded}, 'collapsible item']" v-if="$store.state.auth.authenticated">
|
||||||
<h3 class="header" role="button" @click="myLibraryExpanded = true" tabindex="0" @focus="myLibraryExpanded = true">
|
<h3 class="header" role="button" @click="myLibraryExpanded = true" tabindex="0" @focus="myLibraryExpanded = true">
|
||||||
<translate translate-context="*/*/*/Noun">My Library</translate>
|
<translate translate-context="*/*/*/Noun">My Library</translate>
|
||||||
<i class="angle right icon" v-if="!myLibraryExpanded"></i>
|
<i class="angle right icon" v-if="!myLibraryExpanded"></i>
|
||||||
|
@ -225,7 +227,9 @@ export default {
|
||||||
},
|
},
|
||||||
focusedMenu () {
|
focusedMenu () {
|
||||||
let mapping = {
|
let mapping = {
|
||||||
|
"search": 'exploreExpanded',
|
||||||
"library.index": 'exploreExpanded',
|
"library.index": 'exploreExpanded',
|
||||||
|
"library.podcasts.browse": 'exploreExpanded',
|
||||||
"library.albums.browse": 'exploreExpanded',
|
"library.albums.browse": 'exploreExpanded',
|
||||||
"library.albums.detail": 'exploreExpanded',
|
"library.albums.detail": 'exploreExpanded',
|
||||||
"library.artists.browse": 'exploreExpanded',
|
"library.artists.browse": 'exploreExpanded',
|
||||||
|
|
|
@ -157,7 +157,9 @@ export default {
|
||||||
page: this.page,
|
page: this.page,
|
||||||
tag: this.tags,
|
tag: this.tags,
|
||||||
paginateBy: this.paginateBy,
|
paginateBy: this.paginateBy,
|
||||||
ordering: this.getOrderingAsString()
|
ordering: this.getOrderingAsString(),
|
||||||
|
content_category: 'music',
|
||||||
|
include_channels: true,
|
||||||
}).toString()
|
}).toString()
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -175,6 +177,7 @@ export default {
|
||||||
playable: "true",
|
playable: "true",
|
||||||
tag: this.tags,
|
tag: this.tags,
|
||||||
include_channels: "true",
|
include_channels: "true",
|
||||||
|
content_category: 'music',
|
||||||
}
|
}
|
||||||
logger.default.debug("Fetching artists")
|
logger.default.debug("Fetching artists")
|
||||||
axios.get(
|
axios.get(
|
||||||
|
|
|
@ -0,0 +1,247 @@
|
||||||
|
<template>
|
||||||
|
<main v-title="labels.title">
|
||||||
|
<section class="ui vertical stripe segment">
|
||||||
|
<h2 class="ui header">
|
||||||
|
<translate translate-context="Content/Podcasts/Title">Browsing Podcasts</translate>
|
||||||
|
</h2>
|
||||||
|
<form :class="['ui', {'loading': isLoading}, 'form']" @submit.prevent="updatePage();updateQueryString();fetchData()">
|
||||||
|
<div class="fields">
|
||||||
|
<div class="field">
|
||||||
|
<label for="artist-search">
|
||||||
|
<translate translate-context="Content/Search/Input.Label/Noun">Podcast Title</translate>
|
||||||
|
</label>
|
||||||
|
<div class="ui action input">
|
||||||
|
<input id="artist-search" type="text" name="search" v-model="query" :placeholder="labels.searchPlaceholder"/>
|
||||||
|
<button class="ui icon button" type="submit" :aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')">
|
||||||
|
<i class="search icon"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="tags-search"><translate translate-context="*/*/*/Noun">Tags</translate></label>
|
||||||
|
<tags-selector v-model="tags"></tags-selector>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="artist-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
|
||||||
|
<select id="artist-ordering" class="ui dropdown" v-model="ordering">
|
||||||
|
<option v-for="option in orderingOptions" :value="option[0]">
|
||||||
|
{{ sharedLabels.filters[option[1]] }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="artist-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
|
||||||
|
<select id="artist-ordering-direction" class="ui dropdown" v-model="orderingDirection">
|
||||||
|
<option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
|
||||||
|
<option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="artist-results"><translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate></label>
|
||||||
|
<select id="artist-results" class="ui dropdown" v-model="paginateBy">
|
||||||
|
<option :value="parseInt(12)">12</option>
|
||||||
|
<option :value="parseInt(30)">30</option>
|
||||||
|
<option :value="parseInt(50)">50</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div class="ui hidden divider"></div>
|
||||||
|
<div v-if="result && result.results.length > 0" class="ui five app-cards cards">
|
||||||
|
<div v-if="isLoading" class="ui inverted active dimmer">
|
||||||
|
<div class="ui loader"></div>
|
||||||
|
</div>
|
||||||
|
<artist-card :artist="artist" v-for="artist in result.results" :key="artist.id"></artist-card>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="!isLoading" class="ui placeholder segment sixteen wide column" style="text-align: center; display: flex; align-items: center">
|
||||||
|
<div class="ui icon header">
|
||||||
|
<i class="podcast icon"></i>
|
||||||
|
<translate translate-context="Content/Artists/Placeholder">
|
||||||
|
No results matching your query
|
||||||
|
</translate>
|
||||||
|
</div>
|
||||||
|
<router-link
|
||||||
|
v-if="$store.state.auth.authenticated"
|
||||||
|
:to="{name: 'content.index'}"
|
||||||
|
class="ui success button labeled icon">
|
||||||
|
<i class="upload icon"></i>
|
||||||
|
<translate translate-context="Content/*/Verb">
|
||||||
|
Create a Channel
|
||||||
|
</translate>
|
||||||
|
</router-link>
|
||||||
|
<h1 v-if ="$store.state.auth.authenticated" class="ui with-actions header">
|
||||||
|
<div class="actions">
|
||||||
|
<a @click.stop.prevent="showSubscribeModal = true">
|
||||||
|
<i class="plus icon"></i>
|
||||||
|
<translate translate-context="Content/Profile/Button">Subscribe to feed</translate>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div class="ui center aligned basic segment">
|
||||||
|
<pagination
|
||||||
|
v-if="result && result.count > paginateBy"
|
||||||
|
@page-changed="selectPage"
|
||||||
|
:current="page"
|
||||||
|
:paginate-by="paginateBy"
|
||||||
|
:total="result.count"
|
||||||
|
></pagination>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<modal class="tiny" :show.sync="showSubscribeModal" :fullscreen="false">
|
||||||
|
<h2 class="header">
|
||||||
|
<translate translate-context="*/*/*/Noun">Subscription</translate>
|
||||||
|
</h2>
|
||||||
|
<div class="scrolling content" ref="modalContent">
|
||||||
|
<remote-search-form
|
||||||
|
type="rss"
|
||||||
|
:show-submit="false"
|
||||||
|
:standalone="false"
|
||||||
|
@subscribed="showSubscribeModal = false; fetchData()"
|
||||||
|
:redirect="false"></remote-search-form>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="ui basic deny button">
|
||||||
|
<translate translate-context="*/*/Button.Label/Verb">Cancel</translate>
|
||||||
|
</button>
|
||||||
|
<button form="remote-search" type="submit" class="ui primary button">
|
||||||
|
<i class="bookmark icon"></i>
|
||||||
|
<translate translate-context="*/*/*/Verb">Subscribe</translate>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</modal>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import qs from 'qs'
|
||||||
|
import axios from "axios"
|
||||||
|
import _ from "@/lodash"
|
||||||
|
import $ from "jquery"
|
||||||
|
|
||||||
|
import logger from "@/logging"
|
||||||
|
|
||||||
|
import OrderingMixin from "@/components/mixins/Ordering"
|
||||||
|
import PaginationMixin from "@/components/mixins/Pagination"
|
||||||
|
import TranslationsMixin from "@/components/mixins/Translations"
|
||||||
|
import ArtistCard from "@/components/audio/artist/Card"
|
||||||
|
import Pagination from "@/components/Pagination"
|
||||||
|
import TagsSelector from '@/components/library/TagsSelector'
|
||||||
|
import Modal from '@/components/semantic/Modal'
|
||||||
|
import RemoteSearchForm from "@/components/RemoteSearchForm"
|
||||||
|
|
||||||
|
const FETCH_URL = "artists/"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
|
||||||
|
props: {
|
||||||
|
defaultQuery: { type: String, required: false, default: "" },
|
||||||
|
defaultTags: { type: Array, required: false, default: () => { return [] } },
|
||||||
|
scope: { type: String, required: false, default: "all" },
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ArtistCard,
|
||||||
|
Pagination,
|
||||||
|
TagsSelector,
|
||||||
|
RemoteSearchForm,
|
||||||
|
Modal,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoading: true,
|
||||||
|
result: null,
|
||||||
|
page: parseInt(this.defaultPage),
|
||||||
|
query: this.defaultQuery,
|
||||||
|
tags: (this.defaultTags || []).filter((t) => { return t.length > 0 }),
|
||||||
|
orderingOptions: [["creation_date", "creation_date"], ["name", "name"]],
|
||||||
|
showSubscribeModal: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
$(".ui.dropdown").dropdown()
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
labels() {
|
||||||
|
let searchPlaceholder = this.$pgettext('Content/Search/Input.Placeholder', "Search…")
|
||||||
|
let title = this.$pgettext('*/*/*/Noun', "Podcasts")
|
||||||
|
return {
|
||||||
|
searchPlaceholder,
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateQueryString: function() {
|
||||||
|
history.pushState(
|
||||||
|
{},
|
||||||
|
null,
|
||||||
|
this.$route.path + '?' + new URLSearchParams(
|
||||||
|
{
|
||||||
|
query: this.query,
|
||||||
|
page: this.page,
|
||||||
|
tag: this.tags,
|
||||||
|
paginateBy: this.paginateBy,
|
||||||
|
ordering: this.getOrderingAsString(),
|
||||||
|
include_channels: true,
|
||||||
|
content_category: 'podcast',
|
||||||
|
}).toString()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
fetchData: function() {
|
||||||
|
var self = this
|
||||||
|
this.isLoading = true
|
||||||
|
let url = FETCH_URL
|
||||||
|
let params = {
|
||||||
|
scope: this.scope,
|
||||||
|
page: this.page,
|
||||||
|
page_size: this.paginateBy,
|
||||||
|
has_albums: this.excludeCompilation,
|
||||||
|
q: this.query,
|
||||||
|
ordering: this.getOrderingAsString(),
|
||||||
|
playable: "true",
|
||||||
|
tag: this.tags,
|
||||||
|
include_channels: "true",
|
||||||
|
content_category: 'podcast',
|
||||||
|
}
|
||||||
|
logger.default.debug("Fetching artists")
|
||||||
|
axios.get(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
params: params,
|
||||||
|
paramsSerializer: function(params) {
|
||||||
|
return qs.stringify(params, { indices: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).then(response => {
|
||||||
|
self.result = response.data
|
||||||
|
self.isLoading = false
|
||||||
|
}, error => {
|
||||||
|
self.result = null
|
||||||
|
self.isLoading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
selectPage: function(page) {
|
||||||
|
this.page = page
|
||||||
|
},
|
||||||
|
updatePage() {
|
||||||
|
this.page = this.defaultPage
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
page() {
|
||||||
|
this.updateQueryString()
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
"$store.state.moderation.lastUpdate": function () {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
excludeCompilation() {
|
||||||
|
this.fetchData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -637,6 +637,23 @@ export default new Router({
|
||||||
defaultPage: route.query.page
|
defaultPage: route.query.page
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "podcasts/",
|
||||||
|
name: "library.podcasts.browse",
|
||||||
|
component: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "podcasts" */ "@/components/library/Podcasts"
|
||||||
|
),
|
||||||
|
props: route => ({
|
||||||
|
defaultOrdering: route.query.ordering,
|
||||||
|
defaultQuery: route.query.query,
|
||||||
|
defaultTags: Array.isArray(route.query.tag || [])
|
||||||
|
? route.query.tag
|
||||||
|
: [route.query.tag],
|
||||||
|
defaultPaginateBy: route.query.paginateBy,
|
||||||
|
defaultPage: route.query.page
|
||||||
|
})
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "me/albums",
|
path: "me/albums",
|
||||||
name: "library.albums.me",
|
name: "library.albums.me",
|
||||||
|
|
|
@ -45,6 +45,11 @@ export default {
|
||||||
orderingDirection: "-",
|
orderingDirection: "-",
|
||||||
ordering: "creation_date",
|
ordering: "creation_date",
|
||||||
},
|
},
|
||||||
|
"library.podcasts.browse": {
|
||||||
|
paginateBy: 30,
|
||||||
|
orderingDirection: "-",
|
||||||
|
ordering: "creation_date",
|
||||||
|
},
|
||||||
"library.radios.browse": {
|
"library.radios.browse": {
|
||||||
paginateBy: 12,
|
paginateBy: 12,
|
||||||
orderingDirection: "-",
|
orderingDirection: "-",
|
||||||
|
|
|
@ -1,244 +1,237 @@
|
||||||
|
|
||||||
.ui.wide.left.sidebar {
|
.ui.wide.left.sidebar {
|
||||||
@include media(">desktop") {
|
@include media(">desktop") {
|
||||||
width: $desktop-sidebar-width;
|
width: $desktop-sidebar-width;
|
||||||
}
|
}
|
||||||
|
@include media(">widedesktop") {
|
||||||
@include media(">widedesktop") {
|
width: $widedesktop-sidebar-width;
|
||||||
width: $widedesktop-sidebar-width;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
.logo {
|
.logo {
|
||||||
&.bordered.icon {
|
&.bordered.icon {
|
||||||
padding: .5em .41em !important;
|
padding: .5em .41em !important;
|
||||||
|
}
|
||||||
|
path {
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
path {
|
.tab {
|
||||||
fill: white;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.tab {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.component-sidebar {
|
.component-sidebar {
|
||||||
.ui.search .input {
|
.ui.search .input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
.prompt {
|
.prompt {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.ui.search .results {
|
||||||
.ui.search .results {
|
vertical-align: middle;
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
.ui.search .name {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.sidebar {
|
|
||||||
overflow-y: visible !important;
|
|
||||||
background: var(--sidebar-background);
|
|
||||||
z-index: 1;
|
|
||||||
@include media(">desktop") {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding-bottom: 4em;
|
|
||||||
}
|
}
|
||||||
> nav {
|
.ui.search .name {
|
||||||
flex-grow: 1;
|
vertical-align: middle;
|
||||||
overflow-y: auto;
|
|
||||||
}
|
}
|
||||||
@include media(">desktop") {
|
&.sidebar {
|
||||||
.menu .item.collapse-button-wrapper {
|
overflow-y: visible !important;
|
||||||
|
background: var(--sidebar-background);
|
||||||
|
z-index: 1;
|
||||||
|
@include media(">desktop") {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-bottom: 4em;
|
||||||
|
}
|
||||||
|
>nav {
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
@include media(">desktop") {
|
||||||
|
.menu .item.collapse-button-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.collapse.button {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include media("<=desktop") {
|
||||||
|
position: static !important;
|
||||||
|
width: 100% !important;
|
||||||
|
&.collapsed {
|
||||||
|
.player-wrapper,
|
||||||
|
.search,
|
||||||
|
.signup.segment,
|
||||||
|
nav.secondary {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>div {
|
||||||
|
margin: 0;
|
||||||
|
background-color: var(--sidebar-background);
|
||||||
|
}
|
||||||
|
.menu.vertical {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ui.vertical.menu {
|
||||||
|
.item .item {
|
||||||
|
font-size: 1em;
|
||||||
|
>i.icon {
|
||||||
|
float: none;
|
||||||
|
margin: 0 0.5em 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item.active {
|
||||||
|
border-right: 5px solid var(--vibrant-color);
|
||||||
|
border-radius: 0 !important;
|
||||||
|
background: var(--sidebar-active-item-background) !important;
|
||||||
|
}
|
||||||
|
.item.collapsed {
|
||||||
|
&:not(:focus)>.menu {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.collapsible.item .header {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ui.secondary.menu {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
.tabs {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
justify-content: space-between;
|
||||||
|
@include media("<=desktop") {
|
||||||
|
max-height: 500px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ui.tab.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.tab[data-tab="queue"] {
|
||||||
|
flex-direction: column;
|
||||||
|
tr {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
td:nth-child(2) {
|
||||||
|
width: 55px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item .header .angle.icon {
|
||||||
|
float: right;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.tab[data-tab="library"] {
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
>.menu {
|
||||||
|
flex: 1;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
>.player-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sidebar .segment {
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.ui.menu .item.inline.admin-dropdown.dropdown>.menu {
|
||||||
|
left: 0;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
.ui.segment.header-wrapper {
|
||||||
|
background: var(--sidebar-header-background);
|
||||||
|
color: var(--sidebar-header-color);
|
||||||
|
box-shadow: var(--sidebar-header-box-shadow);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
display: flex;
|
||||||
.collapse.button {
|
justify-content: space-between;
|
||||||
display: none !important;
|
align-items: center;
|
||||||
}
|
height: 4em;
|
||||||
}
|
|
||||||
@include media("<=desktop") {
|
|
||||||
position: static !important;
|
|
||||||
width: 100% !important;
|
|
||||||
&.collapsed {
|
|
||||||
.player-wrapper,
|
|
||||||
.search,
|
|
||||||
.signup.segment,
|
|
||||||
nav.secondary {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> div {
|
|
||||||
margin: 0;
|
|
||||||
background-color: var(--sidebar-background);
|
|
||||||
}
|
|
||||||
.menu.vertical {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.vertical.menu {
|
|
||||||
.item .item {
|
|
||||||
font-size: 1em;
|
|
||||||
> i.icon {
|
|
||||||
float: none;
|
|
||||||
margin: 0 0.5em 0 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.item.active {
|
|
||||||
border-right: 5px solid var(--vibrant-color);
|
|
||||||
border-radius: 0 !important;
|
|
||||||
background: var(--sidebar-active-item-background) !important;
|
|
||||||
}
|
|
||||||
.item.collapsed {
|
|
||||||
&:not(:focus) > .menu {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
nav {
|
||||||
}
|
>.item,
|
||||||
.collaspable.item .header {
|
>.menu>.item>.item {
|
||||||
cursor: pointer;
|
&:hover {
|
||||||
}
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
.ui.secondary.menu {
|
}
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
.tabs {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow-y: auto;
|
|
||||||
justify-content: space-between;
|
|
||||||
@include media("<=desktop") {
|
|
||||||
max-height: 500px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ui.tab.active {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.tab[data-tab="queue"] {
|
|
||||||
flex-direction: column;
|
|
||||||
tr {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
td:nth-child(2) {
|
|
||||||
width: 55px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.item .header .angle.icon {
|
|
||||||
float: right;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.tab[data-tab="library"] {
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
> .menu {
|
|
||||||
flex: 1;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
> .player-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sidebar .segment {
|
|
||||||
margin: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.menu .item.inline.admin-dropdown.dropdown > .menu {
|
|
||||||
left: 0;
|
|
||||||
right: auto;
|
|
||||||
}
|
|
||||||
.ui.segment.header-wrapper {
|
|
||||||
background: var(--sidebar-header-background);
|
|
||||||
color: var(--sidebar-header-color);
|
|
||||||
box-shadow: var(--sidebar-header-box-shadow);
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
height: 4em;
|
|
||||||
margin-bottom: 0;
|
|
||||||
nav {
|
|
||||||
> .item, > .menu > .item > .item {
|
|
||||||
&:hover {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
nav.top.title-menu {
|
||||||
|
flex-grow: 1;
|
||||||
nav.top.title-menu {
|
.item {
|
||||||
flex-grow: 1;
|
font-size: 1.5em;
|
||||||
.item {
|
}
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
}
|
||||||
}
|
.logo {
|
||||||
|
cursor: pointer;
|
||||||
.logo {
|
display: inline-block;
|
||||||
cursor: pointer;
|
margin: 0px;
|
||||||
display: inline-block;
|
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.collapsed .search-wrapper {
|
|
||||||
@include media("<desktop") {
|
|
||||||
padding: 0;
|
|
||||||
}
|
}
|
||||||
}
|
&.collapsed .search-wrapper {
|
||||||
.ui.search {
|
@include media("<desktop") {
|
||||||
display: flex;
|
padding: 0;
|
||||||
}
|
}
|
||||||
.ui.message.black {
|
|
||||||
background: var(--sidebar-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.mini.image {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
nav.top {
|
|
||||||
align-items: self-end;
|
|
||||||
padding: 0.5em 0;
|
|
||||||
> .item, > .right.menu > .item {
|
|
||||||
// color: rgba(255, 255, 255, 0.9) !important;
|
|
||||||
font-size: 1.2em;
|
|
||||||
&:hover, > .dropdown > .icon {
|
|
||||||
// color: rgba(255, 255, 255, 0.9) !important;
|
|
||||||
}
|
|
||||||
> .label, > .dropdown > .label {
|
|
||||||
font-size: 0.5em;
|
|
||||||
right: 1.7em;
|
|
||||||
bottom: -0.5em;
|
|
||||||
z-index: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
.ui.search {
|
||||||
.ui.user-dropdown > .text > .label {
|
display: flex;
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
.logo-wrapper {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 auto;
|
|
||||||
@include media("<desktop") {
|
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
img {
|
.ui.message.black {
|
||||||
height: 1em;
|
background: var(--sidebar-background);
|
||||||
display: inline-block;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
}
|
||||||
@include media(">tablet") {
|
.ui.mini.image {
|
||||||
img {
|
width: 100%;
|
||||||
height: 1.5em;
|
}
|
||||||
}
|
nav.top {
|
||||||
|
align-items: self-end;
|
||||||
|
padding: 0.5em 0;
|
||||||
|
>.item,
|
||||||
|
>.right.menu>.item {
|
||||||
|
// color: rgba(255, 255, 255, 0.9) !important;
|
||||||
|
font-size: 1.2em;
|
||||||
|
&:hover,
|
||||||
|
>.dropdown>.icon {
|
||||||
|
// color: rgba(255, 255, 255, 0.9) !important;
|
||||||
|
}
|
||||||
|
>.label,
|
||||||
|
>.dropdown>.label {
|
||||||
|
font-size: 0.5em;
|
||||||
|
right: 1.7em;
|
||||||
|
bottom: -0.5em;
|
||||||
|
z-index: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ui.user-dropdown>.text>.label {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
.logo-wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 auto;
|
||||||
|
@include media("<desktop") {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
height: 1em;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
@include media(">tablet") {
|
||||||
|
img {
|
||||||
|
height: 1.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -43,11 +43,11 @@
|
||||||
|
|
||||||
<empty-state v-else-if="!currentResults || currentResults.count === 0" @refresh="search" :refresh="true"></empty-state>
|
<empty-state v-else-if="!currentResults || currentResults.count === 0" @refresh="search" :refresh="true"></empty-state>
|
||||||
|
|
||||||
<div v-else-if="type === 'artists'" class="ui five app-cards cards">
|
<div v-else-if="type === 'artists' || type === 'podcasts'" class="ui five app-cards cards">
|
||||||
<artist-card :artist="artist" v-for="artist in currentResults.results" :key="artist.id"></artist-card>
|
<artist-card :artist="artist" v-for="artist in currentResults.results" :key="artist.id"></artist-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="type === 'albums'" class="ui five app-cards cards">
|
<div v-else-if="type === 'albums' || type === 'series'" class="ui five app-cards cards">
|
||||||
<album-card
|
<album-card
|
||||||
v-for="album in currentResults.results"
|
v-for="album in currentResults.results"
|
||||||
:key="album.id"
|
:key="album.id"
|
||||||
|
@ -124,6 +124,8 @@ export default {
|
||||||
playlists: null,
|
playlists: null,
|
||||||
radios: null,
|
radios: null,
|
||||||
tags: null,
|
tags: null,
|
||||||
|
podcasts: null,
|
||||||
|
series: null,
|
||||||
},
|
},
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
paginateBy: 25,
|
paginateBy: 25,
|
||||||
|
@ -147,15 +149,28 @@ export default {
|
||||||
submitSearch
|
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 () {
|
types () {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: 'artists',
|
id: 'artists',
|
||||||
label: this.$pgettext("*/*/*/Noun", "Artists"),
|
label: this.$pgettext("*/*/*/Noun", "Artists"),
|
||||||
|
includeChannels: true,
|
||||||
|
contentCategory: 'music',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'albums',
|
id: 'albums',
|
||||||
label: this.$pgettext("*/*/*", "Albums"),
|
label: this.$pgettext("*/*/*", "Albums"),
|
||||||
|
includeChannels: true,
|
||||||
|
contentCategory: 'music',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'tracks',
|
id: 'tracks',
|
||||||
|
@ -174,6 +189,20 @@ export default {
|
||||||
id: 'tags',
|
id: 'tags',
|
||||||
label: this.$pgettext("*/*/*", "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 () {
|
currentType () {
|
||||||
|
@ -197,13 +226,18 @@ export default {
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
let response = await axios.get(
|
let response = await axios.get(
|
||||||
this.currentType.endpoint || this.currentType.id,
|
this.currentType.endpoint || this.currentType.id,
|
||||||
{params: {q: this.query, page: this.page, page_size: this.paginateBy}}
|
{params: this.axiosParams}
|
||||||
)
|
)
|
||||||
this.results[this.currentType.id] = response.data
|
this.results[this.currentType.id] = response.data
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
this.types.forEach(t => {
|
this.types.forEach(t => {
|
||||||
if (t.id != this.currentType.id) {
|
if (t.id != this.currentType.id) {
|
||||||
axios.get(t.endpoint || t.id, {params: {q: this.query, page_size: 1}}).then(response => {
|
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
|
this.results[t.id] = response.data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue