search results sorting #804, better transitions between succesive searches

This commit is contained in:
2020-07-26 00:18:07 +02:00
parent 57e869bfd5
commit 3ed132147c
5 changed files with 200 additions and 45 deletions
+24 -3
View File
@@ -529,13 +529,13 @@ section[role="main-content"]{
} }
footer[role="tools"]{ footer[role="tools"]{
$list-item-h: 5em; $list-item-h: 7em;
@mixin resultItem{ @mixin resultItem{
box-sizing: border-box; box-sizing: border-box;
// we are only on 10 colls as 2 are occupied by sides // we are only on 10 colls as 2 are occupied by sides
flex-basis: percentage(2/($default_sum - 2)); flex-basis: percentage(2/($default_sum - 2));
height: $list-item-h; max-height: $list-item-h;
overflow: hidden; overflow: hidden;
padding-bottom: 1em; padding-bottom: 1em;
padding-right: $default_gap; padding-right: $default_gap;
@@ -602,7 +602,7 @@ footer[role="tools"]{
z-index: 9; z-index: 9;
background-color: $gris; background-color: $gris;
padding:1.2em $side-padding; padding:1.2em $side-padding;
max-height: $list-item-h * 5; max-height: $list-item-h * 3;
@include accordeon-transition($list-item-h * 3); @include accordeon-transition($list-item-h * 3);
>header{ >header{
.search-keys{ .search-keys{
@@ -636,6 +636,17 @@ footer[role="tools"]{
// TODO: how to center the loading // TODO: how to center the loading
} }
} }
>header, section.results-list{
transition: opacity 0.2s ease-in-out;
}
&.loading {
>header, section.results-list{
transition: opacity 0.5s ease-in-out;
opacity:0.5;
pointer-events: none;
}
}
} }
#footer-bottom{ #footer-bottom{
z-index: 10; z-index: 10;
@@ -740,6 +751,16 @@ footer[role="tools"]{
padding-top:0.2em; padding-top:0.2em;
} }
} }
form{
transition: opacity 0.2s ease-in-out;
}
&.loading{
form{
opacity:0.5;
transition: opacity 0.5s ease-in-out;
pointer-events: none;
}
}
} }
} }
h2{ h2{
+11 -11
View File
@@ -35,9 +35,9 @@ export default {
required: true required: true
} }
}, },
data: () => ({ // data: () => ({
preview: '' // preview: ''
}), // }),
computed: { computed: {
...mapState({ ...mapState({
editionsbyuuid: state => state.Corpus.editionsbyuuid editionsbyuuid: state => state.Corpus.editionsbyuuid
@@ -46,14 +46,14 @@ export default {
return this.editionsbyuuid[this.result.textId].title return this.editionsbyuuid[this.result.textId].title
} }
}, },
created () { // created () {
if (this.result.extract) { // if (this.result.extract) {
const subString = this.result.extract.substr(0, 80) // const subString = this.result.extract.substr(0, 80)
this.preview = subString.substr(0, subString.lastIndexOf(' ')) + ' …' // this.preview = subString.substr(0, subString.lastIndexOf(' ')) + ' …'
} else { // } else {
console.warn(`No extract for ${this.result.textId}/${this.result.uuid}`) // console.warn(`No extract for ${this.result.textId}/${this.result.uuid}`)
} // }
}, // },
methods: { methods: {
...mapActions({ ...mapActions({
addHistoryItem: 'History/addItem' addHistoryItem: 'History/addItem'
+73 -4
View File
@@ -4,11 +4,22 @@
v-if="opened" v-if="opened"
id="results" id="results"
class="row" class="row"
:class="{ loading: isloading }"
> >
<header class="col-1"> <header class="col-1">
<h2>Resultats</h2> <h2>Resultats</h2>
<span class="search-keys">{{ keys }}</span><br> <span class="search-keys">{{ keys }}</span><br>
<span v-if="resultsQuantity" class="results-count">{{ resultsCount }}</span> <span v-if="resultsQuantity" class="results-count">{{ resultsCount }}</span>
<v-select
id="sorting"
type="select"
placeholder="trier par"
append-to-body
:calculate-position="dropDownMenuPos"
:options="sortOptions"
:value="sorting"
@input="onSortingSelected"
/>
</header> </header>
<section class="col-10 results-list"> <section class="col-10 results-list">
<div class="wrapper"> <div class="wrapper">
@@ -19,6 +30,7 @@
<infinite-loading <infinite-loading
v-if="offset < resultsQuantity.quantity" v-if="offset < resultsQuantity.quantity"
@infinite="nextResultsBatch" @infinite="nextResultsBatch"
:identifier="isloading"
/> />
</ul> </ul>
</div> </div>
@@ -37,24 +49,31 @@
<script> <script>
import { createPopper } from '@popperjs/core'
import ResultItem from '../Content/ResultItem' import ResultItem from '../Content/ResultItem'
import { mapState, mapActions } from 'vuex' import { mapState, mapActions, mapMutations } from 'vuex'
export default { export default {
name: 'Results', name: 'Results',
components: { components: {
ResultItem ResultItem
}, },
// data: () => ({
// }),
computed: { computed: {
opened: { opened: {
get () { return this.$store.state.Search.opened }, get () { return this.$store.state.Search.opened },
set (value) { this.$store.commit('Search/setOpened', value) } set (value) { this.$store.commit('Search/setOpened', value) }
}, },
...mapState({ ...mapState({
isloading: state => state.Search.isloading,
keys: state => state.Search.keys, keys: state => state.Search.keys,
results: state => state.Search.results, results: state => state.Search.results,
resultsQuantity: state => state.Search.resultsQuantity, resultsQuantity: state => state.Search.resultsQuantity,
offset: state => state.Search.offset offset: state => state.Search.offset,
sortOptions: state => state.Search.sortOptions,
sorting: state => state.Search.sorting
}), }),
resultsCount () { resultsCount () {
return `${this.$store.state.Search.resultsQuantity.quantity} ${this.$store.state.Search.resultsQuantity.unit}` return `${this.$store.state.Search.resultsQuantity.quantity} ${this.$store.state.Search.resultsQuantity.unit}`
@@ -65,9 +84,59 @@ export default {
console.log('clicked on close results') console.log('clicked on close results')
this.opened = false this.opened = false
}, },
...mapMutations({
setSorting: 'Search/setSorting'
}),
...mapActions({ ...mapActions({
nextResultsBatch: 'Search/nextResultsBatch' nextResultsBatch: 'Search/nextResultsBatch',
}) updateSearch: 'Search/updateSearch'
}),
dropDownMenuPos (dropdownList, component, { width }) {
/**
* We need to explicitly define the dropdown width since
* it is usually inherited from the parent with CSS.
*/
dropdownList.style.width = width
/**
* Here we position the dropdownList relative to the $refs.toggle Element.
*
* The 'offset' modifier aligns the dropdown so that the $refs.toggle and
* the dropdownList overlap by 1 pixel.
*
* The 'toggleClass' modifier adds a 'drop-up' class to the Vue Select
* wrapper so that we can set some styles for when the dropdown is placed
* above.
*/
const popper = createPopper(component.$refs.toggle, dropdownList, {
placement: 'top',
modifiers: [
{
name: 'offset',
options: {
offset: [0, -1]
}
},
{
name: 'toggleClass',
enabled: true,
phase: 'write',
fn ({ state }) {
component.$el.classList.toggle('drop-up', state.placement === 'top')
}
}]
})
/**
* To prevent memory leaks Popper needs to be destroyed.
* If you return function, it will be called just before dropdown is removed from DOM.
*/
return () => popper.destroy()
},
onSortingSelected (o) {
this.setSorting(o)
this.updateSearch()
}
} }
} }
</script> </script>
+13 -7
View File
@@ -1,6 +1,6 @@
<template> <template>
<div id="search" class="col-11"> <div id="search" class="col-11" :class="{ loading: isloading }">
<form v-if="corpusLoaded" class="search-form row"> <form class="search-form row">
<fieldset class="search"> <fieldset class="search">
<div> <div>
<label for="keys">Search</label> <label for="keys">Search</label>
@@ -84,7 +84,7 @@
import { createPopper } from '@popperjs/core' import { createPopper } from '@popperjs/core'
import { mapActions, mapState } from 'vuex' import { mapActions, mapMutations, mapState } from 'vuex'
export default { export default {
name: 'Search', name: 'Search',
@@ -114,10 +114,13 @@ export default {
} }
}, },
methods: { methods: {
...mapMutations({
setActiveFilters: 'Search/setActiveFilters'
}),
...mapActions({ ...mapActions({
newSearch: 'Search/newSearch', newSearch: 'Search/newSearch',
setSearchTypeValue: 'Search/setSearchTypeValue', setSearchTypeValue: 'Search/setSearchTypeValue',
setSearchActiveFilters: 'Search/setSearchActiveFilters' updateSearch: 'Search/updateSearch'
}), }),
submit () { submit () {
console.log('submited', this.keys) console.log('submited', this.keys)
@@ -171,15 +174,18 @@ export default {
}, },
onFiltersNominumSelected (e) { onFiltersNominumSelected (e) {
console.log('onFiltersNominumSelected', e) console.log('onFiltersNominumSelected', e)
this.setSearchActiveFilters({ index: 'persons', value: e }) this.setActiveFilters({ index: 'persons', value: e })
this.updateSearch()
}, },
onFiltersLocorumSelected (e) { onFiltersLocorumSelected (e) {
console.log('onFiltersLocorumSelected', e) console.log('onFiltersLocorumSelected', e)
this.setSearchActiveFilters({ index: 'places', value: e }) this.setActiveFilters({ index: 'places', value: e })
this.updateSearch()
}, },
onFiltersOperumSelected (e) { onFiltersOperumSelected (e) {
console.log('onFiltersOperumSelected', e) console.log('onFiltersOperumSelected', e)
this.setSearchActiveFilters({ index: 'objects', value: e }) this.setActiveFilters({ index: 'objects', value: e })
this.updateSearch()
} }
} }
} }
+79 -20
View File
@@ -1,8 +1,15 @@
// import axios from 'axios'
import { REST } from 'api/rest-axios' import { REST } from 'api/rest-axios'
import qs from 'querystring' import qs from 'querystring'
// const _CancelToken = axios.CancelToken
// const _cancelTokenSource = _CancelToken.source()
// let _cancel
export default { export default {
namespaced: true, namespaced: true,
testconst: 'Hello',
// initial state // initial state
state: { state: {
@@ -16,9 +23,18 @@ export default {
searchTypeValue: { 'code': 'text', 'label': 'Dans les textes' }, searchTypeValue: { 'code': 'text', 'label': 'Dans les textes' },
filters: { persons: [], places: [], objects: [] }, filters: { persons: [], places: [], objects: [] },
activeFilters: { persons: [], places: [], objects: [] }, activeFilters: { persons: [], places: [], objects: [] },
sortOptions: [
{ code: 'date', label: 'date' },
{ code: 'score', label: 'pertinence' },
{ code: 'size', label: 'nombre de mots' }
],
sorting: null,
results: [], results: [],
resultsQuantity: null, resultsQuantity: null,
isloading: false, isloading: false,
// infiniteLoadingIsLoading: false,
// infiniteLoadingCancelToken: null,
// infiniteLoadingCancelTokenSource: null,
limit: 10, limit: 10,
offset: 0, offset: 0,
opened: false opened: false
@@ -93,6 +109,10 @@ export default {
for (var index of ['persons', 'places', 'objects']) { for (var index of ['persons', 'places', 'objects']) {
state.activeFilters[index] = [] state.activeFilters[index] = []
} }
},
setSorting (state, sort) {
console.log('setSorting', sort)
state.sorting = sort
} }
}, },
@@ -101,7 +121,13 @@ export default {
getResults ({ dispatch, commit, state }, $infiniteLoadingState = null) { getResults ({ dispatch, commit, state }, $infiniteLoadingState = null) {
console.log('getResults', state.keys, $infiniteLoadingState) console.log('getResults', state.keys, $infiniteLoadingState)
// reset results on new search // reset results on new search
commit('setIsloading', true) if (!$infiniteLoadingState) {
commit('setIsloading', true)
}
// else {
// state.infiniteLoadingIsLoading = true
// }
let params = { let params = {
search: `${state.keys}`, search: `${state.keys}`,
start: state.offset, start: state.offset,
@@ -121,42 +147,75 @@ export default {
} }
} }
// params.filterPersons = ['nomLouisXIII', 'nomChampagnePhilippeDe'] // params.filterPersons = ['nomLouisXIII', 'nomChampagnePhilippeDe']
if (state.sorting) {
params.sort = state.sorting.code
}
// console.log('Search getResults params', params); // console.log('Search getResults params', params);
let q = qs.stringify(params) let q = qs.stringify(params)
return REST.get(`${window.apipath}/search?` + q)
let ops = {}
// if ($infiniteLoadingState) {
// ops.cancelToken = new _CancelToken(function executor (c) {
// _cancel = c
// })
// }
return REST.get(`${window.apipath}/search?` + q, ops)
.then(({ data }) => { .then(({ data }) => {
console.log('search REST: data', data.meta.quantity.quantity, state.offset + state.limit, data) console.log('search REST: data', data.meta.quantity.quantity, state.offset + state.limit, data)
commit('setIsloading', false)
commit('setOpened', true)
commit('setResults', data.content)
commit('setResultsCount', data.meta.quantity) commit('setResultsCount', data.meta.quantity)
commit('setFilters', data.meta.filters) commit('setFilters', data.meta.filters)
if ($infiniteLoadingState) { if ($infiniteLoadingState) {
if (state.offset + state.limit > data.meta.quantity.quantity) { if (state.isLoading) {
console.log('Search infinite completed') // we are in a new search or an update so we dont apply the infinite loading received results
// tell to vue-infinite-loading plugin that there si no new page
$infiniteLoadingState.complete() $infiniteLoadingState.complete()
} else { } else {
console.log('Search infinite loaded') commit('setResults', data.content)
// tell to vue-infinite-loading plugin that newpage is loaded if (state.offset + state.limit > data.meta.quantity.quantity) {
$infiniteLoadingState.loaded() console.log('Search infinite completed')
// tell to vue-infinite-loading plugin that there si no new page
$infiniteLoadingState.complete()
} else {
console.log('Search infinite loaded')
// tell to vue-infinite-loading plugin that newpage is loaded
$infiniteLoadingState.loaded()
}
// state.infiniteLoadingIsLoading = false
} }
} else {
commit('resetResults')
commit('setIsloading', false)
commit('setOpened', true)
commit('setResults', data.content)
} }
}) })
.catch((error) => { .catch((error) => {
console.warn('Issue with search', error) console.warn('Issue with search', error)
commit('setIsloading', false) commit('setIsloading', false)
$infiniteLoadingState.error() // if (axios.isCancel(error)) {
// console.log('Request canceled', error.message)
// if ($infiniteLoadingState) {
// $infiniteLoadingState.complete()
// }
// } else {
Promise.reject(error) Promise.reject(error)
if ($infiniteLoadingState) {
$infiniteLoadingState.error()
}
// }
}) })
}, },
newSearch ({ dispatch, commit, state }) { newSearch ({ dispatch, commit, state }) {
commit('resetResults') // commit('resetResults')
commit('resetActiveFilters') commit('resetActiveFilters')
// if (_cancel) {
// _cancel('new search fired')
// }
dispatch('getResults') dispatch('getResults')
}, },
filteredSearch ({ dispatch, commit, state }) { updateSearch ({ dispatch, commit, state }) {
commit('resetResults') // TODO: wait for new results came to reset results list
// TODO: indicate loading state
// commit('resetResults')
dispatch('getResults') dispatch('getResults')
}, },
nextResultsBatch ({ dispatch, commit, state }, $infiniteLoadingState) { nextResultsBatch ({ dispatch, commit, state }, $infiniteLoadingState) {
@@ -170,11 +229,11 @@ export default {
}, },
setSearchTypeValue ({ dispatch, commit, state }, value) { setSearchTypeValue ({ dispatch, commit, state }, value) {
commit('setSearchTypeValue', value) commit('setSearchTypeValue', value)
},
setSearchActiveFilters ({ dispatch, commit, state }, filters) {
// console.log('setSearchFiltersValue', filters)
commit('setActiveFilters', filters)
dispatch('filteredSearch')
} }
// setSearchActiveFilters ({ dispatch, commit, state }, filters) {
// // console.log('setSearchFiltersValue', filters)
// commit('setActiveFilters', filters)
// dispatch('updateSearch')
// }
} }
} }