diff --git a/front/src/ui/modals/Search.vue b/front/src/ui/modals/Search.vue index 03695c705..850181c69 100644 --- a/front/src/ui/modals/Search.vue +++ b/front/src/ui/modals/Search.vue @@ -36,10 +36,6 @@ const { isOpen, value: query } = useModal( isOn: (value) => value !== undefined && value !== '' }) -const startRadio = () => { - // TODO: Start the radio -} - // TODO: // - Limit search results to 4 // - Add Link to specific search pages in each section where it applies @@ -64,10 +60,7 @@ const startRadio = () => { */ -const page = ref(1) -const paginateBy = ref(4) - -// Search +// Search query const queryDebounced = refDebounced(query, 500) 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 Results = { - 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 SearchResponse = paths['/api/v2/search']['get']['responses']['200']['content']['application/json'] type Response = { + artists: SearchResponse, + albums: SearchResponse, + tracks: SearchResponse, + tags: SearchResponse, 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'], + 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>() +/** 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>({}) +const results = ref>({}) const categories = computed(() => [ { @@ -110,7 +113,9 @@ const categories = computed(() => [ endpoint: '/search', params: { contentCategory: 'music', - includeChannels: 'true' + includeChannels: 'true', + page: 1, + page_size: 4 } }, { @@ -119,19 +124,29 @@ const categories = computed(() => [ more: '/library/albums', endpoint: '/search', params: { + contentCategory: 'music', includeChannels: 'true', - contentCategory: 'music' + page: 1, + page_size: 4 } }, { type: 'tracks', label: t('views.Search.label.tracks'), - endpoint: '/search' + endpoint: '/search', + params: { + page: 1, + page_size: 24 + } }, { type: 'tags', label: t('views.Search.label.tags'), - endpoint: '/search' + endpoint: '/search', + params: { + page: 1, + page_size: 24 + } }, { type: 'playlists', @@ -143,7 +158,11 @@ const categories = computed(() => [ type: 'radios', label: t('views.Search.label.radios'), more: '/library/radios', - endpoint: '/radios/radios/' + endpoint: '/radios/radios/', + params: { + page: 1, + page_size: 4 + } }, { type: 'podcasts', @@ -152,7 +171,9 @@ const categories = computed(() => [ endpoint: '/artists/', params: { contentCategory: 'podcast', - includeChannels: 'true' + includeChannels: 'true', + page: 1, + page_size: 4 } }, { @@ -161,7 +182,9 @@ const categories = computed(() => [ endpoint: '/albums/', params: { contentCategory: 'podcast', - includeChannels: 'true' + includeChannels: 'true', + page: 1, + page_size: 4 } }, { @@ -188,7 +211,7 @@ const categories = computed(() => [ post?: true more?: string params?: { - [key: string]: string + [key: string]: string | number } 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 const availableCategories = computed(() => categories.value.filter(({ type }) => - isFetch.value - ? type==='federation' - : isRss.value - ? type==='rss' - : type!=='federation' && type!=='rss' + isFetch.value ? type === 'federation' + : isRss.value ? type === 'rss' + : type !== 'federation' && type !== 'rss' )) // 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 - * @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 = (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 = (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 = (category: { type: C }) => - results.value?.[category.type] ? true : false + results.value[category.type] ? true : false // Display @@ -235,10 +269,10 @@ const openSections = ref>(new Set()) * If no results are in currently expanded categories but some collapsed have results, show those */ watch(results, () => { - if (openCategories.value.some(category => resultsPerCategory(category).length > 0)) return + if (openCategories.value.some(category => count(category) > 0)) return const categoriesWithResults - = availableCategories.value.filter(category => resultsPerCategory(category).length > 0) + = availableCategories.value.filter(category => count(category) > 0) if (categoriesWithResults.length === 0) return @@ -257,8 +291,6 @@ const search = async () => { const params = new URLSearchParams({ q: queryDebounced.value, - page: page.value.toString(), - page_size: paginateBy.value.toString(), ...(openCategories.value && 'params' in openCategories.value && openCategories.value.params ? openCategories.value.params : {} @@ -279,32 +311,61 @@ const search = async () => { for (const category of categories) { try { if (category.endpoint === '/search') { - console.log("SEARCHING BEGIN") - const response = await axios.get( + const response = await axios.get( category.endpoint, { params } ) - console.log("STORING BEGIN") - console.log("Response:", response.data) - console.log("STORING BEGIN") - console.log(results.value) + // Store the four search results results.value = { ...results.value, ...response.data } - console.log("STORING END") - console.log(results.value) + responses.value[category.type] = response.data } else { - const response = await axios['post' in category ? 'post' : 'get']( - category.endpoint, - isRss.value ? ({ url: trimmedQuery.value }) : ({ params }) - ) - results.value = { - ...results.value, - [category.type]: response.data + if (category.type === 'rss') { + const response = await axios.post( + category.endpoint, + { url: trimmedQuery.value } + ) + results.value.rss = [response.data] + responses.value[category.type] = response.data + } else if (category.type === 'federation') { + const response = await axios.post( + category.endpoint, + { params } + ) + results.value.federation = [response.data] + responses.value[category.type] = response.data + } else if (category.type === 'playlists') { + const response = await axios.get( + 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( + 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( + 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( + category.endpoint, + { params } + ) + results.value.series = response.data.results + responses.value[category.type] = response.data } } - } catch (error) { useErrorHandler(error as Error) } @@ -316,17 +377,18 @@ const search = async () => { // Configure the radio const radioConfig = computed(() => - resultsPerCategory({ type: 'tags' }).length > 0 + count({ type: 'tags' }) > 0 ? ({ 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', ids: resultsPerCategory({ type: 'playlists' }).map(({ id }) => id.toString()) }) - : resultsPerCategory({ type: 'artists' }).length > 0 + : count({ type: 'artists' }) > 0 ? ({ type: 'artist', ids: resultsPerCategory({ type: 'artists' }).map(({ id }) => id.toString()) @@ -343,6 +405,7 @@ watch(queryDebounced, search, { immediate: true }) - + If the following link does not work, wait a few seconds and try again TODO: {{ resultsPerCategory(category) }} + + + + {{ t('components.Home.link.viewMore') }} + diff --git a/front/src/ui/modals/Shortcuts.vue b/front/src/ui/modals/Shortcuts.vue index 35187dce6..7db682451 100644 --- a/front/src/ui/modals/Shortcuts.vue +++ b/front/src/ui/modals/Shortcuts.vue @@ -28,6 +28,10 @@ const general = computed(() => [ key: 'shift + f', summary: t('components.ShortcutsModal.shortcut.general.focus') }, + { + key: '/', + summary: t('components.ShortcutsModal.shortcut.general.focus') + }, { key: 'esc', summary: t('components.ShortcutsModal.shortcut.general.unfocus') @@ -134,7 +138,7 @@ const player = computed(() => [ >{{ shortcut.summary }}