Browse Source

search results sorting #804, better transitions between succesive searches

Bachir Soussi Chiadmi 3 years ago
parent
commit
3ed132147c

+ 24 - 3
assets/css/app.scss

@@ -529,13 +529,13 @@ section[role="main-content"]{
 }
 
 footer[role="tools"]{
-  $list-item-h: 5em;
+  $list-item-h: 7em;
 
   @mixin resultItem{
     box-sizing: border-box;
     // we are only on 10 colls as 2 are occupied by sides
     flex-basis: percentage(2/($default_sum - 2));
-    height: $list-item-h;
+    max-height: $list-item-h;
     overflow: hidden;
     padding-bottom: 1em;
     padding-right: $default_gap;
@@ -602,7 +602,7 @@ footer[role="tools"]{
     z-index: 9;
     background-color: $gris;
     padding:1.2em $side-padding;
-    max-height: $list-item-h * 5;
+    max-height: $list-item-h * 3;
     @include accordeon-transition($list-item-h * 3);
     >header{
       .search-keys{
@@ -636,6 +636,17 @@ footer[role="tools"]{
         // 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{
     z-index: 10;
@@ -740,6 +751,16 @@ footer[role="tools"]{
           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{

+ 11 - 11
src/components/Content/ResultItem.vue

@@ -35,9 +35,9 @@ export default {
       required: true
     }
   },
-  data: () => ({
-    preview: ''
-  }),
+  // data: () => ({
+  //   preview: ''
+  // }),
   computed: {
     ...mapState({
       editionsbyuuid: state => state.Corpus.editionsbyuuid
@@ -46,14 +46,14 @@ export default {
       return this.editionsbyuuid[this.result.textId].title
     }
   },
-  created () {
-    if (this.result.extract) {
-      const subString = this.result.extract.substr(0, 80)
-      this.preview = subString.substr(0, subString.lastIndexOf(' ')) + ' …'
-    } else {
-      console.warn(`No extract for ${this.result.textId}/${this.result.uuid}`)
-    }
-  },
+  // created () {
+  //   if (this.result.extract) {
+  //     const subString = this.result.extract.substr(0, 80)
+  //     this.preview = subString.substr(0, subString.lastIndexOf(' ')) + ' …'
+  //   } else {
+  //     console.warn(`No extract for ${this.result.textId}/${this.result.uuid}`)
+  //   }
+  // },
   methods: {
     ...mapActions({
       addHistoryItem: 'History/addItem'

+ 73 - 4
src/components/nav/Results.vue

@@ -4,11 +4,22 @@
       v-if="opened"
       id="results"
       class="row"
+      :class="{ loading: isloading }"
     >
       <header class="col-1">
         <h2>Resultats</h2>
         <span class="search-keys">{{ keys }}</span><br>
         <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>
       <section class="col-10 results-list">
         <div class="wrapper">
@@ -19,6 +30,7 @@
             <infinite-loading
               v-if="offset < resultsQuantity.quantity"
               @infinite="nextResultsBatch"
+              :identifier="isloading"
             />
           </ul>
         </div>
@@ -37,24 +49,31 @@
 
 <script>
 
+import { createPopper } from '@popperjs/core'
+
 import ResultItem from '../Content/ResultItem'
-import { mapState, mapActions } from 'vuex'
+import { mapState, mapActions, mapMutations } from 'vuex'
 
 export default {
   name: 'Results',
   components: {
     ResultItem
   },
+  // data: () => ({
+  // }),
   computed: {
     opened: {
       get () { return this.$store.state.Search.opened },
       set (value) { this.$store.commit('Search/setOpened', value) }
     },
     ...mapState({
+      isloading: state => state.Search.isloading,
       keys: state => state.Search.keys,
       results: state => state.Search.results,
       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 () {
       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')
       this.opened = false
     },
+    ...mapMutations({
+      setSorting: 'Search/setSorting'
+    }),
     ...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>

+ 13 - 7
src/components/nav/Search.vue

@@ -1,6 +1,6 @@
 <template>
-  <div id="search" class="col-11">
-    <form v-if="corpusLoaded" class="search-form row">
+  <div id="search" class="col-11" :class="{ loading: isloading }">
+    <form class="search-form row">
       <fieldset class="search">
         <div>
           <label for="keys">Search</label>
@@ -84,7 +84,7 @@
 
 import { createPopper } from '@popperjs/core'
 
-import { mapActions, mapState } from 'vuex'
+import { mapActions, mapMutations, mapState } from 'vuex'
 
 export default {
   name: 'Search',
@@ -114,10 +114,13 @@ export default {
     }
   },
   methods: {
+    ...mapMutations({
+      setActiveFilters: 'Search/setActiveFilters'
+    }),
     ...mapActions({
       newSearch: 'Search/newSearch',
       setSearchTypeValue: 'Search/setSearchTypeValue',
-      setSearchActiveFilters: 'Search/setSearchActiveFilters'
+      updateSearch: 'Search/updateSearch'
     }),
     submit () {
       console.log('submited', this.keys)
@@ -171,15 +174,18 @@ export default {
     },
     onFiltersNominumSelected (e) {
       console.log('onFiltersNominumSelected', e)
-      this.setSearchActiveFilters({ index: 'persons', value: e })
+      this.setActiveFilters({ index: 'persons', value: e })
+      this.updateSearch()
     },
     onFiltersLocorumSelected (e) {
       console.log('onFiltersLocorumSelected', e)
-      this.setSearchActiveFilters({ index: 'places', value: e })
+      this.setActiveFilters({ index: 'places', value: e })
+      this.updateSearch()
     },
     onFiltersOperumSelected (e) {
       console.log('onFiltersOperumSelected', e)
-      this.setSearchActiveFilters({ index: 'objects', value: e })
+      this.setActiveFilters({ index: 'objects', value: e })
+      this.updateSearch()
     }
   }
 }

+ 79 - 20
src/store/modules/search.js

@@ -1,8 +1,15 @@
+// import axios from 'axios'
+
 import { REST } from 'api/rest-axios'
 import qs from 'querystring'
 
+// const _CancelToken = axios.CancelToken
+// const _cancelTokenSource = _CancelToken.source()
+// let _cancel
+
 export default {
   namespaced: true,
+  testconst: 'Hello',
 
   // initial state
   state: {
@@ -16,9 +23,18 @@ export default {
     searchTypeValue: { 'code': 'text', 'label': 'Dans les textes' },
     filters: { 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: [],
     resultsQuantity: null,
     isloading: false,
+    // infiniteLoadingIsLoading: false,
+    // infiniteLoadingCancelToken: null,
+    // infiniteLoadingCancelTokenSource: null,
     limit: 10,
     offset: 0,
     opened: false
@@ -93,6 +109,10 @@ export default {
       for (var index of ['persons', 'places', 'objects']) {
         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) {
       console.log('getResults', state.keys, $infiniteLoadingState)
       // reset results on new search
-      commit('setIsloading', true)
+      if (!$infiniteLoadingState) {
+        commit('setIsloading', true)
+      }
+      // else {
+      //   state.infiniteLoadingIsLoading = true
+      // }
+
       let params = {
         search: `${state.keys}`,
         start: state.offset,
@@ -121,42 +147,75 @@ export default {
         }
       }
       // params.filterPersons = ['nomLouisXIII', 'nomChampagnePhilippeDe']
+      if (state.sorting) {
+        params.sort = state.sorting.code
+      }
       // console.log('Search getResults params', 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 }) => {
           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('setFilters', data.meta.filters)
           if ($infiniteLoadingState) {
-            if (state.offset + state.limit > data.meta.quantity.quantity) {
-              console.log('Search infinite completed')
-              // tell to vue-infinite-loading plugin that there si no new page
+            if (state.isLoading) {
+              // we are in a new search or an update so we dont apply the infinite loading received results
               $infiniteLoadingState.complete()
             } else {
-              console.log('Search infinite loaded')
-              // tell to vue-infinite-loading plugin that newpage is loaded
-              $infiniteLoadingState.loaded()
+              commit('setResults', data.content)
+              if (state.offset + state.limit > data.meta.quantity.quantity) {
+                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) => {
           console.warn('Issue with search', error)
           commit('setIsloading', false)
-          $infiniteLoadingState.error()
+          // if (axios.isCancel(error)) {
+          //   console.log('Request canceled', error.message)
+          //   if ($infiniteLoadingState) {
+          //     $infiniteLoadingState.complete()
+          //   }
+          // } else {
           Promise.reject(error)
+          if ($infiniteLoadingState) {
+            $infiniteLoadingState.error()
+          }
+          // }
         })
     },
     newSearch ({ dispatch, commit, state }) {
-      commit('resetResults')
+      // commit('resetResults')
       commit('resetActiveFilters')
+      // if (_cancel) {
+      //   _cancel('new search fired')
+      // }
       dispatch('getResults')
     },
-    filteredSearch ({ dispatch, commit, state }) {
-      commit('resetResults')
+    updateSearch ({ dispatch, commit, state }) {
+      // TODO: wait for new results came to reset results list
+      // TODO: indicate loading state
+      // commit('resetResults')
       dispatch('getResults')
     },
     nextResultsBatch ({ dispatch, commit, state }, $infiniteLoadingState) {
@@ -170,11 +229,11 @@ export default {
     },
     setSearchTypeValue ({ dispatch, commit, state }, 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')
+    // }
   }
 }