feat(front): [WIP] improve search modal
This commit is contained in:
parent
7902c43702
commit
6d7b764786
|
@ -36,10 +36,6 @@ const { isOpen, value: query } = useModal(
|
||||||
isOn: (value) => value !== undefined && value !== ''
|
isOn: (value) => value !== undefined && value !== ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const startRadio = () => {
|
|
||||||
// TODO: Start the radio
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// - Limit search results to 4
|
// - Limit search results to 4
|
||||||
// - Add Link to specific search pages in each section where it applies
|
// - Add Link to specific search pages in each section where it applies
|
||||||
|
@ -64,10 +60,7 @@ const startRadio = () => {
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const page = ref<number>(1)
|
// Search query
|
||||||
const paginateBy = ref<number>(4)
|
|
||||||
|
|
||||||
// Search
|
|
||||||
|
|
||||||
const queryDebounced = refDebounced(query, 500)
|
const queryDebounced = refDebounced(query, 500)
|
||||||
const trimmedQuery = computed(() => trim(trim(queryDebounced.value), '@'))
|
const trimmedQuery = computed(() => trim(trim(queryDebounced.value), '@'))
|
||||||
|
@ -80,27 +73,37 @@ 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 SearchResponse = paths['/api/v2/search']['get']['responses']['200']['content']['application/json']
|
||||||
artists: paths['/api/v2/search']['get']['responses']['200']['content']['application/json']['artists'],
|
|
||||||
albums: paths['/api/v2/search']['get']['responses']['200']['content']['application/json']['albums'],
|
|
||||||
tracks: paths['/api/v2/search']['get']['responses']['200']['content']['application/json']['tracks'],
|
|
||||||
tags: paths['/api/v2/search']['get']['responses']['200']['content']['application/json']['tags'],
|
|
||||||
playlists: Response['playlists']['results'],
|
|
||||||
radios: Response['radios']['results'],
|
|
||||||
podcasts: Response['podcasts']['results'],
|
|
||||||
series: Response['series']['results'],
|
|
||||||
rss: paths['/api/v2/channels/rss-subscribe/']['post']['responses']['200']['content']['application/json'],
|
|
||||||
federation: paths['/api/v2/federation/fetches/']['post']['responses']['201']['content']['application/json']
|
|
||||||
}
|
|
||||||
|
|
||||||
type Response = {
|
type Response = {
|
||||||
|
artists: SearchResponse,
|
||||||
|
albums: SearchResponse,
|
||||||
|
tracks: SearchResponse,
|
||||||
|
tags: SearchResponse,
|
||||||
playlists: paths['/api/v2/playlists/']['get']['responses']['200']['content']['application/json'],
|
playlists: paths['/api/v2/playlists/']['get']['responses']['200']['content']['application/json'],
|
||||||
radios: paths['/api/v2/radios/radios/']['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'],
|
podcasts: paths['/api/v2/artists/']['get']['responses']['200']['content']['application/json'],
|
||||||
series: paths['/api/v2/albums/']['get']['responses']['200']['content']['application/json'],
|
series: paths['/api/v2/albums/']['get']['responses']['200']['content']['application/json'],
|
||||||
|
rss: paths['/api/v2/channels/rss-subscribe/']['post']['responses']['200']['content']['application/json'],
|
||||||
|
federation: paths['/api/v2/federation/fetches/']['post']['responses']['201']['content']['application/json']
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = ref<Partial<Results>>()
|
/** Note that `federation` is a singleton list so that each result is a list */
|
||||||
|
type Results = {
|
||||||
|
artists: SearchResponse['artists'],
|
||||||
|
albums: SearchResponse['albums'],
|
||||||
|
tracks: SearchResponse['tracks'],
|
||||||
|
tags: SearchResponse['tags'],
|
||||||
|
playlists: Response['playlists']['results'],
|
||||||
|
radios: Response['radios']['results'],
|
||||||
|
podcasts: Response['podcasts']['results'],
|
||||||
|
series: Response['series']['results'],
|
||||||
|
rss: [Response['rss']],
|
||||||
|
federation: [Response['federation']]
|
||||||
|
}
|
||||||
|
|
||||||
|
const responses = ref<Partial<Response>>({})
|
||||||
|
const results = ref<Partial<Results>>({})
|
||||||
|
|
||||||
const categories = computed(() => [
|
const categories = computed(() => [
|
||||||
{
|
{
|
||||||
|
@ -110,7 +113,9 @@ const categories = computed(() => [
|
||||||
endpoint: '/search',
|
endpoint: '/search',
|
||||||
params: {
|
params: {
|
||||||
contentCategory: 'music',
|
contentCategory: 'music',
|
||||||
includeChannels: 'true'
|
includeChannels: 'true',
|
||||||
|
page: 1,
|
||||||
|
page_size: 4
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -119,19 +124,29 @@ const categories = computed(() => [
|
||||||
more: '/library/albums',
|
more: '/library/albums',
|
||||||
endpoint: '/search',
|
endpoint: '/search',
|
||||||
params: {
|
params: {
|
||||||
|
contentCategory: 'music',
|
||||||
includeChannels: 'true',
|
includeChannels: 'true',
|
||||||
contentCategory: 'music'
|
page: 1,
|
||||||
|
page_size: 4
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'tracks',
|
type: 'tracks',
|
||||||
label: t('views.Search.label.tracks'),
|
label: t('views.Search.label.tracks'),
|
||||||
endpoint: '/search'
|
endpoint: '/search',
|
||||||
|
params: {
|
||||||
|
page: 1,
|
||||||
|
page_size: 24
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'tags',
|
type: 'tags',
|
||||||
label: t('views.Search.label.tags'),
|
label: t('views.Search.label.tags'),
|
||||||
endpoint: '/search'
|
endpoint: '/search',
|
||||||
|
params: {
|
||||||
|
page: 1,
|
||||||
|
page_size: 24
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'playlists',
|
type: 'playlists',
|
||||||
|
@ -143,7 +158,11 @@ const categories = computed(() => [
|
||||||
type: 'radios',
|
type: 'radios',
|
||||||
label: t('views.Search.label.radios'),
|
label: t('views.Search.label.radios'),
|
||||||
more: '/library/radios',
|
more: '/library/radios',
|
||||||
endpoint: '/radios/radios/'
|
endpoint: '/radios/radios/',
|
||||||
|
params: {
|
||||||
|
page: 1,
|
||||||
|
page_size: 4
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'podcasts',
|
type: 'podcasts',
|
||||||
|
@ -152,7 +171,9 @@ const categories = computed(() => [
|
||||||
endpoint: '/artists/',
|
endpoint: '/artists/',
|
||||||
params: {
|
params: {
|
||||||
contentCategory: 'podcast',
|
contentCategory: 'podcast',
|
||||||
includeChannels: 'true'
|
includeChannels: 'true',
|
||||||
|
page: 1,
|
||||||
|
page_size: 4
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -161,7 +182,9 @@ const categories = computed(() => [
|
||||||
endpoint: '/albums/',
|
endpoint: '/albums/',
|
||||||
params: {
|
params: {
|
||||||
contentCategory: 'podcast',
|
contentCategory: 'podcast',
|
||||||
includeChannels: 'true'
|
includeChannels: 'true',
|
||||||
|
page: 1,
|
||||||
|
page_size: 4
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -188,7 +211,7 @@ const categories = computed(() => [
|
||||||
post?: true
|
post?: true
|
||||||
more?: string
|
more?: string
|
||||||
params?: {
|
params?: {
|
||||||
[key: string]: string
|
[key: string]: string | number
|
||||||
}
|
}
|
||||||
endpoint: `/${string}`
|
endpoint: `/${string}`
|
||||||
}[])
|
}[])
|
||||||
|
@ -197,11 +220,9 @@ const categories = computed(() => [
|
||||||
// Show fetch if the query is a URL; show RSS if the query is an email address; show all other cateories otherwise
|
// Show fetch if the query is a URL; show RSS if the query is an email address; show all other cateories otherwise
|
||||||
const availableCategories = computed(() =>
|
const availableCategories = computed(() =>
|
||||||
categories.value.filter(({ type }) =>
|
categories.value.filter(({ type }) =>
|
||||||
isFetch.value
|
isFetch.value ? type === 'federation'
|
||||||
? type==='federation'
|
: isRss.value ? type === 'rss'
|
||||||
: isRss.value
|
: type !== 'federation' && type !== 'rss'
|
||||||
? type==='rss'
|
|
||||||
: type!=='federation' && type!=='rss'
|
|
||||||
))
|
))
|
||||||
|
|
||||||
// Whenever available categories change, if there is exactly one, open it
|
// Whenever available categories change, if there is exactly one, open it
|
||||||
|
@ -211,15 +232,28 @@ watch(availableCategories, () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the results for a given category
|
* Get a list of the loaded results for a given category (max. 4)
|
||||||
* @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, in the form of an Array; `[]` if the category has not yet been queried
|
||||||
*/
|
*/
|
||||||
const resultsPerCategory = <C extends Category>(category: { type: C }) =>
|
const resultsPerCategory = <C extends Category>(category: { type: C }) =>
|
||||||
results.value?.[category.type] || []
|
results.value[category.type] || []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the total number of results
|
||||||
|
* @param category The category to get the results for
|
||||||
|
* @returns The number of results for the given category according to the backend; `0` if the category has not yet been queried
|
||||||
|
*/
|
||||||
|
const count = <C extends Category>(category: { type: C }) => (
|
||||||
|
response => response && 'count' in response ? response.count : resultsPerCategory(category).length
|
||||||
|
) (responses.value[category.type])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find out whether a category has been queried before
|
||||||
|
* @param category The category to which may have been queried
|
||||||
|
*/
|
||||||
const isCategoryQueried = <C extends Category>(category: { type: C }) =>
|
const isCategoryQueried = <C extends Category>(category: { type: C }) =>
|
||||||
results.value?.[category.type] ? true : false
|
results.value[category.type] ? true : false
|
||||||
|
|
||||||
// Display
|
// Display
|
||||||
|
|
||||||
|
@ -235,10 +269,10 @@ const openSections = ref<Set<Category>>(new Set())
|
||||||
* If no results are in currently expanded categories but some collapsed have results, show those
|
* If no results are in currently expanded categories but some collapsed have results, show those
|
||||||
*/
|
*/
|
||||||
watch(results, () => {
|
watch(results, () => {
|
||||||
if (openCategories.value.some(category => resultsPerCategory(category).length > 0)) return
|
if (openCategories.value.some(category => count(category) > 0)) return
|
||||||
|
|
||||||
const categoriesWithResults
|
const categoriesWithResults
|
||||||
= availableCategories.value.filter(category => resultsPerCategory(category).length > 0)
|
= availableCategories.value.filter(category => count(category) > 0)
|
||||||
|
|
||||||
if (categoriesWithResults.length === 0) return
|
if (categoriesWithResults.length === 0) return
|
||||||
|
|
||||||
|
@ -257,8 +291,6 @@ const search = async () => {
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
q: queryDebounced.value,
|
q: queryDebounced.value,
|
||||||
page: page.value.toString(),
|
|
||||||
page_size: paginateBy.value.toString(),
|
|
||||||
...(openCategories.value && 'params' in openCategories.value && openCategories.value.params
|
...(openCategories.value && 'params' in openCategories.value && openCategories.value.params
|
||||||
? openCategories.value.params
|
? openCategories.value.params
|
||||||
: {}
|
: {}
|
||||||
|
@ -279,32 +311,61 @@ const search = async () => {
|
||||||
for (const category of categories) {
|
for (const category of categories) {
|
||||||
try {
|
try {
|
||||||
if (category.endpoint === '/search') {
|
if (category.endpoint === '/search') {
|
||||||
console.log("SEARCHING BEGIN")
|
const response = await axios.get<Response[typeof category.type]>(
|
||||||
const response = await axios.get<components['schemas']['SearchResult']>(
|
|
||||||
category.endpoint,
|
category.endpoint,
|
||||||
{ params }
|
{ params }
|
||||||
)
|
)
|
||||||
console.log("STORING BEGIN")
|
// Store the four search results
|
||||||
console.log("Response:", response.data)
|
|
||||||
console.log("STORING BEGIN")
|
|
||||||
console.log(results.value)
|
|
||||||
results.value = {
|
results.value = {
|
||||||
...results.value,
|
...results.value,
|
||||||
...response.data
|
...response.data
|
||||||
}
|
}
|
||||||
console.log("STORING END")
|
responses.value[category.type] = response.data
|
||||||
console.log(results.value)
|
|
||||||
} else {
|
} else {
|
||||||
const response = await axios['post' in category ? 'post' : 'get']<Results[typeof category.type][0]>(
|
if (category.type === 'rss') {
|
||||||
category.endpoint,
|
const response = await axios.post<Response['rss']>(
|
||||||
isRss.value ? ({ url: trimmedQuery.value }) : ({ params })
|
category.endpoint,
|
||||||
)
|
{ url: trimmedQuery.value }
|
||||||
results.value = {
|
)
|
||||||
...results.value,
|
results.value.rss = [response.data]
|
||||||
[category.type]: response.data
|
responses.value[category.type] = response.data
|
||||||
|
} else if (category.type === 'federation') {
|
||||||
|
const response = await axios.post<Response['federation']>(
|
||||||
|
category.endpoint,
|
||||||
|
{ params }
|
||||||
|
)
|
||||||
|
results.value.federation = [response.data]
|
||||||
|
responses.value[category.type] = response.data
|
||||||
|
} else if (category.type === 'playlists') {
|
||||||
|
const response = await axios.get<Response['playlists']>(
|
||||||
|
category.endpoint,
|
||||||
|
{ params }
|
||||||
|
)
|
||||||
|
results.value.playlists = response.data.results
|
||||||
|
responses.value[category.type] = response.data
|
||||||
|
} else if (category.type === 'podcasts') {
|
||||||
|
const response = await axios.get<Response['podcasts']>(
|
||||||
|
category.endpoint,
|
||||||
|
{ params }
|
||||||
|
)
|
||||||
|
results.value.podcasts = response.data.results
|
||||||
|
responses.value[category.type] = response.data
|
||||||
|
} else if (category.type === 'radios') {
|
||||||
|
const response = await axios.get<Response['radios']>(
|
||||||
|
category.endpoint,
|
||||||
|
{ params }
|
||||||
|
)
|
||||||
|
results.value.radios = response.data.results
|
||||||
|
responses.value[category.type] = response.data
|
||||||
|
} else if (category.type === 'series') {
|
||||||
|
const response = await axios.get<Response['series']>(
|
||||||
|
category.endpoint,
|
||||||
|
{ params }
|
||||||
|
)
|
||||||
|
results.value.series = response.data.results
|
||||||
|
responses.value[category.type] = response.data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
useErrorHandler(error as Error)
|
useErrorHandler(error as Error)
|
||||||
}
|
}
|
||||||
|
@ -316,17 +377,18 @@ const search = async () => {
|
||||||
// Configure the radio
|
// Configure the radio
|
||||||
|
|
||||||
const radioConfig = computed<RadioConfig | null>(() =>
|
const radioConfig = computed<RadioConfig | null>(() =>
|
||||||
resultsPerCategory({ type: 'tags' }).length > 0
|
count({ type: 'tags' }) > 0
|
||||||
? ({
|
? ({
|
||||||
type: 'tag',
|
type: 'tag',
|
||||||
names: resultsPerCategory({ type: 'tags' }).map(({ name }) => name)
|
names: resultsPerCategory({ type: 'tags' })
|
||||||
|
.map((({ name }) => name))
|
||||||
})
|
})
|
||||||
: resultsPerCategory({ type: 'playlists' }).length > 0
|
: count({ type: 'playlists' }) > 0
|
||||||
? ({
|
? ({
|
||||||
type: 'playlist',
|
type: 'playlist',
|
||||||
ids: resultsPerCategory({ type: 'playlists' }).map(({ id }) => id.toString())
|
ids: resultsPerCategory({ type: 'playlists' }).map(({ id }) => id.toString())
|
||||||
})
|
})
|
||||||
: resultsPerCategory({ type: 'artists' }).length > 0
|
: count({ type: 'artists' }) > 0
|
||||||
? ({
|
? ({
|
||||||
type: 'artist',
|
type: 'artist',
|
||||||
ids: resultsPerCategory({ type: 'artists' }).map(({ id }) => id.toString())
|
ids: resultsPerCategory({ type: 'artists' }).map(({ id }) => id.toString())
|
||||||
|
@ -343,6 +405,7 @@ watch(queryDebounced, search, { immediate: true })
|
||||||
<Modal
|
<Modal
|
||||||
v-model="isOpen"
|
v-model="isOpen"
|
||||||
over-popover
|
over-popover
|
||||||
|
autofocus="off"
|
||||||
title=""
|
title=""
|
||||||
>
|
>
|
||||||
<template #topleft>
|
<template #topleft>
|
||||||
|
@ -352,13 +415,6 @@ watch(queryDebounced, search, { immediate: true })
|
||||||
:autofocus="openCategories.length===0"
|
:autofocus="openCategories.length===0"
|
||||||
icon="bi-search"
|
icon="bi-search"
|
||||||
/>
|
/>
|
||||||
<!-- <Button
|
|
||||||
v-bind="{[results? 'primary' : 'disabled']: true}"
|
|
||||||
min-width
|
|
||||||
@click="startRadio"
|
|
||||||
>
|
|
||||||
{{ t('components.audio.podcast.Modal.button.startRadio') }}
|
|
||||||
</Button> -->
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
v-if="radioConfig"
|
v-if="radioConfig"
|
||||||
class="ui right floated medium button"
|
class="ui right floated medium button"
|
||||||
|
@ -374,7 +430,7 @@ watch(queryDebounced, search, { immediate: true })
|
||||||
|
|
||||||
<template
|
<template
|
||||||
v-for="category in availableCategories"
|
v-for="category in availableCategories"
|
||||||
:key="category.type"
|
:key="category.type + isCategoryQueried(category)"
|
||||||
>
|
>
|
||||||
<Section
|
<Section
|
||||||
align-left
|
align-left
|
||||||
|
@ -382,30 +438,16 @@ watch(queryDebounced, search, { immediate: true })
|
||||||
:h3="`${
|
:h3="`${
|
||||||
!isCategoryQueried(category)
|
!isCategoryQueried(category)
|
||||||
? '...'
|
? '...'
|
||||||
: resultsPerCategory(category).length > 0
|
: count(category) > 0
|
||||||
? `${resultsPerCategory(category).length} `
|
? `${count(category)} `
|
||||||
: ''
|
: ''
|
||||||
}${category.label}`"
|
}${category.label}`"
|
||||||
:v-bind="
|
v-bind="
|
||||||
openSections.has(category.type)
|
openSections.has(category.type)
|
||||||
? ({ collapse: () => openSections.delete(category.type) })
|
? ({ collapse: () => openSections.delete(category.type) })
|
||||||
: ({ expand: () => openSections.add(category.type) })
|
: ({ expand: () => openSections.add(category.type) })
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<EmptyState
|
|
||||||
v-if="resultsPerCategory(category).length === 0"
|
|
||||||
style="grid-column: 1 / -1"
|
|
||||||
:refresh="true"
|
|
||||||
@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 -->
|
||||||
|
|
||||||
|
@ -418,7 +460,7 @@ watch(queryDebounced, search, { immediate: true })
|
||||||
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="category.params.page_size"
|
||||||
:tags="(resultsPerCategory(category)).map(t => t.name)"
|
:tags="(resultsPerCategory(category)).map(t => t.name)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -432,12 +474,12 @@ watch(queryDebounced, search, { immediate: true })
|
||||||
:artist="resultsPerCategory(category)[index]"
|
: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="resultsPerCategory(category)[index]"
|
:album="resultsPerCategory(category)[index]"
|
||||||
/> -->
|
/>
|
||||||
|
|
||||||
<!-- <PlaylistCard
|
<PlaylistCard
|
||||||
v-else-if="category.type === 'playlists'"
|
v-else-if="category.type === 'playlists'"
|
||||||
:playlist="resultsPerCategory(category)[index]"
|
:playlist="resultsPerCategory(category)[index]"
|
||||||
/>
|
/>
|
||||||
|
@ -446,12 +488,12 @@ watch(queryDebounced, search, { immediate: true })
|
||||||
v-else-if="category.type === 'radios'"
|
v-else-if="category.type === 'radios'"
|
||||||
type="custom"
|
type="custom"
|
||||||
:custom-radio="resultsPerCategory(category)[index]"
|
:custom-radio="resultsPerCategory(category)[index]"
|
||||||
/> -->
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 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' && resultsPerCategory(category).length > 0">
|
<span v-if="category.type === 'rss' && count(category) > 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)"
|
v-for="channel in resultsPerCategory(category)"
|
||||||
|
@ -466,6 +508,21 @@ watch(queryDebounced, search, { immediate: true })
|
||||||
<span v-else-if="category.type === 'federation'">
|
<span v-else-if="category.type === 'federation'">
|
||||||
TODO: {{ resultsPerCategory(category) }}
|
TODO: {{ resultsPerCategory(category) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<EmptyState
|
||||||
|
v-if="count(category) === 0"
|
||||||
|
style="grid-column: 1 / -1"
|
||||||
|
:refresh="true"
|
||||||
|
@refresh="search"
|
||||||
|
/>
|
||||||
|
<Link
|
||||||
|
v-else-if="'more' in category"
|
||||||
|
ghost
|
||||||
|
full
|
||||||
|
:to="category.more"
|
||||||
|
>
|
||||||
|
{{ t('components.Home.link.viewMore') }}
|
||||||
|
</Link>
|
||||||
</Section>
|
</Section>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -28,6 +28,10 @@ const general = computed(() => [
|
||||||
key: 'shift + f',
|
key: 'shift + f',
|
||||||
summary: t('components.ShortcutsModal.shortcut.general.focus')
|
summary: t('components.ShortcutsModal.shortcut.general.focus')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: '/',
|
||||||
|
summary: t('components.ShortcutsModal.shortcut.general.focus')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'esc',
|
key: 'esc',
|
||||||
summary: t('components.ShortcutsModal.shortcut.general.unfocus')
|
summary: t('components.ShortcutsModal.shortcut.general.unfocus')
|
||||||
|
@ -134,7 +138,7 @@ const player = computed(() => [
|
||||||
>{{ shortcut.summary }}</span>
|
>{{ shortcut.summary }}</span>
|
||||||
<Spacer grow />
|
<Spacer grow />
|
||||||
<Button
|
<Button
|
||||||
style="pointer-events:none;"
|
style="pointer-events: none;"
|
||||||
min-content
|
min-content
|
||||||
>
|
>
|
||||||
{{ shortcut.key }}
|
{{ shortcut.key }}
|
||||||
|
|
Loading…
Reference in New Issue