108 lines
2.3 KiB
TypeScript
108 lines
2.3 KiB
TypeScript
import { ref, watch } from 'vue'
|
|||
|
|
import { mockFilms } from '@/mocks/films'
|
||
|
|
import { mockSongs } from '@/mocks/songs'
|
||
|
|
import { mockPodcasts } from '@/mocks/podcasts'
|
||
|
|
|
||
|
|
export interface SearchResult {
|
||
|
|
type: 'film' | 'song' | 'podcast'
|
||
|
|
title: string
|
||
|
|
subtitle: string
|
||
|
|
id: string
|
||
|
|
data: unknown
|
||
|
|
}
|
||
|
|
|
||
|
|
const query = ref('')
|
||
|
|
const results = ref<SearchResult[]>([])
|
||
|
|
const isSearching = ref(false)
|
||
|
|
|
||
|
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
||
|
|
|
||
|
|
function searchLibrary(q: string): SearchResult[] {
|
||
|
|
const lower = q.toLowerCase()
|
||
|
|
const matched: SearchResult[] = []
|
||
|
|
|
||
|
|
for (const film of mockFilms) {
|
||
|
|
if (
|
||
|
|
film.title.toLowerCase().includes(lower) ||
|
||
|
|
film.director.toLowerCase().includes(lower) ||
|
||
|
|
film.genres.some(g => g.toLowerCase().includes(lower))
|
||
|
|
) {
|
||
|
|
matched.push({
|
||
|
|
type: 'film',
|
||
|
|
title: film.title,
|
||
|
|
subtitle: `${film.year} · ${film.director}`,
|
||
|
|
id: film.id,
|
||
|
|
data: film,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const song of mockSongs) {
|
||
|
|
if (
|
||
|
|
song.title.toLowerCase().includes(lower) ||
|
||
|
|
song.artist.toLowerCase().includes(lower) ||
|
||
|
|
(song.album ?? '').toLowerCase().includes(lower)
|
||
|
|
) {
|
||
|
|
matched.push({
|
||
|
|
type: 'song',
|
||
|
|
title: song.title,
|
||
|
|
subtitle: song.artist,
|
||
|
|
id: song.id,
|
||
|
|
data: song,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const podcast of mockPodcasts) {
|
||
|
|
if (
|
||
|
|
podcast.title.toLowerCase().includes(lower) ||
|
||
|
|
(podcast.host ?? '').toLowerCase().includes(lower)
|
||
|
|
) {
|
||
|
|
matched.push({
|
||
|
|
type: 'podcast',
|
||
|
|
title: podcast.title,
|
||
|
|
subtitle: podcast.host ?? 'Unknown',
|
||
|
|
id: podcast.id,
|
||
|
|
data: podcast,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return matched.slice(0, 20)
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useFederatedSearch() {
|
||
|
|
function search(q: string) {
|
||
|
|
query.value = q
|
||
|
|
}
|
||
|
|
|
||
|
|
function clear() {
|
||
|
|
query.value = ''
|
||
|
|
results.value = []
|
||
|
|
isSearching.value = false
|
||
|
|
}
|
||
|
|
|
||
|
|
watch(query, (q) => {
|
||
|
|
if (debounceTimer) clearTimeout(debounceTimer)
|
||
|
|
if (!q.trim()) {
|
||
|
|
results.value = []
|
||
|
|
isSearching.value = false
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
isSearching.value = true
|
||
|
|
debounceTimer = setTimeout(() => {
|
||
|
|
results.value = searchLibrary(q.trim())
|
||
|
|
isSearching.value = false
|
||
|
|
}, 150)
|
||
|
|
})
|
||
|
|
|
||
|
|
return {
|
||
|
|
query,
|
||
|
|
results,
|
||
|
|
isSearching,
|
||
|
|
search,
|
||
|
|
clear,
|
||
|
|
}
|
||
|
|
}
|