feat(front): [WIP] improve search modal
This commit is contained in:
parent
c42f08babe
commit
f4fee5dc8c
|
@ -1,9 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type components, type paths } from '~/generated/types.ts'
|
import type { paths, components } from '~/generated/types.ts'
|
||||||
|
import type { RadioConfig } from '~/store/radios'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { ref, watch, computed } from 'vue'
|
import { ref, watch, computed } from 'vue'
|
||||||
import { refDebounced } from '@vueuse/core'
|
import { refDebounced } from '@vueuse/core'
|
||||||
import { trim } from 'lodash-es'
|
import { trim, uniqBy } from 'lodash-es'
|
||||||
|
|
||||||
import useErrorHandler from '~/composables/useErrorHandler'
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
@ -14,6 +15,7 @@ import PlaylistCard from '~/components/playlists/Card.vue'
|
||||||
import TrackTable from '~/components/audio/track/Table.vue'
|
import TrackTable from '~/components/audio/track/Table.vue'
|
||||||
import AlbumCard from '~/components/album/Card.vue'
|
import AlbumCard from '~/components/album/Card.vue'
|
||||||
import RadioCard from '~/components/radios/Card.vue'
|
import RadioCard from '~/components/radios/Card.vue'
|
||||||
|
import RadioButton from '~/components/radios/Button.vue'
|
||||||
import TagsList from '~/components/tags/List.vue'
|
import TagsList from '~/components/tags/List.vue'
|
||||||
|
|
||||||
import Modal from '~/components/ui/Modal.vue'
|
import Modal from '~/components/ui/Modal.vue'
|
||||||
|
@ -28,20 +30,42 @@ import EmptyState from '~/components/common/EmptyState.vue'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const { isOpen, value: query } = useModal('search', { on: () => '', isOn: (value) => value !== undefined && value !== '' })
|
const { isOpen, value: query } = useModal(
|
||||||
|
'search', {
|
||||||
|
on: () => '',
|
||||||
|
isOn: (value) => value !== undefined && value !== ''
|
||||||
|
})
|
||||||
|
|
||||||
const startRadio = () => {
|
const startRadio = () => {
|
||||||
// TODO: Start the radio
|
// TODO: Start the radio
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pagination
|
// TODO:
|
||||||
|
// - Limit search results to 4
|
||||||
|
// - Add Link to specific search pages in each section where it applies
|
||||||
|
// - Read out the count from `result` (instead of the max. 4 visible results)
|
||||||
|
|
||||||
// Note: In funkwhale v1, the page is stored as a parameter in the Url. This may make sense if we get 100+ search results.
|
/*
|
||||||
// I wonder if anyone would comb through 100 results though instead of fine-tuning the filter to condense the list, but there may be a case where it's useful.
|
|
||||||
// -> TODO: Implement pagination in the search results
|
- Categories (an Array of all categories) <- static configuration
|
||||||
|
|
|
||||||
|
| filter according to search query
|
||||||
|
v
|
||||||
|
- Available Categories (also an Array of category configs)
|
||||||
|
|
|
||||||
|
| make sure that `open sections` is a
|
||||||
|
| subset of available category types
|
||||||
|
|
|
||||||
|
* Open Sections (a Set of category types) <- user can expand/collapse sections
|
||||||
|
| <- new results can also open a category:
|
||||||
|
| if all were closed or none had results
|
||||||
|
v
|
||||||
|
- Open Categories (this value is just computed, based on open sections)
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
const page = ref<number>(1)
|
const page = ref<number>(1)
|
||||||
const paginateBy = ref<number>(100)
|
const paginateBy = ref<number>(4)
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
|
|
||||||
|
@ -57,16 +81,23 @@ const isLoading = ref(false)
|
||||||
type Category = 'artists' | 'albums' | 'tracks' | 'playlists' | 'tags' | 'radios' | 'podcasts' | 'series' | 'rss' | 'federation'
|
type Category = 'artists' | 'albums' | 'tracks' | 'playlists' | 'tags' | 'radios' | 'podcasts' | 'series' | 'rss' | 'federation'
|
||||||
|
|
||||||
type Results = {
|
type Results = {
|
||||||
artists: components['schemas']['SearchResult']['artists'],
|
artists: paths['/api/v2/search']['get']['responses']['200']['content']['application/json']['artists'],
|
||||||
albums: components['schemas']['SearchResult']['albums'],
|
albums: paths['/api/v2/search']['get']['responses']['200']['content']['application/json']['albums'],
|
||||||
tracks: components['schemas']['SearchResult']['tracks'],
|
tracks: paths['/api/v2/search']['get']['responses']['200']['content']['application/json']['tracks'],
|
||||||
tags: components['schemas']['SearchResult']['tags'],
|
tags: paths['/api/v2/search']['get']['responses']['200']['content']['application/json']['tags'],
|
||||||
playlists: components['schemas']['Playlist'][],
|
playlists: Response['playlists']['results'],
|
||||||
radios: components['schemas']['Radio'][],
|
radios: Response['radios']['results'],
|
||||||
podcasts: components['schemas']['Artist'][],
|
podcasts: Response['podcasts']['results'],
|
||||||
series: components['schemas']['Album'][],
|
series: Response['series']['results'],
|
||||||
rss: components['schemas']['Channel'][],
|
rss: paths['/api/v2/channels/rss-subscribe/']['post']['responses']['200']['content']['application/json'],
|
||||||
federation: components['schemas']['Fetch'][]
|
federation: paths['/api/v2/federation/fetches/']['post']['responses']['201']['content']['application/json']
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response = {
|
||||||
|
playlists: paths['/api/v2/playlists/']['get']['responses']['200']['content']['application/json'],
|
||||||
|
radios: paths['/api/v2/radios/radios/']['get']['responses']['200']['content']['application/json'],
|
||||||
|
podcasts: paths['/api/v2/artists/']['get']['responses']['200']['content']['application/json'],
|
||||||
|
series: paths['/api/v2/albums/']['get']['responses']['200']['content']['application/json'],
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = ref<Partial<Results>>()
|
const results = ref<Partial<Results>>()
|
||||||
|
@ -75,6 +106,7 @@ const categories = computed(() => [
|
||||||
{
|
{
|
||||||
type: 'artists',
|
type: 'artists',
|
||||||
label: t('views.Search.label.artists'),
|
label: t('views.Search.label.artists'),
|
||||||
|
more: '/library/artists',
|
||||||
endpoint: '/search',
|
endpoint: '/search',
|
||||||
params: {
|
params: {
|
||||||
contentCategory: 'music',
|
contentCategory: 'music',
|
||||||
|
@ -84,6 +116,7 @@ const categories = computed(() => [
|
||||||
{
|
{
|
||||||
type: 'albums',
|
type: 'albums',
|
||||||
label: t('views.Search.label.albums'),
|
label: t('views.Search.label.albums'),
|
||||||
|
more: '/library/albums',
|
||||||
endpoint: '/search',
|
endpoint: '/search',
|
||||||
params: {
|
params: {
|
||||||
includeChannels: 'true',
|
includeChannels: 'true',
|
||||||
|
@ -103,17 +136,20 @@ const categories = computed(() => [
|
||||||
{
|
{
|
||||||
type: 'playlists',
|
type: 'playlists',
|
||||||
label: t('views.Search.label.playlists'),
|
label: t('views.Search.label.playlists'),
|
||||||
|
more: '/library/playlists/',
|
||||||
endpoint: '/TODO'
|
endpoint: '/TODO'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'radios',
|
type: 'radios',
|
||||||
label: t('views.Search.label.radios'),
|
label: t('views.Search.label.radios'),
|
||||||
endpoint: '/radios/radios'
|
more: '/library/radios',
|
||||||
|
endpoint: '/radios/radios/'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'podcasts',
|
type: 'podcasts',
|
||||||
label: t('views.Search.label.podcasts'),
|
label: t('views.Search.label.podcasts'),
|
||||||
endpoint: '/artists',
|
more: '/library/podcasts',
|
||||||
|
endpoint: '/artists/',
|
||||||
params: {
|
params: {
|
||||||
contentCategory: 'podcast',
|
contentCategory: 'podcast',
|
||||||
includeChannels: 'true'
|
includeChannels: 'true'
|
||||||
|
@ -122,7 +158,7 @@ const categories = computed(() => [
|
||||||
{
|
{
|
||||||
type: 'series',
|
type: 'series',
|
||||||
label: t('views.Search.label.series'),
|
label: t('views.Search.label.series'),
|
||||||
endpoint: '/albums',
|
endpoint: '/albums/',
|
||||||
params: {
|
params: {
|
||||||
contentCategory: 'podcast',
|
contentCategory: 'podcast',
|
||||||
includeChannels: 'true'
|
includeChannels: 'true'
|
||||||
|
@ -150,6 +186,7 @@ const categories = computed(() => [
|
||||||
type: Category
|
type: Category
|
||||||
label: string
|
label: string
|
||||||
post?: true
|
post?: true
|
||||||
|
more?: string
|
||||||
params?: {
|
params?: {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
}
|
}
|
||||||
|
@ -164,14 +201,13 @@ const availableCategories = computed(() =>
|
||||||
? type==='federation'
|
? type==='federation'
|
||||||
: isRss.value
|
: isRss.value
|
||||||
? type==='rss'
|
? type==='rss'
|
||||||
: !['federation', 'rss'].includes(type)
|
: type!=='federation' && type!=='rss'
|
||||||
))
|
))
|
||||||
|
|
||||||
// If there is only one available category, open it immediately
|
// Whenever available categories change, if there is exactly one, open it
|
||||||
watch(availableCategories, () => {
|
watch(availableCategories, () => {
|
||||||
if (availableCategories.value.length === 1 && openSectionHistory.value.at(0) !== availableCategories.value[0].type) {
|
if (availableCategories.value.length === 1)
|
||||||
openSectionHistory.value.unshift(availableCategories.value[0].type)
|
openSections.value = new Set(availableCategories.value.map(category => category.type))
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -179,49 +215,37 @@ watch(availableCategories, () => {
|
||||||
* @param category The category to get the results for
|
* @param category The category to get the results for
|
||||||
* @returns The results for the given category
|
* @returns The results for the given category
|
||||||
*/
|
*/
|
||||||
const resultsPerCategory = (category: Category) =>
|
const resultsPerCategory = <C extends Category>(category: { type: C }) =>
|
||||||
results.value?.[category] || []
|
results.value?.[category.type] || []
|
||||||
|
|
||||||
const isCategoryQueried = (category: Category) =>
|
const isCategoryQueried = <C extends Category>(category: { type: C }) =>
|
||||||
results.value?.[category] ? true : false
|
results.value?.[category.type] ? true : false
|
||||||
|
|
||||||
// Display
|
// Display
|
||||||
// At most one section is open ('current') at a given moment. It determines which category is 'current'.
|
|
||||||
|
|
||||||
const currentCategory = computed(() => categories.value.find(({ type }) => type === openSectionHistory.value.at(0)))
|
const openCategories = computed(() =>
|
||||||
|
categories.value.filter(({ type }) => openSections.value.has(type))
|
||||||
|
)
|
||||||
|
|
||||||
// Parse the category to match a field in the SearchResults type
|
// Sections can be manually or automatically toggled
|
||||||
type SearchResultCategories = keyof components['schemas']['SearchResult']
|
|
||||||
const searchResultCategories: SearchResultCategories[] = ['artists', 'albums', 'tracks', 'tags'] satisfies Category[]
|
|
||||||
const parseCategoryExpected = (category: Category | undefined): SearchResultCategories | undefined =>
|
|
||||||
searchResultCategories.find(expected => category === expected)
|
|
||||||
|
|
||||||
|
const openSections = ref<Set<Category>>(new Set())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If no results are in currently expanded categories but some collapsed have results, show those
|
||||||
|
*/
|
||||||
watch(results, () => {
|
watch(results, () => {
|
||||||
// Currently open section has some items?
|
if (openCategories.value.some(category => resultsPerCategory(category).length > 0)) return
|
||||||
if ((currentCategory.value && results.value?.[currentCategory.value.type]?.length) || 0 > 0)
|
|
||||||
return
|
|
||||||
|
|
||||||
// Any other section has some items?
|
const categoriesWithResults
|
||||||
const firstCategoryWithResults = categories.value.find(category =>
|
= availableCategories.value.filter(category => resultsPerCategory(category).length > 0)
|
||||||
(results.value?.[category.type]?.length || 0) > 0
|
|
||||||
)
|
|
||||||
|
|
||||||
// Then open it!
|
if (categoriesWithResults.length === 0) return
|
||||||
if (firstCategoryWithResults)
|
|
||||||
openSectionHistory.value.unshift(firstCategoryWithResults.type)
|
openSections.value = new Set(categoriesWithResults.map(({ type }) => type))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Showing one section at a time (Accordion behaviour; clicking an open section navigates to the previous section)
|
// Search
|
||||||
|
|
||||||
const openSectionHistory = ref<Category[]>([])
|
|
||||||
|
|
||||||
const toggleSection = (id: Category): void => {
|
|
||||||
if (id === openSectionHistory.value.at(0)) {
|
|
||||||
openSectionHistory.value.shift()
|
|
||||||
} else {
|
|
||||||
openSectionHistory.value.unshift(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const search = async () => {
|
const search = async () => {
|
||||||
|
|
||||||
|
@ -231,57 +255,87 @@ const search = async () => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If query has the shape of an Uri, search the federation
|
|
||||||
// Else, use the user database endpoint to search
|
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
q: queryDebounced.value,
|
q: queryDebounced.value,
|
||||||
page: page.value.toString(),
|
page: page.value.toString(),
|
||||||
page_size: paginateBy.value.toString(),
|
page_size: paginateBy.value.toString(),
|
||||||
...(currentCategory.value && 'params' in currentCategory.value && currentCategory.value.params
|
...(openCategories.value && 'params' in openCategories.value && openCategories.value.params
|
||||||
? currentCategory.value.params
|
? openCategories.value.params
|
||||||
: {}
|
: {}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Query either the user-secelcted category or the first one that is compatible with the query
|
// Only query category that are open / available. Omit duplicate queries (`uniqBy`).
|
||||||
const category = currentCategory.value || availableCategories.value[0]
|
const categories
|
||||||
|
= uniqBy(
|
||||||
|
openCategories.value.length > 0
|
||||||
|
? openCategories.value
|
||||||
|
: availableCategories.value,
|
||||||
|
(category => category.endpoint + ('params' in category && JSON.stringify(category.params)))
|
||||||
|
)
|
||||||
|
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
|
|
||||||
try {
|
for (const category of categories) {
|
||||||
const searchResultCategory = parseCategoryExpected(category.type)
|
try {
|
||||||
|
if (category.endpoint === '/search') {
|
||||||
if (searchResultCategory) {
|
console.log("SEARCHING BEGIN")
|
||||||
|
const response = await axios.get<components['schemas']['SearchResult']>(
|
||||||
const response = await axios.get<components['schemas']['SearchResult']>(
|
category.endpoint,
|
||||||
category.endpoint,
|
{ params }
|
||||||
{ params }
|
)
|
||||||
)
|
console.log("STORING BEGIN")
|
||||||
results.value = {
|
console.log("Response:", response.data)
|
||||||
...results.value,
|
console.log("STORING BEGIN")
|
||||||
...response.data
|
console.log(results.value)
|
||||||
}
|
results.value = {
|
||||||
} else {
|
...results.value,
|
||||||
console.log("endpoint", category.endpoint)
|
...response.data
|
||||||
console.log("params", params)
|
}
|
||||||
const response = await axios['post' in category ? 'post' : 'get']<Results[typeof category.type][0]>(
|
console.log("STORING END")
|
||||||
|
console.log(results.value)
|
||||||
|
} else {
|
||||||
|
const response = await axios['post' in category ? 'post' : 'get']<Results[typeof category.type][0]>(
|
||||||
category.endpoint,
|
category.endpoint,
|
||||||
isRss.value ? ({ url: trimmedQuery.value }) : ({ params })
|
isRss.value ? ({ url: trimmedQuery.value }) : ({ params })
|
||||||
)
|
)
|
||||||
results.value = {
|
results.value = {
|
||||||
...results.value,
|
...results.value,
|
||||||
[category.type]: response.data
|
[category.type]: response.data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
useErrorHandler(error as Error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configure the radio
|
||||||
|
|
||||||
|
const radioConfig = computed<RadioConfig | null>(() =>
|
||||||
|
resultsPerCategory({ type: 'tags' }).length > 0
|
||||||
|
? ({
|
||||||
|
type: 'tag',
|
||||||
|
names: resultsPerCategory({ type: 'tags' }).map(({ name }) => name)
|
||||||
|
})
|
||||||
|
: resultsPerCategory({ type: 'playlists' }).length > 0
|
||||||
|
? ({
|
||||||
|
type: 'playlist',
|
||||||
|
ids: resultsPerCategory({ type: 'playlists' }).map(({ id }) => id.toString())
|
||||||
|
})
|
||||||
|
: resultsPerCategory({ type: 'artists' }).length > 0
|
||||||
|
? ({
|
||||||
|
type: 'artist',
|
||||||
|
ids: resultsPerCategory({ type: 'artists' }).map(({ id }) => id.toString())
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start the search
|
||||||
|
|
||||||
watch(queryDebounced, search, { immediate: true })
|
watch(queryDebounced, search, { immediate: true })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -294,17 +348,23 @@ watch(queryDebounced, search, { immediate: true })
|
||||||
<template #topleft>
|
<template #topleft>
|
||||||
<Input
|
<Input
|
||||||
v-model="query"
|
v-model="query"
|
||||||
ghost
|
raised
|
||||||
autofocus
|
:autofocus="openCategories.length===0"
|
||||||
icon="bi-search"
|
icon="bi-search"
|
||||||
/>
|
/>
|
||||||
<Button
|
<!-- <Button
|
||||||
v-bind="{[results? 'primary' : 'disabled']: true}"
|
v-bind="{[results? 'primary' : 'disabled']: true}"
|
||||||
min-width
|
min-width
|
||||||
@click="startRadio"
|
@click="startRadio"
|
||||||
>
|
>
|
||||||
{{ t('components.audio.podcast.Modal.button.startRadio') }}
|
{{ t('components.audio.podcast.Modal.button.startRadio') }}
|
||||||
</Button>
|
</Button> -->
|
||||||
|
<RadioButton
|
||||||
|
v-if="radioConfig"
|
||||||
|
class="ui right floated medium button"
|
||||||
|
type="custom_multiple"
|
||||||
|
:radio-config="radioConfig"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
|
|
||||||
|
@ -316,80 +376,86 @@ watch(queryDebounced, search, { immediate: true })
|
||||||
v-for="category in availableCategories"
|
v-for="category in availableCategories"
|
||||||
:key="category.type"
|
:key="category.type"
|
||||||
>
|
>
|
||||||
<!-- Each section collapses if it is not current -->
|
|
||||||
<Section
|
<Section
|
||||||
align-left
|
align-left
|
||||||
:action="{
|
|
||||||
text: `${
|
|
||||||
!isCategoryQueried(category.type)
|
|
||||||
? '...'
|
|
||||||
: resultsPerCategory(category.type).length > 0
|
|
||||||
? `${resultsPerCategory(category.type).length} `
|
|
||||||
: ''
|
|
||||||
}${category.label}`,
|
|
||||||
onClick: () => {
|
|
||||||
toggleSection(category.type)
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
no-items
|
no-items
|
||||||
:collapsed="currentCategory?.type !== category.type"
|
:h3="`${
|
||||||
|
!isCategoryQueried(category)
|
||||||
|
? '...'
|
||||||
|
: resultsPerCategory(category).length > 0
|
||||||
|
? `${resultsPerCategory(category).length} `
|
||||||
|
: ''
|
||||||
|
}${category.label}`"
|
||||||
|
:v-bind="
|
||||||
|
openSections.has(category.type)
|
||||||
|
? ({ collapse: () => openSections.delete(category.type) })
|
||||||
|
: ({ expand: () => openSections.add(category.type) })
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<EmptyState
|
<EmptyState
|
||||||
v-if="resultsPerCategory(category.type).length === 0"
|
v-if="resultsPerCategory(category).length === 0"
|
||||||
style="grid-column: 1 / -1"
|
style="grid-column: 1 / -1"
|
||||||
:refresh="true"
|
:refresh="true"
|
||||||
@refresh="search"
|
@refresh="search"
|
||||||
/>
|
/>
|
||||||
|
<Link
|
||||||
|
v-else-if="'more' in category"
|
||||||
|
secondary
|
||||||
|
style="grid-column: -1"
|
||||||
|
:to="category.more"
|
||||||
|
>
|
||||||
|
{{ t('components.Home.link.viewMore') }}
|
||||||
|
</Link>
|
||||||
|
|
||||||
<!-- Categories that have one list-style item -->
|
<!-- Categories that have one list-style item -->
|
||||||
|
|
||||||
<TrackTable
|
<TrackTable
|
||||||
v-if="category.type === 'tracks'"
|
v-if="category.type === 'tracks'"
|
||||||
style="grid-column: 1 / -1"
|
style="grid-column: 1 / -1"
|
||||||
:tracks="resultsPerCategory('tracks') as components['schemas']['SearchResult']['tracks']"
|
:tracks="resultsPerCategory(category)"
|
||||||
/>
|
/>
|
||||||
<TagsList
|
<TagsList
|
||||||
v-else-if="category.type === 'tags'"
|
v-else-if="category.type === 'tags'"
|
||||||
style="grid-column: 1 / -1"
|
style="grid-column: 1 / -1"
|
||||||
:truncate-size="200"
|
:truncate-size="200"
|
||||||
:limit="paginateBy"
|
:limit="paginateBy"
|
||||||
:tags="(resultsPerCategory('tags') as components['schemas']['SearchResult']['tags']).map(t => t.name)"
|
:tags="(resultsPerCategory(category)).map(t => t.name)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Categories that show individual cards -->
|
<!-- Categories that show individual cards -->
|
||||||
<!-- If the category is expected to yield results, display the results in Cards or Activities -->
|
|
||||||
<template
|
<template
|
||||||
v-for="(result, index) in (resultsPerCategory(category.type))"
|
v-for="(_, index) in (resultsPerCategory(category))"
|
||||||
:key="category.type + index"
|
:key="category.type + index"
|
||||||
>
|
>
|
||||||
<ArtistCard
|
<ArtistCard
|
||||||
v-if="category.type === 'artists' || category.type === 'podcasts'"
|
v-if="(category.type === 'artists' || category.type === 'podcasts')"
|
||||||
:artist="result"
|
:artist="resultsPerCategory(category)[index]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AlbumCard
|
<!-- <AlbumCard
|
||||||
v-else-if="category.type === 'albums' || category.type === 'series'"
|
v-else-if="category.type === 'albums' || category.type === 'series'"
|
||||||
:album="result as components['schemas']['SearchResult']['albums'][0]"
|
:album="resultsPerCategory(category)[index]"
|
||||||
/>
|
/> -->
|
||||||
|
|
||||||
<PlaylistCard
|
<!-- <PlaylistCard
|
||||||
v-else-if="category.type === 'playlists'"
|
v-else-if="category.type === 'playlists'"
|
||||||
:playlist="result as components['schemas']['Playlist']"
|
:playlist="resultsPerCategory(category)[index]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RadioCard
|
<RadioCard
|
||||||
v-else-if="category.type === 'radios'"
|
v-else-if="category.type === 'radios'"
|
||||||
type="custom"
|
type="custom"
|
||||||
:custom-radio="result as components['schemas']['Radio']"
|
:custom-radio="resultsPerCategory(category)[index]"
|
||||||
/>
|
/> -->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- TODO: Implement results from non-`/v2/search` requests (federated/uri results etc.) here -->
|
|
||||||
<!-- If response has "url": "webfinger://node1@node1.funkwhale.test" -> Link to go directly to the federation page -->
|
<!-- If response has "url": "webfinger://node1@node1.funkwhale.test" -> Link to go directly to the federation page -->
|
||||||
|
|
||||||
<span v-if="category.type === 'rss'">
|
<span v-if="category.type === 'rss' && resultsPerCategory(category).length > 0">
|
||||||
<Alert>If the following link does not work, wait a few seconds and try again</Alert>
|
<Alert>If the following link does not work, wait a few seconds and try again</Alert>
|
||||||
<Link
|
<Link
|
||||||
|
v-for="channel in resultsPerCategory(category)"
|
||||||
|
:key="channel.artist.fid"
|
||||||
:to="channel.artist.fid"
|
:to="channel.artist.fid"
|
||||||
autofocus
|
autofocus
|
||||||
>
|
>
|
||||||
|
@ -398,7 +464,7 @@ watch(queryDebounced, search, { immediate: true })
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-else-if="category.type === 'federation'">
|
<span v-else-if="category.type === 'federation'">
|
||||||
{{ resultsPerCategory(category.type) }}
|
TODO: {{ resultsPerCategory(category) }}
|
||||||
</span>
|
</span>
|
||||||
</Section>
|
</Section>
|
||||||
</template>
|
</template>
|
||||||
|
|
Loading…
Reference in New Issue