feat(front): [WIP] search modal with accordion UI as filter

environments/review-docs-feat-z0hkbz/deployments/20803
upsiflu 2025-03-12 22:41:16 +01:00
rodzic f1d6b11686
commit c8fa78ef28
1 zmienionych plików z 127 dodań i 11 usunięć

Wyświetl plik

@ -1,30 +1,31 @@
<script setup lang="ts">
import { type components } from '~/generated/types.ts'
import axios from 'axios'
import { ref, watch } from 'vue'
import { ref, watch, computed } from 'vue'
import { refDebounced } from '@vueuse/core'
import useErrorHandler from '~/composables/useErrorHandler'
import { useI18n } from 'vue-i18n'
import { useModal } from '~/ui/composables/useModal.ts'
import onKeyboardShortcut from '~/composables/onKeyboardShortcut'
import Modal from '~/components/ui/Modal.vue'
import Button from '~/components/ui/Button.vue'
import Layout from '~/components/ui/Layout.vue'
import Spacer from '~/components/ui/Spacer.vue'
import Input from '~/components/ui/Input.vue'
import Section from '~/components/ui/Section.vue'
import ArtistCard from '~/components/artist/Card.vue'
const { t } = useI18n()
const { isOpen, toggle, value: query } = useModal('search', { on: () => '', isOn: (value) => value !== undefined && value !== '' })
onKeyboardShortcut('u', () => toggle())
const { isOpen, value: query } = useModal('search', { on: () => '', isOn: (value) => value !== undefined && value !== '' })
const startRadio = () => {
// TODO: Start the radio
console.log('start radio')
}
// Search
const queryDebounced = refDebounced(query, 500)
const isLoading = ref(false)
@ -52,7 +53,107 @@ const search = async () => {
isLoading.value = false
}
watch(queryDebounced, search, { immediate: true })
// Filter
type QueryType = 'artists' | 'albums' | 'tracks' | 'playlists' | 'tags' | 'radios' | 'podcasts' | 'series' | 'rss'
type SearchType = {
id: QueryType
label: string
includeChannels?: boolean
contentCategory?: string
endpoint?: string
}
const types = computed(() => [
{
id: 'artists',
label: t('views.Search.label.artists'),
includeChannels: true,
contentCategory: 'music'
},
{
id: 'albums',
label: t('views.Search.label.albums'),
includeChannels: true,
contentCategory: 'music'
},
{
id: 'tracks',
label: t('views.Search.label.tracks')
},
{
id: 'playlists',
label: t('views.Search.label.playlists')
},
{
id: 'radios',
label: t('views.Search.label.radios'),
endpoint: 'radios/radios'
},
{
id: 'tags',
label: t('views.Search.label.tags')
},
{
id: 'podcasts',
label: t('views.Search.label.podcasts'),
endpoint: '/artists',
contentCategory: 'podcast',
includeChannels: true
},
{
id: 'series',
label: t('views.Search.label.series'),
endpoint: '/albums',
includeChannels: true,
contentCategory: 'podcast'
}
] as const satisfies SearchType[])
// Display
const implementedType = (id: QueryType | undefined) =>
id && id !== 'playlists' && id !== 'radios' && id !== 'podcasts' && id !== 'series' && id !== 'rss'
? id
: undefined
watch(results, () => {
if (!results.value || results.value === null) return;
// If currently open section has no items, or no section is open, then switch to the first section with any items
const noOpenSection = openSection.value.length === 0
const currentImplementedSection = implementedType(openSection.value.at(0))
const currentImplementedSectionIsEmpty = () => currentImplementedSection && results.value?.[currentImplementedSection].length === 0
if (noOpenSection || currentImplementedSectionIsEmpty()) {
const firstTypeWithResults = types.value.find(({ id }) => {
const idIsImplemented = implementedType(id)
return idIsImplemented && results.value?.[idIsImplemented].length && results.value?.[idIsImplemented].length > 0
})
if (firstTypeWithResults)
openSection.value.unshift(firstTypeWithResults.id)
}
})
// Show one section at a time (Accordion behaviour; clicking an open section navigates to the previous section)
const openSection = ref<QueryType[]>([])
const toggle = (id: QueryType): void => {
if (id === openSection.value.at(0)) {
openSection.value.shift()
} else {
openSection.value.unshift(id)
}
}
</script>
<template>
@ -75,11 +176,26 @@ watch(queryDebounced, search, { immediate: true })
{{ t('components.audio.podcast.Modal.button.startRadio') }}
</Button>
</Layout>
<Section
v-for="type in types"
:key="type.id"
:action="{ text: type.label, onClick: () => { toggle(type.id) } }"
tiny-items
align-left
>
<template
v-for="(result, index) in (results && type.id !== 'playlists' && type.id !== 'radios' && type.id !== 'podcasts' && type.id !== 'series' ? results[type.id] : [])"
:key="type.id+index"
>
{{ result }}
<ArtistCard
v-if="type.id === 'artists'"
:artist="result"
/>
<!-- TODO: Implement all the other cards here -->
</template>
</Section>
</Modal>
</template>
<style module>
.description {
font-size: 0.875em;
}
</style>