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")
|
||||
has_albums = filters.BooleanFilter(field_name="_", method="filter_has_albums")
|
||||
tag = TAG_FILTER
|
||||
content_category = filters.CharFilter("content_category")
|
||||
scope = common_filters.ActorScopeFilter(
|
||||
actor_field="tracks__uploads__library__actor",
|
||||
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"
|
||||
- "domain:example.com"
|
||||
|
||||
ContentType:
|
||||
name: "content_type"
|
||||
ContentCategory:
|
||||
name: "content_category"
|
||||
in: "query"
|
||||
default: "all"
|
||||
description: |
|
||||
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#/Related"
|
||||
- $ref: "./api/parameters.yml#/Scope"
|
||||
- $ref: "./api/parameters.yml#/ContentCategory"
|
||||
responses:
|
||||
200:
|
||||
content:
|
||||
|
@ -505,7 +506,7 @@ paths:
|
|||
- $ref: "./api/parameters.yml#/PageSize"
|
||||
- $ref: "./api/parameters.yml#/Related"
|
||||
- $ref: "./api/parameters.yml#/Scope"
|
||||
- $ref: "./api/parameters.yml#/ContentType"
|
||||
- $ref: "./api/parameters.yml#/ContentCategory"
|
||||
|
||||
responses:
|
||||
200:
|
||||
|
|
|
@ -114,20 +114,22 @@
|
|||
<div class="ui small hidden divider"></div>
|
||||
<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">
|
||||
<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">
|
||||
<translate translate-context="*/*/*/Verb">Explore</translate>
|
||||
<i class="angle right icon" v-if="!exploreExpanded"></i>
|
||||
</h2>
|
||||
<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" :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.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.radios.browse'}"><i class="feed icon"></i><translate translate-context="*/*/*">Radios</translate></router-link>
|
||||
</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">
|
||||
<translate translate-context="*/*/*/Noun">My Library</translate>
|
||||
<i class="angle right icon" v-if="!myLibraryExpanded"></i>
|
||||
|
@ -225,7 +227,9 @@ export default {
|
|||
},
|
||||
focusedMenu () {
|
||||
let mapping = {
|
||||
"search": 'exploreExpanded',
|
||||
"library.index": 'exploreExpanded',
|
||||
"library.podcasts.browse": 'exploreExpanded',
|
||||
"library.albums.browse": 'exploreExpanded',
|
||||
"library.albums.detail": 'exploreExpanded',
|
||||
"library.artists.browse": 'exploreExpanded',
|
||||
|
|
|
@ -157,7 +157,9 @@ export default {
|
|||
page: this.page,
|
||||
tag: this.tags,
|
||||
paginateBy: this.paginateBy,
|
||||
ordering: this.getOrderingAsString()
|
||||
ordering: this.getOrderingAsString(),
|
||||
content_category: 'music',
|
||||
include_channels: true,
|
||||
}).toString()
|
||||
)
|
||||
},
|
||||
|
@ -175,6 +177,7 @@ export default {
|
|||
playable: "true",
|
||||
tag: this.tags,
|
||||
include_channels: "true",
|
||||
content_category: 'music',
|
||||
}
|
||||
logger.default.debug("Fetching artists")
|
||||
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
|
||||
})
|
||||
},
|
||||
{
|
||||
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",
|
||||
name: "library.albums.me",
|
||||
|
|
|
@ -45,6 +45,11 @@ export default {
|
|||
orderingDirection: "-",
|
||||
ordering: "creation_date",
|
||||
},
|
||||
"library.podcasts.browse": {
|
||||
paginateBy: 30,
|
||||
orderingDirection: "-",
|
||||
ordering: "creation_date",
|
||||
},
|
||||
"library.radios.browse": {
|
||||
paginateBy: 12,
|
||||
orderingDirection: "-",
|
||||
|
|
|
@ -1,244 +1,237 @@
|
|||
|
||||
.ui.wide.left.sidebar {
|
||||
@include media(">desktop") {
|
||||
width: $desktop-sidebar-width;
|
||||
}
|
||||
|
||||
@include media(">widedesktop") {
|
||||
width: $widedesktop-sidebar-width;
|
||||
}
|
||||
@include media(">desktop") {
|
||||
width: $desktop-sidebar-width;
|
||||
}
|
||||
@include media(">widedesktop") {
|
||||
width: $widedesktop-sidebar-width;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.sidebar {
|
||||
.logo {
|
||||
&.bordered.icon {
|
||||
padding: .5em .41em !important;
|
||||
.logo {
|
||||
&.bordered.icon {
|
||||
padding: .5em .41em !important;
|
||||
}
|
||||
path {
|
||||
fill: white;
|
||||
}
|
||||
}
|
||||
path {
|
||||
fill: white;
|
||||
.tab {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
.tab {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.component-sidebar {
|
||||
.ui.search .input {
|
||||
flex: 1;
|
||||
.prompt {
|
||||
border-radius: 0;
|
||||
.ui.search .input {
|
||||
flex: 1;
|
||||
.prompt {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ui.search .results {
|
||||
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;
|
||||
.ui.search .results {
|
||||
vertical-align: middle;
|
||||
}
|
||||
> nav {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
.ui.search .name {
|
||||
vertical-align: middle;
|
||||
}
|
||||
@include media(">desktop") {
|
||||
.menu .item.collapse-button-wrapper {
|
||||
&.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 {
|
||||
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;
|
||||
}
|
||||
.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 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 4em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.collaspable.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;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 4em;
|
||||
margin-bottom: 0;
|
||||
nav {
|
||||
> .item, > .menu > .item > .item {
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
nav {
|
||||
>.item,
|
||||
>.menu>.item>.item {
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nav.top.title-menu {
|
||||
flex-grow: 1;
|
||||
.item {
|
||||
font-size: 1.5em;
|
||||
nav.top.title-menu {
|
||||
flex-grow: 1;
|
||||
.item {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
&.collapsed .search-wrapper {
|
||||
@include media("<desktop") {
|
||||
padding: 0;
|
||||
.logo {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin: 0px;
|
||||
}
|
||||
}
|
||||
.ui.search {
|
||||
display: flex;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
&.collapsed .search-wrapper {
|
||||
@include media("<desktop") {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ui.user-dropdown > .text > .label {
|
||||
margin-right: 0;
|
||||
}
|
||||
.logo-wrapper {
|
||||
display: inline-block;
|
||||
margin: 0 auto;
|
||||
@include media("<desktop") {
|
||||
margin: 0;
|
||||
.ui.search {
|
||||
display: flex;
|
||||
}
|
||||
img {
|
||||
height: 1em;
|
||||
display: inline-block;
|
||||
margin: 0 auto;
|
||||
.ui.message.black {
|
||||
background: var(--sidebar-background);
|
||||
}
|
||||
@include media(">tablet") {
|
||||
img {
|
||||
height: 1.5em;
|
||||
}
|
||||
.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.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>
|
||||
|
||||
<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>
|
||||
</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
|
||||
v-for="album in currentResults.results"
|
||||
:key="album.id"
|
||||
|
@ -124,6 +124,8 @@ export default {
|
|||
playlists: null,
|
||||
radios: null,
|
||||
tags: null,
|
||||
podcasts: null,
|
||||
series: null,
|
||||
},
|
||||
isLoading: false,
|
||||
paginateBy: 25,
|
||||
|
@ -147,15 +149,28 @@ export default {
|
|||
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',
|
||||
|
@ -174,6 +189,20 @@ export default {
|
|||
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 () {
|
||||
|
@ -197,13 +226,18 @@ export default {
|
|||
this.isLoading = true
|
||||
let response = await axios.get(
|
||||
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.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}}).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
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue