Skip to content

Commit

Permalink
Completed load more functionality on search results
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianrudnik committed Nov 1, 2023
1 parent 25458ed commit d318ed7
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 118 deletions.
148 changes: 70 additions & 78 deletions frontend/src/components/parts/QueryInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,33 @@
<div class="QueryInput">
<div class="p-inputgroup flex-1">
<AutoComplete
v-model="currentSelection"
:forceSelection="false"
:panel-class="{ HidePanel: hidePanel }"
:pt="{ token: { class: 'SearchTagChip' } }"
:suggestions="suggestions"
:virtualScrollerOptions="{ itemSize: 50, scrollWidth: '100vw', scrollHeight: '300px' }"
multiple
:placeholder="t('query-input-component.placeholder')"
@complete="aComplete"
@item-select="clearAfterSelect"
@clear="clearInput"
:class="{ 'p-invalid': !currentRequestValid }"
v-model="currentSelection"
:class="{ 'p-invalid': !currentRequestValid }"
:forceSelection="false"
:panel-class="{ HidePanel: hidePanel }"
:placeholder="t('query-input-component.placeholder')"
:pt="{ token: { class: 'SearchTagChip' } }"
:suggestions="suggestions"
:virtualScrollerOptions="{ itemSize: 50, scrollWidth: '100vw', scrollHeight: '300px' }"
multiple
@clear="clearInput"
@complete="onComplete"
@item-select="clearAfterSelect"
>
<template #option="slotProps">
<div class="flex align-options-center">
<SearchTag :tag="slotProps.option" />
<SearchTag :tag="slotProps.option"/>
</div>
</template>

<template #chip="slotProps">
<SearchTag :tag="slotProps.value" />
<SearchTag :tag="slotProps.value"/>
</template>

<template #removetokenicon="slotProps">
<div
@click="slotProps.onClick"
class="RemoveItem inline-flex bg-red-500 align-items-center px-1 text-white cursor-pointer"
@click="slotProps.onClick"
class="RemoveItem inline-flex bg-red-500 align-items-center px-1 text-white cursor-pointer"
>
<i class="pi pi-times"></i>
</div>
Expand All @@ -38,55 +38,47 @@

<div class="Options flex justify-content-between gap-2 mt-2 mb-3">
<div class="my-2 text flex gap-2">
<span>{{ t('query-input-component.hits', { count: currentResultCount }) }}</span>
<span>{{ t('query-input-component.hits', {count: currentResultCount}) }}</span>
<span class="text-red-500" v-if="!currentRequestValid">
{{ t('query-input-component.invalid-query') }}
</span>
</div>

<div class="flex justify-content-end gap-2">
<Button
@click="showHelp = !showHelp"
size="small"
:label="
@click="showHelp = !showHelp"
size="small"
:label="
!showHelp
? t('query-input-component.action.show-examples')
: t('query-input-component.action.hide-examples')
"
:plain="!showHelp"
:text="!showHelp"
/>

<Button
@click="clearInput"
plain
text
size="small"
:label="t('query-input-component.action.reset-search')"
:plain="!showHelp"
:text="!showHelp"
/>
</div>
</div>

<SearchExamples v-if="showHelp" />
<SearchExamples v-if="showHelp"/>
</div>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue'
import type { Tag } from '@/stores/tags'
import { useTagStore } from '@/stores/tags'
import type { AutoCompleteCompleteEvent } from 'primevue/autocomplete'
import {computed, ref} from 'vue'
import type {Tag} from '@/stores/tags'
import {useTagStore} from '@/stores/tags'
import type {AutoCompleteCompleteEvent} from 'primevue/autocomplete'
import AutoComplete from 'primevue/autocomplete'
import SearchTag from '@/components/structure/SearchTag.vue'
import { useSearchResultStore } from '@/stores/results'
import { useStatStore } from '@/stores/stats'
import { useI18n } from 'vue-i18n'
import { watchDebounced } from '@vueuse/core'
import { executeQuerySearch } from '@/plugins/search'
import {useSearchResultStore} from '@/stores/results'
import {useStatStore} from '@/stores/stats'
import {useI18n} from 'vue-i18n'
import {watchDebounced} from '@vueuse/core'
import Button from 'primevue/button'
import SearchExamples from '@/components/parts/search/SearchExamples.vue'
import {useSearchStore} from "@/stores/search";
const { t } = useI18n()
const {t} = useI18n()
const currentSelection = ref<Tag[]>([])
const suggestions = ref<Tag[]>([])
const currentPlainValue = ref('')
Expand All @@ -97,12 +89,12 @@ const showHelp = ref(false)
const resultStore = useSearchResultStore()
const statStore = useStatStore()
const aComplete = (event: AutoCompleteCompleteEvent) => {
const onComplete = (event: AutoCompleteCompleteEvent) => {
currentPlainValue.value = event.query
suggestions.value = useTagStore().entries.filter(
(entry) =>
entry.trans.plain?.toLowerCase().includes(event.query.toLowerCase()) ||
entry.id.includes(event.query.toLowerCase())
(entry) =>
entry.trans.plain?.toLowerCase().includes(event.query.toLowerCase()) ||
entry.id.includes(event.query.toLowerCase())
)
// Hide the panel if no suggestions have been found
Expand All @@ -114,11 +106,11 @@ const clearAfterSelect = () => {
}
const clearInput = () => {
currentSelection.value = []
currentPlainValue.value = ''
currentSelection.value = []
currentResultCount.value = 0
currentRequestValid.value = true
resultStore.clear()
useSearchStore().clear()
}
const currentResultCount = ref(0)
Expand All @@ -132,38 +124,38 @@ const query = computed(() => {
})
watchDebounced(
query,
async () => {
if (query.value.trim() === '') return
statStore.isSearching = true
try {
const result = await executeQuerySearch({
size: 10,
query: {
query: query.value
},
fields: ['*']
})
resultStore.overwrite(
result.hits.map((h) => {
h.fields.id = h.id
return h.fields
query,
async () => {
if (query.value.trim() === '') return
statStore.isSearching = true
try {
const result = await useSearchStore().executeQuerySearch({
size: 4,
query: {
query: query.value
},
sort: ['-_score', '_id'],
fields: ['*']
})
)
currentResultCount.value = result.total_hits
currentRequestValid.value = true
} catch {
currentRequestValid.value = false
} finally {
statStore.isSearching = false
}
},
{ debounce: 200, maxWait: 500 }
resultStore.overwrite(
result.hits.map((h) => {
h.fields.id = h.id
return h.fields
})
)
currentResultCount.value = result.total_hits
currentRequestValid.value = true
} catch {
currentRequestValid.value = false
} finally {
statStore.isSearching = false
}
},
{debounce: 200, maxWait: 500}
)
</script>

Expand Down
26 changes: 17 additions & 9 deletions frontend/src/components/parts/search/SearchExamples.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,8 @@
<i18n-t keypath="search-examples.basic.rules.example.explain.text" tag="p">
<template v-slot:e1>
<code>
[
{{ t('common.label.tag.s') }}
{{ t('common.label.beats-per-minute.nc', { count: 110 }) }}
] [
{{ t('common.label.tag.s') }}
{{ t('common.label.beats-per-minute.nc', { count: 120 }) }}
] Taste
[{{ t('common.label.tag.s') }} {{ t('common.label.beats-per-minute.nc', { count: 110 }) }}]
[{{ t('common.label.tag.s') }} {{ t('common.label.beats-per-minute.nc', { count: 120 }) }}] Taste
</code>
</template>

Expand Down Expand Up @@ -76,7 +71,15 @@
<ul>
<li>
<i18n-t keypath="search-examples.basic.advanced.examples.list.1" tag="label">
<code>+type:AudioTrack</code>
<template v-slot:e1>
<code>+type:AudioTrack</code>
</template>
<template v-slot:e2>
<code>MidiTrack</code>
</template>
<template v-slot:e3>
<code>LiveSet</code>
</template>
</i18n-t>
</li>

Expand Down Expand Up @@ -119,10 +122,14 @@
<li>
<i18n-t keypath="search-examples.basic.advanced.examples.list.7" tag="label">
<template v-slot:e1>
<code>MID</code>
<code>annotation:MID</code>
</template>

<template v-slot:e2>
<code>MID</code>
</template>

<template v-slot:e3>
<code>MIDI</code>
</template>
</i18n-t>
Expand Down Expand Up @@ -151,6 +158,7 @@ const { t } = useI18n()
code {
white-space: nowrap;
font-size: 0.9em;
margin-right: 0.20em;
background-color: black;
color: white;
padding: 0.25rem;
Expand Down
34 changes: 34 additions & 0 deletions frontend/src/components/structure/InfiniteScrollTrigger.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<template>
<div class="infinite-scroll-trigger" ref="trigger"></div>
</template>

<script setup lang="ts">
import {onBeforeUnmount, onMounted, ref} from "vue";
const emit = defineEmits(["trigger"]);
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
emit("trigger")
}
});
}, {threshold: 0.5});
const trigger = ref<Element>();
onMounted(() => {
if (trigger.value) {
console.log('OBSERVE')
observer.observe(trigger.value)
}
})
onBeforeUnmount(() => {
if(trigger.value) {
console.log('UNOBSERVE')
observer.unobserve(trigger.value)
}
})
</script>
34 changes: 34 additions & 0 deletions frontend/src/components/structure/SearchResultList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
:is="resolveComponent(result.type)"
:result="result"
/>

<InfiniteScrollTrigger @trigger="loadMore" class="mb-6"/>
</template>

<script lang="ts" setup>
Expand All @@ -16,6 +18,14 @@ import UnknownResultItem from '@/components/parts/search/UnknownResultItem.vue'
import { useSearchResultStore } from '@/stores/results'
import { computed } from 'vue'
import type { ResultType } from '@/plugins/search/result'
import InfiniteScrollTrigger from "@/components/structure/InfiniteScrollTrigger.vue";
import {useSearchStore} from "@/stores/search";
const currentQuery = computed(() => useSearchStore().currentQuery)
const lastSortKey = computed(() => useSearchStore().lastSortKey)
const { executeQuerySearch } = useSearchStore()
function resolveComponent(type: ResultType): any {
switch (type) {
Expand All @@ -30,5 +40,29 @@ function resolveComponent(type: ResultType): any {
}
}
const loadMore = async () => {
if (!currentQuery.value) {
return
}
// Get the last result to retrieve the sort offset
if (!lastSortKey.value) return
const q = currentQuery.value
if (lastSortKey.value) {
q.search_after = lastSortKey.value
}
const result = await executeQuerySearch(q)
useSearchResultStore().updateBatch(
result.hits.map((h) => {
h.fields.id = h.id
return h.fields
})
)
}
const results = computed(() => useSearchResultStore().entries)
</script>
6 changes: 3 additions & 3 deletions frontend/src/locales/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -383,8 +383,8 @@ search-examples:
Advanced search is possible via simple binary entries. Here are some examples to give ideas:
list:
1: >-
{0} searches only for audio tracks, regardless of whether additional
search parameters have been specified. Other types are also 'MidiTrack' and 'LiveSet'.
{e1} searches only for audio tracks, regardless of whether additional
search parameters have been specified. Other types are also {e2} and {e3}.
2: >-
{0} searches for all hits that are not MIDI tracks.
3: >-
Expand All @@ -400,7 +400,7 @@ search-examples:
absolute path of a file with {e2}.
7: >-
{e1} without any further information searches all manual info field entries for
the word {e1}, which includes e.g. the word {e2}.
the word {e2}, which includes e.g. the word {e3}.
live-set-result-item-component:
header: '@:common.label.live-set.s'
Expand Down
Loading

0 comments on commit d318ed7

Please sign in to comment.