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,40 +255,46 @@ 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 | ||||||
| 
 | 
 | ||||||
|  |   for (const category of categories) { | ||||||
|     try { |     try { | ||||||
|     const searchResultCategory = parseCategoryExpected(category.type) |       if (category.endpoint === '/search') { | ||||||
| 
 |         console.log("SEARCHING BEGIN") | ||||||
|     if (searchResultCategory) { |  | ||||||
| 
 |  | ||||||
|         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") | ||||||
|  |         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") | ||||||
|  |         console.log(results.value) | ||||||
|       } else { |       } else { | ||||||
|       console.log("endpoint", category.endpoint) |  | ||||||
|       console.log("params", params) |  | ||||||
|         const response = await axios['post' in category ? 'post' : 'get']<Results[typeof category.type][0]>( |         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 }) | ||||||
|  | @ -279,9 +309,33 @@ const search = async () => { | ||||||
|       useErrorHandler(error as 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
	
	 upsiflu
						upsiflu