feat(front): [WIP] search modal with accordion UI as filter
This commit is contained in:
parent
f1d6b11686
commit
c8fa78ef28
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue