311 lines
7.8 KiB
Vue
311 lines
7.8 KiB
Vue
<template>
|
|
<div id="search_tab">
|
|
<div v-show="isQueryEmpty && !isSearching">
|
|
<h2>{{ $t('search.startSearching') }}</h2>
|
|
<p>{{ $t('search.description') }}</p>
|
|
</div>
|
|
|
|
<BaseLoadingPlaceholder text="Searching..." :hidden="!isSearching" />
|
|
|
|
<div v-show="!isQueryEmpty && !isSearching">
|
|
<BaseTabs>
|
|
<BaseTab
|
|
v-for="tab in tabs"
|
|
:key="tab.name"
|
|
:class="{ active: currentTab.name === tab.name }"
|
|
@click="changeSearchTab(tab.searchType)"
|
|
>
|
|
{{ tab.name }}
|
|
</BaseTab>
|
|
</BaseTabs>
|
|
|
|
<keep-alive>
|
|
<component
|
|
:is="currentTab.component"
|
|
:view-info="getViewInfo()"
|
|
want-headers
|
|
:items-to-show="Infinity"
|
|
@add-to-queue="addToQueue"
|
|
@change-search-tab="changeSearchTab"
|
|
></component>
|
|
</keep-alive>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { computed, onMounted, reactive, ref, toRefs, watch, defineComponent } from '@vue/composition-api'
|
|
import { uniqWith } from 'lodash-es'
|
|
|
|
import BaseLoadingPlaceholder from '@components/globals/BaseLoadingPlaceholder.vue'
|
|
import ResultsAll from '@components/search/ResultsAll.vue'
|
|
import ResultsAlbums from '@components/search/ResultsAlbums.vue'
|
|
import ResultsArtists from '@components/search/ResultsArtists.vue'
|
|
import ResultsPlaylists from '@components/search/ResultsPlaylists.vue'
|
|
import ResultsTracks from '@components/search/ResultsTracks.vue'
|
|
import { BaseTabs, BaseTab } from '@components/globals/BaseTabs'
|
|
import { sendAddToQueue } from '@/utils/downloads'
|
|
import { numberWithDots, convertDuration } from '@/utils/utils'
|
|
|
|
import { formatSingleTrack, formatAlbums, formatArtist, formatPlaylist } from '@/data/search'
|
|
import { standardizeData } from '@/data/standardize'
|
|
import { useMainSearch } from '@/use/main-search'
|
|
import { useSearch } from '@/use/search'
|
|
|
|
const resetObj = { data: [], next: 0, total: 0, hasLoaded: false }
|
|
|
|
const lastTab = ref(null)
|
|
|
|
export default defineComponent({
|
|
components: {
|
|
BaseLoadingPlaceholder,
|
|
BaseTabs,
|
|
BaseTab
|
|
},
|
|
props: {
|
|
performScrolledSearch: {
|
|
type: Boolean,
|
|
required: false
|
|
}
|
|
},
|
|
setup(_, ctx) {
|
|
const state = reactive({
|
|
currentTab: {
|
|
name: '',
|
|
searchType: '',
|
|
component: {},
|
|
viewInfo: '',
|
|
formatFunc: () => {}
|
|
},
|
|
results: {
|
|
query: '',
|
|
allTab: {
|
|
ORDER: [],
|
|
TOP_RESULT: [],
|
|
ALBUM: {
|
|
hasLoaded: false
|
|
},
|
|
ARTIST: {
|
|
hasLoaded: false
|
|
},
|
|
TRACK: {
|
|
hasLoaded: false
|
|
},
|
|
PLAYLIST: {
|
|
hasLoaded: false
|
|
}
|
|
},
|
|
trackTab: { ...resetObj },
|
|
albumTab: { ...resetObj },
|
|
artistTab: { ...resetObj },
|
|
playlistTab: { ...resetObj }
|
|
},
|
|
tabs: [
|
|
{
|
|
name: ctx.root.$i18n.t('globals.listTabs.all'),
|
|
searchType: 'all',
|
|
component: ResultsAll,
|
|
viewInfo: 'allTab'
|
|
},
|
|
{
|
|
name: ctx.root.$i18n.tc('globals.listTabs.track', 2),
|
|
searchType: 'track',
|
|
component: ResultsTracks,
|
|
viewInfo: 'trackTab',
|
|
formatFunc: formatSingleTrack
|
|
},
|
|
{
|
|
name: ctx.root.$i18n.tc('globals.listTabs.album', 2),
|
|
searchType: 'album',
|
|
component: ResultsAlbums,
|
|
viewInfo: 'albumTab',
|
|
formatFunc: formatAlbums
|
|
},
|
|
{
|
|
name: ctx.root.$i18n.tc('globals.listTabs.artist', 2),
|
|
searchType: 'artist',
|
|
component: ResultsArtists,
|
|
viewInfo: 'artistTab',
|
|
formatFunc: formatArtist
|
|
},
|
|
{
|
|
name: ctx.root.$i18n.tc('globals.listTabs.playlist', 2),
|
|
searchType: 'playlist',
|
|
component: ResultsPlaylists,
|
|
viewInfo: 'playlistTab',
|
|
formatFunc: formatPlaylist
|
|
}
|
|
]
|
|
})
|
|
const { searchResult, performMainSearch } = useMainSearch()
|
|
const { result, performSearch } = useSearch()
|
|
const searchedTerm = computed(() => ctx.root.$route.query.term)
|
|
const isQueryEmpty = computed(() => state.results.query === '')
|
|
const isSearching = ref(false)
|
|
const isMainSearchCached = computed(() => Object.keys(searchResult.value).length !== 0)
|
|
const isNewSearch = computed(() => searchResult.value.QUERY !== searchedTerm.value)
|
|
|
|
if (isMainSearchCached.value && !isNewSearch.value) {
|
|
onMounted(() => {
|
|
handleMainSearch(searchResult.value)
|
|
})
|
|
}
|
|
|
|
if (searchedTerm.value && (!isMainSearchCached.value || isNewSearch.value)) {
|
|
performMainSearch(searchedTerm.value)
|
|
isSearching.value = true
|
|
}
|
|
|
|
function handleMainSearch(newValue) {
|
|
// Hide loading placeholder
|
|
isSearching.value = false
|
|
|
|
state.results.query = newValue.QUERY
|
|
|
|
state.results.allTab = newValue
|
|
state.results.allTab.TRACK.hasLoaded = true
|
|
state.results.allTab.ALBUM.hasLoaded = true
|
|
state.results.allTab.ARTIST.hasLoaded = true
|
|
state.results.allTab.PLAYLIST.hasLoaded = true
|
|
|
|
if (lastTab.value && lastTab.value.searchType !== 'all') {
|
|
state.currentTab = lastTab.value
|
|
|
|
performSearch({
|
|
term: newValue.QUERY,
|
|
type: state.currentTab.searchType
|
|
})
|
|
} else {
|
|
state.currentTab = state.tabs.find(tab => tab.searchType === 'all')
|
|
}
|
|
}
|
|
|
|
// Main search watcher
|
|
watch(searchResult, handleMainSearch)
|
|
|
|
// Search watcher
|
|
watch(result, newValue => {
|
|
const { next: nextResult, total, type, data: newData } = newValue
|
|
|
|
const currentTabKey = `${type}Tab`
|
|
let next = total
|
|
|
|
if (nextResult) {
|
|
next = parseInt(nextResult.match(/index=(\d*)/)[1])
|
|
}
|
|
|
|
if (state.results[currentTabKey].total !== total) {
|
|
state.results[currentTabKey].total = total
|
|
}
|
|
|
|
if (state.results[currentTabKey].next !== next) {
|
|
state.results[currentTabKey].next = next
|
|
|
|
// Preventing duplicate entries by filtering them by ID
|
|
const rawData = state.results[currentTabKey].data.concat(newData)
|
|
const filteredData = uniqWith(rawData, (obj1, obj2) => {
|
|
return obj1.id === obj2.id
|
|
})
|
|
|
|
state.results[currentTabKey].data = filteredData
|
|
}
|
|
|
|
state.results[currentTabKey].hasLoaded = true
|
|
})
|
|
|
|
state.currentTab = state.tabs.find(tab => tab.searchType === 'all')
|
|
|
|
return {
|
|
...toRefs(state),
|
|
isSearching,
|
|
isQueryEmpty,
|
|
searchResult,
|
|
performMainSearch,
|
|
performSearch
|
|
}
|
|
},
|
|
computed: {
|
|
loadedTabs() {
|
|
const tabsLoaded = []
|
|
|
|
for (const resultKey in this.results) {
|
|
if (this.results.hasOwnProperty(resultKey) && resultKey !== 'query') {
|
|
const currentResult = this.results[resultKey]
|
|
|
|
if (currentResult.hasLoaded) {
|
|
tabsLoaded.push(resultKey.replace(/Tab/g, ''))
|
|
}
|
|
}
|
|
}
|
|
|
|
return tabsLoaded
|
|
}
|
|
},
|
|
watch: {
|
|
performScrolledSearch(needToSearch) {
|
|
if (!needToSearch) return
|
|
|
|
this.scrolledSearch(needToSearch)
|
|
},
|
|
currentTab(newTab) {
|
|
if (this.isTabLoaded(newTab)) return
|
|
|
|
this.performSearch({
|
|
term: this.results.query,
|
|
type: newTab.searchType,
|
|
start: this.results[`${newTab.searchType}Tab`].next
|
|
})
|
|
}
|
|
},
|
|
methods: {
|
|
numberWithDots,
|
|
convertDuration,
|
|
addToQueue(e) {
|
|
sendAddToQueue(e.currentTarget.dataset.link)
|
|
},
|
|
getViewInfo() {
|
|
if (this.currentTab.searchType === 'all') {
|
|
return this.results.allTab
|
|
}
|
|
|
|
return standardizeData(this.results[this.currentTab.viewInfo], this.currentTab.formatFunc)
|
|
},
|
|
changeSearchTab(tabName) {
|
|
tabName = tabName.toLowerCase()
|
|
|
|
const newTab = this.tabs.find(tab => {
|
|
return tab.searchType === tabName
|
|
})
|
|
|
|
if (!newTab) {
|
|
console.error(`No tab ${tabName} found`)
|
|
return
|
|
}
|
|
|
|
window.scrollTo(0, 0)
|
|
this.currentTab = newTab
|
|
lastTab.value = newTab
|
|
},
|
|
scrolledSearch() {
|
|
if (this.currentTab.searchType === 'all') return
|
|
|
|
const currentTabKey = `${this.currentTab.searchType}Tab`
|
|
const needToPerformScrolledSearch = this.results[currentTabKey].next < this.results[currentTabKey].total
|
|
|
|
if (needToPerformScrolledSearch) {
|
|
this.performSearch({
|
|
term: this.results.query,
|
|
type: this.currentTab.searchType,
|
|
start: this.results[`${this.currentTab.searchType}Tab`].next
|
|
})
|
|
}
|
|
},
|
|
isTabLoaded(tab) {
|
|
return this.loadedTabs.includes(tab.searchType) || tab.searchType === 'all'
|
|
}
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style></style>
|