search results sorting #804, better transitions between succesive searches
This commit is contained in:
+24
-3
@@ -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{
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
@@ -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')
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user